diff options
-rw-r--r-- | core/types/transaction.go | 14 | ||||
-rw-r--r-- | core/types/transaction_signing.go | 174 | ||||
-rw-r--r-- | core/types/transaction_test.go | 5 | ||||
-rw-r--r-- | ethclient/ethclient.go | 85 | ||||
-rw-r--r-- | ethclient/signer.go | 59 | ||||
-rw-r--r-- | mobile/ethclient.go | 7 | ||||
-rw-r--r-- | mobile/types.go | 9 |
7 files changed, 204 insertions, 149 deletions
diff --git a/core/types/transaction.go b/core/types/transaction.go index 7f54860fc..a46521236 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -209,12 +209,6 @@ func (tx *Transaction) Hash() common.Hash { return v } -// SigHash returns the hash to be signed by the sender. -// It does not uniquely identify the transaction. -func (tx *Transaction) SigHash(signer Signer) common.Hash { - return signer.Hash(tx) -} - func (tx *Transaction) Size() common.StorageSize { if size := tx.size.Load(); size != nil { return size.(common.StorageSize) @@ -249,7 +243,13 @@ func (tx *Transaction) AsMessage(s Signer) (Message, error) { // WithSignature returns a new transaction with the given signature. // This signature needs to be formatted as described in the yellow paper (v+27). func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error) { - return signer.WithSignature(tx, sig) + r, s, v, err := signer.SignatureValues(tx, sig) + if err != nil { + return nil, err + } + cpy := &Transaction{data: tx.data} + cpy.data.R, cpy.data.S, cpy.data.V = r, s, v + return cpy, nil } // Cost returns amount + gasprice * gaslimit. diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index ba4f2aa03..dfc84fdac 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -29,9 +29,6 @@ import ( var ( ErrInvalidChainId = errors.New("invalid chain id for signer") - - errAbstractSigner = errors.New("abstract signer") - abstractSignerAddress = common.HexToAddress("ffffffffffffffffffffffffffffffffffffffff") ) // sigCache is used to cache the derived sender and contains @@ -62,12 +59,9 @@ func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, err if err != nil { return nil, err } - return s.WithSignature(tx, sig) + return tx.WithSignature(s, sig) } -// Sender derives the sender from the tx using the signer derivation -// functions. - // Sender returns the address derived from the signature (V, R, S) using secp256k1 // elliptic curve and an error if it failed deriving or upon an incorrect // signature. @@ -86,33 +80,30 @@ func Sender(signer Signer, tx *Transaction) (common.Address, error) { } } - pubkey, err := signer.PublicKey(tx) + addr, err := signer.Sender(tx) if err != nil { return common.Address{}, err } - var addr common.Address - copy(addr[:], crypto.Keccak256(pubkey[1:])[12:]) tx.from.Store(sigCache{signer: signer, from: addr}) return addr, nil } +// Signer encapsulates transaction signature handling. Note that this interface is not a +// stable API and may change at any time to accommodate new protocol rules. type Signer interface { - // Hash returns the rlp encoded hash for signatures + // Sender returns the sender address of the transaction. + Sender(tx *Transaction) (common.Address, error) + // SignatureValues returns the raw R, S, V values corresponding to the + // given signature. + SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) + // Hash returns the hash to be signed. Hash(tx *Transaction) common.Hash - // PubilcKey returns the public key derived from the signature - PublicKey(tx *Transaction) ([]byte, error) - // WithSignature returns a copy of the transaction with the given signature. - // The signature must be encoded in [R || S || V] format where V is 0 or 1. - WithSignature(tx *Transaction, sig []byte) (*Transaction, error) - // Checks for equality on the signers + // Equal returns true if the given signer is the same as the receiver. Equal(Signer) bool } -// EIP155Transaction implements TransactionInterface using the -// EIP155 rules +// EIP155Transaction implements Signer using the EIP155 rules. type EIP155Signer struct { - HomesteadSigner - chainId, chainIdMul *big.Int } @@ -131,55 +122,32 @@ func (s EIP155Signer) Equal(s2 Signer) bool { return ok && eip155.chainId.Cmp(s.chainId) == 0 } -func (s EIP155Signer) PublicKey(tx *Transaction) ([]byte, error) { - // if the transaction is not protected fall back to homestead signer +var big8 = big.NewInt(8) + +func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) { if !tx.Protected() { - return (HomesteadSigner{}).PublicKey(tx) + return HomesteadSigner{}.Sender(tx) } - if tx.ChainId().Cmp(s.chainId) != 0 { - return nil, ErrInvalidChainId - } - - V := byte(new(big.Int).Sub(tx.data.V, s.chainIdMul).Uint64() - 35) - if !crypto.ValidateSignatureValues(V, tx.data.R, tx.data.S, true) { - return nil, ErrInvalidSig - } - // encode the signature in uncompressed format - R, S := tx.data.R.Bytes(), tx.data.S.Bytes() - sig := make([]byte, 65) - copy(sig[32-len(R):32], R) - copy(sig[64-len(S):64], S) - sig[64] = V - - // recover the public key from the signature - hash := s.Hash(tx) - pub, err := crypto.Ecrecover(hash[:], sig) - if err != nil { - return nil, err - } - if len(pub) == 0 || pub[0] != 4 { - return nil, errors.New("invalid public key") + return common.Address{}, ErrInvalidChainId } - return pub, nil + V := new(big.Int).Sub(tx.data.V, s.chainIdMul) + V.Sub(V, big8) + return recoverPlain(s.Hash(tx), tx.data.R, tx.data.S, V, true) } // WithSignature returns a new transaction with the given signature. This signature // needs to be in the [R || S || V] format where V is 0 or 1. -func (s EIP155Signer) WithSignature(tx *Transaction, sig []byte) (*Transaction, error) { - if len(sig) != 65 { - panic(fmt.Sprintf("wrong size for signature: got %d, want 65", len(sig))) +func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + R, S, V, err = HomesteadSigner{}.SignatureValues(tx, sig) + if err != nil { + return nil, nil, nil, err } - - cpy := &Transaction{data: tx.data} - cpy.data.R = new(big.Int).SetBytes(sig[:32]) - cpy.data.S = new(big.Int).SetBytes(sig[32:64]) - cpy.data.V = new(big.Int).SetBytes([]byte{sig[64]}) if s.chainId.Sign() != 0 { - cpy.data.V = big.NewInt(int64(sig[64] + 35)) - cpy.data.V.Add(cpy.data.V, s.chainIdMul) + V = big.NewInt(int64(sig[64] + 35)) + V.Add(V, s.chainIdMul) } - return cpy, nil + return R, S, V, nil } // Hash returns the hash to be signed by the sender. @@ -205,44 +173,14 @@ func (s HomesteadSigner) Equal(s2 Signer) bool { return ok } -// WithSignature returns a new transaction with the given signature. This signature +// SignatureValues returns signature values. This signature // needs to be in the [R || S || V] format where V is 0 or 1. -func (hs HomesteadSigner) WithSignature(tx *Transaction, sig []byte) (*Transaction, error) { - if len(sig) != 65 { - panic(fmt.Sprintf("wrong size for snature: got %d, want 65", len(sig))) - } - cpy := &Transaction{data: tx.data} - cpy.data.R = new(big.Int).SetBytes(sig[:32]) - cpy.data.S = new(big.Int).SetBytes(sig[32:64]) - cpy.data.V = new(big.Int).SetBytes([]byte{sig[64] + 27}) - return cpy, nil +func (hs HomesteadSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) { + return hs.FrontierSigner.SignatureValues(tx, sig) } -func (hs HomesteadSigner) PublicKey(tx *Transaction) ([]byte, error) { - if tx.data.V.BitLen() > 8 { - return nil, ErrInvalidSig - } - V := byte(tx.data.V.Uint64() - 27) - if !crypto.ValidateSignatureValues(V, tx.data.R, tx.data.S, true) { - return nil, ErrInvalidSig - } - // encode the snature in uncompressed format - r, s := tx.data.R.Bytes(), tx.data.S.Bytes() - sig := make([]byte, 65) - copy(sig[32-len(r):32], r) - copy(sig[64-len(s):64], s) - sig[64] = V - - // recover the public key from the snature - hash := hs.Hash(tx) - pub, err := crypto.Ecrecover(hash[:], sig) - if err != nil { - return nil, err - } - if len(pub) == 0 || pub[0] != 4 { - return nil, errors.New("invalid public key") - } - return pub, nil +func (hs HomesteadSigner) Sender(tx *Transaction) (common.Address, error) { + return recoverPlain(hs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, true) } type FrontierSigner struct{} @@ -252,20 +190,19 @@ func (s FrontierSigner) Equal(s2 Signer) bool { return ok } -// WithSignature returns a new transaction with the given signature. This signature +// SignatureValues returns signature values. This signature // needs to be in the [R || S || V] format where V is 0 or 1. -func (fs FrontierSigner) WithSignature(tx *Transaction, sig []byte) (*Transaction, error) { +func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) { if len(sig) != 65 { - panic(fmt.Sprintf("wrong size for snature: got %d, want 65", len(sig))) + panic(fmt.Sprintf("wrong size for signature: got %d, want 65", len(sig))) } - cpy := &Transaction{data: tx.data} - cpy.data.R = new(big.Int).SetBytes(sig[:32]) - cpy.data.S = new(big.Int).SetBytes(sig[32:64]) - cpy.data.V = new(big.Int).SetBytes([]byte{sig[64] + 27}) - return cpy, nil + r = new(big.Int).SetBytes(sig[:32]) + s = new(big.Int).SetBytes(sig[32:64]) + v = new(big.Int).SetBytes([]byte{sig[64] + 27}) + return r, s, v, nil } -// Hash returns the hash to be sned by the sender. +// Hash returns the hash to be signed by the sender. // It does not uniquely identify the transaction. func (fs FrontierSigner) Hash(tx *Transaction) common.Hash { return rlpHash([]interface{}{ @@ -278,32 +215,35 @@ func (fs FrontierSigner) Hash(tx *Transaction) common.Hash { }) } -func (fs FrontierSigner) PublicKey(tx *Transaction) ([]byte, error) { - if tx.data.V.BitLen() > 8 { - return nil, ErrInvalidSig - } +func (fs FrontierSigner) Sender(tx *Transaction) (common.Address, error) { + return recoverPlain(fs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, false) +} - V := byte(tx.data.V.Uint64() - 27) - if !crypto.ValidateSignatureValues(V, tx.data.R, tx.data.S, false) { - return nil, ErrInvalidSig +func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (common.Address, error) { + if Vb.BitLen() > 8 { + return common.Address{}, ErrInvalidSig + } + V := byte(Vb.Uint64() - 27) + if !crypto.ValidateSignatureValues(V, R, S, homestead) { + return common.Address{}, ErrInvalidSig } // encode the snature in uncompressed format - r, s := tx.data.R.Bytes(), tx.data.S.Bytes() + r, s := R.Bytes(), S.Bytes() sig := make([]byte, 65) copy(sig[32-len(r):32], r) copy(sig[64-len(s):64], s) sig[64] = V - // recover the public key from the snature - hash := fs.Hash(tx) - pub, err := crypto.Ecrecover(hash[:], sig) + pub, err := crypto.Ecrecover(sighash[:], sig) if err != nil { - return nil, err + return common.Address{}, err } if len(pub) == 0 || pub[0] != 4 { - return nil, errors.New("invalid public key") + return common.Address{}, errors.New("invalid public key") } - return pub, nil + var addr common.Address + copy(addr[:], crypto.Keccak256(pub[1:])[12:]) + return addr, nil } // deriveChainId derives the chain id from the given v parameter diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index df9d7ffd1..30ecb84dd 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -52,10 +52,11 @@ var ( ) func TestTransactionSigHash(t *testing.T) { - if emptyTx.SigHash(HomesteadSigner{}) != common.HexToHash("c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386") { + var homestead HomesteadSigner + if homestead.Hash(emptyTx) != common.HexToHash("c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386") { t.Errorf("empty transaction hash mismatch, got %x", emptyTx.Hash()) } - if rightvrsTx.SigHash(HomesteadSigner{}) != common.HexToHash("fe7a79529ed5f7c3375d06b26b186a8644e0e16c373d7a12be41c62d6042b77a") { + if homestead.Hash(rightvrsTx) != common.HexToHash("fe7a79529ed5f7c3375d06b26b186a8644e0e16c373d7a12be41c62d6042b77a") { t.Errorf("RightVRS transaction hash mismatch, got %x", rightvrsTx.Hash()) } } diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 48639d949..7f73ab113 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -20,6 +20,7 @@ package ethclient import ( "context" "encoding/json" + "errors" "fmt" "math/big" @@ -70,9 +71,9 @@ func (ec *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Bl } type rpcBlock struct { - Hash common.Hash `json:"hash"` - Transactions []*types.Transaction `json:"transactions"` - UncleHashes []common.Hash `json:"uncles"` + Hash common.Hash `json:"hash"` + Transactions []rpcTransaction `json:"transactions"` + UncleHashes []common.Hash `json:"uncles"` } func (ec *Client) getBlock(ctx context.Context, method string, args ...interface{}) (*types.Block, error) { @@ -129,7 +130,13 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface } } } - return types.NewBlockWithHeader(head).WithBody(body.Transactions, uncles), nil + // Fill the sender cache of transactions in the block. + txs := make([]*types.Transaction, len(body.Transactions)) + for i, tx := range body.Transactions { + setSenderFromServer(tx.tx, tx.From, body.Hash) + txs[i] = tx.tx + } + return types.NewBlockWithHeader(head).WithBody(txs, uncles), nil } // HeaderByHash returns the block header with the given hash. @@ -153,25 +160,62 @@ func (ec *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.H return head, err } +type rpcTransaction struct { + tx *types.Transaction + txExtraInfo +} + +type txExtraInfo struct { + BlockNumber *string + BlockHash common.Hash + From common.Address +} + +func (tx *rpcTransaction) UnmarshalJSON(msg []byte) error { + if err := json.Unmarshal(msg, &tx.tx); err != nil { + return err + } + return json.Unmarshal(msg, &tx.txExtraInfo) +} + // TransactionByHash returns the transaction with the given hash. func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) { - var raw json.RawMessage - err = ec.c.CallContext(ctx, &raw, "eth_getTransactionByHash", hash) + var json *rpcTransaction + err = ec.c.CallContext(ctx, &json, "eth_getTransactionByHash", hash) if err != nil { return nil, false, err - } else if len(raw) == 0 { + } else if json == nil { return nil, false, ethereum.NotFound - } - if err := json.Unmarshal(raw, &tx); err != nil { - return nil, false, err - } else if _, r, _ := tx.RawSignatureValues(); r == nil { + } else if _, r, _ := json.tx.RawSignatureValues(); r == nil { return nil, false, fmt.Errorf("server returned transaction without signature") } - var block struct{ BlockNumber *string } - if err := json.Unmarshal(raw, &block); err != nil { - return nil, false, err + setSenderFromServer(json.tx, json.From, json.BlockHash) + return json.tx, json.BlockNumber == nil, nil +} + +// TransactionSender returns the sender address of the given transaction. The transaction +// must be known to the remote node and included in the blockchain at the given block and +// index. The sender is the one derived by the protocol at the time of inclusion. +// +// There is a fast-path for transactions retrieved by TransactionByHash and +// TransactionInBlock. Getting their sender address can be done without an RPC interaction. +func (ec *Client) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) { + // Try to load the address from the cache. + sender, err := types.Sender(&senderFromServer{blockhash: block}, tx) + if err == nil { + return sender, nil + } + var meta struct { + Hash common.Hash + From common.Address + } + if err = ec.c.CallContext(ctx, &meta, "eth_getTransactionByBlockHashAndIndex", block, hexutil.Uint64(index)); err != nil { + return common.Address{}, err + } + if meta.Hash == (common.Hash{}) || meta.Hash != tx.Hash() { + return common.Address{}, errors.New("wrong inclusion block/index") } - return tx, block.BlockNumber == nil, nil + return meta.From, nil } // TransactionCount returns the total number of transactions in the given block. @@ -183,16 +227,17 @@ func (ec *Client) TransactionCount(ctx context.Context, blockHash common.Hash) ( // TransactionInBlock returns a single transaction at index in the given block. func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { - var tx *types.Transaction - err := ec.c.CallContext(ctx, &tx, "eth_getTransactionByBlockHashAndIndex", blockHash, hexutil.Uint64(index)) + var json *rpcTransaction + err := ec.c.CallContext(ctx, &json, "eth_getTransactionByBlockHashAndIndex", blockHash, hexutil.Uint64(index)) if err == nil { - if tx == nil { + if json == nil { return nil, ethereum.NotFound - } else if _, r, _ := tx.RawSignatureValues(); r == nil { + } else if _, r, _ := json.tx.RawSignatureValues(); r == nil { return nil, fmt.Errorf("server returned transaction without signature") } } - return tx, err + setSenderFromServer(json.tx, json.From, json.BlockHash) + return json.tx, err } // TransactionReceipt returns the receipt of a transaction by transaction hash. diff --git a/ethclient/signer.go b/ethclient/signer.go new file mode 100644 index 000000000..74a93f1e2 --- /dev/null +++ b/ethclient/signer.go @@ -0,0 +1,59 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +package ethclient + +import ( + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// senderFromServer is a types.Signer that remembers the sender address returned by the RPC +// server. It is stored in the transaction's sender address cache to avoid an additional +// request in TransactionSender. +type senderFromServer struct { + addr common.Address + blockhash common.Hash +} + +var errNotCached = errors.New("sender not cached") + +func setSenderFromServer(tx *types.Transaction, addr common.Address, block common.Hash) { + // Use types.Sender for side-effect to store our signer into the cache. + types.Sender(&senderFromServer{addr, block}, tx) +} + +func (s *senderFromServer) Equal(other types.Signer) bool { + os, ok := other.(*senderFromServer) + return ok && os.blockhash == s.blockhash +} + +func (s *senderFromServer) Sender(tx *types.Transaction) (common.Address, error) { + if s.blockhash == (common.Hash{}) { + return common.Address{}, errNotCached + } + return s.addr, nil +} + +func (s *senderFromServer) Hash(tx *types.Transaction) common.Hash { + panic("can't sign with senderFromServer") +} +func (s *senderFromServer) SignatureValues(tx *types.Transaction, sig []byte) (R, S, V *big.Int, err error) { + panic("can't sign with senderFromServer") +} diff --git a/mobile/ethclient.go b/mobile/ethclient.go index 4e8328501..7f31a8998 100644 --- a/mobile/ethclient.go +++ b/mobile/ethclient.go @@ -77,6 +77,13 @@ func (ec *EthereumClient) GetTransactionByHash(ctx *Context, hash *Hash) (tx *Tr return &Transaction{rawTx}, err } +// GetTransactionSender returns the sender address of a transaction. The transaction must +// be included in blockchain at the given block and index. +func (ec *EthereumClient) GetTransactionSender(ctx *Context, tx *Transaction, blockhash *Hash, index int) (sender *Address, _ error) { + addr, err := ec.client.TransactionSender(ctx.context, tx.tx, blockhash.hash, uint(index)) + return &Address{addr}, err +} + // GetTransactionCount returns the total number of transactions in the given block. func (ec *EthereumClient) GetTransactionCount(ctx *Context, hash *Hash) (count int, _ error) { rawCount, err := ec.client.TransactionCount(ctx.context, hash.hash) diff --git a/mobile/types.go b/mobile/types.go index 088c7c6b3..b7f8a3bc1 100644 --- a/mobile/types.go +++ b/mobile/types.go @@ -261,10 +261,13 @@ func (tx *Transaction) GetGasPrice() *BigInt { return &BigInt{tx.tx.GasPrice()} func (tx *Transaction) GetValue() *BigInt { return &BigInt{tx.tx.Value()} } func (tx *Transaction) GetNonce() int64 { return int64(tx.tx.Nonce()) } -func (tx *Transaction) GetHash() *Hash { return &Hash{tx.tx.Hash()} } -func (tx *Transaction) GetSigHash() *Hash { return &Hash{tx.tx.SigHash(types.HomesteadSigner{})} } -func (tx *Transaction) GetCost() *BigInt { return &BigInt{tx.tx.Cost()} } +func (tx *Transaction) GetHash() *Hash { return &Hash{tx.tx.Hash()} } +func (tx *Transaction) GetCost() *BigInt { return &BigInt{tx.tx.Cost()} } +// Deprecated: GetSigHash cannot know which signer to use. +func (tx *Transaction) GetSigHash() *Hash { return &Hash{types.HomesteadSigner{}.Hash(tx.tx)} } + +// Deprecated: use EthereumClient.TransactionSender func (tx *Transaction) GetFrom(chainID *BigInt) (address *Address, _ error) { var signer types.Signer = types.HomesteadSigner{} if chainID != nil { |