authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-09-27 00:31:09+02:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-09-30 17:19:43+02:00
logb9d112d5a7b41f21aeaee17537000ce41eecab42
tree10dc58f132435be177d725472384709d3079e895
parent087ee6f4efce2bb635c11282a43b0014e56f366f

crypto.ecdsa: speed up signature verification

For ECDSA verification, we can use mulDoubleBasePublic() to combine the two scalar multiplication like we do for ed25519. But more importantly, we don't need a field inversion. The points store x as X/Z, so we can compare X with r*Z instead of dividing. A valid x can also be r + n, where n is the curve order, so we check (r + n)*Z too, when r + n stays below the field size. That makes verification 1.5x-2.5x faster depending on the curve. This optimization was suggest by Jack Boykin -- Thanks!

1 files changed, 12 insertions(+), 5 deletions(-)

lib/std/crypto/ecdsa.zig+12-5
...@@ -256,6 +256,7 @@ pub fn Ecdsa(comptime C: type, comptime H: type) type {...@@ -256,6 +256,7 @@ pub fn Ecdsa(comptime C: type, comptime H: type) type {
256 const r = try Curve.scalar.Scalar.fromBytes(sig.r, .big);256 const r = try Curve.scalar.Scalar.fromBytes(sig.r, .big);
257 const s = try Curve.scalar.Scalar.fromBytes(sig.s, .big);257 const s = try Curve.scalar.Scalar.fromBytes(sig.s, .big);
258 if (r.isZero() or s.isZero()) return error.IdentityElement;258 if (r.isZero() or s.isZero()) return error.IdentityElement;
259 try public_key.p.rejectIdentity();
259260
260 return Verifier{261 return Verifier{
261 .h = Hash.init(.{}),262 .h = Hash.init(.{}),
...@@ -287,13 +288,19 @@ pub fn Ecdsa(comptime C: type, comptime H: type) type {...@@ -287,13 +288,19 @@ pub fn Ecdsa(comptime C: type, comptime H: type) type {
287 const s_inv = self.s.invert();288 const s_inv = self.s.invert();
288 const v1 = z.mul(s_inv).toBytes(.little);289 const v1 = z.mul(s_inv).toBytes(.little);
289 const v2 = self.r.mul(s_inv).toBytes(.little);290 const v2 = self.r.mul(s_inv).toBytes(.little);
290 const v1g = try Curve.basePoint.mulPublic(v1, .little);291 const v = Curve.mulDoubleBasePublic(Curve.basePoint, v1, self.public_key.p, v2, .little) catch
291 const v2pk = try self.public_key.p.mulPublic(v2, .little);
292 const vxs = v1g.add(v2pk).affineCoordinates().x.toBytes(.big);
293 const vr = reduceToScalar(Curve.Fe.encoded_length, vxs);
294 if (!self.r.equivalent(vr)) {
295 return error.SignatureVerificationFailed;292 return error.SignatureVerificationFailed;
293
294 const Fe = Curve.Fe;
295 const n = Curve.scalar.field_order;
296 comptime std.debug.assert(n < Fe.field_order and Fe.field_order < 2 * n);
297 const r = Fe.fromBytes(self.r.toBytes(.little), .little) catch unreachable;
298 if (v.x.equivalent(r.mul(v.z))) return;
299 if (r.toInt() < Fe.field_order - n) {
300 const n_fe = comptime Fe.fromInt(n) catch unreachable;
301 if (v.x.equivalent(r.add(n_fe).mul(v.z))) return;
296 }302 }
303 return error.SignatureVerificationFailed;
297 }304 }
298305
299 /// Verify that the signature is valid for the entire message.306 /// Verify that the signature is valid for the entire message.