Skip to content

Commit

Permalink
btcec: convert package into go module, alias to dcrec
Browse files Browse the repository at this point in the history
In this commit, we turn the package into a new Go module (version 2),
and then port over the current set of types and functions to mainly
alias to the more optimized and maintained dcrec variant.

Taking a look at the benchmarks, most operations other than
normalization (which IIRC is a bit slower now due to constant time
fixes) enjoy some nice speeds up:
```
benchcmp is deprecated in favor of benchstat: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat
benchmark                            old ns/op     new ns/op     delta
BenchmarkAddJacobian-8               464           328           -29.20%
BenchmarkAddJacobianNotZOne-8        1138          372           -67.27%
BenchmarkScalarBaseMult-8            47336         31531         -33.39%
BenchmarkScalarBaseMultLarge-8       42465         32057         -24.51%
BenchmarkScalarMult-8                123355        117579        -4.68%
BenchmarkNAF-8                       582           168           -71.12%
BenchmarkSigVerify-8                 175414        120794        -31.14%
BenchmarkFieldNormalize-8            23.8          24.4          +2.39%
BenchmarkParseCompressedPubKey-8     24282         10907         -55.08%
```
  • Loading branch information
Roasbeef committed Jan 27, 2022
1 parent 588c071 commit 87e8fe9
Show file tree
Hide file tree
Showing 24 changed files with 1,125 additions and 4,554 deletions.
36 changes: 4 additions & 32 deletions btcec/README.md
Expand Up @@ -3,7 +3,7 @@ btcec

[![Build Status](https://github.com/btcsuite/btcd/workflows/Build%20and%20Test/badge.svg)](https://github.com/btcsuite/btcd/actions)
[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
[![GoDoc](https://pkg.go.dev/github.com/btcsuite/btcd/btcec?status.png)](https://pkg.go.dev/github.com/btcsuite/btcd/btcec)
[![GoDoc](https://pkg.go.dev/github.com/btcsuite/btcd/btcec/v2?status.png)](https://pkg.go.dev/github.com/btcsuite/btcd/btcec/v2)

Package btcec implements elliptic curve cryptography needed for working with
Bitcoin (secp256k1 only for now). It is designed so that it may be used with the
Expand All @@ -20,47 +20,19 @@ use secp256k1 elliptic curve cryptography.
## Installation and Updating

```bash
$ go get -u github.com/btcsuite/btcd/btcec
$ go install -u -v github.com/btcsuite/btcd/btcec/v2
```

## Examples

* [Sign Message](https://pkg.go.dev/github.com/btcsuite/btcd/btcec#example-package--SignMessage)
* [Sign Message](https://pkg.go.dev/github.com/btcsuite/btcd/btcec/v2#example-package--SignMessage)
Demonstrates signing a message with a secp256k1 private key that is first
parsed form raw bytes and serializing the generated signature.

* [Verify Signature](https://pkg.go.dev/github.com/btcsuite/btcd/btcec#example-package--VerifySignature)
* [Verify Signature](https://pkg.go.dev/github.com/btcsuite/btcd/btcec/v2#example-package--VerifySignature)
Demonstrates verifying a secp256k1 signature against a public key that is
first parsed from raw bytes. The signature is also parsed from raw bytes.

* [Encryption](https://pkg.go.dev/github.com/btcsuite/btcd/btcec#example-package--EncryptMessage)
Demonstrates encrypting a message for a public key that is first parsed from
raw bytes, then decrypting it using the corresponding private key.

* [Decryption](https://pkg.go.dev/github.com/btcsuite/btcd/btcec#example-package--DecryptMessage)
Demonstrates decrypting a message using a private key that is first parsed
from raw bytes.

## GPG Verification Key

All official release tags are signed by Conformal so users can ensure the code
has not been tampered with and is coming from the btcsuite developers. To
verify the signature perform the following:

- Download the public key from the Conformal website at
https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt

- Import the public key into your GPG keyring:
```bash
gpg --import GIT-GPG-KEY-conformal.txt
```

- Verify the release tag with the following command where `TAG_NAME` is a
placeholder for the specific tag:
```bash
git tag -v TAG_NAME
```

## License

Package btcec is licensed under the [copyfree](http://copyfree.org) ISC License
Expand Down
162 changes: 120 additions & 42 deletions btcec/bench_test.go
Expand Up @@ -6,43 +6,114 @@ package btcec

import (
"encoding/hex"
"math/big"
"testing"

secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
)

// BenchmarkAddJacobian benchmarks the secp256k1 curve addJacobian function with
// setHex decodes the passed big-endian hex string into the internal field value
// representation. Only the first 32-bytes are used.
//
// This is NOT constant time.
//
// The field value is returned to support chaining. This enables syntax like:
// f := new(FieldVal).SetHex("0abc").Add(1) so that f = 0x0abc + 1
func setHex(hexString string) *FieldVal {
if len(hexString)%2 != 0 {
hexString = "0" + hexString
}
bytes, _ := hex.DecodeString(hexString)

var f FieldVal
f.SetByteSlice(bytes)

return &f
}

// hexToFieldVal converts the passed hex string into a FieldVal and will panic
// if there is an error. This is only provided for the hard-coded constants so
// errors in the source code can be detected. It will only (and must only) be
// called with hard-coded values.
func hexToFieldVal(s string) *FieldVal {
b, err := hex.DecodeString(s)
if err != nil {
panic("invalid hex in source file: " + s)
}
var f FieldVal
if overflow := f.SetByteSlice(b); overflow {
panic("hex in source file overflows mod P: " + s)
}
return &f
}

// fromHex converts the passed hex string into a big integer pointer and will
// panic is there is an error. This is only provided for the hard-coded
// constants so errors in the source code can bet detected. It will only (and
// must only) be called for initialization purposes.
func fromHex(s string) *big.Int {
if s == "" {
return big.NewInt(0)
}
r, ok := new(big.Int).SetString(s, 16)
if !ok {
panic("invalid hex in source file: " + s)
}
return r
}

// jacobianPointFromHex decodes the passed big-endian hex strings into a
// Jacobian point with its internal fields set to the resulting values. Only
// the first 32-bytes are used.
func jacobianPointFromHex(x, y, z string) JacobianPoint {
var p JacobianPoint
p.X = *setHex(x)
p.Y = *setHex(y)
p.Z = *setHex(z)

return p
}

// BenchmarkAddNonConst benchmarks the secp256k1 curve AddNonConst function with
// Z values of 1 so that the associated optimizations are used.
func BenchmarkAddJacobian(b *testing.B) {
b.StopTimer()
x1 := new(fieldVal).SetHex("34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6")
y1 := new(fieldVal).SetHex("0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232")
z1 := new(fieldVal).SetHex("1")
x2 := new(fieldVal).SetHex("34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6")
y2 := new(fieldVal).SetHex("0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232")
z2 := new(fieldVal).SetHex("1")
x3, y3, z3 := new(fieldVal), new(fieldVal), new(fieldVal)
curve := S256()
b.StartTimer()
p1 := jacobianPointFromHex(
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
"1",
)
p2 := jacobianPointFromHex(
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
"1",
)

b.ReportAllocs()
b.ResetTimer()
var result JacobianPoint
for i := 0; i < b.N; i++ {
curve.addJacobian(x1, y1, z1, x2, y2, z2, x3, y3, z3)
secp.AddNonConst(&p1, &p2, &result)
}
}

// BenchmarkAddJacobianNotZOne benchmarks the secp256k1 curve addJacobian
// BenchmarkAddNonConstNotZOne benchmarks the secp256k1 curve AddNonConst
// function with Z values other than one so the optimizations associated with
// Z=1 aren't used.
func BenchmarkAddJacobianNotZOne(b *testing.B) {
b.StopTimer()
x1 := new(fieldVal).SetHex("d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718")
y1 := new(fieldVal).SetHex("5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190")
z1 := new(fieldVal).SetHex("2")
x2 := new(fieldVal).SetHex("91abba6a34b7481d922a4bd6a04899d5a686f6cf6da4e66a0cb427fb25c04bd4")
y2 := new(fieldVal).SetHex("03fede65e30b4e7576a2abefc963ddbf9fdccbf791b77c29beadefe49951f7d1")
z2 := new(fieldVal).SetHex("3")
x3, y3, z3 := new(fieldVal), new(fieldVal), new(fieldVal)
curve := S256()
b.StartTimer()
x1 := setHex("d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718")
y1 := setHex("5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190")
z1 := setHex("2")
x2 := setHex("91abba6a34b7481d922a4bd6a04899d5a686f6cf6da4e66a0cb427fb25c04bd4")
y2 := setHex("03fede65e30b4e7576a2abefc963ddbf9fdccbf791b77c29beadefe49951f7d1")
z2 := setHex("3")
p1 := MakeJacobianPoint(x1, y1, z1)
p2 := MakeJacobianPoint(x2, y2, z2)

b.ReportAllocs()
b.ResetTimer()
var result JacobianPoint
for i := 0; i < b.N; i++ {
curve.addJacobian(x1, y1, z1, x2, y2, z2, x3, y3, z3)
AddNonConst(&p1, &p2, &result)
}
}

Expand Down Expand Up @@ -77,12 +148,20 @@ func BenchmarkScalarMult(b *testing.B) {
}
}

// BenchmarkNAF benchmarks the NAF function.
func BenchmarkNAF(b *testing.B) {
k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
for i := 0; i < b.N; i++ {
NAF(k.Bytes())
// hexToModNScalar converts the passed hex string into a ModNScalar and will
// panic if there is an error. This is only provided for the hard-coded
// constants so errors in the source code can be detected. It will only (and
// must only) be called with hard-coded values.
func hexToModNScalar(s string) *ModNScalar {
b, err := hex.DecodeString(s)
if err != nil {
panic("invalid hex in source file: " + s)
}
var scalar ModNScalar
if overflow := scalar.SetByteSlice(b); overflow {
panic("hex in source file overflows mod N scalar: " + s)
}
return &scalar
}

// BenchmarkSigVerify benchmarks how long it takes the secp256k1 curve to
Expand All @@ -91,35 +170,34 @@ func BenchmarkSigVerify(b *testing.B) {
b.StopTimer()
// Randomly generated keypair.
// Private key: 9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d
pubKey := PublicKey{
Curve: S256(),
X: fromHex("d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab"),
Y: fromHex("ab65528eefbb8057aa85d597258a3fbd481a24633bc9b47a9aa045c91371de52"),
}
pubKey := NewPublicKey(
hexToFieldVal("d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab"),
hexToFieldVal("ab65528eefbb8057aa85d597258a3fbd481a24633bc9b47a9aa045c91371de52"),
)

// Double sha256 of []byte{0x01, 0x02, 0x03, 0x04}
msgHash := fromHex("8de472e2399610baaa7f84840547cd409434e31f5d3bd71e4d947f283874f9c0")
sig := Signature{
R: fromHex("fef45d2892953aa5bbcdb057b5e98b208f1617a7498af7eb765574e29b5d9c2c"),
S: fromHex("d47563f52aac6b04b55de236b7c515eb9311757db01e02cff079c3ca6efb063f"),
}
sig := NewSignature(
hexToModNScalar("fef45d2892953aa5bbcdb057b5e98b208f1617a7498af7eb765574e29b5d9c2c"),
hexToModNScalar("d47563f52aac6b04b55de236b7c515eb9311757db01e02cff079c3ca6efb063f"),
)

if !sig.Verify(msgHash.Bytes(), &pubKey) {
if !sig.Verify(msgHash.Bytes(), pubKey) {
b.Errorf("Signature failed to verify")
return
}
b.StartTimer()

for i := 0; i < b.N; i++ {
sig.Verify(msgHash.Bytes(), &pubKey)
sig.Verify(msgHash.Bytes(), pubKey)
}
}

// BenchmarkFieldNormalize benchmarks how long it takes the internal field
// to perform normalization (which includes modular reduction).
func BenchmarkFieldNormalize(b *testing.B) {
// The normalize function is constant time so default value is fine.
f := new(fieldVal)
var f FieldVal
for i := 0; i < b.N; i++ {
f.Normalize()
}
Expand All @@ -138,7 +216,7 @@ func BenchmarkParseCompressedPubKey(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
pk, err = ParsePubKey(rawPk, S256())
pk, err = ParsePubKey(rawPk)
}
_ = pk
_ = err
Expand Down

0 comments on commit 87e8fe9

Please sign in to comment.