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

btcec: create new btcec/v2 module that type aliases into the dcrec module #1773

Merged
merged 5 commits into from Jan 27, 2022
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
5 changes: 5 additions & 0 deletions .github/workflows/go.yml
Expand Up @@ -43,6 +43,11 @@ jobs:
with:
path-to-profile: coverage.txt

- name: Send btcec
uses: shogo82148/actions-goveralls@v1
with:
path-to-profile: btcec/coverage.txt

- name: Send btcutil coverage
uses: shogo82148/actions-goveralls@v1
with:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Expand Up @@ -36,3 +36,7 @@ _testmain.go
# Code coverage files
profile.tmp
profile.cov
coverage.txt
btcec/coverage.txt
btcutil/coverage.txt
btcutil/psbt/coverage.txt
10 changes: 9 additions & 1 deletion Makefile
Expand Up @@ -80,18 +80,26 @@ check: unit
unit:
@$(call print, "Running unit tests.")
$(GOTEST_DEV) ./... -test.timeout=20m
cd btcec; $(GOTEST_DEV) ./... -test.timeout=20m
cd btcutil; $(GOTEST_DEV) ./... -test.timeout=20m
cd btcutil/psbt; $(GOTEST_DEV) ./... -test.timeout=20m

unit-cover: $(GOACC_BIN)
@$(call print, "Running unit coverage tests.")
$(GOACC_BIN) ./...

# We need to remove the /v2 pathing from the module to have it work
# nicely with the CI tool we use to render live code coverage.
cd btcec; $(GOACC_BIN) ./...; sed -i.bak 's/v2\///g' coverage.txt

cd btcutil; $(GOACC_BIN) ./...

cd btcutil/psbt; $(GOACC_BIN) ./...

unit-race:
@$(call print, "Running unit race tests.")
env CGO_ENABLED=1 GORACE="history_size=7 halt_on_errors=1" $(GOTEST) -race -test.timeout=20m ./...
cd btcec; env CGO_ENABLED=1 GORACE="history_size=7 halt_on_errors=1" $(GOTEST) -race -test.timeout=20m ./...
cd btcutil; env CGO_ENABLED=1 GORACE="history_size=7 halt_on_errors=1" $(GOTEST) -race -test.timeout=20m ./...
cd btcutil/psbt; env CGO_ENABLED=1 GORACE="history_size=7 halt_on_errors=1" $(GOTEST) -race -test.timeout=20m ./...

Expand All @@ -111,7 +119,7 @@ lint: $(LINT_BIN)

clean:
@$(call print, "Cleaning source.$(NC)")
$(RM) coverage.txt btcutil/coverage.txt btcutil/psbt/coverage.txt
$(RM) coverage.txt btcec/coverage.txt btcutil/coverage.txt btcutil/psbt/coverage.txt
Roasbeef marked this conversation as resolved.
Show resolved Hide resolved

.PHONY: all \
default \
Expand Down
8 changes: 4 additions & 4 deletions blockchain/compress.go
Expand Up @@ -5,7 +5,7 @@
package blockchain

import (
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/txscript"
)

Expand Down Expand Up @@ -218,7 +218,7 @@ func isPubKey(script []byte) (bool, []byte) {

// Ensure the public key is valid.
serializedPubKey := script[1:34]
_, err := btcec.ParsePubKey(serializedPubKey, btcec.S256())
_, err := btcec.ParsePubKey(serializedPubKey)
if err == nil {
return true, serializedPubKey
}
Expand All @@ -230,7 +230,7 @@ func isPubKey(script []byte) (bool, []byte) {

// Ensure the public key is valid.
serializedPubKey := script[1:66]
_, err := btcec.ParsePubKey(serializedPubKey, btcec.S256())
_, err := btcec.ParsePubKey(serializedPubKey)
if err == nil {
return true, serializedPubKey
}
Expand Down Expand Up @@ -399,7 +399,7 @@ func decompressScript(compressedPkScript []byte) []byte {
compressedKey := make([]byte, 33)
compressedKey[0] = byte(encodedScriptSize - 2)
copy(compressedKey[1:], compressedPkScript[1:])
key, err := btcec.ParsePubKey(compressedKey, btcec.S256())
key, err := btcec.ParsePubKey(compressedKey)
if err != nil {
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions blockchain/fullblocktests/generate.go
Expand Up @@ -19,7 +19,7 @@ import (
"time"

"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
Expand Down Expand Up @@ -198,7 +198,7 @@ type testGenerator struct {
// makeTestGenerator returns a test generator instance initialized with the
// genesis block as the tip.
func makeTestGenerator(params *chaincfg.Params) (testGenerator, error) {
privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), []byte{0x01})
privKey, _ := btcec.PrivKeyFromBytes([]byte{0x01})
genesis := params.GenesisBlock
genesisHash := genesis.BlockHash()
return testGenerator{
Expand Down
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment seems slightly wrong since this function is setHex and not (* FieldVal).SetHex

func setHex(hexString string) *FieldVal {
if len(hexString)%2 != 0 {
hexString = "0" + hexString
}
bytes, _ := hex.DecodeString(hexString)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it assumed that only valid hex is passed in?


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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/panic is/panic if
s/bet detected/be detected

// 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be the wrong godoc comment? If this is changed in a later commit I'll delete this

// 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