Optimize ScalarBaseMult
Code uses a windowing/precomputing strategy to minimize ECC math. Every 8-bit window of the 256 bits that compose a possible scalar multiple has a complete map that's pre-computed. The precomputed data is in secp256k1.go and the generator for that file is in gensecp256k1.go Also fixed a spelling error in a benchmark test. Results so far seem to indicate the time taken is about 35% of what it was before. Closes #2
This commit is contained in:
parent
4ca0daacc1
commit
d69442834c
5 changed files with 41149 additions and 5 deletions
|
@ -48,9 +48,9 @@ func BenchmarkAddJacobianNotZOne(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
// BechmarkScalarBaseMult benchmarks the secp256k1 curve ScalarBaseMult
|
||||
// BenchmarkScalarBaseMult benchmarks the secp256k1 curve ScalarBaseMult
|
||||
// function.
|
||||
func BechmarkScalarBaseMult(b *testing.B) {
|
||||
func BenchmarkScalarBaseMult(b *testing.B) {
|
||||
k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
|
||||
curve := btcec.S256()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
|
29
btcec.go
29
btcec.go
|
@ -37,8 +37,9 @@ var (
|
|||
// interface from crypto/elliptic.
|
||||
type KoblitzCurve struct {
|
||||
*elliptic.CurveParams
|
||||
q *big.Int
|
||||
H int // cofactor of the curve.
|
||||
q *big.Int
|
||||
H int // cofactor of the curve.
|
||||
bytePoints *[32][256][3]fieldVal
|
||||
}
|
||||
|
||||
// Params returns the parameters for the curve.
|
||||
|
@ -627,7 +628,28 @@ func (curve *KoblitzCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big
|
|||
// big endian integer.
|
||||
// Part of the elliptic.Curve interface.
|
||||
func (curve *KoblitzCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
|
||||
return curve.ScalarMult(curve.Gx, curve.Gy, k)
|
||||
// Fall back to slower generic scalar point multiplication when the integer is
|
||||
// larger than what can be used with the precomputed table which enables
|
||||
// accelerated multiplication by the known fixed point.
|
||||
if len(k) > len(curve.bytePoints) {
|
||||
return curve.ScalarMult(curve.Gx, curve.Gy, k)
|
||||
}
|
||||
|
||||
diff := len(curve.bytePoints) - len(k)
|
||||
|
||||
// Point Q = ∞ (point at infinity).
|
||||
qx, qy, qz := new(fieldVal), new(fieldVal), new(fieldVal)
|
||||
|
||||
// curve.bytePoints has all 256 byte points for each 8-bit window. The
|
||||
// strategy is to add up the byte points. This is best understood by
|
||||
// expressing k in base-256 which it already sort of is.
|
||||
// Each "digit" in the 8-bit window can be looked up using bytePoints
|
||||
// and added together.
|
||||
for i, byteVal := range k {
|
||||
point := &curve.bytePoints[diff+i][byteVal]
|
||||
curve.addJacobian(qx, qy, qz, &point[0], &point[1], &point[2], qx, qy, qz)
|
||||
}
|
||||
return curve.fieldJacobianToBigAffine(qx, qy, qz)
|
||||
}
|
||||
|
||||
// QPlus1Div4 returns the Q+1/4 constant for the curve for use in calculating
|
||||
|
@ -656,6 +678,7 @@ func initS256() {
|
|||
secp256k1.H = 1
|
||||
secp256k1.q = new(big.Int).Div(new(big.Int).Add(secp256k1.P,
|
||||
big.NewInt(1)), big.NewInt(4))
|
||||
secp256k1.bytePoints = &secp256k1BytePoints
|
||||
}
|
||||
|
||||
// S256 returns a Curve which implements secp256k1.
|
||||
|
|
|
@ -555,6 +555,28 @@ func TestBaseMult(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBaseMultVerify(t *testing.T) {
|
||||
s256 := btcec.S256()
|
||||
for bytes := 1; bytes < 35; bytes++ {
|
||||
for i := 0; i < 30; i++ {
|
||||
data := make([]byte, bytes)
|
||||
_, err := rand.Read(data)
|
||||
if err != nil {
|
||||
t.Errorf("failed to read random data for %s", i)
|
||||
continue
|
||||
}
|
||||
x, y := s256.ScalarBaseMult(data)
|
||||
xWant, yWant := s256.ScalarMult(s256.Gx, s256.Gy, data)
|
||||
if x.Cmp(xWant) != 0 || y.Cmp(yWant) != 0 {
|
||||
t.Errorf("%d: bad output for %X: got (%X, %X), want (%X, %X)", i, data, x, y, xWant, yWant)
|
||||
}
|
||||
if testing.Short() && i > 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: test more curves?
|
||||
func BenchmarkBaseMult(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
|
66
gensecp256k1.go
Normal file
66
gensecp256k1.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) 2014 Conformal Systems LLC.
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
// +build gensecp256k1
|
||||
|
||||
package btcec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// getDoublingPoints returns all the possible G^(2^i) for i in
|
||||
// 0..n-1 where n is the curve's bit size (256 in the case of secp256k1)
|
||||
// the coordinates are recorded as Jacobian coordinates.
|
||||
func (curve *KoblitzCurve) getDoublingPoints() [][3]fieldVal {
|
||||
bitSize := curve.Params().BitSize
|
||||
doublingPoints := make([][3]fieldVal, bitSize)
|
||||
|
||||
// initialize px, py, pz to the Jacobian coordinates for the base point
|
||||
px, py := curve.bigAffineToField(curve.Gx, curve.Gy)
|
||||
pz := new(fieldVal).SetInt(1)
|
||||
for i := 0; i < bitSize; i++ {
|
||||
doublingPoints[i] = [3]fieldVal{*px, *py, *pz}
|
||||
// P = 2*P
|
||||
curve.doubleJacobian(px, py, pz, px, py, pz)
|
||||
}
|
||||
return doublingPoints
|
||||
}
|
||||
|
||||
// PrintBytePoints prints all the possible points per 8-bit window.
|
||||
// normally, this is used to generate secp256k1.go
|
||||
func (curve *KoblitzCurve) PrintBytePoints() {
|
||||
bitSize := curve.Params().BitSize
|
||||
byteSize := bitSize / 8
|
||||
doublingPoints := curve.getDoublingPoints()
|
||||
fmt.Println("// Copyright (c) 2014 Conformal Systems LLC.")
|
||||
fmt.Println("// Use of this source code is governed by an ISC")
|
||||
fmt.Println("// license that can be found in the LICENSE file.")
|
||||
fmt.Println()
|
||||
fmt.Println("package btcec")
|
||||
fmt.Println()
|
||||
fmt.Println("// Auto-generated file (see gensecp256k1.go)")
|
||||
fmt.Printf("var secp256k1BytePoints = [%d][256][3]fieldVal{\n", byteSize)
|
||||
// Segregate the bits into byte-sized windows
|
||||
for byteNum := 0; byteNum < byteSize; byteNum++ {
|
||||
fmt.Printf("\t{\n")
|
||||
// grab the 8 bits that make up this byte from doublingPoints
|
||||
startingBit := 8 * (byteSize - byteNum - 1)
|
||||
computingPoints := doublingPoints[startingBit : startingBit+8]
|
||||
// compute all points in this window
|
||||
for i := 0; i < 256; i++ {
|
||||
px, py, pz := new(fieldVal), new(fieldVal), new(fieldVal)
|
||||
for j := 0; j < 8; j++ {
|
||||
if i>>uint(j)&1 == 1 {
|
||||
curve.addJacobian(px, py, pz, &computingPoints[j][0],
|
||||
&computingPoints[j][1], &computingPoints[j][2], px, py, pz)
|
||||
}
|
||||
}
|
||||
fmt.Printf("\t\t{\n\t\t\tfieldVal{[10]uint32{%d, %d, %d, %d, %d, %d, %d, %d, %d, %d}},\n", px.n[0], px.n[1], px.n[2], px.n[3], px.n[4], px.n[5], px.n[6], px.n[7], px.n[8], px.n[9])
|
||||
fmt.Printf("\t\t\tfieldVal{[10]uint32{%d, %d, %d, %d, %d, %d, %d, %d, %d, %d}},\n", py.n[0], py.n[1], py.n[2], py.n[3], py.n[4], py.n[5], py.n[6], py.n[7], py.n[8], py.n[9])
|
||||
fmt.Printf("\t\t\tfieldVal{[10]uint32{%d, %d, %d, %d, %d, %d, %d, %d, %d, %d}},\n\t\t},\n", pz.n[0], pz.n[1], pz.n[2], pz.n[3], pz.n[4], pz.n[5], pz.n[6], pz.n[7], pz.n[8], pz.n[9])
|
||||
}
|
||||
fmt.Printf("\t},\n")
|
||||
}
|
||||
fmt.Printf("}\n")
|
||||
}
|
41033
secp256k1.go
Normal file
41033
secp256k1.go
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue