|
const std = @import("std"); const crypto = std.crypto; const debug = std.debug; const fmt = std.fmt; const mem = std.mem; |
Ed25519The underlying elliptic curve. |
const Sha512 = crypto.hash.sha2.Sha512; |
CurveLength (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_lengthAn Ed25519 secret key. |
/// Ed25519 (EdDSA) signatures. pub const Ed25519 = struct { /// The underlying elliptic curve. pub const Curve = std.crypto.ecc.Edwards25519; |
SecretKeyLength (in bytes) of a raw secret key. |
/// Length (in bytes) of optional random bytes, for non-deterministic signatures. pub const noise_length = 32; |
encoded_lengthReturn 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 |
/// 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..].*; } |
SignerAdd 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); |
PublicKeyLength (in bytes) of a raw public key. |
var s = az[0..32].*; Curve.scalar.clamp(&s); |
encoded_lengthCreate 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 |
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(); |
VerifierAdd 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); |
InitErrorVerify 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); } |
VerifyErrorLength (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); |
SignatureThe S component of an EdDSA signature. |
return Signature{ .r = self.r_bytes, .s = s }; } }; |
encoded_lengthReturn 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; } |
VerifyErrorAn 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); |
KeyPairPublic part. |
const nonce = Curve.scalar.reduce64(nonce64); |
seed_lengthSecret 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 |
/// 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.
|
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
|
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 }; } |
BatchElementA (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_blindingEd25519 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_lengthLength (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 |_| {} } }; |
BlindSecretKeyA 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); |
BlindPublicKeyA 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; } |
BlindKeyPairRecover 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 creationSign 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:signatureCompute 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()); } |
Generated by zstd-live on 2025-08-10 02:45:58 UTC. |