Skip to content

Commit

Permalink
crypto: add btcec fallback for sign/recover without cgo (#3680)
Browse files Browse the repository at this point in the history
* vendor: add github.com/btcsuite/btcd/btcec

* crypto: add btcec fallback for sign/recover without cgo

This commit adds a non-cgo fallback implementation of secp256k1
operations.

* crypto, core/vm: remove wrappers for sha256, ripemd160
  • Loading branch information
fjl authored and obscuren committed Feb 18, 2017
1 parent bf21549 commit 9b0af51
Show file tree
Hide file tree
Showing 32 changed files with 3,929 additions and 224 deletions.
5 changes: 2 additions & 3 deletions accounts/keystore/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/pborman/uuid"
)

Expand Down Expand Up @@ -157,7 +156,7 @@ func NewKeyForDirectICAP(rand io.Reader) *Key {
panic("key generation: could not read from random source: " + err.Error())
}
reader := bytes.NewReader(randBytes)
privateKeyECDSA, err := ecdsa.GenerateKey(secp256k1.S256(), reader)
privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), reader)
if err != nil {
panic("key generation: ecdsa.GenerateKey failed: " + err.Error())
}
Expand All @@ -169,7 +168,7 @@ func NewKeyForDirectICAP(rand io.Reader) *Key {
}

func newKey(rand io.Reader) (*Key, error) {
privateKeyECDSA, err := ecdsa.GenerateKey(secp256k1.S256(), rand)
privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand)
if err != nil {
return nil, err
}
Expand Down
26 changes: 16 additions & 10 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
package vm

import (
"crypto/sha256"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/params"
"golang.org/x/crypto/ripemd160"
)

// Precompiled contract is the basic interface for native Go contracts. The implementation
Expand All @@ -35,8 +38,8 @@ type PrecompiledContract interface {
// Precompiled contains the default set of ethereum contracts
var PrecompiledContracts = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256{},
common.BytesToAddress([]byte{3}): &ripemd160{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
}

Expand Down Expand Up @@ -88,31 +91,34 @@ func (c *ecrecover) Run(in []byte) []byte {
}

// SHA256 implemented as a native contract
type sha256 struct{}
type sha256hash struct{}

// RequiredGas returns the gas required to execute the pre-compiled contract.
//
// This method does not require any overflow checking as the input size gas costs
// required for anything significant is so high it's impossible to pay for.
func (c *sha256) RequiredGas(inputSize int) uint64 {
func (c *sha256hash) RequiredGas(inputSize int) uint64 {
return uint64(inputSize+31)/32*params.Sha256WordGas + params.Sha256Gas
}
func (c *sha256) Run(in []byte) []byte {
return crypto.Sha256(in)
func (c *sha256hash) Run(in []byte) []byte {
h := sha256.Sum256(in)
return h[:]
}

// RIPMED160 implemented as a native contract
type ripemd160 struct{}
type ripemd160hash struct{}

// RequiredGas returns the gas required to execute the pre-compiled contract.
//
// This method does not require any overflow checking as the input size gas costs
// required for anything significant is so high it's impossible to pay for.
func (c *ripemd160) RequiredGas(inputSize int) uint64 {
func (c *ripemd160hash) RequiredGas(inputSize int) uint64 {
return uint64(inputSize+31)/32*params.Ripemd160WordGas + params.Ripemd160Gas
}
func (c *ripemd160) Run(in []byte) []byte {
return common.LeftPadBytes(crypto.Ripemd160(in), 32)
func (c *ripemd160hash) Run(in []byte) []byte {
ripemd := ripemd160.New()
ripemd.Write(in)
return common.LeftPadBytes(ripemd.Sum(nil), 32)
}

// data copy implemented as a native contract
Expand Down
95 changes: 16 additions & 79 deletions crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,21 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"fmt"
"encoding/hex"
"errors"
"io"
"io/ioutil"
"math/big"
"os"

"encoding/hex"
"errors"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/ripemd160"
)

var (
secp256k1_N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16)
secp256k1_halfN = new(big.Int).Div(secp256k1_N, big.NewInt(2))
)

func Keccak256(data ...[]byte) []byte {
Expand All @@ -56,7 +55,6 @@ func Keccak256Hash(data ...[]byte) (h common.Hash) {
}

// Deprecated: For backward compatibility as other packages depend on these
func Sha3(data ...[]byte) []byte { return Keccak256(data...) }
func Sha3Hash(data ...[]byte) common.Hash { return Keccak256Hash(data...) }

// Creates an ethereum address given the bytes and the nonce
Expand All @@ -65,39 +63,16 @@ func CreateAddress(b common.Address, nonce uint64) common.Address {
return common.BytesToAddress(Keccak256(data)[12:])
}

func Sha256(data []byte) []byte {
hash := sha256.Sum256(data)

return hash[:]
}

func Ripemd160(data []byte) []byte {
ripemd := ripemd160.New()
ripemd.Write(data)

return ripemd.Sum(nil)
}

// Ecrecover returns the public key for the private key that was used to
// calculate the signature.
//
// Note: secp256k1 expects the recover id to be either 0, 1. Ethereum
// signatures have a recover id with an offset of 27. Callers must take
// this into account and if "recovering" from an Ethereum signature adjust.
func Ecrecover(hash, sig []byte) ([]byte, error) {
return secp256k1.RecoverPubkey(hash, sig)
}

// New methods using proper ecdsa keys from the stdlib
// ToECDSA creates a private key with the given D value.
func ToECDSA(prv []byte) *ecdsa.PrivateKey {
if len(prv) == 0 {
return nil
}

priv := new(ecdsa.PrivateKey)
priv.PublicKey.Curve = secp256k1.S256()
priv.PublicKey.Curve = S256()
priv.D = common.BigD(prv)
priv.PublicKey.X, priv.PublicKey.Y = secp256k1.S256().ScalarBaseMult(prv)
priv.PublicKey.X, priv.PublicKey.Y = priv.PublicKey.Curve.ScalarBaseMult(prv)
return priv
}

Expand All @@ -112,15 +87,15 @@ func ToECDSAPub(pub []byte) *ecdsa.PublicKey {
if len(pub) == 0 {
return nil
}
x, y := elliptic.Unmarshal(secp256k1.S256(), pub)
return &ecdsa.PublicKey{Curve: secp256k1.S256(), X: x, Y: y}
x, y := elliptic.Unmarshal(S256(), pub)
return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y}
}

func FromECDSAPub(pub *ecdsa.PublicKey) []byte {
if pub == nil || pub.X == nil || pub.Y == nil {
return nil
}
return elliptic.Marshal(secp256k1.S256(), pub.X, pub.Y)
return elliptic.Marshal(S256(), pub.X, pub.Y)
}

// HexToECDSA parses a secp256k1 private key.
Expand Down Expand Up @@ -164,7 +139,7 @@ func SaveECDSA(file string, key *ecdsa.PrivateKey) error {
}

func GenerateKey() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(secp256k1.S256(), rand.Reader)
return ecdsa.GenerateKey(S256(), rand.Reader)
}

// ValidateSignatureValues verifies whether the signature values are valid with
Expand All @@ -175,49 +150,11 @@ func ValidateSignatureValues(v byte, r, s *big.Int, homestead bool) bool {
}
// reject upper range of s values (ECDSA malleability)
// see discussion in secp256k1/libsecp256k1/include/secp256k1.h
if homestead && s.Cmp(secp256k1.HalfN) > 0 {
if homestead && s.Cmp(secp256k1_halfN) > 0 {
return false
}
// Frontier: allow s to be in full N range
return r.Cmp(secp256k1.N) < 0 && s.Cmp(secp256k1.N) < 0 && (v == 0 || v == 1)
}

func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
s, err := Ecrecover(hash, sig)
if err != nil {
return nil, err
}

x, y := elliptic.Unmarshal(secp256k1.S256(), s)
return &ecdsa.PublicKey{Curve: secp256k1.S256(), X: x, Y: 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
// 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(data []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) {
if len(data) != 32 {
return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(data))
}

seckey := common.LeftPadBytes(prv.D.Bytes(), prv.Params().BitSize/8)
defer zeroBytes(seckey)
sig, err = secp256k1.Sign(data, seckey)
return
}

func Encrypt(pub *ecdsa.PublicKey, message []byte) ([]byte, error) {
return ecies.Encrypt(rand.Reader, ecies.ImportECDSAPublic(pub), message, nil, nil)
}

func Decrypt(prv *ecdsa.PrivateKey, ct []byte) ([]byte, error) {
key := ecies.ImportECDSA(prv)
return key.Decrypt(rand.Reader, ct, nil, nil)
return r.Cmp(secp256k1_N) < 0 && s.Cmp(secp256k1_N) < 0 && (v == 0 || v == 1)
}

func PubkeyToAddress(p ecdsa.PublicKey) common.Address {
Expand Down
36 changes: 8 additions & 28 deletions crypto/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
)

var testAddrHex = "970e8128ab834e8eac17ab8e3812f010678cf791"
Expand All @@ -37,30 +36,12 @@ var testPrivHex = "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232
// These tests are sanity checks.
// They should ensure that we don't e.g. use Sha3-224 instead of Sha3-256
// and that the sha3 library uses keccak-f permutation.
func TestSha3(t *testing.T) {
msg := []byte("abc")
exp, _ := hex.DecodeString("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45")
checkhash(t, "Sha3-256", func(in []byte) []byte { return Keccak256(in) }, msg, exp)
}

func TestSha3Hash(t *testing.T) {
msg := []byte("abc")
exp, _ := hex.DecodeString("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45")
checkhash(t, "Sha3-256-array", func(in []byte) []byte { h := Keccak256Hash(in); return h[:] }, msg, exp)
}

func TestSha256(t *testing.T) {
msg := []byte("abc")
exp, _ := hex.DecodeString("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
checkhash(t, "Sha256", Sha256, msg, exp)
}

func TestRipemd160(t *testing.T) {
msg := []byte("abc")
exp, _ := hex.DecodeString("8eb208f7e05d987a9b044a8e98c6b087f15a0bfc")
checkhash(t, "Ripemd160", Ripemd160, msg, exp)
}

func BenchmarkSha3(b *testing.B) {
a := []byte("hello world")
amount := 1000000
Expand Down Expand Up @@ -170,7 +151,7 @@ func TestValidateSignatureValues(t *testing.T) {
minusOne := big.NewInt(-1)
one := common.Big1
zero := common.Big0
secp256k1nMinus1 := new(big.Int).Sub(secp256k1.N, common.Big1)
secp256k1nMinus1 := new(big.Int).Sub(secp256k1_N, common.Big1)

// correct v,r,s
check(true, 0, one, one)
Expand All @@ -197,9 +178,9 @@ func TestValidateSignatureValues(t *testing.T) {
// correct sig with max r,s
check(true, 0, secp256k1nMinus1, secp256k1nMinus1)
// correct v, combinations of incorrect r,s at upper limit
check(false, 0, secp256k1.N, secp256k1nMinus1)
check(false, 0, secp256k1nMinus1, secp256k1.N)
check(false, 0, secp256k1.N, secp256k1.N)
check(false, 0, secp256k1_N, secp256k1nMinus1)
check(false, 0, secp256k1nMinus1, secp256k1_N)
check(false, 0, secp256k1_N, secp256k1_N)

// current callers ensures r,s cannot be negative, but let's test for that too
// as crypto package could be used stand-alone
Expand All @@ -225,14 +206,13 @@ func checkAddr(t *testing.T, addr0, addr1 common.Address) {
func TestPythonIntegration(t *testing.T) {
kh := "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"
k0, _ := HexToECDSA(kh)
k1 := FromECDSA(k0)

msg0 := Keccak256([]byte("foo"))
sig0, _ := secp256k1.Sign(msg0, k1)
sig0, _ := Sign(msg0, k0)

msg1 := common.FromHex("00000000000000000000000000000000")
sig1, _ := secp256k1.Sign(msg0, k1)
sig1, _ := Sign(msg0, k0)

fmt.Printf("msg: %x, privkey: %x sig: %x\n", msg0, k1, sig0)
fmt.Printf("msg: %x, privkey: %x sig: %x\n", msg1, k1, sig1)
t.Logf("msg: %x, privkey: %s sig: %x\n", msg0, kh, sig0)
t.Logf("msg: %x, privkey: %s sig: %x\n", msg1, kh, sig1)
}
6 changes: 3 additions & 3 deletions crypto/ecies/asn1.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import (
"hash"
"math/big"

"github.com/ethereum/go-ethereum/crypto/secp256k1"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
)

var (
Expand Down Expand Up @@ -120,7 +120,7 @@ func (curve secgNamedCurve) Equal(curve2 secgNamedCurve) bool {
func namedCurveFromOID(curve secgNamedCurve) elliptic.Curve {
switch {
case curve.Equal(secgNamedCurveS256):
return secp256k1.S256()
return ethcrypto.S256()
case curve.Equal(secgNamedCurveP256):
return elliptic.P256()
case curve.Equal(secgNamedCurveP384):
Expand All @@ -139,7 +139,7 @@ func oidFromNamedCurve(curve elliptic.Curve) (secgNamedCurve, bool) {
return secgNamedCurveP384, true
case elliptic.P521():
return secgNamedCurveP521, true
case secp256k1.S256():
case ethcrypto.S256():
return secgNamedCurveS256, true
}

Expand Down
Loading

0 comments on commit 9b0af51

Please sign in to comment.