From b9d112d5a7b41f21aeaee17537000ce41eecab42 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sun, 27 Sep 2026 00:31:09 +0200 Subject: [PATCH] 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! --- lib/std/crypto/ecdsa.zig | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/std/crypto/ecdsa.zig b/lib/std/crypto/ecdsa.zig index 77e0349341168fd3d6e0b68979d95cf45753bc4f..d2b6c0c2f82e0f6dc7681fae545813d00c665f43 100644 --- a/lib/std/crypto/ecdsa.zig +++ b/lib/std/crypto/ecdsa.zig @@ -256,6 +256,7 @@ pub fn Ecdsa(comptime C: type, comptime H: type) type { const r = try Curve.scalar.Scalar.fromBytes(sig.r, .big); const s = try Curve.scalar.Scalar.fromBytes(sig.s, .big); if (r.isZero() or s.isZero()) return error.IdentityElement; + try public_key.p.rejectIdentity(); return Verifier{ .h = Hash.init(.{}), @@ -287,13 +288,19 @@ pub fn Ecdsa(comptime C: type, comptime H: type) type { const s_inv = self.s.invert(); const v1 = z.mul(s_inv).toBytes(.little); const v2 = self.r.mul(s_inv).toBytes(.little); - const v1g = try Curve.basePoint.mulPublic(v1, .little); - const v2pk = try self.public_key.p.mulPublic(v2, .little); - const vxs = v1g.add(v2pk).affineCoordinates().x.toBytes(.big); - const vr = reduceToScalar(Curve.Fe.encoded_length, vxs); - if (!self.r.equivalent(vr)) { + const v = Curve.mulDoubleBasePublic(Curve.basePoint, v1, self.public_key.p, v2, .little) catch return error.SignatureVerificationFailed; + + const Fe = Curve.Fe; + const n = Curve.scalar.field_order; + comptime std.debug.assert(n < Fe.field_order and Fe.field_order < 2 * n); + const r = Fe.fromBytes(self.r.toBytes(.little), .little) catch unreachable; + if (v.x.equivalent(r.mul(v.z))) return; + if (r.toInt() < Fe.field_order - n) { + const n_fe = comptime Fe.fromInt(n) catch unreachable; + if (v.x.equivalent(r.add(n_fe).mul(v.z))) return; } + return error.SignatureVerificationFailed; } /// Verify that the signature is valid for the entire message. -- 2.54.0