@ -29,8 +29,6 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/accounts/scwallet"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
@ -51,7 +49,6 @@ import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
"github.com/tyler-smith/go-bip39"
)
// estimateGasErrorRatio is the amount of overestimation eth_estimateGas is
@ -298,344 +295,6 @@ func (api *EthereumAccountAPI) Accounts() []common.Address {
return api . am . Accounts ( )
}
// PersonalAccountAPI provides an API to access accounts managed by this node.
// It offers methods to create, (un)lock en list accounts. Some methods accept
// passwords and are therefore considered private by default.
type PersonalAccountAPI struct {
am * accounts . Manager
nonceLock * AddrLocker
b Backend
}
// NewPersonalAccountAPI creates a new PersonalAccountAPI.
func NewPersonalAccountAPI ( b Backend , nonceLock * AddrLocker ) * PersonalAccountAPI {
return & PersonalAccountAPI {
am : b . AccountManager ( ) ,
nonceLock : nonceLock ,
b : b ,
}
}
// ListAccounts will return a list of addresses for accounts this node manages.
func ( api * PersonalAccountAPI ) ListAccounts ( ) [ ] common . Address {
return api . am . Accounts ( )
}
// rawWallet is a JSON representation of an accounts.Wallet interface, with its
// data contents extracted into plain fields.
type rawWallet struct {
URL string ` json:"url" `
Status string ` json:"status" `
Failure string ` json:"failure,omitempty" `
Accounts [ ] accounts . Account ` json:"accounts,omitempty" `
}
// ListWallets will return a list of wallets this node manages.
func ( api * PersonalAccountAPI ) ListWallets ( ) [ ] rawWallet {
wallets := make ( [ ] rawWallet , 0 ) // return [] instead of nil if empty
for _ , wallet := range api . am . Wallets ( ) {
status , failure := wallet . Status ( )
raw := rawWallet {
URL : wallet . URL ( ) . String ( ) ,
Status : status ,
Accounts : wallet . Accounts ( ) ,
}
if failure != nil {
raw . Failure = failure . Error ( )
}
wallets = append ( wallets , raw )
}
return wallets
}
// OpenWallet initiates a hardware wallet opening procedure, establishing a USB
// connection and attempting to authenticate via the provided passphrase. Note,
// the method may return an extra challenge requiring a second open (e.g. the
// Trezor PIN matrix challenge).
func ( api * PersonalAccountAPI ) OpenWallet ( url string , passphrase * string ) error {
wallet , err := api . am . Wallet ( url )
if err != nil {
return err
}
pass := ""
if passphrase != nil {
pass = * passphrase
}
return wallet . Open ( pass )
}
// DeriveAccount requests an HD wallet to derive a new account, optionally pinning
// it for later reuse.
func ( api * PersonalAccountAPI ) DeriveAccount ( url string , path string , pin * bool ) ( accounts . Account , error ) {
wallet , err := api . am . Wallet ( url )
if err != nil {
return accounts . Account { } , err
}
derivPath , err := accounts . ParseDerivationPath ( path )
if err != nil {
return accounts . Account { } , err
}
if pin == nil {
pin = new ( bool )
}
return wallet . Derive ( derivPath , * pin )
}
// NewAccount will create a new account and returns the address for the new account.
func ( api * PersonalAccountAPI ) NewAccount ( password string ) ( common . AddressEIP55 , error ) {
ks , err := fetchKeystore ( api . am )
if err != nil {
return common . AddressEIP55 { } , err
}
acc , err := ks . NewAccount ( password )
if err == nil {
addrEIP55 := common . AddressEIP55 ( acc . Address )
log . Info ( "Your new key was generated" , "address" , addrEIP55 . String ( ) )
log . Warn ( "Please backup your key file!" , "path" , acc . URL . Path )
log . Warn ( "Please remember your password!" )
return addrEIP55 , nil
}
return common . AddressEIP55 { } , err
}
// fetchKeystore retrieves the encrypted keystore from the account manager.
func fetchKeystore ( am * accounts . Manager ) ( * keystore . KeyStore , error ) {
if ks := am . Backends ( keystore . KeyStoreType ) ; len ( ks ) > 0 {
return ks [ 0 ] . ( * keystore . KeyStore ) , nil
}
return nil , errors . New ( "local keystore not used" )
}
// ImportRawKey stores the given hex encoded ECDSA key into the key directory,
// encrypting it with the passphrase.
func ( api * PersonalAccountAPI ) ImportRawKey ( privkey string , password string ) ( common . Address , error ) {
key , err := crypto . HexToECDSA ( privkey )
if err != nil {
return common . Address { } , err
}
ks , err := fetchKeystore ( api . am )
if err != nil {
return common . Address { } , err
}
acc , err := ks . ImportECDSA ( key , password )
return acc . Address , err
}
// UnlockAccount will unlock the account associated with the given address with
// the given password for duration seconds. If duration is nil it will use a
// default of 300 seconds. It returns an indication if the account was unlocked.
func ( api * PersonalAccountAPI ) UnlockAccount ( ctx context . Context , addr common . Address , password string , duration * uint64 ) ( bool , error ) {
// When the API is exposed by external RPC(http, ws etc), unless the user
// explicitly specifies to allow the insecure account unlocking, otherwise
// it is disabled.
if api . b . ExtRPCEnabled ( ) && ! api . b . AccountManager ( ) . Config ( ) . InsecureUnlockAllowed {
return false , errors . New ( "account unlock with HTTP access is forbidden" )
}
const max = uint64 ( time . Duration ( gomath . MaxInt64 ) / time . Second )
var d time . Duration
if duration == nil {
d = 300 * time . Second
} else if * duration > max {
return false , errors . New ( "unlock duration too large" )
} else {
d = time . Duration ( * duration ) * time . Second
}
ks , err := fetchKeystore ( api . am )
if err != nil {
return false , err
}
err = ks . TimedUnlock ( accounts . Account { Address : addr } , password , d )
if err != nil {
log . Warn ( "Failed account unlock attempt" , "address" , addr , "err" , err )
}
return err == nil , err
}
// LockAccount will lock the account associated with the given address when it's unlocked.
func ( api * PersonalAccountAPI ) LockAccount ( addr common . Address ) bool {
if ks , err := fetchKeystore ( api . am ) ; err == nil {
return ks . Lock ( addr ) == nil
}
return false
}
// signTransaction sets defaults and signs the given transaction
// NOTE: the caller needs to ensure that the nonceLock is held, if applicable,
// and release it after the transaction has been submitted to the tx pool
func ( api * PersonalAccountAPI ) signTransaction ( ctx context . Context , args * TransactionArgs , passwd string ) ( * types . Transaction , error ) {
// Look up the wallet containing the requested signer
account := accounts . Account { Address : args . from ( ) }
wallet , err := api . am . Find ( account )
if err != nil {
return nil , err
}
// Set some sanity defaults and terminate on failure
if err := args . setDefaults ( ctx , api . b , false ) ; err != nil {
return nil , err
}
// Assemble the transaction and sign with the wallet
tx := args . ToTransaction ( types . LegacyTxType )
return wallet . SignTxWithPassphrase ( account , passwd , tx , api . b . ChainConfig ( ) . ChainID )
}
// SendTransaction will create a transaction from the given arguments and
// tries to sign it with the key associated with args.From. If the given
// passwd isn't able to decrypt the key it fails.
func ( api * PersonalAccountAPI ) SendTransaction ( ctx context . Context , args TransactionArgs , passwd string ) ( common . Hash , error ) {
if args . Nonce == nil {
// Hold the mutex around signing to prevent concurrent assignment of
// the same nonce to multiple accounts.
api . nonceLock . LockAddr ( args . from ( ) )
defer api . nonceLock . UnlockAddr ( args . from ( ) )
}
if args . IsEIP4844 ( ) {
return common . Hash { } , errBlobTxNotSupported
}
signed , err := api . signTransaction ( ctx , & args , passwd )
if err != nil {
log . Warn ( "Failed transaction send attempt" , "from" , args . from ( ) , "to" , args . To , "value" , args . Value . ToInt ( ) , "err" , err )
return common . Hash { } , err
}
return SubmitTransaction ( ctx , api . b , signed )
}
// SignTransaction will create a transaction from the given arguments and
// tries to sign it with the key associated with args.From. If the given passwd isn't
// able to decrypt the key it fails. The transaction is returned in RLP-form, not broadcast
// to other nodes
func ( api * PersonalAccountAPI ) SignTransaction ( ctx context . Context , args TransactionArgs , passwd string ) ( * SignTransactionResult , error ) {
// No need to obtain the noncelock mutex, since we won't be sending this
// tx into the transaction pool, but right back to the user
if args . From == nil {
return nil , errors . New ( "sender not specified" )
}
if args . Gas == nil {
return nil , errors . New ( "gas not specified" )
}
if args . GasPrice == nil && ( args . MaxFeePerGas == nil || args . MaxPriorityFeePerGas == nil ) {
return nil , errors . New ( "missing gasPrice or maxFeePerGas/maxPriorityFeePerGas" )
}
if args . IsEIP4844 ( ) {
return nil , errBlobTxNotSupported
}
if args . Nonce == nil {
return nil , errors . New ( "nonce not specified" )
}
// Before actually signing the transaction, ensure the transaction fee is reasonable.
tx := args . ToTransaction ( types . LegacyTxType )
if err := checkTxFee ( tx . GasPrice ( ) , tx . Gas ( ) , api . b . RPCTxFeeCap ( ) ) ; err != nil {
return nil , err
}
signed , err := api . signTransaction ( ctx , & args , passwd )
if err != nil {
log . Warn ( "Failed transaction sign attempt" , "from" , args . from ( ) , "to" , args . To , "value" , args . Value . ToInt ( ) , "err" , err )
return nil , err
}
data , err := signed . MarshalBinary ( )
if err != nil {
return nil , err
}
return & SignTransactionResult { data , signed } , nil
}
// Sign calculates an Ethereum ECDSA signature for:
// keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))
//
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
// where the V value will be 27 or 28 for legacy reasons.
//
// The key used to calculate the signature is decrypted with the given password.
//
// https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-personal#personal-sign
func ( api * PersonalAccountAPI ) Sign ( ctx context . Context , data hexutil . Bytes , addr common . Address , passwd string ) ( hexutil . Bytes , error ) {
// Look up the wallet containing the requested signer
account := accounts . Account { Address : addr }
wallet , err := api . b . AccountManager ( ) . Find ( account )
if err != nil {
return nil , err
}
// Assemble sign the data with the wallet
signature , err := wallet . SignTextWithPassphrase ( account , passwd , data )
if err != nil {
log . Warn ( "Failed data sign attempt" , "address" , addr , "err" , err )
return nil , err
}
signature [ crypto . RecoveryIDOffset ] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
return signature , nil
}
// EcRecover returns the address for the account that was used to create the signature.
// Note, this function is compatible with eth_sign and personal_sign. As such it recovers
// the address of:
// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message})
// addr = ecrecover(hash, signature)
//
// Note, the signature must conform to the secp256k1 curve R, S and V values, where
// the V value must be 27 or 28 for legacy reasons.
//
// https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-personal#personal-ecrecover
func ( api * PersonalAccountAPI ) EcRecover ( ctx context . Context , data , sig hexutil . Bytes ) ( common . Address , error ) {
if len ( sig ) != crypto . SignatureLength {
return common . Address { } , fmt . Errorf ( "signature must be %d bytes long" , crypto . SignatureLength )
}
if sig [ crypto . RecoveryIDOffset ] != 27 && sig [ crypto . RecoveryIDOffset ] != 28 {
return common . Address { } , errors . New ( "invalid Ethereum signature (V is not 27 or 28)" )
}
sig [ crypto . RecoveryIDOffset ] -= 27 // Transform yellow paper V from 27/28 to 0/1
rpk , err := crypto . SigToPub ( accounts . TextHash ( data ) , sig )
if err != nil {
return common . Address { } , err
}
return crypto . PubkeyToAddress ( * rpk ) , nil
}
// InitializeWallet initializes a new wallet at the provided URL, by generating and returning a new private key.
func ( api * PersonalAccountAPI ) InitializeWallet ( ctx context . Context , url string ) ( string , error ) {
wallet , err := api . am . Wallet ( url )
if err != nil {
return "" , err
}
entropy , err := bip39 . NewEntropy ( 256 )
if err != nil {
return "" , err
}
mnemonic , err := bip39 . NewMnemonic ( entropy )
if err != nil {
return "" , err
}
seed := bip39 . NewSeed ( mnemonic , "" )
switch wallet := wallet . ( type ) {
case * scwallet . Wallet :
return mnemonic , wallet . Initialize ( seed )
default :
return "" , errors . New ( "specified wallet does not support initialization" )
}
}
// Unpair deletes a pairing between wallet and geth.
func ( api * PersonalAccountAPI ) Unpair ( ctx context . Context , url string , pin string ) error {
wallet , err := api . am . Wallet ( url )
if err != nil {
return err
}
switch wallet := wallet . ( type ) {
case * scwallet . Wallet :
return wallet . Unpair ( [ ] byte ( pin ) )
default :
return errors . New ( "specified wallet does not support pairing" )
}
}
// BlockChainAPI provides an API to access Ethereum blockchain data.
type BlockChainAPI struct {
b Backend