From f6ddfd61e82b15c2b3ecb56463d2309f068b5f29 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 28 Dec 2020 08:55:31 +0100 Subject: [PATCH 1/9] EthereumSigner EthereumSigner provides functions to sign a message and verify a signature using Ethereum-specific signature format. It also provides functions for conversion of a public key to address. The code has been extracted from keep-core with slight modifications that will allow it to be imported from keep-core and keep-ecdsa. --- pkg/chain/ethereum/ethutil/signer.go | 146 +++++++++++++++++++ pkg/chain/ethereum/ethutil/signer_test.go | 165 ++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 pkg/chain/ethereum/ethutil/signer.go create mode 100644 pkg/chain/ethereum/ethutil/signer_test.go diff --git a/pkg/chain/ethereum/ethutil/signer.go b/pkg/chain/ethereum/ethutil/signer.go new file mode 100644 index 0000000..2e388d4 --- /dev/null +++ b/pkg/chain/ethereum/ethutil/signer.go @@ -0,0 +1,146 @@ +package ethutil + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "fmt" + + "github.com/ethereum/go-ethereum/crypto" +) + +// SignatureSize is a byte size of a signature calculated by Ethereum with +// recovery-id, V, included. The signature consist of three values (R,V,S) +// in the following order: +// R = [0:31] +// S = [32:63] +// V = [64] +const SignatureSize = 65 + +// EthereumSigner provides functions to sign a message and verify a signature +// using the Ethereum-specific signature format. It also provides functions for +// convertion of a public key to address. +type EthereumSigner struct { + operatorKey *ecdsa.PrivateKey +} + +// NewSigner creates a new EthereumSigner instance for the provided private key. +func NewSigner(privateKey *ecdsa.PrivateKey) *EthereumSigner { + return &EthereumSigner{privateKey} +} + +// PublicKey returns byte representation of a public key for the private key +// signer was created with. +func (es *EthereumSigner) PublicKey() []byte { + publicKey := es.operatorKey.PublicKey + return elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y) +} + +// Sign signs the provided message using Ethereum-specific format. +func (es *EthereumSigner) Sign(message []byte) ([]byte, error) { + prefixedHash := crypto.Keccak256( + []byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%v", len(message))), + message, + ) + + signature, err := crypto.Sign(prefixedHash, es.operatorKey) + if err != nil { + return nil, err + } + + if len(signature) == SignatureSize { + // go-ethereum/crypto produces signature with v={0, 1} and we need to add + // 27 to v-part (signature[64]) to conform wtih the on-chain signature + // validation code that accepts v={27, 28} as specified in the + // Appendix F of the Ethereum Yellow Paper + // https://ethereum.github.io/yellowpaper/paper.pdf + signature[len(signature)-1] = signature[len(signature)-1] + 27 + } + + return signature, nil +} + +// Verify verifies the provided message against a signature using the key +// EthereumSigner was created with. The signature has to be provided in +// Ethereum-specific format. +func (es *EthereumSigner) Verify(message []byte, signature []byte) (bool, error) { + return verifySignature(message, signature, &es.operatorKey.PublicKey) +} + +// VerifyWithPublicKey verifies the provided message against a signature and +// public key. The signature has to be provided in Ethereum-specific format. +func (es *EthereumSigner) VerifyWithPublicKey( + message []byte, + signature []byte, + publicKey []byte, +) (bool, error) { + unmarshalledPubKey, err := unmarshalPublicKey( + publicKey, + es.operatorKey.Curve, + ) + if err != nil { + return false, err + } + + return verifySignature(message, signature, unmarshalledPubKey) +} + +func verifySignature( + message []byte, + signature []byte, + publicKey *ecdsa.PublicKey, +) (bool, error) { + // Convert the operator's static key into an uncompressed public key + // which should be 65 bytes in length. + uncompressedPubKey := crypto.FromECDSAPub(publicKey) + // If our signature is in the [R || S || V] format, ensure we strip out + // the Ethereum-specific recovery-id, V, if it already hasn't been done. + if len(signature) == SignatureSize { + signature = signature[:len(signature)-1] + } + + // The signature should be now 64 bytes long. + if len(signature) != 64 { + return false, fmt.Errorf( + "signature should have 64 bytes; has: [%v]", + len(signature), + ) + } + + prefixedHash := crypto.Keccak256( + []byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%v", len(message))), + message, + ) + + return crypto.VerifySignature( + uncompressedPubKey, + prefixedHash, + signature, + ), nil +} + +func unmarshalPublicKey( + bytes []byte, + curve elliptic.Curve, +) (*ecdsa.PublicKey, error) { + x, y := elliptic.Unmarshal(curve, bytes) + if x == nil { + return nil, fmt.Errorf( + "invalid public key bytes", + ) + } + ecdsaPublicKey := &ecdsa.PublicKey{Curve: curve, X: x, Y: y} + return (*ecdsa.PublicKey)(ecdsaPublicKey), nil +} + +// PublicKeyToAddress transforms the provided ECDSA public key into Ethereum +// address represented in bytes. +func (es *EthereumSigner) PublicKeyToAddress(publicKey ecdsa.PublicKey) []byte { + return crypto.PubkeyToAddress(publicKey).Bytes() +} + +// PublicKeyBytesToAddress transforms the provided ECDSA public key in a bytes +// format into Ethereum address represented in bytes. +func (es *EthereumSigner) PublicKeyBytesToAddress(publicKey []byte) []byte { + // Does the same as crypto.PubkeyToAddress but directly on public key bytes. + return crypto.Keccak256(publicKey[1:])[12:] +} diff --git a/pkg/chain/ethereum/ethutil/signer_test.go b/pkg/chain/ethereum/ethutil/signer_test.go new file mode 100644 index 0000000..ba609ee --- /dev/null +++ b/pkg/chain/ethereum/ethutil/signer_test.go @@ -0,0 +1,165 @@ +package ethutil + +import ( + "testing" + + "github.com/ethereum/go-ethereum/crypto" +) + +func TestSignAndVerify(t *testing.T) { + signing, err := newSigning() + if err != nil { + t.Fatal(err) + } + + message := []byte("He that breaks a thing to find out what it is, has " + + "left the path of wisdom.") + + signature, err := signing.Sign(message) + if err != nil { + t.Fatal(err) + } + + var tests = map[string]struct { + message []byte + signature []byte + validSignatureExpected bool + validationErrorExpected bool + }{ + "valid signature for message": { + message: message, + signature: signature, + validSignatureExpected: true, + validationErrorExpected: false, + }, + "invalid signature for message": { + message: []byte("I am sorry"), + signature: signature, + validSignatureExpected: false, + validationErrorExpected: false, + }, + "corrupted signature": { + message: message, + signature: []byte("I am so sorry"), + validSignatureExpected: false, + validationErrorExpected: true, + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + ok, err := signing.Verify(test.message, test.signature) + + if !ok && test.validSignatureExpected { + t.Errorf("expected valid signature but verification failed") + } + if ok && !test.validSignatureExpected { + t.Errorf("expected invalid signature but verification succeeded") + } + + if err == nil && test.validationErrorExpected { + t.Errorf("expected signature validation error; none happened") + } + if err != nil && !test.validationErrorExpected { + t.Errorf("unexpected signature validation error [%v]", err) + } + }) + } +} + +func TestSignAndVerifyWithProvidedPublicKey(t *testing.T) { + message := []byte("I am looking for someone to share in an adventure") + + signing1, err := newSigning() + if err != nil { + t.Fatal(err) + } + + signing2, err := newSigning() + if err != nil { + t.Fatal(err) + } + + publicKey := signing1.PublicKey() + signature, err := signing1.Sign(message) + if err != nil { + t.Fatal(err) + } + + var tests = map[string]struct { + message []byte + signature []byte + publicKey []byte + validSignatureExpected bool + validationErrorExpected bool + }{ + "valid signature for message": { + message: message, + signature: signature, + publicKey: publicKey, + validSignatureExpected: true, + validationErrorExpected: false, + }, + "invalid signature for message": { + message: []byte("And here..."), + signature: signature, + publicKey: publicKey, + validSignatureExpected: false, + validationErrorExpected: false, + }, + "corrupted signature": { + message: message, + signature: []byte("we..."), + publicKey: publicKey, + validSignatureExpected: false, + validationErrorExpected: true, + }, + "invalid remote public key": { + message: message, + signature: signature, + publicKey: signing2.PublicKey(), + validSignatureExpected: false, + validationErrorExpected: false, + }, + "corrupted remote public key": { + message: message, + signature: signature, + publicKey: []byte("go..."), + validSignatureExpected: false, + validationErrorExpected: true, + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + ok, err := signing2.VerifyWithPublicKey( + test.message, + test.signature, + test.publicKey, + ) + + if !ok && test.validSignatureExpected { + t.Errorf("expected valid signature but verification failed") + } + if ok && !test.validSignatureExpected { + t.Errorf("expected invalid signature but verification succeeded") + } + + if err == nil && test.validationErrorExpected { + t.Errorf("expected signature validation error; none happened") + } + if err != nil && !test.validationErrorExpected { + t.Errorf("unexpected signature validation error [%v]", err) + } + }) + } +} + +func newSigning() (*EthereumSigner, error) { + key, err := crypto.GenerateKey() + if err != nil { + return nil, err + } + + return &EthereumSigner{key}, nil +} From 798145525aa2617643107df29df453697d7d6cbd Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 28 Dec 2020 09:48:18 +0100 Subject: [PATCH 2/9] local.Signer Having local Signer implementation in keep-common will let us not duplicate the code between local chain implementations in `keep-core` and `keep-ecdsa`. --- pkg/chain/local/signer.go | 112 ++++++++++++++++++++++ pkg/chain/local/signer_test.go | 166 +++++++++++++++++++++++++++++++++ 2 files changed, 278 insertions(+) create mode 100644 pkg/chain/local/signer.go create mode 100644 pkg/chain/local/signer_test.go diff --git a/pkg/chain/local/signer.go b/pkg/chain/local/signer.go new file mode 100644 index 0000000..37f7210 --- /dev/null +++ b/pkg/chain/local/signer.go @@ -0,0 +1,112 @@ +package local + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/asn1" + "fmt" + "math/big" +) + +// Signer provides functions to sign a message and verify a signature +// using the chain-specific signature format. It also provides functions for +// convertion of a public key to address. +type Signer struct { + operatorKey *ecdsa.PrivateKey +} + +type ecdsaSignature struct { + R, S *big.Int +} + +// NewSigner creates a new Signer instance for the provided private key. +func NewSigner(privateKey *ecdsa.PrivateKey) *Signer { + return &Signer{privateKey} +} + +// PublicKey returns byte representation of a public key for the private key +// signer was created with. +func (ls *Signer) PublicKey() []byte { + publicKey := ls.operatorKey.PublicKey + return elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y) +} + +// Sign signs the provided message. +func (ls *Signer) Sign(message []byte) ([]byte, error) { + hash := sha256.Sum256(message) + + r, s, err := ecdsa.Sign(rand.Reader, ls.operatorKey, hash[:]) + if err != nil { + return nil, err + } + + return asn1.Marshal(ecdsaSignature{r, s}) +} + +// Verify verifies the provided message against a signature using the key +// Signer was created with. +func (ls *Signer) Verify(message []byte, signature []byte) (bool, error) { + return verifySignature(message, signature, &ls.operatorKey.PublicKey) +} + +// VerifyWithPublicKey verifies the provided message against a signature and +// public key. +func (ls *Signer) VerifyWithPublicKey( + message []byte, + signature []byte, + publicKey []byte, +) (bool, error) { + unmarshalledPubKey, err := unmarshalPublicKey( + publicKey, + ls.operatorKey.Curve, + ) + if err != nil { + return false, err + } + + return verifySignature(message, signature, unmarshalledPubKey) +} + +func verifySignature( + message []byte, + signature []byte, + publicKey *ecdsa.PublicKey, +) (bool, error) { + hash := sha256.Sum256(message) + + sig := &ecdsaSignature{} + _, err := asn1.Unmarshal(signature, sig) + if err != nil { + return false, err + } + + return ecdsa.Verify(publicKey, hash[:], sig.R, sig.S), nil +} + +func unmarshalPublicKey( + bytes []byte, + curve elliptic.Curve, +) (*ecdsa.PublicKey, error) { + x, y := elliptic.Unmarshal(curve, bytes) + if x == nil { + return nil, fmt.Errorf( + "invalid public key bytes", + ) + } + ecdsaPublicKey := &ecdsa.PublicKey{Curve: curve, X: x, Y: y} + return (*ecdsa.PublicKey)(ecdsaPublicKey), nil +} + +// PublicKeyToAddress transforms the provided ECDSA public key into chain +// address represented in bytes. +func (ls *Signer) PublicKeyToAddress(publicKey ecdsa.PublicKey) []byte { + return elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y) +} + +// PublicKeyBytesToAddress transforms the provided ECDSA public key in a bytes +// format into chain address represented in bytes. +func (ls *Signer) PublicKeyBytesToAddress(publicKey []byte) []byte { + return publicKey +} diff --git a/pkg/chain/local/signer_test.go b/pkg/chain/local/signer_test.go new file mode 100644 index 0000000..a923592 --- /dev/null +++ b/pkg/chain/local/signer_test.go @@ -0,0 +1,166 @@ +package local + +import ( + "crypto/ecdsa" + "crypto/elliptic" + crand "crypto/rand" + "testing" +) + +func TestSignAndVerify(t *testing.T) { + message := []byte("Two things only the greatest fools do: throw " + + "stones at hornets' nests and threaten a witcher.") + + signer, err := newSigner() + if err != nil { + t.Fatal(err) + } + + signature, err := signer.Sign(message) + if err != nil { + t.Fatal(err) + } + + var tests = map[string]struct { + message []byte + signature []byte + validSignatureExpected bool + validationErrorExpected bool + }{ + "valid signature for message": { + message: message, + signature: signature, + validSignatureExpected: true, + validationErrorExpected: false, + }, + "invalid signature for message": { + message: []byte("I am sorry"), + signature: signature, + validSignatureExpected: false, + validationErrorExpected: false, + }, + "corrupted signature": { + message: message, + signature: []byte("I am so sorry"), + validSignatureExpected: false, + validationErrorExpected: true, + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + ok, err := signer.Verify(test.message, test.signature) + + if !ok && test.validSignatureExpected { + t.Errorf("expected valid signature but verification failed") + } + if ok && !test.validSignatureExpected { + t.Errorf("expected invalid signature but verification succeeded") + } + + if err == nil && test.validationErrorExpected { + t.Errorf("expected signature validation error; none happened") + } + if err != nil && !test.validationErrorExpected { + t.Errorf("unexpected signature validation error [%v]", err) + } + }) + } +} + +func TestSignAndVerifyWithProvidedPublicKey(t *testing.T) { + message := []byte("You shall not pass") + + signer1, err := newSigner() + if err != nil { + t.Fatal(err) + } + + signer2, err := newSigner() + if err != nil { + t.Fatal(err) + } + + publicKey := signer1.PublicKey() + signature, err := signer1.Sign(message) + if err != nil { + t.Fatal(err) + } + + var tests = map[string]struct { + message []byte + signature []byte + publicKey []byte + validSignatureExpected bool + validationErrorExpected bool + }{ + "valid signature for message": { + message: message, + signature: signature, + publicKey: publicKey, + validSignatureExpected: true, + validationErrorExpected: false, + }, + "invalid signature for message": { + message: []byte("Fly you fools"), + signature: signature, + publicKey: publicKey, + validSignatureExpected: false, + validationErrorExpected: false, + }, + "corrupted signature": { + message: message, + signature: []byte("My precious"), + publicKey: publicKey, + validSignatureExpected: false, + validationErrorExpected: true, + }, + "invalid remote public key": { + message: message, + signature: signature, + publicKey: signer2.PublicKey(), + validSignatureExpected: false, + validationErrorExpected: false, + }, + "corrupted remote public key": { + message: message, + signature: signature, + publicKey: []byte("A Balrog"), + validSignatureExpected: false, + validationErrorExpected: true, + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + ok, err := signer2.VerifyWithPublicKey( + test.message, + test.signature, + test.publicKey, + ) + + if !ok && test.validSignatureExpected { + t.Errorf("expected valid signature but verification failed") + } + if ok && !test.validSignatureExpected { + t.Errorf("expected invalid signature but verification succeeded") + } + + if err == nil && test.validationErrorExpected { + t.Errorf("expected signature validation error; none happened") + } + if err != nil && !test.validationErrorExpected { + t.Errorf("unexpected signature validation error [%v]", err) + } + }) + } +} + +func newSigner() (*Signer, error) { + key, err := ecdsa.GenerateKey(elliptic.P256(), crand.Reader) + if err != nil { + return nil, err + } + + return &Signer{key}, nil +} From 7acbaeada69dd654a500baae55f1601b2a524bed Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 28 Dec 2020 13:58:20 +0100 Subject: [PATCH 3/9] Minor function/variable renames in unit tests We create EthereumSigner instance so `newSigner` reads better. Same for variable names: s/signing/signer. --- pkg/chain/ethereum/ethutil/signer_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/chain/ethereum/ethutil/signer_test.go b/pkg/chain/ethereum/ethutil/signer_test.go index ba609ee..203f2dc 100644 --- a/pkg/chain/ethereum/ethutil/signer_test.go +++ b/pkg/chain/ethereum/ethutil/signer_test.go @@ -7,7 +7,7 @@ import ( ) func TestSignAndVerify(t *testing.T) { - signing, err := newSigning() + signer, err := newSigner() if err != nil { t.Fatal(err) } @@ -15,7 +15,7 @@ func TestSignAndVerify(t *testing.T) { message := []byte("He that breaks a thing to find out what it is, has " + "left the path of wisdom.") - signature, err := signing.Sign(message) + signature, err := signer.Sign(message) if err != nil { t.Fatal(err) } @@ -48,7 +48,7 @@ func TestSignAndVerify(t *testing.T) { for testName, test := range tests { t.Run(testName, func(t *testing.T) { - ok, err := signing.Verify(test.message, test.signature) + ok, err := signer.Verify(test.message, test.signature) if !ok && test.validSignatureExpected { t.Errorf("expected valid signature but verification failed") @@ -70,18 +70,18 @@ func TestSignAndVerify(t *testing.T) { func TestSignAndVerifyWithProvidedPublicKey(t *testing.T) { message := []byte("I am looking for someone to share in an adventure") - signing1, err := newSigning() + signer1, err := newSigner() if err != nil { t.Fatal(err) } - signing2, err := newSigning() + signer2, err := newSigner() if err != nil { t.Fatal(err) } - publicKey := signing1.PublicKey() - signature, err := signing1.Sign(message) + publicKey := signer1.PublicKey() + signature, err := signer1.Sign(message) if err != nil { t.Fatal(err) } @@ -117,7 +117,7 @@ func TestSignAndVerifyWithProvidedPublicKey(t *testing.T) { "invalid remote public key": { message: message, signature: signature, - publicKey: signing2.PublicKey(), + publicKey: signer2.PublicKey(), validSignatureExpected: false, validationErrorExpected: false, }, @@ -132,7 +132,7 @@ func TestSignAndVerifyWithProvidedPublicKey(t *testing.T) { for testName, test := range tests { t.Run(testName, func(t *testing.T) { - ok, err := signing2.VerifyWithPublicKey( + ok, err := signer2.VerifyWithPublicKey( test.message, test.signature, test.publicKey, @@ -155,7 +155,7 @@ func TestSignAndVerifyWithProvidedPublicKey(t *testing.T) { } } -func newSigning() (*EthereumSigner, error) { +func newSigner() (*EthereumSigner, error) { key, err := crypto.GenerateKey() if err != nil { return nil, err From 183c6c3b7745e4148c9580c2434ae89a1d15c34b Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 28 Dec 2020 14:07:41 +0100 Subject: [PATCH 4/9] Small docummentation improvements for EthereumSigner --- pkg/chain/ethereum/ethutil/signer.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/chain/ethereum/ethutil/signer.go b/pkg/chain/ethereum/ethutil/signer.go index 2e388d4..47ecf92 100644 --- a/pkg/chain/ethereum/ethutil/signer.go +++ b/pkg/chain/ethereum/ethutil/signer.go @@ -9,7 +9,7 @@ import ( ) // SignatureSize is a byte size of a signature calculated by Ethereum with -// recovery-id, V, included. The signature consist of three values (R,V,S) +// recovery-id, V, included. The signature consists of three values (R,S,V) // in the following order: // R = [0:31] // S = [32:63] @@ -18,12 +18,13 @@ const SignatureSize = 65 // EthereumSigner provides functions to sign a message and verify a signature // using the Ethereum-specific signature format. It also provides functions for -// convertion of a public key to address. +// conversion of a public key to an address. type EthereumSigner struct { operatorKey *ecdsa.PrivateKey } -// NewSigner creates a new EthereumSigner instance for the provided private key. +// NewSigner creates a new EthereumSigner instance for the provided ECDSA +// private key. func NewSigner(privateKey *ecdsa.PrivateKey) *EthereumSigner { return &EthereumSigner{privateKey} } From b5774b2cb5d0cfeea0321116b6763dea280c2363 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 28 Dec 2020 14:08:39 +0100 Subject: [PATCH 5/9] Renamed operatorKey to privateKey We do not need to use the "operator" concept in keep-common ethutil, we can be a little more generic. --- pkg/chain/ethereum/ethutil/signer.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/chain/ethereum/ethutil/signer.go b/pkg/chain/ethereum/ethutil/signer.go index 47ecf92..108f106 100644 --- a/pkg/chain/ethereum/ethutil/signer.go +++ b/pkg/chain/ethereum/ethutil/signer.go @@ -20,7 +20,7 @@ const SignatureSize = 65 // using the Ethereum-specific signature format. It also provides functions for // conversion of a public key to an address. type EthereumSigner struct { - operatorKey *ecdsa.PrivateKey + privateKey *ecdsa.PrivateKey } // NewSigner creates a new EthereumSigner instance for the provided ECDSA @@ -32,7 +32,7 @@ func NewSigner(privateKey *ecdsa.PrivateKey) *EthereumSigner { // PublicKey returns byte representation of a public key for the private key // signer was created with. func (es *EthereumSigner) PublicKey() []byte { - publicKey := es.operatorKey.PublicKey + publicKey := es.privateKey.PublicKey return elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y) } @@ -43,7 +43,7 @@ func (es *EthereumSigner) Sign(message []byte) ([]byte, error) { message, ) - signature, err := crypto.Sign(prefixedHash, es.operatorKey) + signature, err := crypto.Sign(prefixedHash, es.privateKey) if err != nil { return nil, err } @@ -64,7 +64,7 @@ func (es *EthereumSigner) Sign(message []byte) ([]byte, error) { // EthereumSigner was created with. The signature has to be provided in // Ethereum-specific format. func (es *EthereumSigner) Verify(message []byte, signature []byte) (bool, error) { - return verifySignature(message, signature, &es.operatorKey.PublicKey) + return verifySignature(message, signature, &es.privateKey.PublicKey) } // VerifyWithPublicKey verifies the provided message against a signature and @@ -76,7 +76,7 @@ func (es *EthereumSigner) VerifyWithPublicKey( ) (bool, error) { unmarshalledPubKey, err := unmarshalPublicKey( publicKey, - es.operatorKey.Curve, + es.privateKey.Curve, ) if err != nil { return false, err From 3acb8c8f573dfedfed22a14125d55a08d00584b8 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 28 Dec 2020 14:12:15 +0100 Subject: [PATCH 6/9] Return more specific error from VerifyWithPublisKey --- pkg/chain/ethereum/ethutil/signer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/chain/ethereum/ethutil/signer.go b/pkg/chain/ethereum/ethutil/signer.go index 108f106..4ffa150 100644 --- a/pkg/chain/ethereum/ethutil/signer.go +++ b/pkg/chain/ethereum/ethutil/signer.go @@ -50,7 +50,7 @@ func (es *EthereumSigner) Sign(message []byte) ([]byte, error) { if len(signature) == SignatureSize { // go-ethereum/crypto produces signature with v={0, 1} and we need to add - // 27 to v-part (signature[64]) to conform wtih the on-chain signature + // 27 to v-part (signature[64]) to conform with the on-chain signature // validation code that accepts v={27, 28} as specified in the // Appendix F of the Ethereum Yellow Paper // https://ethereum.github.io/yellowpaper/paper.pdf @@ -79,7 +79,7 @@ func (es *EthereumSigner) VerifyWithPublicKey( es.privateKey.Curve, ) if err != nil { - return false, err + return false, fmt.Errorf("failed to unmarshal public key: [%v]", err) } return verifySignature(message, signature, unmarshalledPubKey) From a20f557a2fc5d4608553b4255ce3e298826b8171 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 28 Dec 2020 14:17:45 +0100 Subject: [PATCH 7/9] Use %d for integer types in string formatter --- pkg/chain/ethereum/ethutil/signer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/chain/ethereum/ethutil/signer.go b/pkg/chain/ethereum/ethutil/signer.go index 4ffa150..3fb9183 100644 --- a/pkg/chain/ethereum/ethutil/signer.go +++ b/pkg/chain/ethereum/ethutil/signer.go @@ -102,7 +102,7 @@ func verifySignature( // The signature should be now 64 bytes long. if len(signature) != 64 { return false, fmt.Errorf( - "signature should have 64 bytes; has: [%v]", + "signature should have 64 bytes; has: [%d]", len(signature), ) } From 36e41794ee85329e03c87387b9ddaf50636b6c24 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 28 Dec 2020 14:20:38 +0100 Subject: [PATCH 8/9] Extracted code for prefixing ethereum hashes into a separate function This way, the code can be reused from sign and verify functions. --- pkg/chain/ethereum/ethutil/signer.go | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/pkg/chain/ethereum/ethutil/signer.go b/pkg/chain/ethereum/ethutil/signer.go index 3fb9183..e5ffcef 100644 --- a/pkg/chain/ethereum/ethutil/signer.go +++ b/pkg/chain/ethereum/ethutil/signer.go @@ -38,12 +38,7 @@ func (es *EthereumSigner) PublicKey() []byte { // Sign signs the provided message using Ethereum-specific format. func (es *EthereumSigner) Sign(message []byte) ([]byte, error) { - prefixedHash := crypto.Keccak256( - []byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%v", len(message))), - message, - ) - - signature, err := crypto.Sign(prefixedHash, es.privateKey) + signature, err := crypto.Sign(ethereumPrefixedHash(message), es.privateKey) if err != nil { return nil, err } @@ -107,18 +102,20 @@ func verifySignature( ) } - prefixedHash := crypto.Keccak256( - []byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%v", len(message))), - message, - ) - return crypto.VerifySignature( uncompressedPubKey, - prefixedHash, + ethereumPrefixedHash(message), signature, ), nil } +func ethereumPrefixedHash(message []byte) []byte { + return crypto.Keccak256( + []byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%v", len(message))), + message, + ) +} + func unmarshalPublicKey( bytes []byte, curve elliptic.Curve, From 3de8696768667ebf3b3be49ee7905e7362fb9216 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 28 Dec 2020 14:25:29 +0100 Subject: [PATCH 9/9] More specific error messages in local Signer implementation --- pkg/chain/local/signer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/chain/local/signer.go b/pkg/chain/local/signer.go index 37f7210..53f7a78 100644 --- a/pkg/chain/local/signer.go +++ b/pkg/chain/local/signer.go @@ -63,7 +63,7 @@ func (ls *Signer) VerifyWithPublicKey( ls.operatorKey.Curve, ) if err != nil { - return false, err + return false, fmt.Errorf("failed to unmarshal public key: [%v]", err) } return verifySignature(message, signature, unmarshalledPubKey) @@ -79,7 +79,7 @@ func verifySignature( sig := &ecdsaSignature{} _, err := asn1.Unmarshal(signature, sig) if err != nil { - return false, err + return false, fmt.Errorf("failed to unmarshal signature: [%v]", err) } return ecdsa.Verify(publicKey, hash[:], sig.R, sig.S), nil