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#

  • X448Error for an input with the wrong length, or for a peer key that gives an all-zero secret.

  • Ed448Error for 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)#

silk
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#

silk
pub union X448Error

The reason that an X448 call failed. It holds no secret bytes.

InvalidScalarLength#

silk
InvalidScalarLength

The secret is not 56 bytes.

InvalidPeerLength#

silk
InvalidPeerLength

The public key of the peer is not 56 bytes.

AllZeroSharedSecret#

silk
AllZeroSharedSecret

The 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#

silk
impl Debug for X448Error

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as InvalidScalarLength.

X448#

silk
pub struct X448

An 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#

silk
impl X448

Associated function fromSecret#

silk
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#

  • InvalidScalarLength if bytes is 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#

silk
pub effect fn generate() -> X448 ? &mut Random

Returns 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#

silk
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#

silk
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#

silk
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#

  • InvalidPeerLength if peer is not 56 bytes.

  • AllZeroSharedSecret if the result is all zeros. Stop the exchange.

The key pair is consumed also when it fails.

Method agreeWith#

silk
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#

  • InvalidPeerLength if peer is not 56 bytes.

  • AllZeroSharedSecret if 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#

silk
pub enum Ed448Error

The reason that an Ed448 call failed. It holds no secret bytes.

InvalidKeyLength#

silk
InvalidKeyLength

A seed or a public key whose length is not 57 bytes.

InvalidKey#

silk
InvalidKey

A public key that does not encode a point on the curve.

InvalidSignatureLength#

silk
InvalidSignatureLength

A signature whose length is not 114 bytes.

InvalidSignature#

silk
InvalidSignature

A signature that does not verify for this key, context, and message.

ContextTooLong#

silk
ContextTooLong

A context longer than 255 bytes.

Implementation Debug for Ed448Error#

silk
impl Debug for Ed448Error

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as InvalidKeyLength.

Ed448PrivateKey#

silk
pub struct Ed448PrivateKey

An 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#

silk
impl Ed448PrivateKey

Associated function fromSeed#

silk
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#

silk
pub effect fn generate() -> Ed448PrivateKey ? &mut Random

Returns a new key from 57 bytes of the Random provider.

Gotchas#

Use a secure Random provider.

Method seed#

silk
pub fn seed(self: &Self) -> [u8; 57]

Returns the 57-byte seed.

Method publicKey#

silk
pub fn publicKey(self: &Self) -> [u8; 57]

Returns the 57-byte encoded public key.

Method sign#

silk
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#

silk
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#

silk
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#

silk
pub struct Ed448PublicKey

A 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#

silk
impl Ed448PublicKey

Associated function fromBytes#

silk
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#

silk
pub fn bytes(self: &Ed448PublicKey) -> [u8; 57]

Returns the 57-byte encoding this key was decoded from.

Method verify#

silk
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#

silk
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#

silk
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.

View the source of this page