Skip to content

Commit f01833e

Browse files
authored
crypto.ecdsa: add the ability to sign/verify prehashed messages (ziglang#23607)
1 parent 86d3546 commit f01833e

File tree

1 file changed

+65
-10
lines changed

1 file changed

+65
-10
lines changed

lib/std/crypto/ecdsa.zig

+65-10
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
106106
try st.verify();
107107
}
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+
109117
/// Return the raw signature (r, s) in big-endian format.
110118
pub fn toBytes(sig: Signature) [encoded_length]u8 {
111119
var bytes: [encoded_length]u8 = undefined;
@@ -203,18 +211,16 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
203211
self.h.update(data);
204212
}
205213

206-
/// Compute a signature over the entire message.
207-
pub fn finalize(self: *Signer) (IdentityElementError || NonCanonicalError)!Signature {
214+
/// Compute a signature over a hash.
215+
fn finalizePrehashed(self: *Signer, msg_hash: [Hash.digest_length]u8) (IdentityElementError || NonCanonicalError)!Signature {
208216
const scalar_encoded_length = Curve.scalar.encoded_length;
209217
const h_len = @max(Hash.digest_length, scalar_encoded_length);
210-
var h: [h_len]u8 = [_]u8{0} ** h_len;
211-
const h_slice = h[h_len - Hash.digest_length .. h_len];
212-
self.h.final(h_slice);
218+
var h: [h_len]u8 = [_]u8{0} ** (h_len - Hash.digest_length) ++ msg_hash;
213219

214220
std.debug.assert(h.len >= scalar_encoded_length);
215221
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

219225
const p = try Curve.basePoint.mul(k.toBytes(.big), .big);
220226
const xs = p.affineCoordinates().x.toBytes(.big);
@@ -228,6 +234,13 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
228234

229235
return Signature{ .r = r.toBytes(.big), .s = s.toBytes(.big) };
230236
}
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+
}
231244
};
232245

233246
/// A Verifier is used to incrementally verify a signature.
@@ -261,12 +274,11 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
261274
pub const VerifyError = IdentityElementError || NonCanonicalError ||
262275
SignatureVerificationError;
263276

264-
/// Verify that the signature is valid for the entire message.
265-
pub fn verify(self: *Verifier) VerifyError!void {
277+
/// Verify that the signature is valid for the hash.
278+
fn verifyPrehashed(self: *Verifier, msg_hash: [Hash.digest_length]u8) VerifyError!void {
266279
const ht = Curve.scalar.encoded_length;
267280
const h_len = @max(Hash.digest_length, ht);
268-
var h: [h_len]u8 = [_]u8{0} ** h_len;
269-
self.h.final(h[h_len - Hash.digest_length .. h_len]);
281+
var h: [h_len]u8 = [_]u8{0} ** (h_len - Hash.digest_length) ++ msg_hash;
270282

271283
const z = reduceToScalar(ht, h[0..ht].*);
272284
if (z.isZero()) {
@@ -284,6 +296,13 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
284296
return error.SignatureVerificationFailed;
285297
}
286298
}
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+
}
287306
};
288307

289308
/// An ECDSA key pair.
@@ -334,6 +353,14 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
334353
return st.finalize();
335354
}
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+
337364
/// Create a Signer, that can be used for incremental signature verification.
338365
pub fn signer(key_pair: KeyPair, noise: ?[noise_length]u8) !Signer {
339366
return Signer.init(key_pair.secret_key, noise);
@@ -475,6 +502,34 @@ test "Verifying a existing signature with EcdsaP384Sha256" {
475502
try sig.verify(&msg, kp.public_key);
476503
}
477504

505+
test "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+
478533
const TestVector = struct {
479534
key: []const u8,
480535
msg: []const u8,

0 commit comments

Comments
 (0)