forked from livepeer/go-livepeer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaccountmanager.go
224 lines (183 loc) · 5.96 KB
/
accountmanager.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
package eth
import (
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/keystore"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/core/types"
"github.com/golang/glog"
"github.com/livepeer/go-livepeer/common"
)
var (
ErrAccountNotFound = fmt.Errorf("ETH account not found")
ErrLocked = fmt.Errorf("account locked")
ErrPassphraseMismatch = fmt.Errorf("passphrases do not match")
)
type AccountManager interface {
Unlock(passphrase string) error
Lock() error
CreateTransactOpts(gasLimit uint64, gasPrice *big.Int) (*bind.TransactOpts, error)
SignTx(tx *types.Transaction) (*types.Transaction, error)
Sign(msg []byte) ([]byte, error)
Account() accounts.Account
}
type accountManager struct {
account accounts.Account
signer types.Signer
unlocked bool
keyStore *keystore.KeyStore
}
func NewAccountManager(accountAddr ethcommon.Address, keystoreDir string, signer types.Signer) (AccountManager, error) {
keyStore := keystore.NewKeyStore(keystoreDir, keystore.StandardScryptN, keystore.StandardScryptP)
acctExists := keyStore.HasAddress(accountAddr)
numAccounts := len(keyStore.Accounts())
var acct accounts.Account
var err error
if numAccounts == 0 || ((accountAddr != ethcommon.Address{}) && !acctExists) {
glog.Infof("No Ethereum account found. Creating a new account")
glog.Infof("This process will create a new Ethereum account for this Livepeer node")
glog.Infof("Please enter a passphrase to encrypt the Private Keystore file for the Ethereum account.")
glog.Infof("This process will ask for this passphrase every time it is launched")
glog.Infof("(no characters will appear in Terminal when the passphrase is entered)")
// Account does not exist yet, set it up
acct, err = createAccount(keyStore)
if err != nil {
return nil, err
}
} else {
glog.V(common.SHORT).Infof("Found existing ETH account")
// Account already exists or defaulting to first, load it from keystore
acct, err = getAccount(accountAddr, keyStore)
if err != nil {
return nil, err
}
}
glog.Infof("Using Ethereum account: %v", acct.Address.Hex())
return &accountManager{
account: acct,
signer: signer,
unlocked: false,
keyStore: keyStore,
}, nil
}
// Unlock account indefinitely using underlying keystore
func (am *accountManager) Unlock(pass string) error {
var err error
// We don't care if GetPass() returns an error.
// The string it returns will always be valid.
passphrase, _ := common.GetPass(pass)
err = am.keyStore.Unlock(am.account, passphrase)
if err != nil {
if passphrase != "" {
return err
}
glog.Infof("Please enter the passphrase to unlock Ethereum account %v", am.account.Address.Hex())
passphrase, err = getPassphrase(false)
err = am.keyStore.Unlock(am.account, passphrase)
if err != nil {
return err
}
}
am.unlocked = true
glog.Infof("Unlocked ETH account: %v", am.account.Address.Hex())
return nil
}
// Lock account using underlying keystore and remove associated private key from memory
func (am *accountManager) Lock() error {
err := am.keyStore.Lock(am.account.Address)
if err != nil {
return err
}
am.unlocked = false
return nil
}
// Create transact opts for client use - account must be unlocked
// Can optionally set gas limit and gas price used
func (am *accountManager) CreateTransactOpts(gasLimit uint64, gasPrice *big.Int) (*bind.TransactOpts, error) {
if !am.unlocked {
return nil, ErrLocked
}
return &bind.TransactOpts{
From: am.account.Address,
GasLimit: gasLimit,
GasPrice: gasPrice,
Signer: func(signer types.Signer, address ethcommon.Address, tx *types.Transaction) (*types.Transaction, error) {
if address != am.account.Address {
return nil, errors.New("not authorized to sign this account")
}
return am.SignTx(tx)
},
}, nil
}
// Sign a transaction. Account must be unlocked
func (am *accountManager) SignTx(tx *types.Transaction) (*types.Transaction, error) {
signature, err := am.keyStore.SignHash(am.account, am.signer.Hash(tx).Bytes())
if err != nil {
return nil, err
}
return tx.WithSignature(am.signer, signature)
}
// Sign byte array message. Account must be unlocked
func (am *accountManager) Sign(msg []byte) ([]byte, error) {
ethHash := accounts.TextHash(msg)
sig, err := am.keyStore.SignHash(am.account, ethHash)
if err != nil {
return nil, err
}
// sig is in the [R || S || V] format where V is 0 or 1
// Convert the V param to 27 or 28
v := sig[64]
if v == byte(0) || v == byte(1) {
v += 27
}
return append(sig[:64], v), nil
}
func (am *accountManager) Account() accounts.Account {
return am.account
}
// Get account from keystore using hex address
// If no hex address is provided, default to the first account
func getAccount(accountAddr ethcommon.Address, keyStore *keystore.KeyStore) (accounts.Account, error) {
accts := keyStore.Accounts()
if (accountAddr != ethcommon.Address{}) {
for _, acct := range accts {
if acct.Address == accountAddr {
return acct, nil
}
}
return accounts.Account{}, ErrAccountNotFound
} else {
glog.V(common.SHORT).Infof("Defaulting to first ETH account in keystore %v", accts[0].Address.Hex())
// Default to first account
return accts[0], nil
}
}
// Create account in keystore
func createAccount(keyStore *keystore.KeyStore) (accounts.Account, error) {
passphrase, err := getPassphrase(true)
if err != nil {
return accounts.Account{}, err
}
return keyStore.NewAccount(passphrase)
}
// Prompt for passphrase
func getPassphrase(shouldConfirm bool) (string, error) {
passphrase, err := console.Stdin.PromptPassword("Passphrase: ")
if err != nil {
return "", err
}
if shouldConfirm {
confirmation, err := console.Stdin.PromptPassword("Repeat passphrase: ")
if err != nil {
return "", err
}
if passphrase != confirmation {
return "", ErrPassphraseMismatch
}
}
return passphrase, nil
}