ed25519-compact

repository·master·Indexed 41 days ago

https://github.com/jedisct1/rust-ed25519-compact

A small, self-contained, wasm-friendly Ed25519 and X25519 implementation for Rust version 2.4.2. Designed for WebAssembly and Fastly Compute, it features formally-verified field arithmetic, no_std compatibility, and supports both standard and incremental (streaming) APIs for signing and verification.

Tokens
5.6K
Snippets
20
Records
23
Agent score
100%

What's inside ed25519-compact

  1. Add ed25519-compact to your project

    master

    To use this library, add it to your Cargo.toml dependencies. The library is no_std-friendly and has zero dependencies if you provide your own randomness, or uses getrandom if you use the default features.

    [dependencies]
    ed25519-compact = "2"
  2. What is DHOutput and why is it non-uniform?

    master

    A DHOutput represents the raw result of a scalar multiplication (the shared point on the curve).

    Crucial Security Note: The output of the scalar multiplication is not uniform. This means the bytes in DHOutput do not have a uniform distribution and are not suitable for direct use as cryptographic keys (e.g., for AES). You must pass the bytes of DHOutput through a cryptographic hash function (like SHA-256) to derive a uniform key before use.

  3. How X25519 clamping works

    master

    X25519 requires the scalar (the secret key) to be 'clamped' to ensure certain security properties (like preventing small-subgroup attacks).

    In this library, clamping is handled automatically when you call PublicKey::dh(). However, if you are performing manual operations, you can explicitly create a clamped version of a secret key using SecretKey::clamped(). Clamping modifies the first and last bytes of the 32-byte scalar to clear the cofactor bits.

    let clamped_sk = secret_key.clamped();
  4. Sign and verify large messages using the Incremental API

    master

    For handling large messages without high memory consumption, use the streaming (incremental) API. This allows you to feed message chunks into a state object using .absorb() before finalizing the operation with .sign() or .verify().

    /// Creates a new key pair.
    let kp = KeyPair::generate();
    
    /// Creates a state for an incremental signer.
    let mut st = kp.sk.sign_incremental(Noise::default());
    
    /// Feeds the message as any number of chunks, and sign the concatenation.
    st.absorb("mes");
    st.absorb("sage");
    let signature = st.sign();
    
    /// Creates a state for an incremental verifier.
    let mut st = kp.pk.verify_incremental(&signature)?;
    
    /// Feeds the message as any number of chunks, and verify the concatenation.
    st.absorb("mess");
    st.absorb("age");
    st.verify()?;
  5. Sign and verify messages using the standard API

    master

    The standard API allows you to generate a KeyPair from a seed, sign a complete message using the secret key (sk), and verify that signature using the public key (pk). All cryptographic structures can be converted to raw bytes by dereferencing them (implementing AsRef<[u8]>).

    // A message to sign and verify.
    let message = b"test";
    
    // Generates a new key pair using a random seed.
    // A given seed will always produce the same key pair.
    let key_pair = KeyPair::from_seed(Seed::default());
    
    // Computes a signature for this message using the secret part of the key pair.
    let signature = key_pair.sk.sign(message, Some(Noise::default()));
    
    // Verifies the signature using the public part of the key pair.
    key_pair
        .pk
        .verify(message, &signature)
        .expect("Signature didn't verify");
    
    // Verification of a different message using the same signature and public key fails.
    key_pair
        .pk
        .verify(b"A different message", &signature)
        .expect_err("Signature shouldn't verify");
    
    // All these structures can be viewed as raw bytes simply by dereferencing them:
    let signature_as_bytes: &[u8] = signature.as_ref();
    println!("Signature as bytes: {:?}", signature_as_bytes);
  6. Configure ed25519-compact via Cargo features

    master

    You can customize the library's functionality and footprint using Cargo features:

    • self-verify: Verifies a signature immediately after computing it to improve resilience against fault attacks (enabled by default on WebAssembly).
    • std: Disables no_std compatibility to allow errors to implement the standard Error trait.
    • random (default): Provides Default implementations for Seed and Noise to enable secure random generation.
    • traits: Adds support for ed25519 and signature crate traits.
    • pem: Enables importing/exporting keys as OpenSSL-compatible PEM files.
    • blind-keys: Enables support for key blinding.
    • opt_size: Enables size optimizations (8-15% reduction in size, ~7% performance cost).
    • x25519: Enables support for the X25519 key exchange system.
    • disable-signatures: Disables signature support, compiling only X25519 support.
  7. Basic Ed25519 signing and verification

    master

    Use the KeyPair structure to manage Ed25519 keys. You can generate a key pair from a Seed or use KeyPair::generate() (if the random feature is enabled). The sk (secret key) field provides the sign method, and the pk (public key) field provides the verify method. All key and signature structures can be converted to raw bytes by dereferencing them as &[u8] via as_ref().

    use ed25519_compact::*;
    
    // A message to sign and verify.
    let message = b"test";
    
    // Generates a new key pair using a random seed.
    let key_pair = KeyPair::from_seed(Seed::generate());
    
    // Computes a signature for this message using the secret part of the key pair.
    let signature = key_pair.sk.sign(message, Some(Noise::generate()));
    
    // Verifies the signature using the public part of the key pair.
    key_pair
        .pk
        .verify(message, &signature)
        .expect("Signature didn't verify");
    
    // All these structures can be viewed as raw bytes simply by dereferencing them:
    let signature_as_bytes: &[u8] = signature.as_ref();
  8. Incremental (streaming) Ed25519 signing and verification

    master

    For handling large messages without loading them entirely into memory, use the incremental API.

    1. Signing: Call sk.sign_incremental(noise) to get a signer state. Use .absorb(chunk) to feed message parts and .sign() to produce the final signature.
    2. Verification: Call pk.verify_incremental(&signature) to get a verifier state. Use .absorb(chunk) to feed message parts and .verify() to check the validity.
    use ed25519_compact::*;
    
    /// Creates a new key pair.
    let kp = KeyPair::generate();
    
    /// Create a state for an incremental signer.
    let mut st = kp.sk.sign_incremental(Noise::default());
    
    /// Feed the message as any number of chunks, and sign the concatenation.
    st.absorb("mes");
    st.absorb("sage");
    let signature = st.sign();
    
    /// Create a state for an incremental verifier.
    let mut st = kp.pk.verify_incremental(&signature).unwrap();
    
    /// Feed the message as any number of chunks, and verify the concatenation.
    st.absorb("mess");
    st.absorb("age");
    assert!(st.verify().is_ok());
  9. Verify Ed25519 signatures

    master

    Verification is performed using a PublicKey:

    1. Standard Verification: pk.verify(message, signature)

      • Returns Ok(()) if valid, or an Error if invalid.
    2. Streaming Verification: pk.verify_incremental(signature)

      • Returns a VerifyingState object.
      • Use VerifyingState::absorb(chunk) to feed message parts incrementally.
      • Call VerifyingState::verify() to perform the final check.

    Note: VerifyingState implements Drop to wipe the signature from memory.

    // Standard verification
    let is_valid = pk.verify(b"my message", &signature).is_ok();
    
    // Streaming verification
    let mut st = pk.verify_incremental(&signature).unwrap();
    st.absorb(b"part 1");
    st.absorb(b"part 2");
    assert!(st.verify().is_ok());
  10. Generate a random X25519 KeyPair

    master

    If the random feature is enabled, you can generate a new KeyPair containing a random SecretKey and its corresponding PublicKey using KeyPair::generate(). This method uses getrandom to populate the secret key and ensures the resulting key is not a weak (all-zero) key.

    #[cfg(feature = "random")]
    let keypair = KeyPair::generate();
  11. Use Blind Keys for privacy

    master

    If the blind-keys feature is enabled, you can perform operations using blinded keys to enhance privacy.

    • Blinding a Key: Use KeyPair::blind(blind, ctx) or PublicKey::blind(blind, ctx) to create a BlindKeyPair or BlindPublicKey.
    • Unblinding: Use BlindPublicKey::unblind(blind, ctx) to recover the original PublicKey.
    • Blinded Signing: Use BlindSecretKey::sign(message, noise) to sign messages using a blinded secret key.

    Blind is a 16-byte component used to derive the blinding factor.

    // Blinding a keypair
    let blind = Blind::new([0u8; 16]);
    let blind_kp = kp.blind(&blind, "context");
    
    // Signing with blinded key
    let signature = blind_kp.blind_sk.sign(b"message", None);
    
    // Unblinding the public key
    let recovered_pk = blind_kp.blind_pk.unblind(&blind, "context").unwrap();
  12. Sign messages with Ed25519

    master

    You can sign messages using a SecretKey via two primary methods:

    1. Standard Signing: sk.sign(message, noise)

      • message: Any type implementing AsRef<[u8]>.
      • noise: An Option<Noise>. Providing noise is recommended to mitigate fault attacks.
    2. Streaming Signing: sk.sign_incremental(noise)

      • Returns a SigningState object.
      • Use SigningState::absorb(chunk) to feed message parts incrementally.
      • Call SigningState::sign() to produce the final Signature.

    Note: For sign_incremental, it is critical that the noise provided is never repeated.

    // Standard signing
    let signature = sk.sign(b"my message", None);
    
    // Streaming signing
    let mut st = sk.sign_incremental(noise);
    st.absorb(b"part 1");
    st.absorb(b"part 2");
    let signature = st.sign();