silk.crypto.x25519.
Agree on a shared secret with X25519 (RFC 7748) Diffie-Hellman.
When to use#
Use X25519 when two parties must get the same secret over a channel that others can read,
for example in a TLS 1.3 or Noise handshake. Each side makes a key with X25519.generate,
sends X25519.publicKey, and calls X25519.agree with the public key of the other side.
Then derive the real keys from the shared secret with silk.crypto.hkdf.
Details#
Secrets, public keys, and shared secrets are 32 bytes, little-endian. A secret is clamped as RFC
7748 says, so all 32-byte values are valid. X25519.agree consumes the key, because an
ephemeral key is for one exchange. Nothing allocates.
Errors#
X25519Errorfor an input with the wrong length, or for a peer key that gives an all-zero secret.
Gotchas#
The exchange does not tell you who the other side is. An attacker in the middle can run one exchange with each side. Authenticate the exchange, for example with a certificate signature. Do not use the shared secret directly as a key. The key is not erased when it is dropped.
Examples#
Agree on a secret (RFC 7748 test vector)#
import silk.core.result { Result }
import silk.crypto.x25519 { X25519, X25519Error }
pub fn main() -> i32 {
// Real programs use `X25519.generate()` with a secure `Random` provider.
let alice = match X25519.fromSecret(
b"\x77\x07\x6d\x0a\x73\x18\xa5\x7d\x3c\x16\xc1\x72\x51\xb2\x66\x45\xdf\x4c\x2f\x87\xeb\xc0\x99\x2a\xb1\x77\xfb\xa5\x1d\xb9\x2c\x2a"
) {
Result<X25519, X25519Error>.Success { value } => move value
Result<X25519, X25519Error>.Failure { error: _ } => {
return 1
}
}
let bob = match X25519.fromSecret(
b"\x5d\xab\x08\x7e\x62\x4a\x8a\x4b\x79\xe1\x7f\x8b\x83\x80\x0e\xe6\x6f\x3b\xb1\x29\x26\x18\xb6\xfd\x1c\x2f\x8b\x27\xff\x88\xe0\xeb"
) {
Result<X25519, X25519Error>.Success { value } => move value
Result<X25519, X25519Error>.Failure { error: _ } => {
return 1
}
}
let alicePublic = X25519.publicKey(&alice)
let bobPublic = X25519.publicKey(&bob)
let fromAlice = match X25519.agree(move alice, &bobPublic) {
Result<[u8; 32], X25519Error>.Success { value } => value
Result<[u8; 32], X25519Error>.Failure { error: _ } => {
return 1
}
}
let fromBob = match X25519.agree(move bob, &alicePublic) {
Result<[u8; 32], X25519Error>.Success { value } => value
Result<[u8; 32], X25519Error>.Failure { error: _ } => {
return 1
}
}
// Both sides get 4a5d9d5b...1742.
if alicePublic[0] != 0x85 || fromAlice[0] != 0x4a || fromBob[0] != 0x4a || fromBob[31] != 0x42 {
return 1
}
return 0
}Import as X25519 with import silk.crypto.x25519 { X25519 }.
Public declarations: 2.
X25519Error#
pub union X25519ErrorThe reason that an X25519 call failed. It holds no secret bytes.
InvalidScalarLength#
InvalidScalarLengthThe secret is not 32 bytes.
InvalidPeerLength#
InvalidPeerLengthThe public key of the peer is not 32 bytes.
AllZeroSharedSecret#
AllZeroSharedSecretThe public key of the peer gives an all-zero shared secret. This occurs for a few low-order points that a hostile peer can send.
Implementation Debug for X25519Error#
impl Debug for X25519ErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant and its fields, such as InvalidScalarLength.
X25519#
pub struct X25519An X25519 key pair for one exchange: a secret and its public key.
When to use#
Use it when two parties must get the same secret over a channel that others can read, for
example in a TLS 1.3 or Noise handshake. Each side makes a key with X25519.generate, sends
X25519.publicKey, and calls X25519.agree with the public key of the other side.
Details#
X25519.agree consumes it. Make a new one for each exchange. Secrets, public keys, and shared
secrets are 32 bytes, little-endian. Nothing allocates.
Gotchas#
The exchange does not tell you who the other side is. An attacker in the middle can run one
exchange with each side. Authenticate the exchange, for example with a certificate signature.
Derive the real keys from the shared secret with silk.crypto.hkdf. The key is not erased when it is
dropped.
Implementation X25519#
impl X25519Associated function fromSecret#
pub fn fromSecret(bytes: &[u8]) -> Result<X25519, X25519Error>Returns the key pair for the 32-byte secret bytes, after RFC 7748 clamping.
Use it for test vectors or a secret that you stored. The bytes are copied.
Errors#
InvalidScalarLengthifbytesis not 32 bytes.
Gotchas#
For a new exchange, use X25519.generate. A secret that is used for two exchanges gives the
same public key to both, so the exchanges can be linked.
Associated function generate#
pub effect fn generate() -> X25519 ? &mut RandomReturns a new key pair with a secret from 32 bytes of the ambient Random.
The Random provider must be cryptographically secure. If it fails, the program stops. There
is no fallback to a weaker source.
Method publicKey#
pub fn publicKey(self: &X25519) -> [u8; 32]Returns the 32-byte public key to send to the peer. It does not consume the key pair.
Method agree#
pub fn agree(self: X25519, peer: &[u8]) -> Result<[u8; 32], X25519Error>Returns the 32-byte shared secret for the public key peer of the other side, and consumes
the key pair.
As RFC 7748 says, the top bit of peer is ignored and any 32-byte value is accepted, also a
point that is not on the curve.
Errors#
InvalidPeerLengthifpeeris not 32 bytes.AllZeroSharedSecretif the result is all zeros. Stop the exchange.
The key pair is consumed also when it fails.
Gotchas#
Pass the result through a key derivation function such as silk.crypto.hkdf before you use it as a
key.
Category: Cryptography.
Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2.