Skip to content

feat: remove btcd dependency #1201

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

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 104 additions & 29 deletions crypto/signature_nocgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,65 +14,85 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

//go:build nacl || js || !cgo || gofuzz
// +build nacl js !cgo gofuzz
//go:build nacl || js || wasip1 || !cgo || gofuzz || tinygo
// +build nacl js wasip1 !cgo gofuzz tinygo

package crypto

import (
"crypto/ecdsa"
"crypto/elliptic"
"errors"
"fmt"
"math/big"

"github.com/btcsuite/btcd/btcec"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
decred_ecdsa "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
)

// Ecrecover returns the uncompressed public key that created the given signature.
func Ecrecover(hash, sig []byte) ([]byte, error) {
pub, err := SigToPub(hash, sig)
pub, err := sigToPub(hash, sig)
if err != nil {
return nil, err
}
bytes := (*btcec.PublicKey)(pub).SerializeUncompressed()
bytes := pub.SerializeUncompressed()
return bytes, err
}

// SigToPub returns the public key that created the given signature.
func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
// Convert to btcec input format with 'recovery id' v at the beginning.
func sigToPub(hash, sig []byte) (*secp256k1.PublicKey, error) {
if len(sig) != SignatureLength {
return nil, errors.New("invalid signature")
}
// Convert to secp256k1 input format with 'recovery id' v at the beginning.
btcsig := make([]byte, SignatureLength)
btcsig[0] = sig[64] + 27
btcsig[0] = sig[RecoveryIDOffset] + 27
copy(btcsig[1:], sig)

pub, _, err := btcec.RecoverCompact(btcec.S256(), btcsig, hash)
return (*ecdsa.PublicKey)(pub), err
pub, _, err := decred_ecdsa.RecoverCompact(btcsig, hash)
return pub, err
}

// SigToPub returns the public key that created the given signature.
func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
pub, err := sigToPub(hash, sig)
if err != nil {
return nil, err
}
// We need to explicitly set the curve here, because we're wrapping
// the original curve to add (un-)marshalling
return &ecdsa.PublicKey{
Curve: S256(),
X: pub.X(),
Y: pub.Y(),
}, nil
}

// Sign calculates an ECDSA signature.
//
// This function is susceptible to chosen plaintext attacks that can leak
// information about the private key that is used for signing. Callers must
// be aware that the given hash cannot be chosen by an adversery. Common
// be aware that the given hash cannot be chosen by an adversary. Common
// solution is to hash any input before calculating the signature.
//
// The produced signature is in the [R || S || V] format where V is 0 or 1.
func Sign(hash []byte, prv *ecdsa.PrivateKey) ([]byte, error) {
if len(hash) != 32 {
return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(hash))
}
if prv.Curve != btcec.S256() {
return nil, fmt.Errorf("private key curve is not secp256k1")
if prv.Curve != S256() {
return nil, errors.New("private key curve is not secp256k1")
}
sig, err := btcec.SignCompact(btcec.S256(), (*btcec.PrivateKey)(prv), hash, false)
if err != nil {
return nil, err
// ecdsa.PrivateKey -> secp256k1.PrivateKey
var priv secp256k1.PrivateKey
if overflow := priv.Key.SetByteSlice(prv.D.Bytes()); overflow || priv.Key.IsZero() {
return nil, errors.New("invalid private key")
}
defer priv.Zero()
sig := decred_ecdsa.SignCompact(&priv, hash, false) // ref uncompressed pubkey
// Convert to Ethereum signature format with 'recovery id' v at the end.
v := sig[0] - 27
copy(sig, sig[1:])
sig[64] = v
sig[RecoveryIDOffset] = v
return sig, nil
}

Expand All @@ -83,13 +103,20 @@ func VerifySignature(pubkey, hash, signature []byte) bool {
if len(signature) != 64 {
return false
}
sig := &btcec.Signature{R: new(big.Int).SetBytes(signature[:32]), S: new(big.Int).SetBytes(signature[32:])}
key, err := btcec.ParsePubKey(pubkey, btcec.S256())
var r, s secp256k1.ModNScalar
if r.SetByteSlice(signature[:32]) {
return false // overflow
}
if s.SetByteSlice(signature[32:]) {
return false
}
sig := decred_ecdsa.NewSignature(&r, &s)
key, err := secp256k1.ParsePubKey(pubkey)
if err != nil {
return false
}
// Reject malleable signatures. libsecp256k1 does this check but btcec doesn't.
if sig.S.Cmp(secp256k1halfN) > 0 {
// Reject malleable signatures. libsecp256k1 does this check but decred doesn't.
if s.IsOverHalfOrder() {
return false
}
return sig.Verify(hash, key)
Expand All @@ -100,19 +127,67 @@ func DecompressPubkey(pubkey []byte) (*ecdsa.PublicKey, error) {
if len(pubkey) != 33 {
return nil, errors.New("invalid compressed public key length")
}
key, err := btcec.ParsePubKey(pubkey, btcec.S256())
key, err := secp256k1.ParsePubKey(pubkey)
if err != nil {
return nil, err
}
return key.ToECDSA(), nil
// We need to explicitly set the curve here, because we're wrapping
// the original curve to add (un-)marshalling
return &ecdsa.PublicKey{
Curve: S256(),
X: key.X(),
Y: key.Y(),
}, nil
}

// CompressPubkey encodes a public key to the 33-byte compressed format.
// CompressPubkey encodes a public key to the 33-byte compressed format. The
// provided PublicKey must be valid. Namely, the coordinates must not be larger
// than 32 bytes each, they must be less than the field prime, and it must be a
// point on the secp256k1 curve. This is the case for a PublicKey constructed by
// elliptic.Unmarshal (see UnmarshalPubkey), or by ToECDSA and ecdsa.GenerateKey
// when constructing a PrivateKey.
func CompressPubkey(pubkey *ecdsa.PublicKey) []byte {
return (*btcec.PublicKey)(pubkey).SerializeCompressed()
// NOTE: the coordinates may be validated with
// secp256k1.ParsePubKey(FromECDSAPub(pubkey))
var x, y secp256k1.FieldVal
x.SetByteSlice(pubkey.X.Bytes())
y.SetByteSlice(pubkey.Y.Bytes())
return secp256k1.NewPublicKey(&x, &y).SerializeCompressed()
}

// S256 returns an instance of the secp256k1 curve.
func S256() elliptic.Curve {
return btcec.S256()
func S256() EllipticCurve {
return btCurve{secp256k1.S256()}
}

type btCurve struct {
*secp256k1.KoblitzCurve
}

// Marshal converts a point given as (x, y) into a byte slice.
func (curve btCurve) Marshal(x, y *big.Int) []byte {
byteLen := (curve.Params().BitSize + 7) / 8

ret := make([]byte, 1+2*byteLen)
ret[0] = 4 // uncompressed point

x.FillBytes(ret[1 : 1+byteLen])
y.FillBytes(ret[1+byteLen : 1+2*byteLen])

return ret
}

// Unmarshal converts a point, serialised by Marshal, into an x, y pair. On
// error, x = nil.
func (curve btCurve) Unmarshal(data []byte) (x, y *big.Int) {
byteLen := (curve.Params().BitSize + 7) / 8
if len(data) != 1+2*byteLen {
return nil, nil
}
if data[0] != 4 { // uncompressed form
return nil, nil
}
x = new(big.Int).SetBytes(data[1 : 1+byteLen])
y = new(big.Int).SetBytes(data[1+byteLen:])
return
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/crate-crypto/go-kzg-4844 v1.0.0
github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf
github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48
github.com/edsrzf/mmap-go v1.0.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0=
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU=
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
Expand Down
51 changes: 48 additions & 3 deletions tests/fuzzers/secp256k1/secp_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,53 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package secp256k1

import "testing"
import (
"fmt"
"testing"

dcred_secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/scroll-tech/go-ethereum/crypto/secp256k1"
)

func TestFuzzer(t *testing.T) {
test := "00000000N0000000/R00000000000000000U0000S0000000mkhP000000000000000U"
Fuzz([]byte(test))
a, b := "00000000N0000000/R0000000000000000", "0U0000S0000000mkhP000000000000000U"
fuzz([]byte(a), []byte(b))
}

func Fuzz(f *testing.F) {

Check failure on line 32 in tests/fuzzers/secp256k1/secp_test.go

View workflow job for this annotation

GitHub Actions / check

Fuzz redeclared in this block

Check failure on line 32 in tests/fuzzers/secp256k1/secp_test.go

View workflow job for this annotation

GitHub Actions / check

Fuzz redeclared in this block
f.Fuzz(func(t *testing.T, a, b []byte) {
fuzz(a, b)
})
}
Comment on lines +32 to +36
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Resolve naming conflicts with existing fuzz package.

The function names Fuzz and fuzz conflict with identifiers from the github.com/google/gofuzz package that appears to be imported elsewhere in this package. This is causing compilation failures.

Rename the functions to avoid conflicts:

-func Fuzz(f *testing.F) {
+func FuzzSecp256k1(f *testing.F) {
 	f.Fuzz(func(t *testing.T, a, b []byte) {
-		fuzz(a, b)
+		fuzzPointAddition(a, b)
 	})
 }

-func fuzz(dataP1, dataP2 []byte) {
+func fuzzPointAddition(dataP1, dataP2 []byte) {

Update the TestFuzzer call accordingly:

 func TestFuzzer(t *testing.T) {
 	a, b := "00000000N0000000/R0000000000000000", "0U0000S0000000mkhP000000000000000U"
-	fuzz([]byte(a), []byte(b))
+	fuzzPointAddition([]byte(a), []byte(b))
 }

Also applies to: 38-53

🧰 Tools
🪛 GitHub Check: check

[failure] 32-32:
Fuzz redeclared in this block

🪛 GitHub Actions: CI

[error] 32-32: Fuzz redeclared in this block

🤖 Prompt for AI Agents
In tests/fuzzers/secp256k1/secp_test.go around lines 32 to 53, the function
names Fuzz and fuzz conflict with identifiers from the imported
github.com/google/gofuzz package, causing compilation errors. Rename the outer
Fuzz function to a unique name like RunFuzz or SecpFuzz, and rename the inner
fuzz function to a distinct name such as runFuzz or fuzzTest. Update all calls
accordingly to use the new function names to avoid naming conflicts.


func fuzz(dataP1, dataP2 []byte) {

Check failure on line 38 in tests/fuzzers/secp256k1/secp_test.go

View workflow job for this annotation

GitHub Actions / check

fuzz already declared through import of package fuzz ("github.com/google/gofuzz")

Check failure on line 38 in tests/fuzzers/secp256k1/secp_test.go

View workflow job for this annotation

GitHub Actions / check

fuzz already declared through import of package fuzz ("github.com/google/gofuzz")
var (
curveA = secp256k1.S256()
curveB = dcred_secp256k1.S256()
)
// first point
x1, y1 := curveB.ScalarBaseMult(dataP1)
// second points
x2, y2 := curveB.ScalarBaseMult(dataP2)
resAX, resAY := curveA.Add(x1, y1, x2, y2)
resBX, resBY := curveB.Add(x1, y1, x2, y2)
if resAX.Cmp(resBX) != 0 || resAY.Cmp(resBY) != 0 {
fmt.Printf("%s %s %s %s\n", x1, y1, x2, y2)
panic(fmt.Sprintf("Addition failed: geth: %s %s btcd: %s %s", resAX, resAY, resBX, resBY))
}
}
Loading