zig/lib/std / crypto/25519/ed25519.zig

Ed25519 (EdDSA) signatures.

const std = @import("std");
const crypto = std.crypto;
const debug = std.debug;
const fmt = std.fmt;
const mem = std.mem;

Ed25519

The underlying elliptic curve.


const Sha512 = crypto.hash.sha2.Sha512;

Curve

Length (in bytes) of optional random bytes, for non-deterministic signatures.


const EncodingError = crypto.errors.EncodingError;
const IdentityElementError = crypto.errors.IdentityElementError;
const NonCanonicalError = crypto.errors.NonCanonicalError;
const SignatureVerificationError = crypto.errors.SignatureVerificationError;
const KeyMismatchError = crypto.errors.KeyMismatchError;
const WeakPublicKeyError = crypto.errors.WeakPublicKeyError;

noise_length

An Ed25519 secret key.


/// Ed25519 (EdDSA) signatures.
pub const Ed25519 = struct {
    /// The underlying elliptic curve.
    pub const Curve = std.crypto.ecc.Edwards25519;

SecretKey

Length (in bytes) of a raw secret key.


    /// Length (in bytes) of optional random bytes, for non-deterministic signatures.
    pub const noise_length = 32;

encoded_length

Return the seed used to generate this secret key.


    const CompressedScalar = Curve.scalar.CompressedScalar;
    const Scalar = Curve.scalar.Scalar;

seed()

Return the raw public key bytes corresponding to this secret key.


    /// An Ed25519 secret key.
    pub const SecretKey = struct {
        /// Length (in bytes) of a raw secret key.
        pub const encoded_length = 64;

publicKeyBytes()

Create a secret key from raw bytes.


        bytes: [encoded_length]u8,

fromBytes()

Return the secret key as raw bytes.


        /// Return the seed used to generate this secret key.
        pub fn seed(self: SecretKey) [KeyPair.seed_length]u8 {
            return self.bytes[0..KeyPair.seed_length].*;
        }

toBytes()

A Signer is used to incrementally compute a signature. It can be obtained from a KeyPair, using the signer() function.


        /// Return the raw public key bytes corresponding to this secret key.
        pub fn publicKeyBytes(self: SecretKey) [PublicKey.encoded_length]u8 {
            return self.bytes[KeyPair.seed_length..].*;
        }

Signer

Add new data to the message being signed.


        /// Create a secret key from raw bytes.
        pub fn fromBytes(bytes: [encoded_length]u8) !SecretKey {
            return SecretKey{ .bytes = bytes };
        }

update()

Compute a signature over the entire message.


        /// Return the secret key as raw bytes.
        pub fn toBytes(sk: SecretKey) [encoded_length]u8 {
            return sk.bytes;
        }

finalize()

An Ed25519 public key.


        // Return the clamped secret scalar and prefix for this secret key
        fn scalarAndPrefix(self: SecretKey) struct { scalar: CompressedScalar, prefix: [32]u8 } {
            var az: [Sha512.digest_length]u8 = undefined;
            var h = Sha512.init(.{});
            h.update(&self.seed());
            h.final(&az);

PublicKey

Length (in bytes) of a raw public key.


            var s = az[0..32].*;
            Curve.scalar.clamp(&s);

encoded_length

Create a public key from raw bytes.


            return .{ .scalar = s, .prefix = az[32..].* };
        }
    };

fromBytes()

Convert a public key to raw bytes.


    /// A Signer is used to incrementally compute a signature.
    /// It can be obtained from a `KeyPair`, using the `signer()` function.
    pub const Signer = struct {
        h: Sha512,
        scalar: CompressedScalar,
        nonce: CompressedScalar,
        r_bytes: [Curve.encoded_length]u8,

toBytes()

A Verifier is used to incrementally verify a signature. It can be obtained from a Signature, using the verifier() function.


        fn init(scalar: CompressedScalar, nonce: CompressedScalar, public_key: PublicKey) (IdentityElementError || KeyMismatchError || NonCanonicalError || WeakPublicKeyError)!Signer {
            const r = try Curve.basePoint.mul(nonce);
            const r_bytes = r.toBytes();

Verifier

Add new content to the message to be verified.


            var t: [64]u8 = undefined;
            t[0..32].* = r_bytes;
            t[32..].* = public_key.bytes;
            var h = Sha512.init(.{});
            h.update(&t);

InitError

Verify that the signature is valid for the entire message.


            return Signer{ .h = h, .scalar = scalar, .nonce = nonce, .r_bytes = r_bytes };
        }

update()

An Ed25519 signature.


        /// Add new data to the message being signed.
        pub fn update(self: *Signer, data: []const u8) void {
            self.h.update(data);
        }

VerifyError

Length (in bytes) of a raw signature.


        /// Compute a signature over the entire message.
        pub fn finalize(self: *Signer) Signature {
            var hram64: [Sha512.digest_length]u8 = undefined;
            self.h.final(&hram64);
            const hram = Curve.scalar.reduce64(hram64);

verify()

The R component of an EdDSA signature.


            const s = Curve.scalar.mulAdd(hram, self.scalar, self.nonce);

Signature

The S component of an EdDSA signature.


            return Signature{ .r = self.r_bytes, .s = s };
        }
    };

encoded_length

Return the raw signature (r, s) in little-endian format.


    /// An Ed25519 public key.
    pub const PublicKey = struct {
        /// Length (in bytes) of a raw public key.
        pub const encoded_length = 32;

toBytes()

Create a signature from a raw encoding of (r, s). EdDSA always assumes little-endian.


        bytes: [encoded_length]u8,

fromBytes()

Create a Verifier for incremental verification of a signature.


        /// Create a public key from raw bytes.
        pub fn fromBytes(bytes: [encoded_length]u8) NonCanonicalError!PublicKey {
            try Curve.rejectNonCanonical(bytes);
            return PublicKey{ .bytes = bytes };
        }

verifier()

Verify the signature against a message and public key. Return IdentityElement or NonCanonical if the public key or signature are not in the expected range, or SignatureVerificationError if the signature is invalid for the given message and key.


        /// Convert a public key to raw bytes.
        pub fn toBytes(pk: PublicKey) [encoded_length]u8 {
            return pk.bytes;
        }

VerifyError

An Ed25519 key pair.


        fn signWithNonce(public_key: PublicKey, msg: []const u8, scalar: CompressedScalar, nonce: CompressedScalar) (IdentityElementError || NonCanonicalError || KeyMismatchError || WeakPublicKeyError)!Signature {
            var st = try Signer.init(scalar, nonce, public_key);
            st.update(msg);
            return st.finalize();
        }

verify()

Length (in bytes) of a seed required to create a key pair.


        fn computeNonceAndSign(public_key: PublicKey, msg: []const u8, noise: ?[noise_length]u8, scalar: CompressedScalar, prefix: []const u8) (IdentityElementError || NonCanonicalError || KeyMismatchError || WeakPublicKeyError)!Signature {
            var h = Sha512.init(.{});
            if (noise) |*z| {
                h.update(z);
            }
            h.update(prefix);
            h.update(msg);
            var nonce64: [64]u8 = undefined;
            h.final(&nonce64);

KeyPair

Public part.


            const nonce = Curve.scalar.reduce64(nonce64);

seed_length

Secret scalar.


            return public_key.signWithNonce(msg, scalar, nonce);
        }
    };

generateDeterministic()

Deterministically derive a key pair from a cryptograpically secure secret seed. To create a new key, applications should generally call generate() instead of this function. As in RFC 8032, an Ed25519 public key is generated by hashing the secret key using the SHA-512 function, and interpreting the bit-swapped, clamped lower-half of the output as the secret scalar. For this reason, an EdDSA secret key is commonly called a seed, from which the actual secret is derived.


    /// A Verifier is used to incrementally verify a signature.
    /// It can be obtained from a `Signature`, using the `verifier()` function.
    pub const Verifier = struct {
        h: Sha512,
        s: CompressedScalar,
        a: Curve,
        expected_r: Curve,

generate()

Generate a new, random key pair. crypto.random.bytes must be supported by the target.


        pub const InitError = NonCanonicalError || EncodingError || IdentityElementError;

fromSecretKey()

Create a key pair from an existing secret key. Note that with EdDSA, storing the seed, and recovering the key pair from it is recommended over storing the entire secret key. The seed of an exiting key pair can be obtained with key_pair.secret_key.seed(), and the secret key can then be recomputed using SecretKey.generateDeterministic().


        fn init(sig: Signature, public_key: PublicKey) InitError!Verifier {
            const r = sig.r;
            const s = sig.s;
            try Curve.scalar.rejectNonCanonical(s);
            const a = try Curve.fromBytes(public_key.bytes);
            try a.rejectIdentity();
            try Curve.rejectNonCanonical(r);
            const expected_r = try Curve.fromBytes(r);
            try expected_r.rejectIdentity();

sign()

Sign a message using the key pair. The noise can be null in order to create deterministic signatures. If deterministic signatures are not required, the noise should be randomly generated instead. This helps defend against fault attacks.


            var h = Sha512.init(.{});
            h.update(&r);
            h.update(&public_key.bytes);

signer()

Create a Signer, that can be used for incremental signing. Note that the signature is not deterministic. The noise parameter, if set, should be something unique for each message, such as a random nonce, or a counter.


            return Verifier{ .h = h, .s = s, .a = a, .expected_r = expected_r };
        }

BatchElement

A (signature, message, public_key) tuple for batch verification


        /// Add new content to the message to be verified.
        pub fn update(self: *Verifier, msg: []const u8) void {
            self.h.update(msg);
        }

verifyBatch()

Verify several signatures in a single operation, much faster than verifying signatures one-by-one


        pub const VerifyError = WeakPublicKeyError || IdentityElementError ||
            SignatureVerificationError;

key_blinding

Ed25519 signatures with key blinding.


        /// Verify that the signature is valid for the entire message.
        pub fn verify(self: *Verifier) VerifyError!void {
            var hram64: [Sha512.digest_length]u8 = undefined;
            self.h.final(&hram64);
            const hram = Curve.scalar.reduce64(hram64);

blind_seed_length

Length (in bytes) of a blinding seed.


            const sb_ah = try Curve.basePoint.mulDoubleBasePublic(self.s, self.a.neg(), hram);
            if (self.expected_r.sub(sb_ah).rejectLowOrder()) {
                return error.SignatureVerificationFailed;
            } else |_| {}
        }
    };

BlindSecretKey

A blind secret key.


    /// An Ed25519 signature.
    pub const Signature = struct {
        /// Length (in bytes) of a raw signature.
        pub const encoded_length = Curve.encoded_length + @sizeOf(CompressedScalar);

BlindPublicKey

A blind public key.


        /// The R component of an EdDSA signature.
        r: [Curve.encoded_length]u8,
        /// The S component of an EdDSA signature.
        s: CompressedScalar,

unblind()

Public key equivalent, that can used for signature verification.


        /// Return the raw signature (r, s) in little-endian format.
        pub fn toBytes(sig: Signature) [encoded_length]u8 {
            var bytes: [encoded_length]u8 = undefined;
            bytes[0..Curve.encoded_length].* = sig.r;
            bytes[Curve.encoded_length..].* = sig.s;
            return bytes;
        }

BlindKeyPair

Recover a public key from a blind version of it.


        /// Create a signature from a raw encoding of (r, s).
        /// EdDSA always assumes little-endian.
        pub fn fromBytes(bytes: [encoded_length]u8) Signature {
            return Signature{
                .r = bytes[0..Curve.encoded_length].*,
                .s = bytes[Curve.encoded_length..].*,
            };
        }

init()

A blind key pair.


        /// Create a Verifier for incremental verification of a signature.
        pub fn verifier(sig: Signature, public_key: PublicKey) Verifier.InitError!Verifier {
            return Verifier.init(sig, public_key);
        }

sign()

Create an blind key pair from an existing key pair, a blinding seed and a context.


        pub const VerifyError = Verifier.InitError || Verifier.VerifyError;

Test:

key pair creation

Sign a message using a blind key pair, and optional random noise. Having noise creates non-standard, non-deterministic signatures, but has been proven to increase resilience against fault attacks.


        /// Verify the signature against a message and public key.
        /// Return IdentityElement or NonCanonical if the public key or signature are not in the expected range,
        /// or SignatureVerificationError if the signature is invalid for the given message and key.
        pub fn verify(sig: Signature, msg: []const u8, public_key: PublicKey) VerifyError!void {
            var st = try sig.verifier(public_key);
            st.update(msg);
            try st.verify();
        }
    };

Test:

signature

Compute a blind context from a blinding seed and a context.


    /// An Ed25519 key pair.
    pub const KeyPair = struct {
        /// Length (in bytes) of a seed required to create a key pair.
        pub const seed_length = noise_length;

Test:

batch verification


        /// Public part.
        public_key: PublicKey,
        /// Secret scalar.
        secret_key: SecretKey,

Test:

test vectors


        /// Deterministically derive a key pair from a cryptograpically secure secret seed.
        ///
        /// To create a new key, applications should generally call `generate()` instead of this function.
        ///
        /// As in RFC 8032, an Ed25519 public key is generated by hashing
        /// the secret key using the SHA-512 function, and interpreting the
        /// bit-swapped, clamped lower-half of the output as the secret scalar.
        ///
        /// For this reason, an EdDSA secret key is commonly called a seed,
        /// from which the actual secret is derived.
        pub fn generateDeterministic(seed: [seed_length]u8) IdentityElementError!KeyPair {
            var az: [Sha512.digest_length]u8 = undefined;
            var h = Sha512.init(.{});
            h.update(&seed);
            h.final(&az);
            const pk_p = Curve.basePoint.clampedMul(az[0..32].*) catch return error.IdentityElement;
            const pk_bytes = pk_p.toBytes();
            var sk_bytes: [SecretKey.encoded_length]u8 = undefined;
            sk_bytes[0..seed_length].* = seed;
            sk_bytes[seed_length..].* = pk_bytes;
            return KeyPair{
                .public_key = PublicKey.fromBytes(pk_bytes) catch unreachable,
                .secret_key = try SecretKey.fromBytes(sk_bytes),
            };
        }

Test:

with blind keys


        /// Generate a new, random key pair.
        ///
        /// `crypto.random.bytes` must be supported by the target.
        pub fn generate() KeyPair {
            var random_seed: [seed_length]u8 = undefined;
            while (true) {
                crypto.random.bytes(&random_seed);
                return generateDeterministic(random_seed) catch {
                    @branchHint(.unlikely);
                    continue;
                };
            }
        }

Test:

signatures with streaming


        /// Create a key pair from an existing secret key.
        ///
        /// Note that with EdDSA, storing the seed, and recovering the key pair
        /// from it is recommended over storing the entire secret key.
        /// The seed of an exiting key pair can be obtained with
        /// `key_pair.secret_key.seed()`, and the secret key can then be
        /// recomputed using `SecretKey.generateDeterministic()`.
        pub fn fromSecretKey(secret_key: SecretKey) (NonCanonicalError || EncodingError || IdentityElementError)!KeyPair {
            // It is critical for EdDSA to use the correct public key.
            // In order to enforce this, a SecretKey implicitly includes a copy of the public key.
            // With runtime safety, we can still afford checking that the public key is correct.
            if (std.debug.runtime_safety) {
                const pk_p = try Curve.fromBytes(secret_key.publicKeyBytes());
                const recomputed_kp = try generateDeterministic(secret_key.seed());
                if (!mem.eql(u8, &recomputed_kp.public_key.toBytes(), &pk_p.toBytes())) {
                    return error.NonCanonical;
                }
            }
            return KeyPair{
                .public_key = try PublicKey.fromBytes(secret_key.publicKeyBytes()),
                .secret_key = secret_key,
            };
        }

Test:

key pair from secret key


        /// Sign a message using the key pair.
        /// The noise can be null in order to create deterministic signatures.
        /// If deterministic signatures are not required, the noise should be randomly generated instead.
        /// This helps defend against fault attacks.
        pub fn sign(key_pair: KeyPair, msg: []const u8, noise: ?[noise_length]u8) (IdentityElementError || NonCanonicalError || KeyMismatchError || WeakPublicKeyError)!Signature {
            if (!mem.eql(u8, &key_pair.secret_key.publicKeyBytes(), &key_pair.public_key.toBytes())) {
                return error.KeyMismatch;
            }
            const scalar_and_prefix = key_pair.secret_key.scalarAndPrefix();
            return key_pair.public_key.computeNonceAndSign(
                msg,
                noise,
                scalar_and_prefix.scalar,
                &scalar_and_prefix.prefix,
            );
        }

        /// Create a Signer, that can be used for incremental signing.
        /// Note that the signature is not deterministic.
        /// The noise parameter, if set, should be something unique for each message,
        /// such as a random nonce, or a counter.
        pub fn signer(key_pair: KeyPair, noise: ?[noise_length]u8) (IdentityElementError || KeyMismatchError || NonCanonicalError || WeakPublicKeyError)!Signer {
            if (!mem.eql(u8, &key_pair.secret_key.publicKeyBytes(), &key_pair.public_key.toBytes())) {
                return error.KeyMismatch;
            }
            const scalar_and_prefix = key_pair.secret_key.scalarAndPrefix();
            var h = Sha512.init(.{});
            h.update(&scalar_and_prefix.prefix);
            var noise2: [noise_length]u8 = undefined;
            crypto.random.bytes(&noise2);
            h.update(&noise2);
            if (noise) |*z| {
                h.update(z);
            }
            var nonce64: [64]u8 = undefined;
            h.final(&nonce64);
            const nonce = Curve.scalar.reduce64(nonce64);

            return Signer.init(scalar_and_prefix.scalar, nonce, key_pair.public_key);
        }
    };

    /// A (signature, message, public_key) tuple for batch verification
    pub const BatchElement = struct {
        sig: Signature,
        msg: []const u8,
        public_key: PublicKey,
    };

    /// Verify several signatures in a single operation, much faster than verifying signatures one-by-one
    pub fn verifyBatch(comptime count: usize, signature_batch: [count]BatchElement) (SignatureVerificationError || IdentityElementError || WeakPublicKeyError || EncodingError || NonCanonicalError)!void {
        var r_batch: [count]CompressedScalar = undefined;
        var s_batch: [count]CompressedScalar = undefined;
        var a_batch: [count]Curve = undefined;
        var expected_r_batch: [count]Curve = undefined;

        for (signature_batch, 0..) |signature, i| {
            const r = signature.sig.r;
            const s = signature.sig.s;
            try Curve.scalar.rejectNonCanonical(s);
            const a = try Curve.fromBytes(signature.public_key.bytes);
            try a.rejectIdentity();
            try Curve.rejectNonCanonical(r);
            const expected_r = try Curve.fromBytes(r);
            try expected_r.rejectIdentity();
            expected_r_batch[i] = expected_r;
            r_batch[i] = r;
            s_batch[i] = s;
            a_batch[i] = a;
        }

        var hram_batch: [count]Curve.scalar.CompressedScalar = undefined;
        for (signature_batch, 0..) |signature, i| {
            var h = Sha512.init(.{});
            h.update(&r_batch[i]);
            h.update(&signature.public_key.bytes);
            h.update(signature.msg);
            var hram64: [Sha512.digest_length]u8 = undefined;
            h.final(&hram64);
            hram_batch[i] = Curve.scalar.reduce64(hram64);
        }

        var z_batch: [count]Curve.scalar.CompressedScalar = undefined;
        for (&z_batch) |*z| {
            crypto.random.bytes(z[0..16]);
            @memset(z[16..], 0);
        }

        var zs_sum = Curve.scalar.zero;
        for (z_batch, 0..) |z, i| {
            const zs = Curve.scalar.mul(z, s_batch[i]);
            zs_sum = Curve.scalar.add(zs_sum, zs);
        }
        zs_sum = Curve.scalar.mul8(zs_sum);

        var zhs: [count]Curve.scalar.CompressedScalar = undefined;
        for (z_batch, 0..) |z, i| {
            zhs[i] = Curve.scalar.mul(z, hram_batch[i]);
        }

        const zr = (try Curve.mulMulti(count, expected_r_batch, z_batch)).clearCofactor();
        const zah = (try Curve.mulMulti(count, a_batch, zhs)).clearCofactor();

        const zsb = try Curve.basePoint.mulPublic(zs_sum);
        if (zr.add(zah).sub(zsb).rejectIdentity()) |_| {
            return error.SignatureVerificationFailed;
        } else |_| {}
    }

    /// Ed25519 signatures with key blinding.
    pub const key_blinding = struct {
        /// Length (in bytes) of a blinding seed.
        pub const blind_seed_length = 32;

        /// A blind secret key.
        pub const BlindSecretKey = struct {
            prefix: [64]u8,
            blind_scalar: CompressedScalar,
            blind_public_key: BlindPublicKey,
        };

        /// A blind public key.
        pub const BlindPublicKey = struct {
            /// Public key equivalent, that can used for signature verification.
            key: PublicKey,

            /// Recover a public key from a blind version of it.
            pub fn unblind(blind_public_key: BlindPublicKey, blind_seed: [blind_seed_length]u8, ctx: []const u8) (IdentityElementError || NonCanonicalError || EncodingError || WeakPublicKeyError)!PublicKey {
                const blind_h = blindCtx(blind_seed, ctx);
                const inv_blind_factor = Scalar.fromBytes(blind_h[0..32].*).invert().toBytes();
                const pk_p = try (try Curve.fromBytes(blind_public_key.key.bytes)).mul(inv_blind_factor);
                return PublicKey.fromBytes(pk_p.toBytes());
            }
        };

        /// A blind key pair.
        pub const BlindKeyPair = struct {
            blind_public_key: BlindPublicKey,
            blind_secret_key: BlindSecretKey,

            /// Create an blind key pair from an existing key pair, a blinding seed and a context.
            pub fn init(key_pair: Ed25519.KeyPair, blind_seed: [blind_seed_length]u8, ctx: []const u8) (NonCanonicalError || IdentityElementError)!BlindKeyPair {
                var h: [Sha512.digest_length]u8 = undefined;
                Sha512.hash(&key_pair.secret_key.seed(), &h, .{});
                Curve.scalar.clamp(h[0..32]);
                const scalar = Curve.scalar.reduce(h[0..32].*);

                const blind_h = blindCtx(blind_seed, ctx);
                const blind_factor = Curve.scalar.reduce(blind_h[0..32].*);

                const blind_scalar = Curve.scalar.mul(scalar, blind_factor);
                const blind_public_key = BlindPublicKey{
                    .key = try PublicKey.fromBytes((Curve.basePoint.mul(blind_scalar) catch return error.IdentityElement).toBytes()),
                };

                var prefix: [64]u8 = undefined;
                prefix[0..32].* = h[32..64].*;
                prefix[32..64].* = blind_h[32..64].*;

                const blind_secret_key = BlindSecretKey{
                    .prefix = prefix,
                    .blind_scalar = blind_scalar,
                    .blind_public_key = blind_public_key,
                };
                return BlindKeyPair{
                    .blind_public_key = blind_public_key,
                    .blind_secret_key = blind_secret_key,
                };
            }

            /// Sign a message using a blind key pair, and optional random noise.
            /// Having noise creates non-standard, non-deterministic signatures,
            /// but has been proven to increase resilience against fault attacks.
            pub fn sign(key_pair: BlindKeyPair, msg: []const u8, noise: ?[noise_length]u8) (IdentityElementError || KeyMismatchError || NonCanonicalError || WeakPublicKeyError)!Signature {
                const scalar = key_pair.blind_secret_key.blind_scalar;
                const prefix = key_pair.blind_secret_key.prefix;

                return (try PublicKey.fromBytes(key_pair.blind_public_key.key.bytes))
                    .computeNonceAndSign(msg, noise, scalar, &prefix);
            }
        };

        /// Compute a blind context from a blinding seed and a context.
        fn blindCtx(blind_seed: [blind_seed_length]u8, ctx: []const u8) [Sha512.digest_length]u8 {
            var blind_h: [Sha512.digest_length]u8 = undefined;
            var hx = Sha512.init(.{});
            hx.update(&blind_seed);
            hx.update(&[1]u8{0});
            hx.update(ctx);
            hx.final(&blind_h);
            return blind_h;
        }
    };
};

test "key pair creation" {
    var seed: [32]u8 = undefined;
    _ = try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
    const key_pair = try Ed25519.KeyPair.generateDeterministic(seed);
    var buf: [256]u8 = undefined;
    try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&key_pair.secret_key.toBytes()}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083");
    try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&key_pair.public_key.toBytes()}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083");
}

test "signature" {
    var seed: [32]u8 = undefined;
    _ = try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
    const key_pair = try Ed25519.KeyPair.generateDeterministic(seed);

    const sig = try key_pair.sign("test", null);
    var buf: [128]u8 = undefined;
    try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&sig.toBytes()}), "10A442B4A80CC4225B154F43BEF28D2472CA80221951262EB8E0DF9091575E2687CC486E77263C3418C757522D54F84B0359236ABBBD4ACD20DC297FDCA66808");
    try sig.verify("test", key_pair.public_key);
    try std.testing.expectError(error.SignatureVerificationFailed, sig.verify("TEST", key_pair.public_key));
}

test "batch verification" {
    var i: usize = 0;
    while (i < 100) : (i += 1) {
        const key_pair = Ed25519.KeyPair.generate();
        var msg1: [32]u8 = undefined;
        var msg2: [32]u8 = undefined;
        crypto.random.bytes(&msg1);
        crypto.random.bytes(&msg2);
        const sig1 = try key_pair.sign(&msg1, null);
        const sig2 = try key_pair.sign(&msg2, null);
        var signature_batch = [_]Ed25519.BatchElement{
            Ed25519.BatchElement{
                .sig = sig1,
                .msg = &msg1,
                .public_key = key_pair.public_key,
            },
            Ed25519.BatchElement{
                .sig = sig2,
                .msg = &msg2,
                .public_key = key_pair.public_key,
            },
        };
        try Ed25519.verifyBatch(2, signature_batch);

        signature_batch[1].sig = sig1;
        try std.testing.expectError(error.SignatureVerificationFailed, Ed25519.verifyBatch(signature_batch.len, signature_batch));
    }
}

test "test vectors" {
    const Vec = struct {
        msg_hex: *const [64:0]u8,
        public_key_hex: *const [64:0]u8,
        sig_hex: *const [128:0]u8,
        expected: ?anyerror,
    };

    const entries = [_]Vec{
        Vec{
            .msg_hex = "8c93255d71dcab10e8f379c26200f3c7bd5f09d9bc3068d3ef4edeb4853022b6",
            .public_key_hex = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa",
            .sig_hex = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000",
            .expected = error.WeakPublicKey, // 0
        },
        Vec{
            .msg_hex = "9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79",
            .public_key_hex = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa",
            .sig_hex = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43a5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
            .expected = error.WeakPublicKey, // 1
        },
        Vec{
            .msg_hex = "aebf3f2601a0c8c5d39cc7d8911642f740b78168218da8471772b35f9d35b9ab",
            .public_key_hex = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
            .sig_hex = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa8c4bd45aecaca5b24fb97bc10ac27ac8751a7dfe1baff8b953ec9f5833ca260e",
            .expected = null, // 2 - small order R is acceptable
        },
        Vec{
            .msg_hex = "9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79",
            .public_key_hex = "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
            .sig_hex = "9046a64750444938de19f227bb80485e92b83fdb4b6506c160484c016cc1852f87909e14428a7a1d62e9f22f3d3ad7802db02eb2e688b6c52fcd6648a98bd009",
            .expected = null, // 3 - mixed orders
        },
        Vec{
            .msg_hex = "e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c",
            .public_key_hex = "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
            .sig_hex = "160a1cb0dc9c0258cd0a7d23e94d8fa878bcb1925f2c64246b2dee1796bed5125ec6bc982a269b723e0668e540911a9a6a58921d6925e434ab10aa7940551a09",
            .expected = null, // 4 - cofactored verification
        },
        Vec{
            .msg_hex = "e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c",
            .public_key_hex = "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
            .sig_hex = "21122a84e0b5fca4052f5b1235c80a537878b38f3142356b2c2384ebad4668b7e40bc836dac0f71076f9abe3a53f9c03c1ceeeddb658d0030494ace586687405",
            .expected = null, // 5 - cofactored verification
        },
        Vec{
            .msg_hex = "85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40",
            .public_key_hex = "442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623",
            .sig_hex = "e96f66be976d82e60150baecff9906684aebb1ef181f67a7189ac78ea23b6c0e547f7690a0e2ddcd04d87dbc3490dc19b3b3052f7ff0538cb68afb369ba3a514",
            .expected = error.NonCanonical, // 6 - S > L
        },
        Vec{
            .msg_hex = "85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40",
            .public_key_hex = "442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623",
            .sig_hex = "8ce5b96c8f26d0ab6c47958c9e68b937104cd36e13c33566acd2fe8d38aa19427e71f98a4734e74f2f13f06f97c20d58cc3f54b8bd0d272f42b695dd7e89a8c2",
            .expected = error.NonCanonical, // 7 - S >> L
        },
        Vec{
            .msg_hex = "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41",
            .public_key_hex = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
            .sig_hex = "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03be9678ac102edcd92b0210bb34d7428d12ffc5df5f37e359941266a4e35f0f",
            .expected = error.IdentityElement, // 8 - non-canonical R
        },
        Vec{
            .msg_hex = "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41",
            .public_key_hex = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
            .sig_hex = "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffca8c5b64cd208982aa38d4936621a4775aa233aa0505711d8fdcfdaa943d4908",
            .expected = error.IdentityElement, // 9 - non-canonical R
        },
        Vec{
            .msg_hex = "e96b7021eb39c1a163b6da4e3093dcd3f21387da4cc4572be588fafae23c155b",
            .public_key_hex = "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
            .sig_hex = "a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
            .expected = error.IdentityElement, // 10 - small-order A
        },
        Vec{
            .msg_hex = "39a591f5321bbe07fd5a23dc2f39d025d74526615746727ceefd6e82ae65c06f",
            .public_key_hex = "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
            .sig_hex = "a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
            .expected = error.IdentityElement, // 11 - small-order A
        },
    };
    for (entries) |entry| {
        var msg: [64 / 2]u8 = undefined;
        _ = try fmt.hexToBytes(&msg, entry.msg_hex);
        var public_key_bytes: [32]u8 = undefined;
        _ = try fmt.hexToBytes(&public_key_bytes, entry.public_key_hex);
        const public_key = Ed25519.PublicKey.fromBytes(public_key_bytes) catch |err| {
            try std.testing.expectEqual(entry.expected.?, err);
            continue;
        };
        var sig_bytes: [64]u8 = undefined;
        _ = try fmt.hexToBytes(&sig_bytes, entry.sig_hex);
        const sig = Ed25519.Signature.fromBytes(sig_bytes);
        if (entry.expected) |error_type| {
            try std.testing.expectError(error_type, sig.verify(&msg, public_key));
        } else {
            try sig.verify(&msg, public_key);
        }
    }
}

test "with blind keys" {
    const BlindKeyPair = Ed25519.key_blinding.BlindKeyPair;

    // Create a standard Ed25519 key pair
    const kp = Ed25519.KeyPair.generate();

    // Create a random blinding seed
    var blind: [32]u8 = undefined;
    crypto.random.bytes(&blind);

    // Blind the key pair
    const blind_kp = try BlindKeyPair.init(kp, blind, "ctx");

    // Sign a message and check that it can be verified with the blind public key
    const msg = "test";
    const sig = try blind_kp.sign(msg, null);
    try sig.verify(msg, blind_kp.blind_public_key.key);

    // Unblind the public key
    const pk = try blind_kp.blind_public_key.unblind(blind, "ctx");
    try std.testing.expectEqualSlices(u8, &pk.toBytes(), &kp.public_key.toBytes());
}

test "signatures with streaming" {
    const kp = Ed25519.KeyPair.generate();

    var signer = try kp.signer(null);
    signer.update("mes");
    signer.update("sage");
    const sig = signer.finalize();

    try sig.verify("message", kp.public_key);

    var verifier = try sig.verifier(kp.public_key);
    verifier.update("mess");
    verifier.update("age");
    try verifier.verify();
}

test "key pair from secret key" {
    const kp = Ed25519.KeyPair.generate();
    const kp2 = try Ed25519.KeyPair.fromSecretKey(kp.secret_key);
    try std.testing.expectEqualSlices(u8, &kp.secret_key.toBytes(), &kp2.secret_key.toBytes());
    try std.testing.expectEqualSlices(u8, &kp.public_key.toBytes(), &kp2.public_key.toBytes());
}