From b540bf6a0e6c6079fbe7d2a6881519ab6a22b97a Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 25 Jan 2018 13:51:55 +0100 Subject: [PATCH 1/3] ethapi: add personal.signTransaction --- internal/ethapi/api.go | 40 +++++++++++++++++++++++++++++++++++++ internal/web3ext/web3ext.go | 6 ++++++ 2 files changed, 46 insertions(+) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 55bd5aa1baa6..34de5d07708c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -370,6 +370,46 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs return submitTransaction(ctx, s.b, signed) } +// SignTransaction will create a transaction from the given arguments and +// tries to sign it with the key associated with args.To. 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 (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs, passwd string) (*SignTransactionResult, error) { + // Look up the wallet containing the requested signer + account := accounts.Account{Address: args.From} + + wallet, err := s.am.Find(account) + if err != nil { + return nil, err + } + if args.Nonce == nil { + // Hold the addresse's mutex around signing to prevent concurrent assignment of + // the same nonce to multiple accounts. + s.nonceLock.LockAddr(args.From) + defer s.nonceLock.UnlockAddr(args.From) + } + // Set some sanity defaults and terminate on failure + if err := args.setDefaults(ctx, s.b); err != nil { + return nil, err + } + // Assemble the transaction and sign with the wallet + tx := args.toTransaction() + + var chainID *big.Int + if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) { + chainID = config.ChainId + } + tx, err = wallet.SignTxWithPassphrase(account, passwd, tx, chainID) + if err != nil { + return nil, err + } + data, err := rlp.EncodeToBytes(tx) + if err != nil { + return nil, err + } + return &SignTransactionResult{data, tx}, nil +} + // signHash is a helper function that calculates a hash for the given message that can be // safely used to calculate a signature from. // diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index e11aa402f5cf..a6b81b4c2a60 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -517,6 +517,12 @@ web3._extend({ call: 'personal_deriveAccount', params: 3 }), + new web3._extend.Method({ + name: 'signTransaction', + call: 'personal_signTransaction', + params: 2, + inputFormatter: [web3._extend.formatters.inputTransactionFormatter, null] + }), ], properties: [ new web3._extend.Property({ From a2ccf4f5264916350cc89128fe1356c8c89c5c82 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 26 Jan 2018 13:07:48 +0100 Subject: [PATCH 2/3] ethapi: refactor to minimize duplicate code --- internal/ethapi/api.go | 68 ++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 43 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 34de5d07708c..c5dd0f254777 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -333,28 +333,19 @@ func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool { return fetchKeystore(s.am).Lock(addr) == nil } -// SendTransaction will create a transaction from the given arguments and -// tries to sign it with the key associated with args.To. If the given passwd isn't -// able to decrypt the key it fails. -func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) { +// signTransactions sets defaults and signs the given transation +// 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 (s *PrivateAccountAPI) signTransaction(ctx context.Context, args SendTxArgs, passwd string) (*types.Transaction, error) { // Look up the wallet containing the requested signer account := accounts.Account{Address: args.From} - wallet, err := s.am.Find(account) if err != nil { - return common.Hash{}, err - } - - if args.Nonce == nil { - // Hold the addresse's mutex around signing to prevent concurrent assignment of - // the same nonce to multiple accounts. - s.nonceLock.LockAddr(args.From) - defer s.nonceLock.UnlockAddr(args.From) + return nil, err } - // Set some sanity defaults and terminate on failure if err := args.setDefaults(ctx, s.b); err != nil { - return common.Hash{}, err + return nil, err } // Assemble the transaction and sign with the wallet tx := args.toTransaction() @@ -363,51 +354,42 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) { chainID = config.ChainId } - signed, err := wallet.SignTxWithPassphrase(account, passwd, tx, chainID) - if err != nil { - return common.Hash{}, err - } - return submitTransaction(ctx, s.b, signed) + return wallet.SignTxWithPassphrase(account, passwd, tx, chainID) } -// SignTransaction will create a transaction from the given arguments and +// SendTransaction will create a transaction from the given arguments and // tries to sign it with the key associated with args.To. 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 (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs, passwd string) (*SignTransactionResult, error) { - // Look up the wallet containing the requested signer - account := accounts.Account{Address: args.From} - - wallet, err := s.am.Find(account) - if err != nil { - return nil, err - } +// able to decrypt the key it fails. +func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) { if args.Nonce == nil { // Hold the addresse's mutex around signing to prevent concurrent assignment of // the same nonce to multiple accounts. s.nonceLock.LockAddr(args.From) defer s.nonceLock.UnlockAddr(args.From) } - // Set some sanity defaults and terminate on failure - if err := args.setDefaults(ctx, s.b); err != nil { - return nil, err + signed, err := s.signTransaction(ctx, args, passwd) + if err != nil { + return common.Hash{}, err } - // Assemble the transaction and sign with the wallet - tx := args.toTransaction() + return submitTransaction(ctx, s.b, signed) +} - var chainID *big.Int - if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) { - chainID = config.ChainId - } - tx, err = wallet.SignTxWithPassphrase(account, passwd, tx, chainID) +// SignTransaction will create a transaction from the given arguments and +// tries to sign it with the key associated with args.To. 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 (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs, 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 + signed, err := s.signTransaction(ctx, args, passwd) if err != nil { return nil, err } - data, err := rlp.EncodeToBytes(tx) + data, err := rlp.EncodeToBytes(signed) if err != nil { return nil, err } - return &SignTransactionResult{data, tx}, nil + return &SignTransactionResult{data, signed}, nil } // signHash is a helper function that calculates a hash for the given message that can be From 9518f94f50762f318c960eae6426e03578e0760e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 26 Jan 2018 14:34:44 +0100 Subject: [PATCH 3/3] ethapi: make nonce,gas,gasPrice obligatory in signTransaction --- internal/ethapi/api.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c5dd0f254777..48eb91b675ee 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -381,6 +381,15 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs, 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.Gas == nil { + return nil, fmt.Errorf("gas not specified") + } + if args.GasPrice == nil { + return nil, fmt.Errorf("gasPrice not specified") + } + if args.Nonce == nil { + return nil, fmt.Errorf("nonce not specified") + } signed, err := s.signTransaction(ctx, args, passwd) if err != nil { return nil, err @@ -1243,11 +1252,14 @@ type SignTransactionResult struct { // The node needs to have the private key of the account corresponding with // the given from address and it needs to be unlocked. func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) { + if args.Gas == nil { + return nil, fmt.Errorf("gas not specified") + } + if args.GasPrice == nil { + return nil, fmt.Errorf("gasPrice not specified") + } if args.Nonce == nil { - // Hold the addresse's mutex around signing to prevent concurrent assignment of - // the same nonce to multiple accounts. - s.nonceLock.LockAddr(args.From) - defer s.nonceLock.UnlockAddr(args.From) + return nil, fmt.Errorf("nonce not specified") } if err := args.setDefaults(ctx, s.b); err != nil { return nil, err