Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

secp256k1: Optimize precomp values to use affine. #2690

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions dcrec/secp256k1/compressedbytepoints.go

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dcrec/secp256k1/curve.go
@@ -1,4 +1,4 @@
// Copyright (c) 2015-2020 The Decred developers
// Copyright (c) 2015-2021 The Decred developers
// Copyright 2013-2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
Expand Down Expand Up @@ -749,7 +749,7 @@ func ScalarMultNonConst(k *ModNScalar, point, result *JacobianPoint) {
//
// NOTE: The resulting point will be normalized.
func ScalarBaseMultNonConst(k *ModNScalar, result *JacobianPoint) {
bytePoints := S256().bytePoints
bytePoints := s256BytePoints()

// Point Q = ∞ (point at infinity).
var q JacobianPoint
Expand All @@ -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)
}

Expand Down
38 changes: 7 additions & 31 deletions dcrec/secp256k1/ellipticadaptor.go
@@ -1,4 +1,4 @@
// Copyright 2020 The Decred developers
// Copyright 2020-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand All @@ -14,7 +14,6 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"math/big"
"sync"
)

// CurveParams contains the parameters for the secp256k1 curve.
Expand Down Expand Up @@ -59,9 +58,6 @@ func Params() *CurveParams {
// interface from crypto/elliptic.
type KoblitzCurve struct {
*elliptic.CurveParams

// bytePoints
bytePoints *[32][256][3]FieldVal
}

// bigAffineToJacobian takes an affine point (x, y) as big integers and converts
Expand Down Expand Up @@ -236,41 +232,21 @@ func fromHex(s string) *big.Int {
return r
}

var (
// initonce is used to initialize the global secp256k1 koblitz curve instance
// dynamically upon first access.
initonce sync.Once

// secp256k1 is a global instance of the KoblitzCurve implementation which
// in turn embeds and implements elliptic.CurveParams.
secp256k1 KoblitzCurve
)

// initS256 initializes the global secp256k1 instance that gets returned by the
// S256 function which provides access to the fields and methods provided by
// elliptic.CurveParms.
func initS256() {
// Curve parameters taken from [SECG] section 2.4.1.
secp256k1.CurveParams = &elliptic.CurveParams{
// secp256k1 is a global instance of the KoblitzCurve implementation which in
// turn embeds and implements elliptic.CurveParams.
var secp256k1 = &KoblitzCurve{
CurveParams: &elliptic.CurveParams{
P: curveParams.P,
N: curveParams.N,
B: fromHex("0000000000000000000000000000000000000000000000000000000000000007"),
Gx: curveParams.Gx,
Gy: curveParams.Gy,
BitSize: curveParams.BitSize,
Name: "secp256k1",
}

// Deserialize and set the pre-computed table used to accelerate scalar
// base multiplication. This is hard-coded data, so any errors are
// panics because it means something is wrong in the source code.
if err := loadBytePoints(); err != nil {
panic(err)
}
},
}

// S256 returns a Curve which implements secp256k1.
func S256() *KoblitzCurve {
initonce.Do(initS256)
return &secp256k1
return secp256k1
}
6 changes: 3 additions & 3 deletions dcrec/secp256k1/genprecomps.go
@@ -1,5 +1,5 @@
// Copyright 2015 The btcsuite developers
// Copyright (c) 2015-2020 The Decred developers
// Copyright (c) 2015-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -29,7 +29,7 @@ func main() {
defer fi.Close()

// Compress the serialized byte points.
serialized := secp256k1.S256().SerializedBytePoints()
serialized := secp256k1.SerializedBytePoints()
var compressed bytes.Buffer
w := zlib.NewWriter(&compressed)
if _, err := w.Write(serialized); err != nil {
Expand All @@ -43,7 +43,7 @@ func main() {
base64.StdEncoding.Encode(encoded, compressed.Bytes())

fmt.Fprintln(fi, "// Copyright (c) 2015 The btcsuite developers")
fmt.Fprintln(fi, "// Copyright (c) 2015-2020 The Decred developers")
fmt.Fprintln(fi, "// Copyright (c) 2015-2021 The Decred developers")
fmt.Fprintln(fi, "// Use of this source code is governed by an ISC")
fmt.Fprintln(fi, "// license that can be found in the LICENSE file.")
fmt.Fprintln(fi)
Expand Down
61 changes: 26 additions & 35 deletions dcrec/secp256k1/genstatics.go
Expand Up @@ -21,58 +21,49 @@ import (
// real values can compile.
var compressedBytePoints = ""

// 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() []JacobianPoint {
doublingPoints := make([]JacobianPoint, curve.BitSize)

// Initialize point to the Jacobian coordinates for the base point.
var point JacobianPoint
bigAffineToJacobian(curve.Gx, curve.Gy, &point)
for i := 0; i < curve.BitSize; i++ {
doublingPoints[i] = point
// P = 2*P
DoubleNonConst(&point, &point)
}
return doublingPoints
}

// SerializedBytePoints returns a serialized byte slice which contains all of
// the possible points per 8-bit window. This is used to when generating
// secp256k1.go.
func (curve *KoblitzCurve) SerializedBytePoints() []byte {
doublingPoints := curve.getDoublingPoints()
// compressedbytepoints.go.
func SerializedBytePoints() []byte {
// Calculate G^(2^i) for i in 0..255. These are used to avoid recomputing
// them for each digit of the 8-bit windows.
doublingPoints := make([]JacobianPoint, curveParams.BitSize)
var q JacobianPoint
bigAffineToJacobian(curveParams.Gx, curveParams.Gy, &q)
for i := 0; i < curveParams.BitSize; i++ {
// Q = 2*Q.
doublingPoints[i] = q
DoubleNonConst(&q, &q)
}

// Segregate the bits into byte-sized windows
curveByteSize := curve.BitSize / 8
serialized := make([]byte, curveByteSize*256*3*10*4)
// Separate the bits into byte-sized windows.
curveByteSize := curveParams.BitSize / 8
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
}
}
}

Expand Down
117 changes: 70 additions & 47 deletions dcrec/secp256k1/loadprecomputed.go
@@ -1,5 +1,5 @@
// Copyright 2015 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Copyright (c) 2015-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand All @@ -11,58 +11,81 @@ import (
"encoding/binary"
"io/ioutil"
"strings"
"sync"
)

//go:generate go run -tags gensecp256k1 genprecomps.go

// loadBytePoints decompresses and deserializes the pre-computed byte points
// used to accelerate scalar base multiplication for the secp256k1 curve. This
// approach is used since it allows the compile to use significantly less ram
// and be performed much faster than it is with hard-coding the final in-memory
// data structure. At the same time, it is quite fast to generate the in-memory
// data structure at init time with this approach versus computing the table.
func loadBytePoints() error {
// There will be no byte points to load when generating them.
bp := compressedBytePoints
if len(bp) == 0 {
return nil
}
// bytePointTable describes a table used to house pre-computed values for
// accelerating scalar base multiplication.
type bytePointTable [32][256][2]FieldVal

// Decompress the pre-computed table used to accelerate scalar base
// multiplication.
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(bp))
r, err := zlib.NewReader(decoder)
if err != nil {
return err
}
serialized, err := ioutil.ReadAll(r)
if err != nil {
return err
}
// s256BytePoints houses pre-computed values used to accelerate scalar base
// multiplication such that they are only loaded on first use.
var s256BytePoints = func() func() *bytePointTable {
davecgh marked this conversation as resolved.
Show resolved Hide resolved
// mustLoadBytePoints decompresses and deserializes the pre-computed byte
// points used to accelerate scalar base multiplication for the secp256k1
// curve.
//
// This approach is used since it allows the compile to use significantly
// less ram and be performed much faster than it is with hard-coding the
// final in-memory data structure. At the same time, it is quite fast to
// generate the in-memory data structure on first use with this approach
// versus computing the table.
//
// It will panic on any errors because the data is hard coded and thus any
// errors means something is wrong in the source code.
var data *bytePointTable
mustLoadBytePoints := func() {
// There will be no byte points to load when generating them.
bp := compressedBytePoints
if len(bp) == 0 {
return
}

// Deserialize the precomputed byte points and set the curve to them.
offset := 0
var bytePoints [32][256][3]FieldVal
for byteNum := 0; byteNum < 32; byteNum++ {
// All points in this window.
for i := 0; i < 256; i++ {
px := &bytePoints[byteNum][i][0]
py := &bytePoints[byteNum][i][1]
pz := &bytePoints[byteNum][i][2]
for i := 0; i < 10; i++ {
px.n[i] = binary.LittleEndian.Uint32(serialized[offset:])
offset += 4
}
for i := 0; i < 10; i++ {
py.n[i] = binary.LittleEndian.Uint32(serialized[offset:])
offset += 4
}
for i := 0; i < 10; i++ {
pz.n[i] = binary.LittleEndian.Uint32(serialized[offset:])
offset += 4
// Decompress the pre-computed table used to accelerate scalar base
// multiplication.
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(bp))
r, err := zlib.NewReader(decoder)
if err != nil {
panic(err)
}
serialized, err := ioutil.ReadAll(r)
if err != nil {
panic(err)
}

// Deserialize the precomputed byte points and set the memory table to
// them.
offset := 0
var bytePoints bytePointTable
for byteNum := 0; byteNum < len(bytePoints); byteNum++ {
// All points in this window.
for i := 0; i < len(bytePoints[byteNum]); i++ {
px := &bytePoints[byteNum][i][0]
py := &bytePoints[byteNum][i][1]
for i := 0; i < len(px.n); i++ {
px.n[i] = binary.LittleEndian.Uint32(serialized[offset:])
offset += 4
}
for i := 0; i < len(py.n); i++ {
py.n[i] = binary.LittleEndian.Uint32(serialized[offset:])
offset += 4
}
}
}
data = &bytePoints
}

// Return a closure that initializes the data on first access. This is done
// because the table takes a non-trivial amount of memory and initializing
// it unconditionally would cause anything that imports the package, either
// directly, or indirectly via transitive deps, to use that memory even if
// the caller never accesses any parts of the package that actually needs
// access to it.
var loadBytePointsOnce sync.Once
return func() *bytePointTable {
loadBytePointsOnce.Do(mustLoadBytePoints)
return data
}
secp256k1.bytePoints = &bytePoints
return nil
}
}()