secp256k1: Reduce EC operation normalizes.

This modifies the various EC operations to reduce the overall number of
normalizations that are needed by requiring the caller to provide
normalized points instead of blindly normalizing the inputs.  It also
updates the operations to ensure the result is normalized along with
updating the comments to call out the new semantics and updating all of
the callers accordingly to ensure the new requirements are met.

This results in less overall normalizations because the caller has more
information about whether or not normalization is needed and can
therefore avoid it in many cases, particularly when it is using the
result that is already normalized as part of a chain of operations.

The following benchmarks show a before and after comparison of the
affected operations:

benchmark                         old ns/op   new ns/op   delta
-----------------------------------------------------------------
BenchmarkAddJacobian              569         442         -22.32%
BenchmarkAddJacobianNotZOne       1087        1080        -0.64%
BenchmarkScalarBaseMult           46966       46164       -1.71%
BenchmarkScalarBaseMultJacobian   34218       34165       -0.15%
BenchmarkScalarBaseMultLarge      47077       46819       -0.55%
BenchmarkScalarMult               155318      148019      -4.70%
BenchmarkSigVerify                176007      168422      -4.31%
BenchmarkSign                     57636       57561       -0.13%
BenchmarkSignCompact              57883       57653       -0.40%
BenchmarkRecoverCompact           206361      197419      -4.33%
This commit is contained in:
Dave Collins 2020-03-16 23:22:33 -05:00
parent 08498b8b24
commit 791fcd166b
No known key found for this signature in database
GPG Key ID: B8904D9D9C93D1F2
2 changed files with 35 additions and 18 deletions

View File

@ -100,6 +100,9 @@ func (p *jacobianPoint) ToAffine() {
// say result = p1 + p2. It performs faster addition than the generic add
// routine since less arithmetic is needed due to the ability to avoid the z
// value multiplications.
//
// NOTE: The points must be normalized for this function to return the correct
// result. The resulting point will be normalized.
func addZ1AndZ2EqualsOne(p1, p2, result *jacobianPoint) {
// To compute the point addition efficiently, this implementation splits
// the equation into intermediate elements which are used to minimize
@ -120,10 +123,6 @@ func addZ1AndZ2EqualsOne(p1, p2, result *jacobianPoint) {
// y coordinates either must be the same, in which case it is point
// doubling, or they are opposite and the result is the point at
// infinity per the group law for elliptic curve cryptography.
x1.Normalize()
y1.Normalize()
x2.Normalize()
y2.Normalize()
if x1.Equals(x2) {
if y1.Equals(y2) {
// Since x1 == x2 and y1 == y2, point doubling must be
@ -168,6 +167,9 @@ func addZ1AndZ2EqualsOne(p1, p2, result *jacobianPoint) {
// same z value and stores the result in the provided result param. That is to
// say result = p1 + p2. It performs faster addition than the generic add
// routine since less arithmetic is needed due to the known equivalence.
//
// NOTE: The points must be normalized for this function to return the correct
// result. The resulting point will be normalized.
func addZ1EqualsZ2(p1, p2, result *jacobianPoint) {
// To compute the point addition efficiently, this implementation splits
// the equation into intermediate elements which are used to minimize
@ -189,10 +191,6 @@ func addZ1EqualsZ2(p1, p2, result *jacobianPoint) {
// y coordinates either must be the same, in which case it is point
// doubling, or they are opposite and the result is the point at
// infinity per the group law for elliptic curve cryptography.
x1.Normalize()
y1.Normalize()
x2.Normalize()
y2.Normalize()
if x1.Equals(x2) {
if y1.Equals(y2) {
// Since x1 == x2 and y1 == y2, point doubling must be
@ -232,6 +230,7 @@ func addZ1EqualsZ2(p1, p2, result *jacobianPoint) {
// Normalize the resulting field values to a magnitude of 1 as needed.
x3.Normalize()
y3.Normalize()
z3.Normalize()
}
// addZ2EqualsOne adds two Jacobian points when the second point is already
@ -240,6 +239,9 @@ func addZ1EqualsZ2(p1, p2, result *jacobianPoint) {
// p1 + p2. It performs faster addition than the generic add routine since
// less arithmetic is needed due to the ability to avoid multiplications by the
// second point's z value.
//
// NOTE: The points must be normalized for this function to return the correct
// result. The resulting point will be normalized.
func addZ2EqualsOne(p1, p2, result *jacobianPoint) {
// To compute the point addition efficiently, this implementation splits
// the equation into intermediate elements which are used to minimize
@ -266,8 +268,6 @@ func addZ2EqualsOne(p1, p2, result *jacobianPoint) {
// the assumption made for this function that the second point has a z
// value of 1 (z2=1), the first point is already "converted".
var z1z1, u2, s2 fieldVal
x1.Normalize()
y1.Normalize()
z1z1.SquareVal(z1) // Z1Z1 = Z1^2 (mag: 1)
u2.Set(x2).Mul(&z1z1).Normalize() // U2 = X2*Z1Z1 (mag: 1)
s2.Set(y2).Mul(&z1z1).Mul(z1).Normalize() // S2 = Y2*Z1*Z1Z1 (mag: 1)
@ -319,6 +319,9 @@ func addZ2EqualsOne(p1, p2, result *jacobianPoint) {
// values of the two points and stores the result in the provided result param.
// That is to say result = p1 + p2. It is the slowest of the add routines due
// to requiring the most arithmetic.
//
// NOTE: The points must be normalized for this function to return the correct
// result. The resulting point will be normalized.
func addGeneric(p1, p2, result *jacobianPoint) {
// To compute the point addition efficiently, this implementation splits
// the equation into intermediate elements which are used to minimize
@ -391,10 +394,14 @@ func addGeneric(p1, p2, result *jacobianPoint) {
// Normalize the resulting field values to a magnitude of 1 as needed.
x3.Normalize()
y3.Normalize()
z3.Normalize()
}
// addJacobian adds the passed Jacobian points together and stores the result
// in the provided result param.
//
// NOTE: The points must be normalized for this function to return the correct
// result. The resulting point will be normalized.
func addJacobian(p1, p2, result *jacobianPoint) {
// A point at infinity is the identity according to the group law for
// elliptic curve cryptography. Thus, ∞ + P = P and P + ∞ = P.
@ -412,8 +419,6 @@ func addJacobian(p1, p2, result *jacobianPoint) {
// on the z values can be avoided. This section thus checks for these
// conditions and calls an appropriate add function which is accelerated
// by using those assumptions.
p1.z.Normalize()
p2.z.Normalize()
isZ1One := p1.z.IsOne()
isZ2One := p2.z.IsOne()
switch {
@ -438,6 +443,8 @@ func addJacobian(p1, p2, result *jacobianPoint) {
// the provided result param. That is to say result = 2*p. It performs faster
// point doubling than the generic routine since less arithmetic is needed due
// to the ability to avoid multiplication by the z value.
//
// NOTE: The resulting point will be normalized.
func doubleZ1EqualsOne(p, result *jacobianPoint) {
// This function uses the assumptions that z1 is 1, thus the point
// doubling formulas reduce to:
@ -490,6 +497,8 @@ func doubleZ1EqualsOne(p, result *jacobianPoint) {
// any assumptions about the z value and stores the result in the provided
// result param. That is to say result = 2*p. It is the slowest of the point
// doubling routines due to requiring the most arithmetic.
//
// NOTE: The resulting point will be normalized.
func doubleGeneric(p, result *jacobianPoint) {
// Point doubling formula for Jacobian coordinates for the secp256k1
// curve:
@ -540,6 +549,9 @@ func doubleGeneric(p, result *jacobianPoint) {
// doubleJacobian doubles the passed Jacobian point and stores the result in the
// provided result parameter.
//
// NOTE: The point must be normalized for this function to return the correct
// result. The resulting point will be normalized.
func doubleJacobian(p, result *jacobianPoint) {
// Doubling a point at infinity is still infinity.
if p.y.IsZero() || p.z.IsZero() {
@ -553,7 +565,7 @@ func doubleJacobian(p, result *jacobianPoint) {
// by avoiding the multiplication on the z value. This section calls
// a point doubling function which is accelerated by using that
// assumption when possible.
if p.z.Normalize().IsOne() {
if p.z.IsOne() {
doubleZ1EqualsOne(p, result)
return
}
@ -609,6 +621,9 @@ func splitK(k []byte) ([]byte, []byte, int, int) {
// scalarMultJacobian multiplies k*P where k is a big endian integer modulo the
// curve order and P is a point in Jacobian projective coordinates and stores
// the result in the provided Jacobian point.
//
// NOTE: The point must be normalized for this function to return the correct
// result. The resulting point will be normalized.
func scalarMultJacobian(k *ModNScalar, point, result *jacobianPoint) {
// Decompose K into k1 and k2 in order to halve the number of EC ops.
// See Algorithm 3.74 in [GECC].
@ -623,15 +638,15 @@ func scalarMultJacobian(k *ModNScalar, point, result *jacobianPoint) {
p1, p1Neg := new(jacobianPoint), new(jacobianPoint)
p1.Set(point)
p1Neg.Set(p1)
p1Neg.y.Negate(1)
p1Neg.y.Negate(1).Normalize()
// NOTE: ϕ(x,y) = (βx,y). The Jacobian z coordinates are the same, so this
// math goes through.
p2, p2Neg := new(jacobianPoint), new(jacobianPoint)
p2.Set(p1)
p2.x.Mul(endomorphismBeta)
p2.x.Mul(endomorphismBeta).Normalize()
p2Neg.Set(p2)
p2Neg.y.Negate(1)
p2Neg.y.Negate(1).Normalize()
// Flip the positive and negative values of the points as needed
// depending on the signs of k1 and k2. As mentioned in the equation
@ -713,6 +728,8 @@ func scalarMultJacobian(k *ModNScalar, point, result *jacobianPoint) {
// scalarBaseMultJacobian multiplies k*G where G is the base point of the group
// and k is a big endian integer. The result is stored in Jacobian coordinates
// (x1, y1, z1).
//
// NOTE: The resulting point will be normalized.
func scalarBaseMultJacobian(k *ModNScalar, result *jacobianPoint) {
bytePoints := S256().bytePoints

View File

@ -753,8 +753,8 @@ func RecoverCompact(signature, hash []byte) (*PublicKey, bool, error) {
//
// X = (r, y)
var X jacobianPoint
X.x.Set(&fieldR)
X.y.Set(&y)
X.x.Set(&fieldR).Normalize()
X.y.Set(&y).Normalize()
X.z.SetInt(1)
// Step 6.