/
signer.go
250 lines (217 loc) · 7.52 KB
/
signer.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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package eth
import (
"crypto/ecdsa"
"crypto/rand"
"fmt"
"math/big"
hdwallet "github.com/divergencetech/go-ethereum-hdwallet"
"github.com/tyler-smith/go-bip39"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/google/tink/go/prf"
)
// A Signer abstracts signing of arbitrary messages by wrapping an ECDSA private
// key and, optionally, its associated BIP39 mnemonic.
type Signer struct {
key *ecdsa.PrivateKey
mnemonic string
}
// NewSigner is equivalent to
// DefaultHDPathPrefix.SignerFromSeedPhrase(NewMnemonic(), "", 0).
func NewSigner(bitSize int) (*Signer, error) {
m, err := NewMnemonic(bitSize)
if err != nil {
return nil, err
}
return DefaultHDPathPrefix.SignerFromSeedPhrase(m, "", 0)
}
// NewMnemonic is a convenience wrapper around go-bip39 entropy and mnemonic
// creation.
func NewMnemonic(bitSize int) (string, error) {
buf, err := bip39.NewEntropy(bitSize)
if err != nil {
return "", fmt.Errorf("generate entropy: %v", err)
}
m, err := bip39.NewMnemonic(buf)
if err != nil {
return "", fmt.Errorf("convert entropy to mnemonic: %v", err)
}
return m, nil
}
// An HDPathPrefix is a prefix for use in deriving private keys from BIP39
// mnemonics. It is appended with the account number. Values MUST include a
// trailing slash.
type HDPathPrefix string
// DefaultHDPathPrefix is the default format for derived accounts when using
// SignerFromSeedPhrase().
const DefaultHDPathPrefix = HDPathPrefix("m/44'/60'/0'/0/")
// SignerFromSeedPhrase confirms that the mnemonic is valid under BIP39 and then
// uses it to derive a private key (see HDPathF)
func (hdp HDPathPrefix) SignerFromSeedPhrase(mnemonic, password string, account uint) (*Signer, error) {
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, password)
if err != nil {
return nil, fmt.Errorf("create seed from mnemoic: %v", err)
}
wallet, err := hdwallet.NewFromSeed(seed)
if err != nil {
return nil, fmt.Errorf("create wallet from seed: %v", err)
}
path, err := hdwallet.ParseDerivationPath(fmt.Sprintf("%s%d", hdp, account))
if err != nil {
return nil, fmt.Errorf("parse derivation path: %v", err)
}
acc, err := wallet.Derive(path, false)
if err != nil {
return nil, fmt.Errorf("derive account: %v", err)
}
key, err := wallet.PrivateKey(acc)
if err != nil {
return nil, fmt.Errorf("obtain private key: %v", err)
}
return &Signer{key, mnemonic}, nil
}
// SignerFromPRF deterministically derives a private key from the pseudo-random
// function and the input bytes. By definition, the output of a PRF is
// indistinguishable from a random function.
//
// The input parameter allows for different sets of HD wallets to be derived
// from the same underlying PRF key. Only the PRF key need be secret.
//
// SignerFromPRF can be thought of as a method for securely creating new
// mnemonic seed phrases from a single underlying key and different input
// parameters. Although the resulting mnemonic is accessible, SignerFromPRF is
// intended for use in an automated environment, which is why it relies on
// Google Tink.
func (hdp HDPathPrefix) SignerFromPRF(src prf.PRF, input []byte, account uint) (*Signer, error) {
entropy, err := src.ComputePRF(input, 32)
if err != nil {
return nil, fmt.Errorf("compute entropy from PRF: %v", err)
}
mn, err := hdwallet.NewMnemonicFromEntropy(entropy)
if err != nil {
return nil, fmt.Errorf("derive mnemonic from entropy: %v", err)
}
return hdp.SignerFromSeedPhrase(mn, "", account)
}
// SignerFromPRFSet returns hdp.SifnerFromPRF() using the set's primary PRF.
// This is simply a convenience function as the prf package doesn't accomodate
// direct creation of a prf.PRF.
func (hdp HDPathPrefix) SignerFromPRFSet(set *prf.Set, input []byte, account uint) (*Signer, error) {
return hdp.SignerFromPRF(set.PRFs[set.PrimaryID], input, account)
}
// String returns s.Address() as a string.
func (s *Signer) String() string {
return s.Address().String()
}
// Mnemonic returns the mnemonic used to derive the Signer's private key. USE
// WITH CAUTION.
func (s *Signer) Mnemonic() string {
return s.mnemonic
}
// Address returns the Signer's public key converted to an Ethereum address.
func (s *Signer) Address() common.Address {
return crypto.PubkeyToAddress(s.key.PublicKey)
}
// AppendRandomNonce appends random 32 bytes to the buffer, commonly used in
// signature nonces.
func appendRandomNonce(buf []byte) ([]byte, [32]byte, error) {
var nonce [32]byte
if n, err := rand.Read(nonce[:]); n != 32 || err != nil {
return nil, nonce, fmt.Errorf("read 32 random bytes: got %d bytes with err %v", n, err)
}
return append(buf, nonce[:]...), nonce, nil
}
// WithPersonalMessagePrefix converts a given message to conform to the signed data
// standard according to EIP-191.
func WithPersonalMessagePrefix(message []byte) []byte {
prefix := []byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(message)))
return append(prefix, message...)
}
type signOpts struct {
raw bool
personal bool
withNonce bool
}
// sign signs a given buffer depending on the chosen options:
// withNonce = true, appends a nonce to the message
// personal = true, adds a prefix to the message to conform to the EIP-191
// personal message standard.
// raw = false, the message is hashed before signing
func (s *Signer) sign(buf []byte, opts signOpts) ([]byte, *[32]byte, error) {
var nonce *[32]byte
var err error
if opts.withNonce {
var n [32]byte
buf, n, err = appendRandomNonce(buf)
nonce = &n
if err != nil {
return nil, nil, err
}
}
if opts.personal {
buf = WithPersonalMessagePrefix(buf)
}
if !opts.raw {
buf = crypto.Keccak256(buf)
}
sig, err := crypto.Sign(buf, s.key)
if err != nil {
return nil, nil, err
}
// yParities are shifted by 27 for Ethereum signatures by convention.
sig[64] += 27
return sig, nonce, nil
}
// RawSign returns an ECDSA signature of buf. USE WITH CAUTION as signed data
// SHOULD be hashed first to avoid chosen-plaintext attacks. Prefer
// Signer.Sign().
func (s *Signer) RawSign(buf []byte) ([]byte, error) {
sig, _, err := s.sign(buf, signOpts{
raw: true,
personal: false,
withNonce: false,
})
return sig, err
}
// Sign returns an ECDSA signature of keccak256(buf).
func (s *Signer) Sign(buf []byte) ([]byte, error) {
sig, _, err := s.sign(buf, signOpts{
raw: false,
personal: false,
withNonce: false,
})
return sig, err
}
// PersonalSign returns an EIP-191 conform personal ECDSA signature of buf
// Convenience wrapper for s.CompactSign(WithPersonalMessagePrefix(buf))
func (s *Signer) PersonalSign(buf []byte) ([]byte, error) {
sig, _, err := s.sign(buf, signOpts{
raw: false,
personal: true,
withNonce: false,
})
return sig, err
}
// PersonalSignWithNonce generates a 32-byte nonce with crypto/rand and returns
// s.PersonalSign(append(buf, nonce)).
func (s *Signer) PersonalSignWithNonce(buf []byte) ([]byte, [32]byte, error) {
sig, nonce, err := s.sign(buf, signOpts{
raw: false,
personal: true,
withNonce: true,
})
if err != nil {
return nil, [32]byte{}, err
}
return sig, *nonce, err
}
// SignAddress is a convenience wrapper for s.PersonalSign(addr.Bytes()).
func (s *Signer) PersonalSignAddress(addr common.Address) ([]byte, error) {
return s.PersonalSign(addr.Bytes())
}
// TransactorWithChainID returns bind.NewKeyedTransactorWithChainID(<key>,
// chainID) where <key> is the Signer's private key.
func (s *Signer) TransactorWithChainID(chainID *big.Int) (*bind.TransactOpts, error) {
return bind.NewKeyedTransactorWithChainID(s.key, chainID)
}