| ... | @@ -179,13 +179,49 @@ pub const Ed25519 = struct { | ... | @@ -179,13 +179,49 @@ pub const Ed25519 = struct { |
| 179 | SignatureVerificationError; | 179 | SignatureVerificationError; |
| 180 | | 180 | |
| 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 | } |
| 186 | | 202 | |
| 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; |
| 227 | | 263 | |
| 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 | }; |
| 237 | | 294 | |
| 238 | /// An Ed25519 key pair. | 295 | /// An Ed25519 key pair. |
| ... | @@ -556,7 +613,7 @@ test "batch verification" { | ... | @@ -556,7 +613,7 @@ test "batch verification" { |
| 556 | | 613 | |
| 557 | test "test vectors" { | 614 | test "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 | |
| | 763 | test "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 | |
| | 787 | test "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 | } |