From d78ad226c26c84635c60fad233de9e6e438a5599 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 1 Oct 2017 11:03:28 +0200 Subject: ethclient, mobile: add TransactionSender (#15127) * core/types: make Signer derive address instead of public key There are two reasons to do this now: The upcoming ethclient signer doesn't know the public key, just the address. EIP 208 will introduce a new signer which derives the 'entry point' address for transactions with zero signature. The entry point has no public key. Other changes to the interface ease the path make to moving signature crypto out of core/types later. * ethclient, mobile: add TransactionSender The new method can get the right signer without any crypto, and without knowledge of the signature scheme that was used when the transaction was included. --- core/types/transaction.go | 14 +-- core/types/transaction_signing.go | 174 +++++++++++++------------------------- core/types/transaction_test.go | 5 +- 3 files changed, 67 insertions(+), 126 deletions(-) (limited to 'core') 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()) } } -- cgit v1.2.3