| ... | @@ -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 | } |
| 108 | | 108 | |
| | 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 | } |
| 205 | | 213 | |
| 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); | | |
| 213 | | 219 | |
| 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].*); |
| 216 | | 222 | |
| 217 | const k = deterministicScalar(h_slice.*, self.secret_key.bytes, self.noise); | 223 | const k = deterministicScalar(msg_hash, self.secret_key.bytes, self.noise); |
| 218 | | 224 | |
| 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 { |
| 228 | | 234 | |
| 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 | }; |
| 232 | | 245 | |
| 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; |
| 263 | | 276 | |
| 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]); | | |
| 270 | | 282 | |
| 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 | }; |
| 288 | | 307 | |
| 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 | } |
| 336 | | 355 | |
| | 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 | } |
| 477 | | 504 | |
| | 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 | |
| 478 | const TestVector = struct { | 533 | const TestVector = struct { |
| 479 | key: []const u8, | 534 | key: []const u8, |
| 480 | msg: []const u8, | 535 | msg: []const u8, |