From b5538c15e051d757b9338623e525e1a3a40c4996 Mon Sep 17 00:00:00 2001 From: vuong Date: Fri, 17 Dec 2021 22:36:04 +0700 Subject: [PATCH] git commit -m "hot fix eth key" --- crypto/keys/ethsecp256k1/bench_test.go | 30 -- crypto/keys/ethsecp256k1/ethsecp256k1.go | 212 ++++++------- crypto/keys/ethsecp256k1/ethsecp256k1_cgo.go | 27 -- .../ethsecp256k1/ethsecp256k1_cgo_test.go | 41 --- .../ethsecp256k1_internal_test.go | 45 --- .../keys/ethsecp256k1/ethsecp256k1_nocgo.go | 71 ----- .../ethsecp256k1/ethsecp256k1_nocgo_test.go | 39 --- crypto/keys/ethsecp256k1/ethsecp256k1_test.go | 297 +++--------------- 8 files changed, 155 insertions(+), 607 deletions(-) delete mode 100644 crypto/keys/ethsecp256k1/bench_test.go delete mode 100644 crypto/keys/ethsecp256k1/ethsecp256k1_cgo.go delete mode 100644 crypto/keys/ethsecp256k1/ethsecp256k1_cgo_test.go delete mode 100644 crypto/keys/ethsecp256k1/ethsecp256k1_internal_test.go delete mode 100644 crypto/keys/ethsecp256k1/ethsecp256k1_nocgo.go delete mode 100644 crypto/keys/ethsecp256k1/ethsecp256k1_nocgo_test.go diff --git a/crypto/keys/ethsecp256k1/bench_test.go b/crypto/keys/ethsecp256k1/bench_test.go deleted file mode 100644 index 7a7622d60341..000000000000 --- a/crypto/keys/ethsecp256k1/bench_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package ethsecp256k1 - -import ( - "io" - "testing" - - "github.com/cosmos/cosmos-sdk/crypto/keys/internal/benchmarking" - "github.com/cosmos/cosmos-sdk/crypto/types" -) - -func BenchmarkKeyGeneration(b *testing.B) { - b.ReportAllocs() - benchmarkKeygenWrapper := func(reader io.Reader) types.PrivKey { - priv := genPrivKey(reader) - return &PrivKey{Key: priv} - } - benchmarking.BenchmarkKeyGeneration(b, benchmarkKeygenWrapper) -} - -func BenchmarkSigning(b *testing.B) { - b.ReportAllocs() - priv := GenPrivKey() - benchmarking.BenchmarkSigning(b, priv) -} - -func BenchmarkVerification(b *testing.B) { - b.ReportAllocs() - priv := GenPrivKey() - benchmarking.BenchmarkVerification(b, priv) -} diff --git a/crypto/keys/ethsecp256k1/ethsecp256k1.go b/crypto/keys/ethsecp256k1/ethsecp256k1.go index e58f0752f84b..4a77b877afd6 100644 --- a/crypto/keys/ethsecp256k1/ethsecp256k1.go +++ b/crypto/keys/ethsecp256k1/ethsecp256k1.go @@ -2,53 +2,78 @@ package ethsecp256k1 import ( "bytes" - "crypto/sha256" + "crypto/ecdsa" "crypto/subtle" "fmt" - "io" - "math/big" - ethsecp256k1 "github.com/btcsuite/btcd/btcec" - "github.com/tendermint/tendermint/crypto" + ethcrypto "github.com/ethereum/go-ethereum/crypto" - // nolint: staticcheck // necessary for Bitcoin address format "github.com/cosmos/cosmos-sdk/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/types/errors" - ethcrypto "github.com/ethereum/go-ethereum/crypto" -) + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" -var _ cryptotypes.PrivKey = &PrivKey{} -var _ codec.AminoMarshaler = &PrivKey{} + tmcrypto "github.com/tendermint/tendermint/crypto" +) const ( + // PrivKeySize defines the size of the PrivKey bytes PrivKeySize = 32 - keyType = "ethsecp256k1" - PrivKeyName = "tendermint/PrivKeyethsecp256k1" - PubKeyName = "tendermint/PubKeyethsecp256k1" + // PubKeySize defines the size of the PubKey bytes + PubKeySize = 33 + // KeyType is the string constant for the Secp256k1 algorithm + KeyType = "ethsecp256k1" +) + +// Amino encoding names +const ( + // PrivKeyName defines the amino encoding name for the EthSecp256k1 private key + PrivKeyName = "ethermint/PrivKeyEthSecp256k1" + // PubKeyName defines the amino encoding name for the EthSecp256k1 public key + PubKeyName = "ethermint/PubKeyEthSecp256k1" +) + +// ---------------------------------------------------------------------------- +// secp256k1 Private Key + +var ( + _ cryptotypes.PrivKey = &PrivKey{} + _ codec.AminoMarshaler = &PrivKey{} ) -// Bytes returns the byte representation of the Private Key. -func (privKey *PrivKey) Bytes() []byte { +// GenerateKey generates a new random private key. It returns an error upon +// failure. +func GenerateKey() (*PrivKey, error) { + priv, err := ethcrypto.GenerateKey() + if err != nil { + return nil, err + } + + return &PrivKey{ + Key: ethcrypto.FromECDSA(priv), + }, nil +} + +// Bytes returns the byte representation of the ECDSA Private Key. +func (privKey PrivKey) Bytes() []byte { return privKey.Key } -// PubKey performs the point-scalar multiplication from the privKey on the -// generator point to get the pubkey. -func (privKey *PrivKey) PubKey() cryptotypes.PubKey { - _, pubkeyObject := ethsecp256k1.PrivKeyFromBytes(ethsecp256k1.S256(), privKey.Key) - pk := pubkeyObject.SerializeCompressed() - return &PubKey{Key: pk} +// PubKey returns the ECDSA private key's public key. +func (privKey PrivKey) PubKey() cryptotypes.PubKey { + ecdsaPrivKey := privKey.ToECDSA() + return &PubKey{ + Key: ethcrypto.CompressPubkey(&ecdsaPrivKey.PublicKey), + } } -// Equals - you probably don't need to use this. -// Runs in constant time based on length of the -func (privKey *PrivKey) Equals(other cryptotypes.LedgerPrivKey) bool { +// Equals returns true if two ECDSA private keys are equal and false otherwise. +func (privKey PrivKey) Equals(other cryptotypes.LedgerPrivKey) bool { return privKey.Type() == other.Type() && subtle.ConstantTimeCompare(privKey.Bytes(), other.Bytes()) == 1 } -func (privKey *PrivKey) Type() string { - return keyType +// Type returns ethsecp256k1 +func (privKey PrivKey) Type() string { + return KeyType } // MarshalAmino overrides Amino binary marshalling. @@ -59,7 +84,7 @@ func (privKey PrivKey) MarshalAmino() ([]byte, error) { // UnmarshalAmino overrides Amino binary marshalling. func (privKey *PrivKey) UnmarshalAmino(bz []byte) error { if len(bz) != PrivKeySize { - return fmt.Errorf("invalid privkey size") + return fmt.Errorf("invalid privkey size, expected %d got %d", PrivKeySize, len(bz)) } privKey.Key = bz @@ -78,101 +103,63 @@ func (privKey *PrivKey) UnmarshalAminoJSON(bz []byte) error { return privKey.UnmarshalAmino(bz) } -// GenPrivKey generates a new ECDSA private key on curve ethsecp256k1 private key. -// It uses OS randomness to generate the private key. -func GenPrivKey() *PrivKey { - return &PrivKey{Key: genPrivKey(crypto.CReader())} -} - -// genPrivKey generates a new ethsecp256k1 private key using the provided reader. -func genPrivKey(rand io.Reader) []byte { - var privKeyBytes [PrivKeySize]byte - d := new(big.Int) - for { - privKeyBytes = [PrivKeySize]byte{} - _, err := io.ReadFull(rand, privKeyBytes[:]) - if err != nil { - panic(err) - } - - d.SetBytes(privKeyBytes[:]) - // break if we found a valid point (i.e. > 0 and < N == curverOrder) - isValidFieldElement := 0 < d.Sign() && d.Cmp(ethsecp256k1.S256().N) < 0 - if isValidFieldElement { - break - } +// Sign creates a recoverable ECDSA signature on the secp256k1 curve over the +// provided hash of the message. The produced signature is 65 bytes +// where the last byte contains the recovery ID. +func (privKey PrivKey) Sign(digestBz []byte) ([]byte, error) { + if len(digestBz) != ethcrypto.DigestLength { + digestBz = ethcrypto.Keccak256Hash(digestBz).Bytes() } - return privKeyBytes[:] + return ethcrypto.Sign(digestBz, privKey.ToECDSA()) } -var one = new(big.Int).SetInt64(1) - -// GenPrivKeyFromSecret hashes the secret with SHA2, and uses -// that 32 byte output to create the private key. -// -// It makes sure the private key is a valid field element by setting: -// -// c = sha256(secret) -// k = (c mod (n − 1)) + 1, where n = curve order. -// -// NOTE: secret should be the output of a KDF like bcrypt, -// if it's derived from user input. -func GenPrivKeyFromSecret(secret []byte) *PrivKey { - secHash := sha256.Sum256(secret) - // to guarantee that we have a valid field element, we use the approach of: - // "Suite B Implementer’s Guide to FIPS 186-3", A.2.1 - // https://apps.nsa.gov/iaarchive/library/ia-guidance/ia-solutions-for-classified/algorithm-guidance/suite-b-implementers-guide-to-fips-186-3-ecdsa.cfm - // see also https://github.com/golang/go/blob/0380c9ad38843d523d9c9804fe300cb7edd7cd3c/src/crypto/ecdsa/ecdsa.go#L89-L101 - fe := new(big.Int).SetBytes(secHash[:]) - n := new(big.Int).Sub(ethsecp256k1.S256().N, one) - fe.Mod(fe, n) - fe.Add(fe, one) - - feB := fe.Bytes() - privKey32 := make([]byte, PrivKeySize) - // copy feB over to fixed 32 byte privKey32 and pad (if necessary) - copy(privKey32[32-len(feB):32], feB) - - return &PrivKey{Key: privKey32} -} - -//------------------------------------- - -var _ cryptotypes.PubKey = &PubKey{} -var _ codec.AminoMarshaler = &PubKey{} - -// PubKeySize is comprised of 32 bytes for one field element -// (the x-coordinate), plus one byte for the parity of the y-coordinate. -const PubKeySize = 33 - -// Address returns a Bitcoin style addresses: RIPEMD160(SHA256(pubkey)) -func (pubKey *PubKey) Address() crypto.Address { - if len(pubKey.Key) != PubKeySize { - panic("length of pubkey is incorrect") +// ToECDSA returns the ECDSA private key as a reference to ecdsa.PrivateKey type. +// The function will panic if the private key is invalid. +func (privKey PrivKey) ToECDSA() *ecdsa.PrivateKey { + key, err := ethcrypto.ToECDSA(privKey.Bytes()) + if err != nil { + panic(err) } + return key +} + +// ---------------------------------------------------------------------------- +// secp256k1 Public Key +var ( + _ cryptotypes.PubKey = &PubKey{} + _ codec.AminoMarshaler = &PubKey{} +) + +// Address returns the address of the ECDSA public key. +// The function will panic if the public key is invalid. +func (pubKey PubKey) Address() tmcrypto.Address { pubk, err := ethcrypto.DecompressPubkey(pubKey.Key) if err != nil { panic(err) } - return crypto.Address(append(ethcrypto.PubkeyToAddress(*pubk).Bytes(), 0)) + + return tmcrypto.Address(append(ethcrypto.PubkeyToAddress(*pubk).Bytes(), 0)) } -// Bytes returns the pubkey byte format. -func (pubKey *PubKey) Bytes() []byte { +// Bytes returns the raw bytes of the ECDSA public key. +func (pubKey PubKey) Bytes() []byte { return pubKey.Key } -func (pubKey *PubKey) String() string { - return fmt.Sprintf("PubKeyethsecp256k1{%X}", pubKey.Key) +// String implements the fmt.Stringer interface. +func (pubKey PubKey) String() string { + return fmt.Sprintf("EthPubKeySecp256k1{%X}", pubKey.Key) } -func (pubKey *PubKey) Type() string { - return keyType +// Type returns ethsecp256k1 +func (pubKey PubKey) Type() string { + return KeyType } -func (pubKey *PubKey) Equals(other cryptotypes.PubKey) bool { +// Equals returns true if the pubkey type is the same and their bytes are deeply equal. +func (pubKey PubKey) Equals(other cryptotypes.PubKey) bool { return pubKey.Type() == other.Type() && bytes.Equal(pubKey.Bytes(), other.Bytes()) } @@ -184,7 +171,7 @@ func (pubKey PubKey) MarshalAmino() ([]byte, error) { // UnmarshalAmino overrides Amino binary marshalling. func (pubKey *PubKey) UnmarshalAmino(bz []byte) error { if len(bz) != PubKeySize { - return errors.Wrap(errors.ErrInvalidPubKey, "invalid pubkey size") + return sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "invalid pubkey size, expected %d, got %d", PubKeySize, len(bz)) } pubKey.Key = bz @@ -202,3 +189,18 @@ func (pubKey PubKey) MarshalAminoJSON() ([]byte, error) { func (pubKey *PubKey) UnmarshalAminoJSON(bz []byte) error { return pubKey.UnmarshalAmino(bz) } + +// VerifySignature verifies that the ECDSA public key created a given signature over +// the provided message. It will calculate the Keccak256 hash of the message +// prior to verification. +// +// CONTRACT: The signature should be in [R || S] format. +func (pubKey PubKey) VerifySignature(msg []byte, sig []byte) bool { + if len(sig) == ethcrypto.SignatureLength { + // remove recovery ID (V) if contained in the signature + sig = sig[:len(sig)-1] + } + + // the signature needs to be in [R || S] format when provided to VerifySignature + return ethcrypto.VerifySignature(pubKey.Key, ethcrypto.Keccak256Hash(msg).Bytes(), sig) +} diff --git a/crypto/keys/ethsecp256k1/ethsecp256k1_cgo.go b/crypto/keys/ethsecp256k1/ethsecp256k1_cgo.go deleted file mode 100644 index 82f2bb0cfc61..000000000000 --- a/crypto/keys/ethsecp256k1/ethsecp256k1_cgo.go +++ /dev/null @@ -1,27 +0,0 @@ -//go:build libethsecp256k1 -// +build libethsecp256k1 - -package ethsecp256k1 - -import ( - "github.com/tendermint/tendermint/crypto" - - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1/internal/secp256k1" -) - -// Sign creates an ECDSA signature on curve ethsecp256k1, using SHA256 on the msg. -func (privKey *PrivKey) Sign(msg []byte) ([]byte, error) { - rsv, err := secp256k1.Sign(crypto.Sha256(msg), privKey.Key) - if err != nil { - return nil, err - } - // we do not need v in r||s||v: - rs := rsv[:len(rsv)-1] - return rs, nil -} - -// VerifySignature validates the signature. -// The msg will be hashed prior to signature verification. -func (pubKey *PrivKey) VerifySignature(msg []byte, sig []byte) bool { - return secp256k1.VerifySignature(pubKey.Key, crypto.Sha256(msg), sig) -} diff --git a/crypto/keys/ethsecp256k1/ethsecp256k1_cgo_test.go b/crypto/keys/ethsecp256k1/ethsecp256k1_cgo_test.go deleted file mode 100644 index 118808fed4e9..000000000000 --- a/crypto/keys/ethsecp256k1/ethsecp256k1_cgo_test.go +++ /dev/null @@ -1,41 +0,0 @@ -//go:build libethsecp256k1 -// +build libethsecp256k1 - -package ethsecp256k1 - -import ( - "testing" - - "github.com/magiconair/properties/assert" - - "github.com/stretchr/testify/require" -) - -func TestPrivKeyethsecp256k1SignVerify(t *testing.T) { - msg := []byte("A.1.2 ECC Key Pair Generation by Testing Candidates") - priv := GenPrivKey() - tests := []struct { - name string - privKey *PrivKey - wantSignErr bool - wantVerifyPasses bool - }{ - {name: "valid sign-verify round", privKey: priv, wantSignErr: false, wantVerifyPasses: true}, - {name: "invalid private key", privKey: &*PrivKey{Key: [32]byte{}}, wantSignErr: true, wantVerifyPasses: false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := tt.privKey.Sign(msg) - if tt.wantSignErr { - require.Error(t, err) - t.Logf("Got error: %s", err) - return - } - require.NoError(t, err) - require.NotNil(t, got) - - pub := tt.privKey.PubKey() - assert.Equal(t, tt.wantVerifyPasses, pub.VerifyBytes(msg, got)) - }) - } -} diff --git a/crypto/keys/ethsecp256k1/ethsecp256k1_internal_test.go b/crypto/keys/ethsecp256k1/ethsecp256k1_internal_test.go deleted file mode 100644 index b33a8b4756f6..000000000000 --- a/crypto/keys/ethsecp256k1/ethsecp256k1_internal_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package ethsecp256k1 - -import ( - "bytes" - "math/big" - "testing" - - btcethsecp256k1 "github.com/btcsuite/btcd/btcec" - "github.com/stretchr/testify/require" -) - -func Test_genPrivKey(t *testing.T) { - - empty := make([]byte, 32) - oneB := big.NewInt(1).Bytes() - onePadded := make([]byte, 32) - copy(onePadded[32-len(oneB):32], oneB) - t.Logf("one padded: %v, len=%v", onePadded, len(onePadded)) - - validOne := append(empty, onePadded...) - tests := []struct { - name string - notSoRand []byte - shouldPanic bool - }{ - {"empty bytes (panics because 1st 32 bytes are zero and 0 is not a valid field element)", empty, true}, - {"curve order: N", btcethsecp256k1.S256().N.Bytes(), true}, - {"valid because 0 < 1 < N", validOne, false}, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - if tt.shouldPanic { - require.Panics(t, func() { - genPrivKey(bytes.NewReader(tt.notSoRand)) - }) - return - } - got := genPrivKey(bytes.NewReader(tt.notSoRand)) - fe := new(big.Int).SetBytes(got[:]) - require.True(t, fe.Cmp(btcethsecp256k1.S256().N) < 0) - require.True(t, fe.Sign() > 0) - }) - } -} diff --git a/crypto/keys/ethsecp256k1/ethsecp256k1_nocgo.go b/crypto/keys/ethsecp256k1/ethsecp256k1_nocgo.go deleted file mode 100644 index a40ac1eb0d65..000000000000 --- a/crypto/keys/ethsecp256k1/ethsecp256k1_nocgo.go +++ /dev/null @@ -1,71 +0,0 @@ -//go:build !libethsecp256k1 -// +build !libethsecp256k1 - -package ethsecp256k1 - -import ( - "math/big" - - ethsecp256k1 "github.com/btcsuite/btcd/btcec" - - "github.com/tendermint/tendermint/crypto" -) - -// used to reject malleable signatures -// see: -// - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93 -// - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/crypto.go#L39 -var ethsecp256k1halfN = new(big.Int).Rsh(ethsecp256k1.S256().N, 1) - -// Sign creates an ECDSA signature on curve ethsecp256k1, using SHA256 on the msg. -// The returned signature will be of the form R || S (in lower-S form). -func (privKey *PrivKey) Sign(msg []byte) ([]byte, error) { - priv, _ := ethsecp256k1.PrivKeyFromBytes(ethsecp256k1.S256(), privKey.Key) - sig, err := priv.Sign(crypto.Sha256(msg)) - if err != nil { - return nil, err - } - sigBytes := serializeSig(sig) - return sigBytes, nil -} - -// VerifyBytes verifies a signature of the form R || S. -// It rejects signatures which are not in lower-S form. -func (pubKey *PubKey) VerifySignature(msg []byte, sigStr []byte) bool { - if len(sigStr) != 64 { - return false - } - pub, err := ethsecp256k1.ParsePubKey(pubKey.Key, ethsecp256k1.S256()) - if err != nil { - return false - } - // parse the signature: - signature := signatureFromBytes(sigStr) - // Reject malleable signatures. libethsecp256k1 does this check but btcec doesn't. - // see: https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93 - if signature.S.Cmp(ethsecp256k1halfN) > 0 { - return false - } - return signature.Verify(crypto.Sha256(msg), pub) -} - -// Read Signature struct from R || S. Caller needs to ensure -// that len(sigStr) == 64. -func signatureFromBytes(sigStr []byte) *ethsecp256k1.Signature { - return ðsecp256k1.Signature{ - R: new(big.Int).SetBytes(sigStr[:32]), - S: new(big.Int).SetBytes(sigStr[32:64]), - } -} - -// Serialize signature to R || S. -// R, S are padded to 32 bytes respectively. -func serializeSig(sig *ethsecp256k1.Signature) []byte { - rBytes := sig.R.Bytes() - sBytes := sig.S.Bytes() - sigBytes := make([]byte, 64) - // 0 pad the byte arrays from the left if they aren't big enough. - copy(sigBytes[32-len(rBytes):32], rBytes) - copy(sigBytes[64-len(sBytes):64], sBytes) - return sigBytes -} diff --git a/crypto/keys/ethsecp256k1/ethsecp256k1_nocgo_test.go b/crypto/keys/ethsecp256k1/ethsecp256k1_nocgo_test.go deleted file mode 100644 index aa6f10f9d091..000000000000 --- a/crypto/keys/ethsecp256k1/ethsecp256k1_nocgo_test.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build !libethsecp256k1 -// +build !libethsecp256k1 - -package ethsecp256k1 - -import ( - "testing" - - ethsecp256k1 "github.com/btcsuite/btcd/btcec" - "github.com/stretchr/testify/require" -) - -// Ensure that signature verification works, and that -// non-canonical signatures fail. -// Note: run with CGO_ENABLED=0 or go test -tags !cgo. -func TestSignatureVerificationAndRejectUpperS(t *testing.T) { - msg := []byte("We have lingered long enough on the shores of the cosmic ocean.") - for i := 0; i < 500; i++ { - priv := GenPrivKey() - sigStr, err := priv.Sign(msg) - require.NoError(t, err) - sig := signatureFromBytes(sigStr) - require.False(t, sig.S.Cmp(ethsecp256k1halfN) > 0) - - pub := priv.PubKey() - require.True(t, pub.VerifySignature(msg, sigStr)) - - // malleate: - sig.S.Sub(ethsecp256k1.S256().CurveParams.N, sig.S) - require.True(t, sig.S.Cmp(ethsecp256k1halfN) > 0) - malSigStr := serializeSig(sig) - - require.False(t, pub.VerifySignature(msg, malSigStr), - "VerifyBytes incorrect with malleated & invalid S. sig=%v, key=%v", - sig, - priv, - ) - } -} diff --git a/crypto/keys/ethsecp256k1/ethsecp256k1_test.go b/crypto/keys/ethsecp256k1/ethsecp256k1_test.go index 84973c3ab58a..e3eb51f319eb 100644 --- a/crypto/keys/ethsecp256k1/ethsecp256k1_test.go +++ b/crypto/keys/ethsecp256k1/ethsecp256k1_test.go @@ -1,225 +1,77 @@ -package ethsecp256k1_test +package ethsecp256k1 import ( - "crypto/ecdsa" "encoding/base64" - "encoding/hex" - "math/big" "testing" - btcethsecp256k1 "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcutil/base58" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" - tmethsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - "github.com/cosmos/cosmos-sdk/crypto/keys/ethsecp256k1" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" -) - -type keyData struct { - priv string - pub string - addr string -} -var secpDataTable = []keyData{ - { - priv: "a96e62ed3955e65be32703f12d87b6b5cf26039ecfa948dc5107a495418e5330", - pub: "02950e1cdfcb133d6024109fd489f734eeb4502418e538c28481f22bce276f248c", - addr: "1CKZ9Nx4zgds8tU7nJHotKSDr4a9bYJCa3", - }, -} + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" -func TestPubKeyethsecp256k1Address(t *testing.T) { - for _, d := range secpDataTable { - privB, _ := hex.DecodeString(d.priv) - pubB, _ := hex.DecodeString(d.pub) - addrBbz, _, _ := base58.CheckDecode(d.addr) - addrB := crypto.Address(addrBbz) + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) - var priv = ethsecp256k1.PrivKey{Key: privB} +func TestPrivKey(t *testing.T) { + // validate type and equality + privKey, err := GenerateKey() + require.NoError(t, err) + require.True(t, privKey.Equals(privKey)) + require.Implements(t, (*cryptotypes.PrivKey)(nil), privKey) - pubKey := priv.PubKey() - pubT, _ := pubKey.(*ethsecp256k1.PubKey) + // validate inequality + privKey2, err := GenerateKey() + require.NoError(t, err) + require.False(t, privKey.Equals(privKey2)) - addr := pubKey.Address() - assert.Equal(t, pubT, ðsecp256k1.PubKey{Key: pubB}, "Expected pub keys to match") - assert.Equal(t, addr, addrB, "Expected addresses to match") - } -} + // validate Ethereum address equality + addr := privKey.PubKey().Address() + expectedAddr := ethcrypto.PubkeyToAddress(privKey.ToECDSA().PublicKey) + require.Equal(t, expectedAddr.Bytes(), addr.Bytes()) -func TestSignAndValidateethsecp256k1(t *testing.T) { - privKey := ethsecp256k1.GenPrivKey() - pubKey := privKey.PubKey() + // validate we can sign some bytes + msg := []byte("hello world") + sigHash := ethcrypto.Keccak256Hash(msg) + expectedSig, err := secp256k1.Sign(sigHash.Bytes(), privKey.Bytes()) + require.NoError(t, err) - msg := crypto.CRandBytes(1000) sig, err := privKey.Sign(msg) - require.Nil(t, err) - assert.True(t, pubKey.VerifySignature(msg, sig)) - - // ---- - // Test cross packages verification - msgHash := crypto.Sha256(msg) - btcPrivKey, btcPubKey := btcethsecp256k1.PrivKeyFromBytes(btcethsecp256k1.S256(), privKey.Key) - // This fails: malformed signature: no header magic - // btcSig, err := ethsecp256k1.ParseSignature(sig, ethsecp256k1.S256()) - // require.NoError(t, err) - // assert.True(t, btcSig.Verify(msgHash, btcPubKey)) - // So we do a hacky way: - r := new(big.Int) - s := new(big.Int) - r.SetBytes(sig[:32]) - s.SetBytes(sig[32:]) - ok := ecdsa.Verify(btcPubKey.ToECDSA(), msgHash, r, s) - require.True(t, ok) - - sig2, err := btcPrivKey.Sign(msgHash) require.NoError(t, err) - pubKey.VerifySignature(msg, sig2.Serialize()) - - // ---- - // Mutate the signature, just one bit. - sig[3] ^= byte(0x01) - assert.False(t, pubKey.VerifySignature(msg, sig)) + require.Equal(t, expectedSig, sig) } -// This test is intended to justify the removal of calls to the underlying library -// in creating the privkey. -func Testethsecp256k1LoadPrivkeyAndSerializeIsIdentity(t *testing.T) { - numberOfTests := 256 - for i := 0; i < numberOfTests; i++ { - // Seed the test case with some random bytes - privKeyBytes := [32]byte{} - copy(privKeyBytes[:], crypto.CRandBytes(32)) - - // This function creates a private and public key in the underlying libraries format. - // The private key is basically calling new(big.Int).SetBytes(pk), which removes leading zero bytes - priv, _ := btcethsecp256k1.PrivKeyFromBytes(btcethsecp256k1.S256(), privKeyBytes[:]) - // this takes the bytes returned by `(big int).Bytes()`, and if the length is less than 32 bytes, - // pads the bytes from the left with zero bytes. Therefore these two functions composed - // result in the identity function on privKeyBytes, hence the following equality check - // always returning true. - serializedBytes := priv.Serialize() - require.Equal(t, privKeyBytes[:], serializedBytes) - } -} - -func TestGenPrivKeyFromSecret(t *testing.T) { - // curve oder N - N := btcethsecp256k1.S256().N - tests := []struct { - name string - secret []byte - }{ - {"empty secret", []byte{}}, - { - "some long secret", - []byte("We live in a society exquisitely dependent on science and technology, " + - "in which hardly anyone knows anything about science and technology."), - }, - {"another seed used in cosmos tests #1", []byte{0}}, - {"another seed used in cosmos tests #2", []byte("mySecret")}, - {"another seed used in cosmos tests #3", []byte("")}, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - gotPrivKey := ethsecp256k1.GenPrivKeyFromSecret(tt.secret) - require.NotNil(t, gotPrivKey) - // interpret as a big.Int and make sure it is a valid field element: - fe := new(big.Int).SetBytes(gotPrivKey.Key[:]) - require.True(t, fe.Cmp(N) < 0) - require.True(t, fe.Sign() > 0) - }) - } -} - -func TestPubKeyEquals(t *testing.T) { - ethsecp256k1PubKey := ethsecp256k1.GenPrivKey().PubKey().(*ethsecp256k1.PubKey) - - testCases := []struct { - msg string - pubKey cryptotypes.PubKey - other cryptotypes.PubKey - expectEq bool - }{ - { - "different bytes", - ethsecp256k1PubKey, - ethsecp256k1.GenPrivKey().PubKey(), - false, - }, - { - "equals", - ethsecp256k1PubKey, - ðsecp256k1.PubKey{ - Key: ethsecp256k1PubKey.Key, - }, - true, - }, - { - "different types", - ethsecp256k1PubKey, - ed25519.GenPrivKey().PubKey(), - false, - }, - } +func TestPrivKey_PubKey(t *testing.T) { + privKey, err := GenerateKey() + require.NoError(t, err) - for _, tc := range testCases { - t.Run(tc.msg, func(t *testing.T) { - eq := tc.pubKey.Equals(tc.other) - require.Equal(t, eq, tc.expectEq) - }) + // validate type and equality + pubKey := &PubKey{ + Key: privKey.PubKey().Bytes(), } -} + require.Implements(t, (*cryptotypes.PubKey)(nil), pubKey) -func TestPrivKeyEquals(t *testing.T) { - ethsecp256k1PrivKey := ethsecp256k1.GenPrivKey() + // validate inequality + privKey2, err := GenerateKey() + require.NoError(t, err) + require.False(t, pubKey.Equals(privKey2.PubKey())) - testCases := []struct { - msg string - privKey cryptotypes.PrivKey - other cryptotypes.PrivKey - expectEq bool - }{ - { - "different bytes", - ethsecp256k1PrivKey, - ethsecp256k1.GenPrivKey(), - false, - }, - { - "equals", - ethsecp256k1PrivKey, - ðsecp256k1.PrivKey{ - Key: ethsecp256k1PrivKey.Key, - }, - true, - }, - { - "different types", - ethsecp256k1PrivKey, - ed25519.GenPrivKey(), - false, - }, - } + // validate signature + msg := []byte("hello world") + sig, err := privKey.Sign(msg) + require.NoError(t, err) - for _, tc := range testCases { - t.Run(tc.msg, func(t *testing.T) { - eq := tc.privKey.Equals(tc.other) - require.Equal(t, eq, tc.expectEq) - }) - } + res := pubKey.VerifySignature(msg, sig) + require.True(t, res) } func TestMarshalAmino(t *testing.T) { aminoCdc := codec.NewLegacyAmino() - privKey := ethsecp256k1.GenPrivKey() - pubKey := privKey.PubKey().(*ethsecp256k1.PubKey) + privKey, err := GenerateKey() + require.NoError(t, err) + + pubKey := privKey.PubKey().(*PubKey) testCases := []struct { desc string @@ -231,14 +83,14 @@ func TestMarshalAmino(t *testing.T) { { "ethsecp256k1 private key", privKey, - ðsecp256k1.PrivKey{}, + &PrivKey{}, append([]byte{32}, privKey.Bytes()...), // Length-prefixed. "\"" + base64.StdEncoding.EncodeToString(privKey.Bytes()) + "\"", }, { "ethsecp256k1 public key", pubKey, - ðsecp256k1.PubKey{}, + &PubKey{}, append([]byte{33}, pubKey.Bytes()...), // Length-prefixed. "\"" + base64.StdEncoding.EncodeToString(pubKey.Bytes()) + "\"", }, @@ -268,56 +120,3 @@ func TestMarshalAmino(t *testing.T) { }) } } - -func TestMarshalAmino_BackwardsCompatibility(t *testing.T) { - aminoCdc := codec.NewLegacyAmino() - // Create Tendermint keys. - tmPrivKey := tmethsecp256k1.GenPrivKey() - tmPubKey := tmPrivKey.PubKey() - // Create our own keys, with the same private key as Tendermint's. - privKey := ðsecp256k1.PrivKey{Key: []byte(tmPrivKey)} - pubKey := privKey.PubKey().(*ethsecp256k1.PubKey) - - testCases := []struct { - desc string - tmKey interface{} - ourKey interface{} - marshalFn func(o interface{}) ([]byte, error) - }{ - { - "ethsecp256k1 private key, binary", - tmPrivKey, - privKey, - aminoCdc.Marshal, - }, - { - "ethsecp256k1 private key, JSON", - tmPrivKey, - privKey, - aminoCdc.MarshalJSON, - }, - { - "ethsecp256k1 public key, binary", - tmPubKey, - pubKey, - aminoCdc.Marshal, - }, - { - "ethsecp256k1 public key, JSON", - tmPubKey, - pubKey, - aminoCdc.MarshalJSON, - }, - } - - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - // Make sure Amino encoding override is not breaking backwards compatibility. - bz1, err := tc.marshalFn(tc.tmKey) - require.NoError(t, err) - bz2, err := tc.marshalFn(tc.ourKey) - require.NoError(t, err) - require.Equal(t, bz1, bz2) - }) - } -}