github.com/0chain/gosdk@v1.17.11/zcncore/ethhdwallet/hdwallet.go (about) 1 // Utilities to interact with ethereum wallet. 2 package hdwallet 3 4 import ( 5 "crypto/ecdsa" 6 "crypto/rand" 7 "errors" 8 "fmt" 9 "math/big" 10 "os" 11 "sync" 12 13 "github.com/btcsuite/btcd/btcutil/hdkeychain" 14 15 "github.com/btcsuite/btcd/chaincfg" 16 ethereum "github.com/ethereum/go-ethereum" 17 "github.com/ethereum/go-ethereum/accounts" 18 "github.com/ethereum/go-ethereum/common" 19 "github.com/ethereum/go-ethereum/common/hexutil" 20 "github.com/ethereum/go-ethereum/core/types" 21 "github.com/ethereum/go-ethereum/crypto" 22 "github.com/tyler-smith/go-bip39" 23 ) 24 25 // DefaultRootDerivationPath is the root path to which custom derivation endpoints 26 // are appended. As such, the first account will be at m/44'/60'/0'/0, the second 27 // at m/44'/60'/0'/1, etc. 28 var DefaultRootDerivationPath = accounts.DefaultRootDerivationPath 29 30 // DefaultBaseDerivationPath is the base path from which custom derivation endpoints 31 // are incremented. As such, the first account will be at m/44'/60'/0'/0, the second 32 // at m/44'/60'/0'/1, etc 33 var DefaultBaseDerivationPath = accounts.DefaultBaseDerivationPath 34 35 const issue179FixEnvar = "GO_ETHEREUM_HDWALLET_FIX_ISSUE_179" 36 37 // Wallet is the underlying wallet struct. 38 type Wallet struct { 39 mnemonic string 40 masterKey *hdkeychain.ExtendedKey 41 seed []byte 42 url accounts.URL 43 paths map[common.Address]accounts.DerivationPath 44 accounts []accounts.Account 45 stateLock sync.RWMutex 46 fixIssue172 bool 47 } 48 49 func newWallet(seed []byte) (*Wallet, error) { 50 masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) 51 if err != nil { 52 return nil, err 53 } 54 55 return &Wallet{ 56 masterKey: masterKey, 57 seed: seed, 58 accounts: []accounts.Account{}, 59 paths: map[common.Address]accounts.DerivationPath{}, 60 fixIssue172: false || len(os.Getenv(issue179FixEnvar)) > 0, 61 }, nil 62 } 63 64 // NewFromMnemonic returns a new wallet from a BIP-39 mnemonic. 65 func NewFromMnemonic(mnemonic string) (*Wallet, error) { 66 if mnemonic == "" { 67 return nil, errors.New("mnemonic is required") 68 } 69 70 if !bip39.IsMnemonicValid(mnemonic) { 71 return nil, errors.New("mnemonic is invalid") 72 } 73 74 seed, err := NewSeedFromMnemonic(mnemonic) 75 if err != nil { 76 return nil, err 77 } 78 79 wallet, err := newWallet(seed) 80 if err != nil { 81 return nil, err 82 } 83 wallet.mnemonic = mnemonic 84 85 return wallet, nil 86 } 87 88 // NewFromSeed returns a new wallet from a BIP-39 seed. 89 func NewFromSeed(seed []byte) (*Wallet, error) { 90 if len(seed) == 0 { 91 return nil, errors.New("seed is required") 92 } 93 94 return newWallet(seed) 95 } 96 97 // URL implements accounts.Wallet, returning the URL of the device that 98 // the wallet is on, however this does nothing since this is not a hardware device. 99 func (w *Wallet) URL() accounts.URL { 100 return w.url 101 } 102 103 // Status implements accounts.Wallet, returning a custom status message 104 // from the underlying vendor-specific hardware wallet implementation, 105 // however this does nothing since this is not a hardware device. 106 func (w *Wallet) Status() (string, error) { 107 return "ok", nil 108 } 109 110 // Open implements accounts.Wallet, however this does nothing since this 111 // is not a hardware device. 112 func (w *Wallet) Open(passphrase string) error { 113 return nil 114 } 115 116 // Close implements accounts.Wallet, however this does nothing since this 117 // is not a hardware device. 118 func (w *Wallet) Close() error { 119 return nil 120 } 121 122 // Accounts implements accounts.Wallet, returning the list of accounts pinned to 123 // the wallet. If self-derivation was enabled, the account list is 124 // periodically expanded based on current chain state. 125 func (w *Wallet) Accounts() []accounts.Account { 126 // Attempt self-derivation if it's running 127 // Return whatever account list we ended up with 128 w.stateLock.RLock() 129 defer w.stateLock.RUnlock() 130 131 cpy := make([]accounts.Account, len(w.accounts)) 132 copy(cpy, w.accounts) 133 return cpy 134 } 135 136 // Contains implements accounts.Wallet, returning whether a particular account is 137 // or is not pinned into this wallet instance. 138 func (w *Wallet) Contains(account accounts.Account) bool { 139 w.stateLock.RLock() 140 defer w.stateLock.RUnlock() 141 142 _, exists := w.paths[account.Address] 143 return exists 144 } 145 146 // Unpin unpins account from list of pinned accounts. 147 func (w *Wallet) Unpin(account accounts.Account) error { 148 w.stateLock.RLock() 149 defer w.stateLock.RUnlock() 150 151 for i, acct := range w.accounts { 152 if acct.Address.String() == account.Address.String() { 153 w.accounts = removeAtIndex(w.accounts, i) 154 delete(w.paths, account.Address) 155 return nil 156 } 157 } 158 159 return errors.New("account not found") 160 } 161 162 // SetFixIssue172 determines whether the standard (correct) bip39 163 // derivation path was used, or if derivation should be affected by 164 // Issue172 [0] which was how this library was originally implemented. 165 // [0] https://github.com/btcsuite/btcutil/pull/182/files 166 func (w *Wallet) SetFixIssue172(fixIssue172 bool) { 167 w.fixIssue172 = fixIssue172 168 } 169 170 // Derive implements accounts.Wallet, deriving a new account at the specific 171 // derivation path. If pin is set to true, the account will be added to the list 172 // of tracked accounts. 173 func (w *Wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { 174 // Try to derive the actual account and update its URL if successful 175 w.stateLock.RLock() // Avoid device disappearing during derivation 176 177 address, err := w.deriveAddress(path) 178 179 w.stateLock.RUnlock() 180 181 // If an error occurred or no pinning was requested, return 182 if err != nil { 183 return accounts.Account{}, err 184 } 185 186 account := accounts.Account{ 187 Address: address, 188 URL: accounts.URL{ 189 Scheme: "", 190 Path: path.String(), 191 }, 192 } 193 194 if !pin { 195 return account, nil 196 } 197 198 // Pinning needs to modify the state 199 w.stateLock.Lock() 200 defer w.stateLock.Unlock() 201 202 if _, ok := w.paths[address]; !ok { 203 w.accounts = append(w.accounts, account) 204 w.paths[address] = path 205 } 206 207 return account, nil 208 } 209 210 // SelfDerive implements accounts.Wallet, trying to discover accounts that the 211 // user used previously (based on the chain state), but ones that he/she did not 212 // explicitly pin to the wallet manually. To avoid chain head monitoring, self 213 // derivation only runs during account listing (and even then throttled). 214 func (w *Wallet) SelfDerive(base []accounts.DerivationPath, chain ethereum.ChainStateReader) { 215 // TODO: self derivation 216 } 217 218 // SignHash implements accounts.Wallet, which allows signing arbitrary data. 219 func (w *Wallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) { 220 // Make sure the requested account is contained within 221 path, ok := w.paths[account.Address] 222 if !ok { 223 return nil, accounts.ErrUnknownAccount 224 } 225 226 privateKey, err := w.derivePrivateKey(path) 227 if err != nil { 228 return nil, err 229 } 230 231 return crypto.Sign(hash, privateKey) 232 } 233 234 // SignTxEIP155 implements accounts.Wallet, which allows the account to sign an ERC-20 transaction. 235 func (w *Wallet) SignTxEIP155(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 236 w.stateLock.RLock() // Comms have own mutex, this is for the state fields 237 defer w.stateLock.RUnlock() 238 239 // Make sure the requested account is contained within 240 path, ok := w.paths[account.Address] 241 if !ok { 242 return nil, accounts.ErrUnknownAccount 243 } 244 245 privateKey, err := w.derivePrivateKey(path) 246 if err != nil { 247 return nil, err 248 } 249 250 signer := types.NewEIP155Signer(chainID) 251 // Sign the transaction and verify the sender to avoid hardware fault surprises 252 signedTx, err := types.SignTx(tx, signer, privateKey) 253 if err != nil { 254 return nil, err 255 } 256 257 sender, err := types.Sender(signer, signedTx) 258 if err != nil { 259 return nil, err 260 } 261 262 if sender != account.Address { 263 return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex()) 264 } 265 266 return signedTx, nil 267 } 268 269 // SignTx implements accounts.Wallet, which allows the account to sign an Ethereum transaction. 270 func (w *Wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 271 w.stateLock.RLock() // Comms have own mutex, this is for the state fields 272 defer w.stateLock.RUnlock() 273 274 // Make sure the requested account is contained within 275 path, ok := w.paths[account.Address] 276 if !ok { 277 return nil, accounts.ErrUnknownAccount 278 } 279 280 privateKey, err := w.derivePrivateKey(path) 281 if err != nil { 282 return nil, err 283 } 284 285 signer := types.HomesteadSigner{} 286 // Sign the transaction and verify the sender to avoid hardware fault surprises 287 signedTx, err := types.SignTx(tx, signer, privateKey) 288 if err != nil { 289 return nil, err 290 } 291 292 sender, err := types.Sender(signer, signedTx) 293 if err != nil { 294 return nil, err 295 } 296 297 if sender != account.Address { 298 return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex()) 299 } 300 301 return signedTx, nil 302 } 303 304 // SignHashWithPassphrase implements accounts.Wallet, attempting 305 // to sign the given hash with the given account using the 306 // passphrase as extra authentication. 307 func (w *Wallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) { 308 return w.SignHash(account, hash) 309 } 310 311 // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given 312 // transaction with the given account using passphrase as extra authentication. 313 func (w *Wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 314 return w.SignTx(account, tx, chainID) 315 } 316 317 // PrivateKey returns the ECDSA private key of the account. 318 func (w *Wallet) PrivateKey(account accounts.Account) (*ecdsa.PrivateKey, error) { 319 path, err := ParseDerivationPath(account.URL.Path) 320 if err != nil { 321 return nil, err 322 } 323 324 return w.derivePrivateKey(path) 325 } 326 327 // PrivateKeyBytes returns the ECDSA private key in bytes format of the account. 328 func (w *Wallet) PrivateKeyBytes(account accounts.Account) ([]byte, error) { 329 privateKey, err := w.PrivateKey(account) 330 if err != nil { 331 return nil, err 332 } 333 334 return crypto.FromECDSA(privateKey), nil 335 } 336 337 // PrivateKeyHex return the ECDSA private key in hex string format of the account. 338 func (w *Wallet) PrivateKeyHex(account accounts.Account) (string, error) { 339 privateKeyBytes, err := w.PrivateKeyBytes(account) 340 if err != nil { 341 return "", err 342 } 343 344 return hexutil.Encode(privateKeyBytes)[2:], nil 345 } 346 347 // PublicKey returns the ECDSA public key of the account. 348 func (w *Wallet) PublicKey(account accounts.Account) (*ecdsa.PublicKey, error) { 349 path, err := ParseDerivationPath(account.URL.Path) 350 if err != nil { 351 return nil, err 352 } 353 354 return w.derivePublicKey(path) 355 } 356 357 // PublicKeyBytes returns the ECDSA public key in bytes format of the account. 358 func (w *Wallet) PublicKeyBytes(account accounts.Account) ([]byte, error) { 359 publicKey, err := w.PublicKey(account) 360 if err != nil { 361 return nil, err 362 } 363 364 return crypto.FromECDSAPub(publicKey), nil 365 } 366 367 // PublicKeyHex return the ECDSA public key in hex string format of the account. 368 func (w *Wallet) PublicKeyHex(account accounts.Account) (string, error) { 369 publicKeyBytes, err := w.PublicKeyBytes(account) 370 if err != nil { 371 return "", err 372 } 373 374 return hexutil.Encode(publicKeyBytes)[4:], nil 375 } 376 377 // Address returns the address of the account. 378 func (w *Wallet) Address(account accounts.Account) (common.Address, error) { 379 publicKey, err := w.PublicKey(account) 380 if err != nil { 381 return common.Address{}, err 382 } 383 384 return crypto.PubkeyToAddress(*publicKey), nil 385 } 386 387 // AddressBytes returns the address in bytes format of the account. 388 func (w *Wallet) AddressBytes(account accounts.Account) ([]byte, error) { 389 address, err := w.Address(account) 390 if err != nil { 391 return nil, err 392 } 393 return address.Bytes(), nil 394 } 395 396 // AddressHex returns the address in hex string format of the account. 397 func (w *Wallet) AddressHex(account accounts.Account) (string, error) { 398 address, err := w.Address(account) 399 if err != nil { 400 return "", err 401 } 402 return address.Hex(), nil 403 } 404 405 // Path return the derivation path of the account. 406 func (w *Wallet) Path(account accounts.Account) (string, error) { 407 return account.URL.Path, nil 408 } 409 410 // SignData signs keccak256(data). The mimetype parameter describes the type of data being signed 411 func (w *Wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { 412 // Make sure the requested account is contained within 413 if !w.Contains(account) { 414 return nil, accounts.ErrUnknownAccount 415 } 416 417 return w.SignHash(account, crypto.Keccak256(data)) 418 } 419 420 // SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed 421 func (w *Wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { 422 // Make sure the requested account is contained within 423 if !w.Contains(account) { 424 return nil, accounts.ErrUnknownAccount 425 } 426 427 return w.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data)) 428 } 429 430 // SignText requests the wallet to sign the hash of a given piece of data, prefixed 431 // the needed details via SignHashWithPassphrase, or by other means (e.g. unlock 432 // the account in a keystore). 433 func (w *Wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { 434 // Make sure the requested account is contained within 435 if !w.Contains(account) { 436 return nil, accounts.ErrUnknownAccount 437 } 438 439 return w.SignHash(account, accounts.TextHash(text)) 440 } 441 442 // SignTextWithPassphrase implements accounts.Wallet, attempting to sign the 443 // given text (which is hashed) with the given account using passphrase as extra authentication. 444 func (w *Wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { 445 // Make sure the requested account is contained within 446 if !w.Contains(account) { 447 return nil, accounts.ErrUnknownAccount 448 } 449 450 return w.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text)) 451 } 452 453 // ParseDerivationPath parses the derivation path in string format into []uint32 454 func ParseDerivationPath(path string) (accounts.DerivationPath, error) { 455 return accounts.ParseDerivationPath(path) 456 } 457 458 // MustParseDerivationPath parses the derivation path in string format into 459 // []uint32 but will panic if it can't parse it. 460 func MustParseDerivationPath(path string) accounts.DerivationPath { 461 parsed, err := accounts.ParseDerivationPath(path) 462 if err != nil { 463 panic(err) 464 } 465 466 return parsed 467 } 468 469 // NewMnemonic returns a randomly generated BIP-39 mnemonic using 128-256 bits of entropy. 470 func NewMnemonic(bits int) (string, error) { 471 entropy, err := bip39.NewEntropy(bits) 472 if err != nil { 473 return "", err 474 } 475 return bip39.NewMnemonic(entropy) 476 } 477 478 // NewMnemonicFromEntropy returns a BIP-39 mnemonic from entropy. 479 func NewMnemonicFromEntropy(entropy []byte) (string, error) { 480 return bip39.NewMnemonic(entropy) 481 } 482 483 // NewEntropy returns a randomly generated entropy. 484 func NewEntropy(bits int) ([]byte, error) { 485 return bip39.NewEntropy(bits) 486 } 487 488 // NewSeed returns a randomly generated BIP-39 seed. 489 func NewSeed() ([]byte, error) { 490 b := make([]byte, 64) 491 _, err := rand.Read(b) 492 return b, err 493 } 494 495 // NewSeedFromMnemonic returns a BIP-39 seed based on a BIP-39 mnemonic. 496 func NewSeedFromMnemonic(mnemonic string) ([]byte, error) { 497 if mnemonic == "" { 498 return nil, errors.New("mnemonic is required") 499 } 500 501 return bip39.NewSeedWithErrorChecking(mnemonic, "") 502 } 503 504 // DerivePrivateKey derives the private key of the derivation path. 505 func (w *Wallet) derivePrivateKey(path accounts.DerivationPath) (*ecdsa.PrivateKey, error) { 506 var err error 507 key := w.masterKey 508 for _, n := range path { 509 if w.fixIssue172 && key.IsAffectedByIssue172() { 510 key, err = key.Derive(n) 511 } else { 512 key, err = key.DeriveNonStandard(n) //nolint 513 } 514 if err != nil { 515 return nil, err 516 } 517 } 518 519 privateKey, err := key.ECPrivKey() 520 privateKeyECDSA := privateKey.ToECDSA() 521 if err != nil { 522 return nil, err 523 } 524 525 return privateKeyECDSA, nil 526 } 527 528 // DerivePublicKey derives the public key of the derivation path. 529 func (w *Wallet) derivePublicKey(path accounts.DerivationPath) (*ecdsa.PublicKey, error) { 530 privateKeyECDSA, err := w.derivePrivateKey(path) 531 if err != nil { 532 return nil, err 533 } 534 535 publicKey := privateKeyECDSA.Public() 536 publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 537 if !ok { 538 return nil, errors.New("failed to get public key") 539 } 540 541 return publicKeyECDSA, nil 542 } 543 544 // DeriveAddress derives the account address of the derivation path. 545 func (w *Wallet) deriveAddress(path accounts.DerivationPath) (common.Address, error) { 546 publicKeyECDSA, err := w.derivePublicKey(path) 547 if err != nil { 548 return common.Address{}, err 549 } 550 551 address := crypto.PubkeyToAddress(*publicKeyECDSA) 552 return address, nil 553 } 554 555 // removeAtIndex removes an account at index. 556 func removeAtIndex(accts []accounts.Account, index int) []accounts.Account { 557 return append(accts[:index], accts[index+1:]...) 558 }