/
sign.go
executable file
·194 lines (166 loc) · 5.97 KB
/
sign.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package signer
import (
"crypto/ecdsa"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rpc"
"github.com/map-bgp/browserbook/browserbook-mesh/constants"
"golang.org/x/crypto/sha3"
)
// Signer defines the methods needed to act as a elliptic curve signer
type Signer interface {
EthSign(message []byte, signerAddress common.Address) (*ECSignature, error)
}
// ECSignature contains the parameters of an elliptic curve signature
type ECSignature struct {
V byte
R common.Hash
S common.Hash
}
// EthRPCSigner is a signer that uses a call to Ethereum JSON-RPC method `eth_call`
// to produce a signature
type EthRPCSigner struct {
rpcClient *rpc.Client
}
// NewEthRPCSigner instantiates a new EthRPCSigner
func NewEthRPCSigner(rpcClient *rpc.Client) Signer {
return &EthRPCSigner{
rpcClient: rpcClient,
}
}
// EthSign signs a message via the `eth_sign` Ethereum JSON-RPC call
func (e *EthRPCSigner) EthSign(message []byte, signerAddress common.Address) (*ECSignature, error) {
var signatureHex string
if err := e.rpcClient.Call(&signatureHex, "eth_sign", signerAddress.Hex(), common.Bytes2Hex(message)); err != nil {
return nil, err
}
// `eth_sign` returns the signature in the [R || S || V] format where V is 0 or 1.
signatureBytes := common.Hex2Bytes(signatureHex[2:])
vParam := signatureBytes[64]
if vParam == byte(0) {
vParam = byte(27)
} else if vParam == byte(1) {
vParam = byte(28)
}
ecSignature := &ECSignature{
V: vParam,
R: common.BytesToHash(signatureBytes[0:32]),
S: common.BytesToHash(signatureBytes[32:64]),
}
return ecSignature, nil
}
// LocalSigner is a signer that produces an `eth_sign`-compatible signature locally using
// a private key
type LocalSigner struct {
privateKey *ecdsa.PrivateKey
}
// NewLocalSigner instantiates a new LocalSigner
func NewLocalSigner(privateKey *ecdsa.PrivateKey) Signer {
return &LocalSigner{
privateKey: privateKey,
}
}
// GetSignerAddress returns the signerAddress corresponding to LocalSigner's private key
func (l *LocalSigner) GetSignerAddress() common.Address {
return crypto.PubkeyToAddress(l.privateKey.PublicKey)
}
// EthSign mimicks the signing of `eth_sign` locally its supplied private key
func (l *LocalSigner) EthSign(message []byte, signerAddress common.Address) (*ECSignature, error) {
// Add message prefix: "\x19Ethereum Signed Message:\n"${message length}
messageWithPrefix, _ := textAndHash(message)
ecSignature, err := l.sign(messageWithPrefix, signerAddress)
if err != nil {
return nil, err
}
return ecSignature, nil
}
// Sign signs the message with the corresponding private key to the supplied signerAddress and returns
// the raw signature byte array
func (l *LocalSigner) simpleSign(message []byte, signerAddress common.Address) ([]byte, error) {
expectedSignerAddress := l.GetSignerAddress()
if signerAddress != expectedSignerAddress {
return nil, fmt.Errorf("Cannot sign with signerAddress %s since LocalSigner contains private key for %s", signerAddress, expectedSignerAddress)
}
// The produced signature is in the [R || S || V] format where V is 0 or 1.
signatureBytes, err := crypto.Sign(message, l.privateKey)
if err != nil {
return nil, err
}
return signatureBytes, nil
}
// Sign signs the message with the corresponding private key to the supplied signerAddress and returns
// the parsed V, R, S values
func (l *LocalSigner) sign(message []byte, signerAddress common.Address) (*ECSignature, error) {
signatureBytes, err := l.simpleSign(message, signerAddress)
if err != nil {
return nil, err
}
vParam := signatureBytes[64]
if vParam == byte(0) {
vParam = byte(27)
} else if vParam == byte(1) {
vParam = byte(28)
}
ecSignature := &ECSignature{
V: vParam,
R: common.BytesToHash(signatureBytes[0:32]),
S: common.BytesToHash(signatureBytes[32:64]),
}
return ecSignature, nil
}
// TestSigner generates `eth_sign` signatures for test accounts available on the test
// Ethereum node Ganache
type TestSigner struct{}
// NewTestSigner instantiates a new LocalSigner
func NewTestSigner() Signer {
return &TestSigner{}
}
// EthSign generates an `eth_sign` equivalent signature using an public/private key
// pair hard-coded in the constants package.
func (t *TestSigner) EthSign(message []byte, signerAddress common.Address) (*ECSignature, error) {
pkBytes, ok := constants.GanacheAccountToPrivateKey[signerAddress]
if !ok {
return nil, errors.New("Unrecognized Ganache account supplied to ECSignForTests")
}
privateKey, err := crypto.ToECDSA(pkBytes)
if err != nil {
return nil, err
}
localSigner := NewLocalSigner(privateKey)
return localSigner.EthSign(message, signerAddress)
}
// SignTx signs an Ethereum transaction with a public/private key pair hard-coded in the constants package.
// It returns the transaction signature.
func (t *TestSigner) SignTx(message []byte, signerAddress common.Address) ([]byte, error) {
pkBytes, ok := constants.GanacheAccountToPrivateKey[signerAddress]
if !ok {
return nil, errors.New("Unrecognized Ganache account supplied to ECSignForTests")
}
privateKey, err := crypto.ToECDSA(pkBytes)
if err != nil {
return nil, err
}
localSigner := NewLocalSigner(privateKey)
signature, err := localSigner.(*LocalSigner).simpleSign(message, signerAddress)
if err != nil {
return []byte{}, err
}
return signature, nil
}
// textAndHash is a helper function that calculates a hash for the given message that can be
// safely used to calculate a signature from.
//
// The hash is calulcated as
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
//
// This gives context to the signed message and prevents signing of transactions.
func textAndHash(data []byte) ([]byte, string) {
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data))
hasher := sha3.NewLegacyKeccak256()
// Note: Write will never return an error here. We added placeholders in order
// to satisfy the linter.
_, _ = hasher.Write([]byte(msg))
return hasher.Sum(nil), msg
}