authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2025-04-20 04:27:10+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-04-20 04:27:10+02:00
logf01833e03eea786a05635cfbe142f581e9281b51
tree0abb8b88f7fb3cfb981fd60e3c828550cc025f4a
parent86d354618450d97b4a53735c0b222fcee655f7e8
signaturebadge-check Signed by PGP key B5690EEEBB952194

crypto.ecdsa: add the ability to sign/verify prehashed messages (#23607)


1 files changed, 65 insertions(+), 10 deletions(-)

lib/std/crypto/ecdsa.zig+65-10
...@@ -106,6 +106,14 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {...@@ -106,6 +106,14 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
106 try st.verify();106 try st.verify();
107 }107 }
108108
109 /// Verify the signature against a pre-hashed message and public key.
110 /// The message must have already been hashed using the scheme's hash function.
111 /// Returns SignatureVerificationError if the signature is invalid for the given message and key.
112 pub fn verifyPrehashed(sig: Signature, msg_hash: [Hash.digest_length]u8, public_key: PublicKey) VerifyError!void {
113 var st = try sig.verifier(public_key);
114 return st.verifyPrehashed(msg_hash);
115 }
116
109 /// Return the raw signature (r, s) in big-endian format.117 /// Return the raw signature (r, s) in big-endian format.
110 pub fn toBytes(sig: Signature) [encoded_length]u8 {118 pub fn toBytes(sig: Signature) [encoded_length]u8 {
111 var bytes: [encoded_length]u8 = undefined;119 var bytes: [encoded_length]u8 = undefined;
...@@ -203,18 +211,16 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {...@@ -203,18 +211,16 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
203 self.h.update(data);211 self.h.update(data);
204 }212 }
205213
206 /// Compute a signature over the entire message.214 /// Compute a signature over a hash.
207 pub fn finalize(self: *Signer) (IdentityElementError || NonCanonicalError)!Signature {215 fn finalizePrehashed(self: *Signer, msg_hash: [Hash.digest_length]u8) (IdentityElementError || NonCanonicalError)!Signature {
208 const scalar_encoded_length = Curve.scalar.encoded_length;216 const scalar_encoded_length = Curve.scalar.encoded_length;
209 const h_len = @max(Hash.digest_length, scalar_encoded_length);217 const h_len = @max(Hash.digest_length, scalar_encoded_length);
210 var h: [h_len]u8 = [_]u8{0} ** h_len;218 var h: [h_len]u8 = [_]u8{0} ** (h_len - Hash.digest_length) ++ msg_hash;
211 const h_slice = h[h_len - Hash.digest_length .. h_len];
212 self.h.final(h_slice);
213219
214 std.debug.assert(h.len >= scalar_encoded_length);220 std.debug.assert(h.len >= scalar_encoded_length);
215 const z = reduceToScalar(scalar_encoded_length, h[0..scalar_encoded_length].*);221 const z = reduceToScalar(scalar_encoded_length, h[0..scalar_encoded_length].*);
216222
217 const k = deterministicScalar(h_slice.*, self.secret_key.bytes, self.noise);223 const k = deterministicScalar(msg_hash, self.secret_key.bytes, self.noise);
218224
219 const p = try Curve.basePoint.mul(k.toBytes(.big), .big);225 const p = try Curve.basePoint.mul(k.toBytes(.big), .big);
220 const xs = p.affineCoordinates().x.toBytes(.big);226 const xs = p.affineCoordinates().x.toBytes(.big);
...@@ -228,6 +234,13 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {...@@ -228,6 +234,13 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
228234
229 return Signature{ .r = r.toBytes(.big), .s = s.toBytes(.big) };235 return Signature{ .r = r.toBytes(.big), .s = s.toBytes(.big) };
230 }236 }
237
238 /// Compute a signature over the entire message.
239 pub fn finalize(self: *Signer) (IdentityElementError || NonCanonicalError)!Signature {
240 var h_slice: [Hash.digest_length]u8 = undefined;
241 self.h.final(&h_slice);
242 return self.finalizePrehashed(h_slice);
243 }
231 };244 };
232245
233 /// A Verifier is used to incrementally verify a signature.246 /// A Verifier is used to incrementally verify a signature.
...@@ -261,12 +274,11 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {...@@ -261,12 +274,11 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
261 pub const VerifyError = IdentityElementError || NonCanonicalError ||274 pub const VerifyError = IdentityElementError || NonCanonicalError ||
262 SignatureVerificationError;275 SignatureVerificationError;
263276
264 /// Verify that the signature is valid for the entire message.277 /// Verify that the signature is valid for the hash.
265 pub fn verify(self: *Verifier) VerifyError!void {278 fn verifyPrehashed(self: *Verifier, msg_hash: [Hash.digest_length]u8) VerifyError!void {
266 const ht = Curve.scalar.encoded_length;279 const ht = Curve.scalar.encoded_length;
267 const h_len = @max(Hash.digest_length, ht);280 const h_len = @max(Hash.digest_length, ht);
268 var h: [h_len]u8 = [_]u8{0} ** h_len;281 var h: [h_len]u8 = [_]u8{0} ** (h_len - Hash.digest_length) ++ msg_hash;
269 self.h.final(h[h_len - Hash.digest_length .. h_len]);
270282
271 const z = reduceToScalar(ht, h[0..ht].*);283 const z = reduceToScalar(ht, h[0..ht].*);
272 if (z.isZero()) {284 if (z.isZero()) {
...@@ -284,6 +296,13 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {...@@ -284,6 +296,13 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
284 return error.SignatureVerificationFailed;296 return error.SignatureVerificationFailed;
285 }297 }
286 }298 }
299
300 /// Verify that the signature is valid for the entire message.
301 pub fn verify(self: *Verifier) VerifyError!void {
302 var h_slice: [Hash.digest_length]u8 = undefined;
303 self.h.final(&h_slice);
304 return self.verifyPrehashed(h_slice);
305 }
287 };306 };
288307
289 /// An ECDSA key pair.308 /// An ECDSA key pair.
...@@ -334,6 +353,14 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {...@@ -334,6 +353,14 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
334 return st.finalize();353 return st.finalize();
335 }354 }
336355
356 /// Sign a pre-hashed message using the key pair.
357 /// The message must have already been hashed using the scheme's hash function.
358 /// The noise parameter can be null for deterministic signatures, or random bytes for enhanced security against fault attacks.
359 pub fn signPrehashed(key_pair: KeyPair, msg_hash: [Hash.digest_length]u8, noise: ?[noise_length]u8) (IdentityElementError || NonCanonicalError)!Signature {
360 var st = try key_pair.signer(noise);
361 return st.finalizePrehashed(msg_hash);
362 }
363
337 /// Create a Signer, that can be used for incremental signature verification.364 /// Create a Signer, that can be used for incremental signature verification.
338 pub fn signer(key_pair: KeyPair, noise: ?[noise_length]u8) !Signer {365 pub fn signer(key_pair: KeyPair, noise: ?[noise_length]u8) !Signer {
339 return Signer.init(key_pair.secret_key, noise);366 return Signer.init(key_pair.secret_key, noise);
...@@ -475,6 +502,34 @@ test "Verifying a existing signature with EcdsaP384Sha256" {...@@ -475,6 +502,34 @@ test "Verifying a existing signature with EcdsaP384Sha256" {
475 try sig.verify(&msg, kp.public_key);502 try sig.verify(&msg, kp.public_key);
476}503}
477504
505test "Prehashed message operations" {
506 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
507
508 const Scheme = EcdsaP256Sha256;
509 const kp = Scheme.KeyPair.generate();
510 const msg = "test message for prehashed signing";
511
512 const Hash = crypto.hash.sha2.Sha256;
513 var msg_hash: [Hash.digest_length]u8 = undefined;
514 Hash.hash(msg, &msg_hash, .{});
515
516 const sig = try kp.signPrehashed(msg_hash, null);
517 try sig.verifyPrehashed(msg_hash, kp.public_key);
518
519 var bad_hash = msg_hash;
520 bad_hash[0] ^= 1;
521 try testing.expectError(error.SignatureVerificationFailed, sig.verifyPrehashed(bad_hash, kp.public_key));
522
523 var noise: [Scheme.noise_length]u8 = undefined;
524 crypto.random.bytes(&noise);
525 const sig_with_noise = try kp.signPrehashed(msg_hash, noise);
526 try sig_with_noise.verifyPrehashed(msg_hash, kp.public_key);
527
528 const regular_sig = try kp.sign(msg, null);
529 try regular_sig.verifyPrehashed(msg_hash, kp.public_key);
530 try sig.verify(msg, kp.public_key);
531}
532
478const TestVector = struct {533const TestVector = struct {
479 key: []const u8,534 key: []const u8,
480 msg: []const u8,535 msg: []const u8,