silk.crypto.curve448.
Agree on a shared secret with X448 (RFC 7748), and sign with Ed448 (RFC 8032), on the Goldilocks curve, Curve448.
When to use#
Use X448 when two parties must get the same secret over a channel that others can read and
a 224-bit security level is wanted, for example in a TLS 1.3 handshake with the x448 group.
Each side makes a key with X448.generate, sends X448.publicKey, and calls
X448.agree with the public key of the other side. Then derive the real keys from the shared
secret with silk.crypto.hkdf. Use Ed448PrivateKey to sign and Ed448PublicKey to verify
Ed448 signatures, with a context or prehashed (Ed448ph) when the protocol asks for it. For the
more common 128-bit level, use silk.crypto.x25519 and silk.crypto.ed25519.
Details#
X448 secrets, public keys, and shared secrets are 56 bytes, little-endian. A secret is clamped as
RFC 7748 says, so all 56-byte values are valid. Ed448 seeds and public keys are 57 bytes, and
signatures 114 bytes. The field is the integers modulo p = 2^448 - 2^224 - 1, held as eight
56-bit limbs, and every step on a secret runs in constant time. Nothing allocates. Use
silk.crypto.okp_key to read and write the keys in PKCS #8 and SubjectPublicKeyInfo.
Errors#
X448Errorfor an input with the wrong length, or for a peer key that gives an all-zero secret.Ed448Errorfor a seed, key, or signature of the wrong length, a key that is not a point, a signature that does not verify, or a context longer than 255 bytes.
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. Keys are not erased when they are dropped. Ed448, Ed448 with a context, and Ed448ph signatures do not verify as each other.
Examples#
Agree on a secret (RFC 7748 test vector)#
import silk.core.result { Result }
import silk.crypto.curve448 { X448, X448Error }
pub fn main() -> i32 {
// Real programs use `X448.generate()` with a secure `Random` provider.
let alice = match X448.fromSecret(
b"\x9a\x8f\x49\x25\xd1\x51\x9f\x57\x75\xcf\x46\xb0\x4b\x58\x00\xd4\xee\x9e\xe8\xba\xe8\xbc\x55\x65\xd4\x98\xc2\x8d\xd9\xc9\xba\xf5\x74\xa9\x41\x97\x44\x89\x73\x91\x00\x63\x82\xa6\xf1\x27\xab\x1d\x9a\xc2\xd8\xc0\xa5\x98\x72\x6b"
) {
Result<X448, X448Error>.Success { value } => move value
Result<X448, X448Error>.Failure { error: _ } => {
return 1
}
}
let bob = match X448.fromSecret(
b"\x1c\x30\x6a\x7a\xc2\xa0\xe2\xe0\x99\x0b\x29\x44\x70\xcb\xa3\x39\xe6\x45\x37\x72\xb0\x75\x81\x1d\x8f\xad\x0d\x1d\x69\x27\xc1\x20\xbb\x5e\xe8\x97\x2b\x0d\x3e\x21\x37\x4c\x9c\x92\x1b\x09\xd1\xb0\x36\x6f\x10\xb6\x51\x73\x99\x2d"
) {
Result<X448, X448Error>.Success { value } => move value
Result<X448, X448Error>.Failure { error: _ } => {
return 1
}
}
let alicePublic = X448.publicKey(&alice)
let bobPublic = X448.publicKey(&bob)
let fromAlice = match X448.agree(move alice, &bobPublic) {
Result<[u8; 56], X448Error>.Success { value } => value
Result<[u8; 56], X448Error>.Failure { error: _ } => {
return 1
}
}
let fromBob = match X448.agree(move bob, &alicePublic) {
Result<[u8; 56], X448Error>.Success { value } => value
Result<[u8; 56], X448Error>.Failure { error: _ } => {
return 1
}
}
// Both sides get 07fff418...a5d.
if alicePublic[0] != 0x9b || fromAlice[0] != 0x07 || fromBob[0] != 0x07 || fromBob[55] != 0x5d {
return 1
}
return 0
}Import as X448 with import silk.crypto.curve448 { X448 }.
Public declarations: 5.
X448Error#
pub union X448ErrorThe reason that an X448 call failed. It holds no secret bytes.
InvalidScalarLength#
InvalidScalarLengthThe secret is not 56 bytes.
InvalidPeerLength#
InvalidPeerLengthThe public key of the peer is not 56 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 X448Error#
impl Debug for X448ErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as InvalidScalarLength.
X448#
pub struct X448An X448 key pair: 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. Each side
makes a key with X448.generate, sends X448.publicKey, and calls X448.agree with the
public key of the other side.
Details#
X448.agree consumes it; X448.agreeWith borrows it, for a static key. Secrets, public
keys, and shared secrets are 56 bytes, little-endian. Nothing allocates.
Gotchas#
The exchange does not tell you who the other side is. 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 X448#
impl X448Associated function fromSecret#
pub fn fromSecret(bytes: &[u8]) -> Result<X448, X448Error>Returns the key pair for the 56-byte secret bytes. The bytes are copied, and clamped as RFC
7748 says before use.
Errors#
InvalidScalarLengthifbytesis not 56 bytes.
Gotchas#
For a new exchange, use X448.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() -> X448 ? &mut RandomReturns a new key pair with a secret from 56 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 secret#
pub fn secret(self: &X448) -> [u8; 56]Returns the 56 secret bytes as they were given, before clamping. PKCS #8 stores these bytes.
Method publicKey#
pub fn publicKey(self: &X448) -> [u8; 56]Returns the 56-byte public key to send to the peer. It does not consume the key pair.
Method agree#
pub fn agree(self: X448, peer: &[u8]) -> Result<[u8; 56], X448Error>Returns the 56-byte shared secret for the public key peer of the other side, and consumes
the key pair.
As RFC 7748 says, any 56-byte value is accepted, also a value that is not below p or a point
that is not on the curve.
Errors#
InvalidPeerLengthifpeeris not 56 bytes.AllZeroSharedSecretif the result is all zeros. Stop the exchange.
The key pair is consumed also when it fails.
Method agreeWith#
pub fn agreeWith(self: &X448, peer: &[u8]) -> Result<[u8; 56], X448Error>Returns the 56-byte shared secret for the public key peer, as X448.agree does, but
borrows the key pair so that a static key can agree with many peers.
Errors#
InvalidPeerLengthifpeeris not 56 bytes.AllZeroSharedSecretif the result is all zeros. Stop the exchange.
Gotchas#
A static key gives no forward secrecy. Prefer a new key pair for each exchange.
Ed448Error#
pub enum Ed448ErrorThe reason that an Ed448 call failed. It holds no secret bytes.
InvalidKeyLength#
InvalidKeyLengthA seed or a public key whose length is not 57 bytes.
InvalidKey#
InvalidKeyA public key that does not encode a point on the curve.
InvalidSignatureLength#
InvalidSignatureLengthA signature whose length is not 114 bytes.
InvalidSignature#
InvalidSignatureA signature that does not verify for this key, context, and message.
ContextTooLong#
ContextTooLongA context longer than 255 bytes.
Implementation Debug for Ed448Error#
impl Debug for Ed448ErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as InvalidKeyLength.
Ed448PrivateKey#
pub struct Ed448PrivateKeyAn Ed448 private key that signs messages (RFC 8032 section 5.2).
When to use#
Make it from a 57-byte seed with Ed448PrivateKey.fromSeed, or make a new one with
Ed448PrivateKey.generate. Then call Ed448PrivateKey.sign. Use
Ed448PrivateKey.signWithContext when the protocol names a context, and
Ed448PrivateKey.signPrehashed for Ed448ph.
Details#
The seed is what RFC 8032 calls the private key, and what PKCS #8 stores for Ed448. The key keeps the seed, the clamped secret scalar, the nonce prefix, and the public key. Signing is deterministic and runs in constant time for the secret values.
Gotchas#
Secrets are not erased.
Implementation Ed448PrivateKey#
impl Ed448PrivateKeyAssociated function fromSeed#
pub fn fromSeed(seed: &[u8]) -> Result<Ed448PrivateKey, Ed448Error>Returns the key for exactly 57 seed bytes.
Errors#
InvalidKeyLength when seed is not 57 bytes long.
Associated function generate#
pub effect fn generate() -> Ed448PrivateKey ? &mut RandomReturns a new key from 57 bytes of the Random provider.
Gotchas#
Use a secure Random provider.
Method seed#
pub fn seed(self: &Self) -> [u8; 57]Returns the 57-byte seed.
Method publicKey#
pub fn publicKey(self: &Self) -> [u8; 57]Returns the 57-byte encoded public key.
Method sign#
pub fn sign(self: &Self, message: &[u8]) -> [u8; 114]Returns the 114-byte Ed448 signature of message with an empty context, R followed by
S. This is what OpenSSL and Node.js give for an Ed448 key.
Method signWithContext#
pub fn signWithContext(self: &Self, message: &[u8], context: &[u8]) -> Result<[u8; 114], Ed448Error>Returns the Ed448 signature of message with context, which binds the signature to one use.
Errors#
ContextTooLong when context is longer than 255 bytes.
Method signPrehashed#
pub fn signPrehashed(self: &Self, message: &[u8], context: &[u8]) -> Result<[u8; 114], Ed448Error>Returns the Ed448ph signature of message with context: the signature of the 64-byte
SHAKE256 hash of message, marked as prehashed.
Errors#
ContextTooLong when context is longer than 255 bytes.
Gotchas#
Ed448ph signatures do not verify as Ed448 signatures, and the other way round.
Ed448PublicKey#
pub struct Ed448PublicKeyA decoded Ed448 public key, for signature verification (RFC 8032 section 5.2).
When to use#
Use it to check that a message was signed by the holder of a known Ed448 key. Decode the key
once with Ed448PublicKey.fromBytes, then call Ed448PublicKey.verify.
Details#
Verification requires the scalar half of the signature to be below the group order, and checks
that [S]B - [k]A encodes to R, as OpenSSL does. It works on public data and does not try to
run in constant time. It does not allocate.
Implementation Ed448PublicKey#
impl Ed448PublicKeyAssociated function fromBytes#
pub fn fromBytes(bytes: &[u8]) -> Result<Ed448PublicKey, Ed448Error>Decodes exactly 57 public key bytes.
Errors#
InvalidKeyLength when bytes is not 57 bytes long, and InvalidKey when it does not
encode a curve point.
Method bytes#
pub fn bytes(self: &Ed448PublicKey) -> [u8; 57]Returns the 57-byte encoding this key was decoded from.
Method verify#
pub fn verify(self: &Ed448PublicKey, message: &[u8], signature: &[u8]) -> Result<(), Ed448Error>Checks that signature is this key's Ed448 signature of message with an empty context.
Errors#
InvalidSignatureLength when signature is not 114 bytes long, and InvalidSignature when
it does not verify.
Method verifyWithContext#
pub fn verifyWithContext(self: &Ed448PublicKey, message: &[u8], context: &[u8], signature: &[u8]) -> Result<
(),
Ed448Error,
>Checks that signature is this key's Ed448 signature of message with context.
Errors#
ContextTooLong, InvalidSignatureLength, or InvalidSignature.
Method verifyPrehashed#
pub fn verifyPrehashed(self: &Ed448PublicKey, message: &[u8], context: &[u8], signature: &[u8]) -> Result<
(),
Ed448Error,
>Checks that signature is this key's Ed448ph signature of message with context.
Errors#
ContextTooLong, InvalidSignatureLength, or InvalidSignature.
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, x86_64-pc-windows-msvc, wasm64-unknown-unknown.