authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2025-08-26 23:03:08+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-08 14:25:57-07:00
log1872c85ac25146036119387d1f376e2cba3ff7be
treeaf598be582437b533c80a4036202b277c7178a86
parentc41b9d75083dc80b56c45673dab167be0f2727e9

std.crypto.ed25519: support cofactorless verification

Add verifyStrict() functions for cofactorless verification. Also: - Support messages < 64 characters in the test vectors - Allow mulDoubleBasePublic to return the identity as a regular value. There are valid use cases for this.

2 files changed, 97 insertions(+), 8 deletions(-)

lib/std/crypto/25519/ed25519.zig+96-6
...@@ -179,13 +179,49 @@ pub const Ed25519 = struct {...@@ -179,13 +179,49 @@ pub const Ed25519 = struct {
179 SignatureVerificationError;179 SignatureVerificationError;
180180
181 /// Verify that the signature is valid for the entire message.181 /// Verify that the signature is valid for the entire message.
182 ///
183 /// This function uses cofactored verification for broad interoperability.
184 /// It aligns single-signature verification with common batch verification approaches.
185 ///
186 /// Return IdentityElement or NonCanonical if the public key or signature are not in the expected range,
187 /// or SignatureVerificationError if the signature is invalid for the given message and key.
182 pub fn verify(self: *Verifier) VerifyError!void {188 pub fn verify(self: *Verifier) VerifyError!void {
183 var hram64: [Sha512.digest_length]u8 = undefined;189 var hram64: [Sha512.digest_length]u8 = undefined;
184 self.h.final(&hram64);190 self.h.final(&hram64);
185 const hram = Curve.scalar.reduce64(hram64);191 const hram = Curve.scalar.reduce64(hram64);
192 const sb_ah = (try Curve.basePoint.mulDoubleBasePublic(
193 Curve.scalar.mul8(self.s),
194 self.a.clearCofactor().neg(),
195 hram,
196 ));
197 const check = sb_ah.sub(self.expected_r.clearCofactor());
198 if (check.rejectIdentity()) |_| {
199 return error.SignatureVerificationFailed;
200 } else |_| {}
201 }
186202
187 const sb_ah = try Curve.basePoint.mulDoubleBasePublic(self.s, self.a.neg(), hram);203 /// Verify that the signature is valid for the entire message using cofactorless verification.
188 if (self.expected_r.sub(sb_ah).rejectLowOrder()) {204 ///
205 /// This function performs strict verification without cofactor multiplication,
206 /// checking the exact equation: [s]B = R + [H(R,A,m)]A
207 ///
208 /// This is more restrictive than the cofactored `verify()` method and may reject
209 /// specially crafted signatures that would be accepted by cofactored verification.
210 /// But it will never reject valid signatures created using the `sign()` method.
211 ///
212 /// Return IdentityElement or NonCanonical if the public key or signature are not in the expected range,
213 /// or SignatureVerificationError if the signature is invalid for the given message and key.
214 pub fn verifyStrict(self: *Verifier) VerifyError!void {
215 var hram64: [Sha512.digest_length]u8 = undefined;
216 self.h.final(&hram64);
217 const hram = Curve.scalar.reduce64(hram64);
218 const sb_ah = (try Curve.basePoint.mulDoubleBasePublic(
219 self.s,
220 self.a.neg(),
221 hram,
222 ));
223 const check = sb_ah.sub(self.expected_r);
224 if (check.rejectIdentity()) |_| {
189 return error.SignatureVerificationFailed;225 return error.SignatureVerificationFailed;
190 } else |_| {}226 } else |_| {}
191 }227 }
...@@ -226,6 +262,10 @@ pub const Ed25519 = struct {...@@ -226,6 +262,10 @@ pub const Ed25519 = struct {
226 pub const VerifyError = Verifier.InitError || Verifier.VerifyError;262 pub const VerifyError = Verifier.InitError || Verifier.VerifyError;
227263
228 /// Verify the signature against a message and public key.264 /// Verify the signature against a message and public key.
265 ///
266 /// This function uses cofactored verification for broad interoperability.
267 /// It aligns single-signature verification with common batch verification approaches.
268 ///
229 /// Return IdentityElement or NonCanonical if the public key or signature are not in the expected range,269 /// Return IdentityElement or NonCanonical if the public key or signature are not in the expected range,
230 /// or SignatureVerificationError if the signature is invalid for the given message and key.270 /// or SignatureVerificationError if the signature is invalid for the given message and key.
231 pub fn verify(sig: Signature, msg: []const u8, public_key: PublicKey) VerifyError!void {271 pub fn verify(sig: Signature, msg: []const u8, public_key: PublicKey) VerifyError!void {
...@@ -233,6 +273,23 @@ pub const Ed25519 = struct {...@@ -233,6 +273,23 @@ pub const Ed25519 = struct {
233 st.update(msg);273 st.update(msg);
234 try st.verify();274 try st.verify();
235 }275 }
276
277 /// Verify the signature against a message and public key using cofactorless verification.
278 ///
279 /// This performs strict verification without cofactor multiplication,
280 /// checking the exact equation: [s]B = R + [H(R,A,m)]A
281 ///
282 /// This is more restrictive than the standard `verify()` method and may reject
283 /// specially crafted signatures that would be accepted by cofactored verification.
284 /// But it will never reject valid signatures created using the `sign()` method.
285 ///
286 /// Return IdentityElement or NonCanonical if the public key or signature are not in the expected range,
287 /// or SignatureVerificationError if the signature is invalid for the given message and key.
288 pub fn verifyStrict(sig: Signature, msg: []const u8, public_key: PublicKey) VerifyError!void {
289 var st = try sig.verifier(public_key);
290 st.update(msg);
291 try st.verifyStrict();
292 }
236 };293 };
237294
238 /// An Ed25519 key pair.295 /// An Ed25519 key pair.
...@@ -556,7 +613,7 @@ test "batch verification" {...@@ -556,7 +613,7 @@ test "batch verification" {
556613
557test "test vectors" {614test "test vectors" {
558 const Vec = struct {615 const Vec = struct {
559 msg_hex: *const [64:0]u8,616 msg_hex: []const u8,
560 public_key_hex: *const [64:0]u8,617 public_key_hex: *const [64:0]u8,
561 sig_hex: *const [128:0]u8,618 sig_hex: *const [128:0]u8,
562 expected: ?anyerror,619 expected: ?anyerror,
...@@ -638,7 +695,8 @@ test "test vectors" {...@@ -638,7 +695,8 @@ test "test vectors" {
638 };695 };
639 for (entries) |entry| {696 for (entries) |entry| {
640 var msg: [64 / 2]u8 = undefined;697 var msg: [64 / 2]u8 = undefined;
641 _ = try fmt.hexToBytes(&msg, entry.msg_hex);698 const msg_len = entry.msg_hex.len / 2;
699 _ = try fmt.hexToBytes(msg[0..msg_len], entry.msg_hex);
642 var public_key_bytes: [32]u8 = undefined;700 var public_key_bytes: [32]u8 = undefined;
643 _ = try fmt.hexToBytes(&public_key_bytes, entry.public_key_hex);701 _ = try fmt.hexToBytes(&public_key_bytes, entry.public_key_hex);
644 const public_key = Ed25519.PublicKey.fromBytes(public_key_bytes) catch |err| {702 const public_key = Ed25519.PublicKey.fromBytes(public_key_bytes) catch |err| {
...@@ -649,9 +707,9 @@ test "test vectors" {...@@ -649,9 +707,9 @@ test "test vectors" {
649 _ = try fmt.hexToBytes(&sig_bytes, entry.sig_hex);707 _ = try fmt.hexToBytes(&sig_bytes, entry.sig_hex);
650 const sig = Ed25519.Signature.fromBytes(sig_bytes);708 const sig = Ed25519.Signature.fromBytes(sig_bytes);
651 if (entry.expected) |error_type| {709 if (entry.expected) |error_type| {
652 try std.testing.expectError(error_type, sig.verify(&msg, public_key));710 try std.testing.expectError(error_type, sig.verify(msg[0..msg_len], public_key));
653 } else {711 } else {
654 try sig.verify(&msg, public_key);712 try sig.verify(msg[0..msg_len], public_key);
655 }713 }
656 }714 }
657}715}
...@@ -701,3 +759,35 @@ test "key pair from secret key" {...@@ -701,3 +759,35 @@ test "key pair from secret key" {
701 try std.testing.expectEqualSlices(u8, &kp.secret_key.toBytes(), &kp2.secret_key.toBytes());759 try std.testing.expectEqualSlices(u8, &kp.secret_key.toBytes(), &kp2.secret_key.toBytes());
702 try std.testing.expectEqualSlices(u8, &kp.public_key.toBytes(), &kp2.public_key.toBytes());760 try std.testing.expectEqualSlices(u8, &kp.public_key.toBytes(), &kp2.public_key.toBytes());
703}761}
762
763test "cofactored vs cofactorless verification" {
764 const msg_hex = "65643235353139766563746f72732033";
765 const public_key_hex = "86e72f5c2a7215151059aa151c0ee6f8e2155d301402f35d7498f078629a8f79";
766 const sig_hex = "fa9dde274f4820efb19a890f8ba2d8791710a4303ceef4aedf9dddc4e81a1f11701a598b9a02ae60505dd0c2938a1a0c2d6ffd4676cfb49125b19e9cb358da06";
767
768 var msg: [16]u8 = undefined;
769 _ = try fmt.hexToBytes(&msg, msg_hex);
770
771 var pk_bytes: [32]u8 = undefined;
772 _ = try fmt.hexToBytes(&pk_bytes, public_key_hex);
773 const pk = try Ed25519.PublicKey.fromBytes(pk_bytes);
774
775 var sig_bytes: [64]u8 = undefined;
776 _ = try fmt.hexToBytes(&sig_bytes, sig_hex);
777 const sig = Ed25519.Signature.fromBytes(sig_bytes);
778
779 try sig.verify(&msg, pk);
780
781 try std.testing.expectError(
782 error.SignatureVerificationFailed,
783 sig.verifyStrict(&msg, pk),
784 );
785}
786
787test "regular signature verifies with both verify and verifyStrict" {
788 const kp = Ed25519.KeyPair.generate();
789 const msg = "test message";
790 const sig = try kp.sign(msg, null);
791 try sig.verify(msg, kp.public_key);
792 try sig.verifyStrict(msg, kp.public_key);
793}
lib/std/crypto/25519/edwards25519.zig+1-2
...@@ -311,7 +311,7 @@ pub const Edwards25519 = struct {...@@ -311,7 +311,7 @@ pub const Edwards25519 = struct {
311311
312 /// Double-base multiplication of public parameters - Compute (p1*s1)+(p2*s2) *IN VARIABLE TIME*312 /// Double-base multiplication of public parameters - Compute (p1*s1)+(p2*s2) *IN VARIABLE TIME*
313 /// This can be used for signature verification.313 /// This can be used for signature verification.
314 pub fn mulDoubleBasePublic(p1: Edwards25519, s1: [32]u8, p2: Edwards25519, s2: [32]u8) (IdentityElementError || WeakPublicKeyError)!Edwards25519 {314 pub fn mulDoubleBasePublic(p1: Edwards25519, s1: [32]u8, p2: Edwards25519, s2: [32]u8) WeakPublicKeyError!Edwards25519 {
315 var pc1_array: [9]Edwards25519 = undefined;315 var pc1_array: [9]Edwards25519 = undefined;
316 const pc1 = if (p1.is_base) basePointPc[0..9] else pc: {316 const pc1 = if (p1.is_base) basePointPc[0..9] else pc: {
317 pc1_array = precompute(p1, 8);317 pc1_array = precompute(p1, 8);
...@@ -344,7 +344,6 @@ pub const Edwards25519 = struct {...@@ -344,7 +344,6 @@ pub const Edwards25519 = struct {
344 if (pos == 0) break;344 if (pos == 0) break;
345 q = q.dbl().dbl().dbl().dbl();345 q = q.dbl().dbl().dbl().dbl();
346 }346 }
347 try q.rejectIdentity();
348 return q;347 return q;
349 }348 }
350349