Skip to content

Commit c7eca9a

Browse files
authored
Merge pull request #61 from keep-network/signer
EthereumSigner and local Signer EthereumSigner provides functions to sign a message and verify a signature using Ethereum-specific signature format. It also provides functions for the 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`. The code is being extracted to allow us decouple ethereum-specific implementation functions we use in generic code of `keep-ecdsa` client, especially in `node.go`. Also extracted `localSigning` from `keep-core` as a `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`.
2 parents e39a681 + 3de8696 commit c7eca9a

File tree

4 files changed

+587
-0
lines changed

4 files changed

+587
-0
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package ethutil
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
6+
"fmt"
7+
8+
"github.com/ethereum/go-ethereum/crypto"
9+
)
10+
11+
// SignatureSize is a byte size of a signature calculated by Ethereum with
12+
// recovery-id, V, included. The signature consists of three values (R,S,V)
13+
// in the following order:
14+
// R = [0:31]
15+
// S = [32:63]
16+
// V = [64]
17+
const SignatureSize = 65
18+
19+
// EthereumSigner provides functions to sign a message and verify a signature
20+
// using the Ethereum-specific signature format. It also provides functions for
21+
// conversion of a public key to an address.
22+
type EthereumSigner struct {
23+
privateKey *ecdsa.PrivateKey
24+
}
25+
26+
// NewSigner creates a new EthereumSigner instance for the provided ECDSA
27+
// private key.
28+
func NewSigner(privateKey *ecdsa.PrivateKey) *EthereumSigner {
29+
return &EthereumSigner{privateKey}
30+
}
31+
32+
// PublicKey returns byte representation of a public key for the private key
33+
// signer was created with.
34+
func (es *EthereumSigner) PublicKey() []byte {
35+
publicKey := es.privateKey.PublicKey
36+
return elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y)
37+
}
38+
39+
// Sign signs the provided message using Ethereum-specific format.
40+
func (es *EthereumSigner) Sign(message []byte) ([]byte, error) {
41+
signature, err := crypto.Sign(ethereumPrefixedHash(message), es.privateKey)
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
if len(signature) == SignatureSize {
47+
// go-ethereum/crypto produces signature with v={0, 1} and we need to add
48+
// 27 to v-part (signature[64]) to conform with the on-chain signature
49+
// validation code that accepts v={27, 28} as specified in the
50+
// Appendix F of the Ethereum Yellow Paper
51+
// https://ethereum.github.io/yellowpaper/paper.pdf
52+
signature[len(signature)-1] = signature[len(signature)-1] + 27
53+
}
54+
55+
return signature, nil
56+
}
57+
58+
// Verify verifies the provided message against a signature using the key
59+
// EthereumSigner was created with. The signature has to be provided in
60+
// Ethereum-specific format.
61+
func (es *EthereumSigner) Verify(message []byte, signature []byte) (bool, error) {
62+
return verifySignature(message, signature, &es.privateKey.PublicKey)
63+
}
64+
65+
// VerifyWithPublicKey verifies the provided message against a signature and
66+
// public key. The signature has to be provided in Ethereum-specific format.
67+
func (es *EthereumSigner) VerifyWithPublicKey(
68+
message []byte,
69+
signature []byte,
70+
publicKey []byte,
71+
) (bool, error) {
72+
unmarshalledPubKey, err := unmarshalPublicKey(
73+
publicKey,
74+
es.privateKey.Curve,
75+
)
76+
if err != nil {
77+
return false, fmt.Errorf("failed to unmarshal public key: [%v]", err)
78+
}
79+
80+
return verifySignature(message, signature, unmarshalledPubKey)
81+
}
82+
83+
func verifySignature(
84+
message []byte,
85+
signature []byte,
86+
publicKey *ecdsa.PublicKey,
87+
) (bool, error) {
88+
// Convert the operator's static key into an uncompressed public key
89+
// which should be 65 bytes in length.
90+
uncompressedPubKey := crypto.FromECDSAPub(publicKey)
91+
// If our signature is in the [R || S || V] format, ensure we strip out
92+
// the Ethereum-specific recovery-id, V, if it already hasn't been done.
93+
if len(signature) == SignatureSize {
94+
signature = signature[:len(signature)-1]
95+
}
96+
97+
// The signature should be now 64 bytes long.
98+
if len(signature) != 64 {
99+
return false, fmt.Errorf(
100+
"signature should have 64 bytes; has: [%d]",
101+
len(signature),
102+
)
103+
}
104+
105+
return crypto.VerifySignature(
106+
uncompressedPubKey,
107+
ethereumPrefixedHash(message),
108+
signature,
109+
), nil
110+
}
111+
112+
func ethereumPrefixedHash(message []byte) []byte {
113+
return crypto.Keccak256(
114+
[]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%v", len(message))),
115+
message,
116+
)
117+
}
118+
119+
func unmarshalPublicKey(
120+
bytes []byte,
121+
curve elliptic.Curve,
122+
) (*ecdsa.PublicKey, error) {
123+
x, y := elliptic.Unmarshal(curve, bytes)
124+
if x == nil {
125+
return nil, fmt.Errorf(
126+
"invalid public key bytes",
127+
)
128+
}
129+
ecdsaPublicKey := &ecdsa.PublicKey{Curve: curve, X: x, Y: y}
130+
return (*ecdsa.PublicKey)(ecdsaPublicKey), nil
131+
}
132+
133+
// PublicKeyToAddress transforms the provided ECDSA public key into Ethereum
134+
// address represented in bytes.
135+
func (es *EthereumSigner) PublicKeyToAddress(publicKey ecdsa.PublicKey) []byte {
136+
return crypto.PubkeyToAddress(publicKey).Bytes()
137+
}
138+
139+
// PublicKeyBytesToAddress transforms the provided ECDSA public key in a bytes
140+
// format into Ethereum address represented in bytes.
141+
func (es *EthereumSigner) PublicKeyBytesToAddress(publicKey []byte) []byte {
142+
// Does the same as crypto.PubkeyToAddress but directly on public key bytes.
143+
return crypto.Keccak256(publicKey[1:])[12:]
144+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package ethutil
2+
3+
import (
4+
"testing"
5+
6+
"github.com/ethereum/go-ethereum/crypto"
7+
)
8+
9+
func TestSignAndVerify(t *testing.T) {
10+
signer, err := newSigner()
11+
if err != nil {
12+
t.Fatal(err)
13+
}
14+
15+
message := []byte("He that breaks a thing to find out what it is, has " +
16+
"left the path of wisdom.")
17+
18+
signature, err := signer.Sign(message)
19+
if err != nil {
20+
t.Fatal(err)
21+
}
22+
23+
var tests = map[string]struct {
24+
message []byte
25+
signature []byte
26+
validSignatureExpected bool
27+
validationErrorExpected bool
28+
}{
29+
"valid signature for message": {
30+
message: message,
31+
signature: signature,
32+
validSignatureExpected: true,
33+
validationErrorExpected: false,
34+
},
35+
"invalid signature for message": {
36+
message: []byte("I am sorry"),
37+
signature: signature,
38+
validSignatureExpected: false,
39+
validationErrorExpected: false,
40+
},
41+
"corrupted signature": {
42+
message: message,
43+
signature: []byte("I am so sorry"),
44+
validSignatureExpected: false,
45+
validationErrorExpected: true,
46+
},
47+
}
48+
49+
for testName, test := range tests {
50+
t.Run(testName, func(t *testing.T) {
51+
ok, err := signer.Verify(test.message, test.signature)
52+
53+
if !ok && test.validSignatureExpected {
54+
t.Errorf("expected valid signature but verification failed")
55+
}
56+
if ok && !test.validSignatureExpected {
57+
t.Errorf("expected invalid signature but verification succeeded")
58+
}
59+
60+
if err == nil && test.validationErrorExpected {
61+
t.Errorf("expected signature validation error; none happened")
62+
}
63+
if err != nil && !test.validationErrorExpected {
64+
t.Errorf("unexpected signature validation error [%v]", err)
65+
}
66+
})
67+
}
68+
}
69+
70+
func TestSignAndVerifyWithProvidedPublicKey(t *testing.T) {
71+
message := []byte("I am looking for someone to share in an adventure")
72+
73+
signer1, err := newSigner()
74+
if err != nil {
75+
t.Fatal(err)
76+
}
77+
78+
signer2, err := newSigner()
79+
if err != nil {
80+
t.Fatal(err)
81+
}
82+
83+
publicKey := signer1.PublicKey()
84+
signature, err := signer1.Sign(message)
85+
if err != nil {
86+
t.Fatal(err)
87+
}
88+
89+
var tests = map[string]struct {
90+
message []byte
91+
signature []byte
92+
publicKey []byte
93+
validSignatureExpected bool
94+
validationErrorExpected bool
95+
}{
96+
"valid signature for message": {
97+
message: message,
98+
signature: signature,
99+
publicKey: publicKey,
100+
validSignatureExpected: true,
101+
validationErrorExpected: false,
102+
},
103+
"invalid signature for message": {
104+
message: []byte("And here..."),
105+
signature: signature,
106+
publicKey: publicKey,
107+
validSignatureExpected: false,
108+
validationErrorExpected: false,
109+
},
110+
"corrupted signature": {
111+
message: message,
112+
signature: []byte("we..."),
113+
publicKey: publicKey,
114+
validSignatureExpected: false,
115+
validationErrorExpected: true,
116+
},
117+
"invalid remote public key": {
118+
message: message,
119+
signature: signature,
120+
publicKey: signer2.PublicKey(),
121+
validSignatureExpected: false,
122+
validationErrorExpected: false,
123+
},
124+
"corrupted remote public key": {
125+
message: message,
126+
signature: signature,
127+
publicKey: []byte("go..."),
128+
validSignatureExpected: false,
129+
validationErrorExpected: true,
130+
},
131+
}
132+
133+
for testName, test := range tests {
134+
t.Run(testName, func(t *testing.T) {
135+
ok, err := signer2.VerifyWithPublicKey(
136+
test.message,
137+
test.signature,
138+
test.publicKey,
139+
)
140+
141+
if !ok && test.validSignatureExpected {
142+
t.Errorf("expected valid signature but verification failed")
143+
}
144+
if ok && !test.validSignatureExpected {
145+
t.Errorf("expected invalid signature but verification succeeded")
146+
}
147+
148+
if err == nil && test.validationErrorExpected {
149+
t.Errorf("expected signature validation error; none happened")
150+
}
151+
if err != nil && !test.validationErrorExpected {
152+
t.Errorf("unexpected signature validation error [%v]", err)
153+
}
154+
})
155+
}
156+
}
157+
158+
func newSigner() (*EthereumSigner, error) {
159+
key, err := crypto.GenerateKey()
160+
if err != nil {
161+
return nil, err
162+
}
163+
164+
return &EthereumSigner{key}, nil
165+
}

0 commit comments

Comments
 (0)