From 08498b8b24b3dfe491bcbe6963d5b874fbcd90eb Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Mon, 16 Mar 2020 20:28:38 -0500 Subject: [PATCH] secp256k1: Avoid inversion in sig verify. This modifies signature verification to perform the final equality check in Jacobian projective space to avoid an extra expensive field inversion by taking advantage of the fact the R component of the signature can be converted to Jacobian projective space and compared directly so long as the two possible cases that arise from R being mod N and the original x coordinate used to create it being mod P. The following is a before and after comparison of verification of a typical signature: benchmark old ns/op new ns/op delta ----------------------------------------------------- BenchmarkSigVerify 189505 177147 -6.52% benchmark old allocs new allocs delta ----------------------------------------------------- BenchmarkSigVerify 34 34 +0.00% benchmark old bytes new bytes delta ----------------------------------------------------- BenchmarkSigVerify 1633 1633 +0.00% --- dcrec/secp256k1/signature.go | 65 +++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/dcrec/secp256k1/signature.go b/dcrec/secp256k1/signature.go index 464031ab..a65768ef 100644 --- a/dcrec/secp256k1/signature.go +++ b/dcrec/secp256k1/signature.go @@ -187,6 +187,41 @@ func (sig *Signature) Verify(hash []byte, pubKey *PublicKey) bool { // 6. Fail if X is the point at infinity // 7. x = X.x mod N (X.x is the x coordinate of X) // 8. Verified if x == R + // + // However, since all group operations are done internally in Jacobian + // projective space, the algorithm is modified slightly here in order to + // avoid an expensive inversion back into affine coordinates at step 7. + // Credits to Greg Maxwell for originally suggesting this optimization. + // + // Ordinarily, step 7 involves converting the x coordinate to affine by + // calculating x = x / z^2 (mod P) and then calculating the remainder as + // x = x (mod N). Then step 8 compares it to R. + // + // Note that since R is the x coordinate mod N from a random point that was + // originally mod P, and the cofactor of the secp256k1 curve is 1, there are + // only two possible x coordinates that the original random point could have + // been to produce R: x, where x < N, and x+N, where x+N < P. + // + // This implies that the signature is valid if either: + // a) R == X.x / X.z^2 (mod P) + // => R * X.z^2 == X.x (mod P) + // --or-- + // b) R + N < P && R + N == X.x / X.z^2 (mod P) + // => R + N < P && (R + N) * X.z^2 == X.x (mod P) + // + // Therefore the following modified algorithm is used: + // + // 1. Fail if R and S are not in [1, N-1] + // 2. e = H(m) + // 3. w = S^-1 mod N + // 4. u1 = e * w mod N + // u2 = R * w mod N + // 5. X = u1G + u2Q + // 6. Fail if X is the point at infinity + // 7. z = (X.z)^2 mod P (X.z is the z coordinate of X) + // 8. Verified if R * z == X.x (mod P) + // 9. Fail if R + N >= P + // 10. Verified if (R + N) * z == X.x (mod P) // Step 1. // @@ -231,17 +266,31 @@ func (sig *Signature) Verify(hash []byte, pubKey *PublicKey) bool { // Step 7. // - // x = X.x mod N (X.x is the x coordinate of X) - // - // Note that the point must be in affine coordinates since R is in affine - // coordinates. - X.ToAffine() - x, _ := fieldToModNScalar(&X.x) + // z = (X.z)^2 mod P (X.z is the z coordinate of X) + z := new(fieldVal).SquareVal(&X.z) // Step 8. // - // Verified if x == R - return x.Equals(&sig.r) + // Verified if R * z == X.x (mod P) + sigRModP := modNScalarToField(&sig.r) + result := new(fieldVal).Mul2(&sigRModP, z).Normalize() + if result.Equals(&X.x) { + return true + } + + // Step 9. + // + // Fail if R + N >= P + if sigRModP.IsGtOrEqPrimeMinusOrder() { + return false + } + + // Step 10. + // + // Verified if (R + N) * z == X.x (mod P) + sigRModP.Add(orderAsFieldVal) + result.Mul2(&sigRModP, z).Normalize() + return result.Equals(&X.x) } // IsEqual compares this Signature instance to the one passed, returning true if