🔢 Scalar Multiplication with CryptoScalarMult
The CryptoScalarMult API exposes the low-level scalar multiplication primitive crypto_scalarmult, based on Curve25519.
This operation implements the X25519 algorithm (ECDH over Curve25519), as defined in RFC 7748.
It forms the foundation of key exchange protocols such as CryptoBox, CryptoKeyExchange, and others.
This API is rarely needed directly. It is recommended only when implementing custom protocols or interoperating with other systems that use raw X25519 as defined in RFC 7748.
🧂 Based on libsodium's Scalar multiplication
ℹ️ API Reference forCryptoScalarMult
🌟 Features
- Curve25519 scalar multiplication (X25519, RFC 7748)
- Directional operation:
Q = n·P - Safe public key calculation:
P = n·B - Deterministic, constant-time implementation
- Fully allocation-free and Span-friendly
- Used internally by
CryptoBox,CryptoKeyExchange
📏 Constants
| Name | Value | Description |
|---|---|---|
PublicKeyLen |
32 | Length of the public key (q = n·B) |
PrivateKeyLen |
32 | Length of the private scalar |
SharedPointLen |
32 | Length of the computed q = n·P |
📋 Working with CryptoScalarMult
Calculate Public Key:
Computes the public key P = n·B given a private scalar n and the curve25519 base point B:
using var privateKey = new SecureMemory<byte>(CryptoScalarMult.PrivateKeyLen);
Span<byte> publicKey = stackalloc byte[CryptoScalarMult.PublicKeyLen];
RandomGenerator.Fill(privateKey);
CryptoScalarMult.CalculatePublicKey(publicKey, privateKey);
Compute Shared Point:
Performs scalar multiplication S₁ = n₁·P₂ with a private scalar and a peer’s public key to compute a shared secret point:
using var sharedPoint = new SecureMemory(CryptoScalarMult.SharedPointLen);
CryptoScalarMult.Compute(sharedPoint, myPrivateKey, peerPublicKey);
❓ Why Both Parties Compute the Same Shared Secret Point
Let:
n₁: Alice's private scalar keyn₂: Bob's private scalar key
Public keys are calculated by multiplying the private scalar with the base point B of the Curve25519:
- Alice's public key:
P₁ = n₁ · B - Bob's public key:
P₂ = n₂ · B
Now both Alice and Bob compute the shared secret by multiplying their private key with the public key of the other party:
S₁ = n₁ · P₂ = n₁ · (n₂ · B) = (n₁ × n₂) · B
S₂ = n₂ · P₁ = n₂ · (n₁ · B) = (n₂ × n₁) · B = (n₁ × n₂) · B = S₁
They arrive at the same point because of the algebraic properties of scalar and integer multiplication.
Particularly, scalar multiplication is associative and integer multiplication is commutative.
Therefore, S₁ = S₂, and both parties share the same secret point.
ℹ️ Note: dot
·means scalar multiplication and×integer multiplication.
❓ Why You Cannot Derive the Private Scalar from the Public Point
Given:
n: the private scalar keyB: the base point (public)P = n · B: the public key
Recovering n from P and B is equivalent to solving the Elliptic Curve Discrete Logarithm Problem (ECDLP).
This problem is considered computationally infeasible with current technology. No classical algorithm can efficiently recover n from P and B, even with massive computing power.
Only a large-scale quantum computer running Shor’s algorithm could break this — but such machines do not exist today.
⚠️ Avoid Using the Shared Secret Directly as a Shared Key
Many (privateKey, publicKey) pairs can produce the same result S when using CryptoScalarMult.
This is because S is a point on the curve, and scalar multiplication is not injective.
A safer and recommended approach is to derive a shared key using a cryptographic hash of the transcript:
sharedKey = H(S || pk1 || pk2)
Or better use a key derivation function:
sharedKey = KDF(S, pk1, pk2)
This binds the result to the specific public keys involved, preventing ambiguity or replay. The order of the public keys must be agreed upon (e.g., lexicographically or based on fixed roles) to ensure both sides derive the same key.
📋 Recommended Derivation Pattern
This pattern illustrates how to compute a shared secret S = n₁·P₂ = n₂·P₁ and derive a symmetric key from it using HKDF.
The derivation uses:
ikm = S(the shared secret point from scalar multiplication)salt = senderPublicKeyinfo = recipientPublicKey
This ensures the derived key is uniquely bound to both the shared secret and the roles of the participants.
🤔 Tip: Swapping
saltandinfowill produce a different key.
Be consistent across sender and recipient (e.g., always use sender's public key as salt).
This pattern prevents accidental key reuse and mitigates reflection or unknown key share attacks by incorporating both parties' public keys into the derivation process.
using var alicePrivateKey = new SecureMemory<byte>(CryptoScalarMult.PrivateKeyLen);
Span<byte> alicePublicKey = stackalloc byte[CryptoScalarMult.PublicKeyLen];
RandomGenerator.Fill(alicePrivateKey);
CryptoScalarMult.CalculatePublicKey(alicePublicKey, alicePrivateKey);
using var bobPrivateKey = new SecureMemory<byte>(CryptoScalarMult.PrivateKeyLen);
Span<byte> bobPublicKey = stackalloc byte[CryptoScalarMult.PublicKeyLen];
RandomGenerator.Fill(bobPrivateKey);
CryptoScalarMult.CalculatePublicKey(bobPublicKey, bobPrivateKey);
using var aliceSharedSecret = new SecureMemory<byte>(CryptoScalarMult.PublicKeyLen);
CryptoScalarMult.Compute(aliceSharedSecret, alicePrivateKey, bobPublicKey);
using var bobSharedSecret = new SecureMemory<byte>(CryptoScalarMult.PublicKeyLen);
CryptoScalarMult.Compute(bobSharedSecret, bobPrivateKey, alicePublicKey);
bool isTheSameSharedSecret = aliceSharedSecret.AsReadOnlySpan()
.SequenceEqual(bobSharedSecret.AsReadOnlySpan());
Debug.Assert(isTheSameSharedSecret, "Shared secrets should match between Alice and Bob.");
using var aliceTxKey = new SecureMemory<byte>(XChaCha20Poly1305.KeyLen);
CryptoHkdf.DeriveKey(HashAlgorithmName.SHA512, ikm: aliceSharedSecret, okm: aliceTxKey,
salt: alicePublicKey, info: bobPublicKey);
using var bobRxKey = new SecureMemory<byte>(XChaCha20Poly1305.KeyLen);
CryptoHkdf.DeriveKey(HashAlgorithmName.SHA512, ikm: bobSharedSecret, okm: bobRxKey,
salt: alicePublicKey, info: bobPublicKey);
bool isTheSameTxRxKey = aliceTxKey.AsReadOnlySpan().SequenceEqual(bobRxKey.AsReadOnlySpan());
Debug.Assert(isTheSameTxRxKey, "Transmission key derived by Alice should match receive key derived by Bob.");
⚠️ Error Handling
ArgumentException— when input buffers have incorrect lengths or invalid parameters.LibSodiumException— when a crypto operation fails.
📝 Notes
- All APIs, except
SecureMemoryare Span-friendly and do not allocate memory internally. CryptoScalarMultis a low-level primitive and does not provide authentication.- Avoid using scalar multiplication output directly as a key — always apply a key derivation function or a hash.