secp256k1: Optimize precomp values to use affine.

This optimizes the pre-computed byte points used to accelerate scalar
base multiplication to store the data in affine coordinates instead of
Jacobian coordinates which reduces the memory usage requirement to 66%
of what it current requires and also has the important benefit of
further speeding up the computation.

This is the case because projecting affine coordinates into Jacobian
space is essentially free and the point doubling and addition routines
have optimizations which allow them to avoid additional operations when
the Z coordinate is 1, which is the case for an initial affine
projection.

Further, since the compressed table is stored in the string table of the
binary, it also reduces the size the of final binary by ~385KiB.

The following benchmark shows a before and after comparison of scalar
base multiplication as well as how that translates to signature
verification:

name                       old time/op    new time/op    delta
-------------------------------------------------------------------------
ScalarBaseMult        34.5µs ± 1%   24.7µs ± 1%   -28.43% (p=0.008 n=5+5)
ScalarBaseMultLarge   48.2µs ± 1%   38.0µs ± 1%   -21.08% (p=0.008 n=5+5)
SigVerify              181µs ± 5%    163µs ± 2%    -9.86% (p=0.008 n=5+5)

While 18 µs less per signature verification might not seem like much on
the surface, consider that every transaction requires at least one
signature operation, so there are a ton of them when doing no checkpoint
syncs.  For a concrete number, verifying 100 million signatures would
take 30 minutes less time.
This commit is contained in:
Dave Collins 2021-07-31 03:16:48 -05:00
parent 8cef307d9e
commit fdfae1afd4
No known key found for this signature in database
GPG Key ID: B8904D9D9C93D1F2
4 changed files with 16 additions and 22 deletions

File diff suppressed because one or more lines are too long

View File

@ -763,7 +763,7 @@ func ScalarBaseMultNonConst(k *ModNScalar, result *JacobianPoint) {
p := bytePoints[i][byteVal]
pt.X.Set(&p[0])
pt.Y.Set(&p[1])
pt.Z.Set(&p[2])
pt.Z.SetInt(1)
AddNonConst(&q, &pt, &q)
}

View File

@ -38,33 +38,32 @@ func SerializedBytePoints() []byte {
// Separate the bits into byte-sized windows.
curveByteSize := curveParams.BitSize / 8
serialized := make([]byte, curveByteSize*256*3*10*4)
serialized := make([]byte, curveByteSize*256*2*10*4)
offset := 0
for byteNum := 0; byteNum < curveByteSize; byteNum++ {
// Grab the 8 bits that make up this byte from doublingPoints.
// Grab the 8 bits that make up this byte from doubling points.
startingBit := 8 * (curveByteSize - byteNum - 1)
computingPoints := doublingPoints[startingBit : startingBit+8]
windowPoints := doublingPoints[startingBit : startingBit+8]
// Compute all points in this window and serialize them.
// Compute all points in this window, convert them to affine, and
// serialize them.
for i := 0; i < 256; i++ {
var point JacobianPoint
for j := 0; j < 8; j++ {
if i>>uint(j)&1 == 1 {
AddNonConst(&point, &computingPoints[j], &point)
for bit := 0; bit < 8; bit++ {
if i>>uint(bit)&1 == 1 {
AddNonConst(&point, &windowPoints[bit], &point)
}
}
for i := 0; i < 10; i++ {
point.ToAffine()
for i := 0; i < len(point.X.n); i++ {
binary.LittleEndian.PutUint32(serialized[offset:], point.X.n[i])
offset += 4
}
for i := 0; i < 10; i++ {
for i := 0; i < len(point.Y.n); i++ {
binary.LittleEndian.PutUint32(serialized[offset:], point.Y.n[i])
offset += 4
}
for i := 0; i < 10; i++ {
binary.LittleEndian.PutUint32(serialized[offset:], point.Z.n[i])
offset += 4
}
}
}

View File

@ -18,7 +18,7 @@ import (
// bytePointTable describes a table used to house pre-computed values for
// accelerating scalar base multiplication.
type bytePointTable [32][256][3]FieldVal
type bytePointTable [32][256][2]FieldVal
// s256BytePoints houses pre-computed values used to accelerate scalar base
// multiplication such that they are only loaded on first use.
@ -64,7 +64,6 @@ var s256BytePoints = func() func() *bytePointTable {
for i := 0; i < len(bytePoints[byteNum]); i++ {
px := &bytePoints[byteNum][i][0]
py := &bytePoints[byteNum][i][1]
pz := &bytePoints[byteNum][i][2]
for i := 0; i < len(px.n); i++ {
px.n[i] = binary.LittleEndian.Uint32(serialized[offset:])
offset += 4
@ -73,10 +72,6 @@ var s256BytePoints = func() func() *bytePointTable {
py.n[i] = binary.LittleEndian.Uint32(serialized[offset:])
offset += 4
}
for i := 0; i < len(pz.n); i++ {
pz.n[i] = binary.LittleEndian.Uint32(serialized[offset:])
offset += 4
}
}
}
data = &bytePoints