From e0564f7b419235bc5c9359843aeb74663ecd9f0e Mon Sep 17 00:00:00 2001 From: libby kent Date: Thu, 11 Jul 2019 13:32:19 -0400 Subject: [PATCH] Adding QuorumPrivateTxSigner for signing and recovering private txs. (#767) Refactor private transaction signing and add a QuorumPrivateTxSigner for signing and recovering private txs. --- accounts/accounts.go | 2 +- accounts/keystore/keystore.go | 15 +++- accounts/keystore/keystore_wallet.go | 4 +- accounts/usbwallet/wallet.go | 8 +- cmd/faucet/faucet.go | 2 +- core/call_helper.go | 11 ++- core/private_state_test.go | 23 +++--- core/types/transaction.go | 12 ++- core/types/transaction_signing.go | 23 ++---- core/types/transaction_signing_private.go | 61 +++++++++++++++ ...transaction_signing_quorum_private_test.go | 63 ++++++++++++++++ internal/ethapi/api.go | 75 +++++++++++-------- internal/guide/guide_test.go | 4 +- mobile/accounts.go | 2 +- 14 files changed, 231 insertions(+), 74 deletions(-) create mode 100644 core/types/transaction_signing_private.go create mode 100644 core/types/transaction_signing_quorum_private_test.go diff --git a/accounts/accounts.go b/accounts/accounts.go index 79f8632e2..cb1eae281 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -111,7 +111,7 @@ type Wallet interface { // about which fields or actions are needed. The user may retry by providing // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock // the account in a keystore). - SignTx(account Account, tx *types.Transaction, chainID *big.Int, isQuorum bool) (*types.Transaction, error) + SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) // SignHashWithPassphrase requests the wallet to sign the given hash with the // given passphrase as extra authentication information. diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index cd5e61be1..d000c70c2 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -25,6 +25,7 @@ import ( crand "crypto/rand" "errors" "fmt" + "github.com/ethereum/go-ethereum/log" "math/big" "os" "path/filepath" @@ -268,7 +269,7 @@ func (ks *KeyStore) SignHash(a accounts.Account, hash []byte) ([]byte, error) { } // SignTx signs the given transaction with the requested account. -func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int, isQuorum bool) (*types.Transaction, error) { +func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { // Look up the key to sign with and abort if it cannot be found ks.mu.RLock() defer ks.mu.RUnlock() @@ -277,8 +278,15 @@ func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *b if !found { return nil, ErrLocked } + + // start quorum specific + if tx.IsPrivate() { + log.Info("Private transaction signing with QuorumPrivateTxSigner") + return types.SignTx(tx, types.QuorumPrivateTxSigner{}, unlockedKey.PrivateKey) + } // End quorum specific + // Depending on the presence of the chain ID, sign with EIP155 or homestead - if chainID != nil && !tx.IsPrivate() { + if chainID != nil { return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey) } return types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey) @@ -305,6 +313,9 @@ func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, } defer zeroKey(key.PrivateKey) + if tx.IsPrivate() { + return types.SignTx(tx, types.QuorumPrivateTxSigner{}, key.PrivateKey) + } // Depending on the presence of the chain ID, sign with EIP155 or homestead if chainID != nil { return types.SignTx(tx, types.NewEIP155Signer(chainID), key.PrivateKey) diff --git a/accounts/keystore/keystore_wallet.go b/accounts/keystore/keystore_wallet.go index 6f809a9bf..758fdfe36 100644 --- a/accounts/keystore/keystore_wallet.go +++ b/accounts/keystore/keystore_wallet.go @@ -98,7 +98,7 @@ func (w *keystoreWallet) SignHash(account accounts.Account, hash []byte) ([]byte // with the given account. If the wallet does not wrap this particular account, // an error is returned to avoid account leakage (even though in theory we may // be able to sign via our shared keystore backend). -func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int, isQuorum bool) (*types.Transaction, error) { +func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { // Make sure the requested account is contained within if account.Address != w.account.Address { return nil, accounts.ErrUnknownAccount @@ -107,7 +107,7 @@ func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, return nil, accounts.ErrUnknownAccount } // Account seems valid, request the keystore to sign - return w.keystore.SignTx(account, tx, chainID, isQuorum) + return w.keystore.SignTx(account, tx, chainID) } // SignHashWithPassphrase implements accounts.Wallet, attempting to sign the diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index fdaf7a946..f8207147b 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -509,12 +509,12 @@ func (w *wallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) // Note, if the version of the Ethereum application running on the Ledger wallet is // too old to sign EIP-155 transactions, but such is requested nonetheless, an error // will be returned opposed to silently signing in Homestead mode. -func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int, isQuorum bool) (*types.Transaction, error) { +func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { w.stateLock.RLock() // Comms have own mutex, this is for the state fields defer w.stateLock.RUnlock() - if isQuorum { - return nil, errors.New("Signing Quorum transactions with a USB wallet not yet supported") + if tx.IsPrivate() { + return nil, errors.New("Signing Quorum Private transactions with a USB wallet not yet supported") } // If the wallet is closed, abort @@ -563,5 +563,5 @@ func (w *wallet) SignHashWithPassphrase(account accounts.Account, passphrase str // transaction with the given account using passphrase as extra authentication. // Since USB wallets don't rely on passphrases, these are silently ignored. func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { - return w.SignTx(account, tx, chainID, false) + return w.SignTx(account, tx, chainID) } diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index c4aadfbf4..2ffe12276 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -473,7 +473,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) { amount = new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(msg.Tier)), nil)) tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, 21000, f.price, nil) - signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainID, false) + signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainID) if err != nil { f.lock.Unlock() if err = sendError(conn, err); err != nil { diff --git a/core/call_helper.go b/core/call_helper.go index 60960e00f..0cb17aea5 100644 --- a/core/call_helper.go +++ b/core/call_helper.go @@ -47,7 +47,12 @@ func (cg *callHelper) MakeCall(private bool, key *ecdsa.PrivateKey, to common.Ad cg.header.GasLimit = 4700000 signer := types.MakeSigner(params.QuorumTestChainConfig, cg.header.Number) + if private { + signer = types.QuorumPrivateTxSigner{} + } + tx, err := types.SignTx(types.NewTransaction(cg.TxNonce(from), to, new(big.Int), 1000000, new(big.Int), input), signer, key) + if err != nil { return err } @@ -60,15 +65,13 @@ func (cg *callHelper) MakeCall(private bool, key *ecdsa.PrivateKey, to common.Ad publicState, privateState := cg.PublicState, cg.PrivateState if !private { privateState = publicState - } else { - tx.SetPrivate() } - // TODO(joel): can we just pass nil instead of bc? bc, _ := NewBlockChain(cg.db, nil, params.QuorumTestChainConfig, ethash.NewFaker(), vm.Config{}, nil) context := NewEVMContext(msg, &cg.header, bc, &from) vmenv := vm.NewEVM(context, publicState, privateState, params.QuorumTestChainConfig, vm.Config{}) - _, _, _, err = ApplyMessage(vmenv, msg, cg.gp) + sender := vm.AccountRef(msg.From()) + vmenv.Call(sender, to, msg.Data(), 100000000, new(big.Int)) if err != nil { return err } diff --git a/core/private_state_test.go b/core/private_state_test.go index eeeb131df..787738aac 100644 --- a/core/private_state_test.go +++ b/core/private_state_test.go @@ -263,13 +263,15 @@ func runTessera() (*osExec.Cmd, error) { } // 600a600055600060006001a1 -// [1] PUSH1 0x0a (store value) -// [3] PUSH1 0x00 (store addr) -// [4] SSTORE -// [6] PUSH1 0x00 -// [8] PUSH1 0x00 -// [10] PUSH1 0x01 -// [11] LOG1 +// 60 0a, 60 00, 55, 60 00, 60 00, 60 01, a1 +// [1] (0x60) PUSH1 0x0a (store value) +// [3] (0x60) PUSH1 0x00 (store addr) +// [4] (0x55) SSTORE (Store (k-00,v-a)) + +// [6] (0x60) PUSH1 0x00 +// [8] (0x60) PUSH1 0x00 +// [10](0x60) PUSH1 0x01 +// [11](0xa1) LOG1 offset(0x01), len(0x00), topic(0x00) // // Store then log func TestPrivateTransaction(t *testing.T) { @@ -294,10 +296,12 @@ func TestPrivateTransaction(t *testing.T) { prvContractAddr := common.Address{1} pubContractAddr := common.Address{2} + // SSTORE (K,V) SSTORE(0, 10): 600a600055 + // + + // LOG1 OFFSET LEN TOPIC, LOG1 (a1) 01, 00, 00: 600060006001a1 privateState.SetCode(prvContractAddr, common.Hex2Bytes("600a600055600060006001a1")) - privateState.SetState(prvContractAddr, common.Hash{}, common.Hash{9}) + // SSTORE (K,V) SSTORE(0, 14): 6014600055 publicState.SetCode(pubContractAddr, common.Hex2Bytes("6014600055")) - publicState.SetState(pubContractAddr, common.Hash{}, common.Hash{19}) if publicState.Exist(prvContractAddr) { t.Error("didn't expect private contract address to exist on public state") @@ -305,6 +309,7 @@ func TestPrivateTransaction(t *testing.T) { // Private transaction 1 err = helper.MakeCall(true, key, prvContractAddr, nil) + if err != nil { t.Fatal(err) } diff --git a/core/types/transaction.go b/core/types/transaction.go index ed0396973..5f4c8d9a9 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -42,6 +42,8 @@ func deriveSigner(V *big.Int) Signer { // joel: this is one of the two places we used a wrong signer to print txes if V.Sign() != 0 && isProtectedV(V) { return NewEIP155Signer(deriveChainId(V)) + } else if isPrivate(V) { + return QuorumPrivateTxSigner{} } else { return HomesteadSigner{} } @@ -404,7 +406,7 @@ func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transa for from, accTxs := range txs { // Ensure the sender address is from the signer acc, err := Sender(signer, accTxs[0]) - if (err == nil) { + if err == nil { heads = append(heads, accTxs[0]) txs[acc] = accTxs[1:] } else { @@ -498,6 +500,14 @@ func (tx *Transaction) IsPrivate() bool { return tx.data.V.Uint64() == 37 || tx.data.V.Uint64() == 38 } +/* + * Indicates that a transaction is private, but doesn't necessarily set the correct v value, as it can be called on + * an unsigned transaction. + * pre homestead signer, all v values were v=27 or v=28, with EIP155Signer that change, + * but SetPrivate() is also used on unsigned transactions to temporarily set the v value to indicate + * the transaction is intended to be private, and so that the correct signer can be selected. The signer will correctly + * set the valid v value (37 or 38): This helps minimize changes vs upstream go-ethereum code. + */ func (tx *Transaction) SetPrivate() { if tx.IsPrivate() { return diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 737e7c301..a467f3220 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -126,7 +126,7 @@ var big8 = big.NewInt(8) func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) { if tx.IsPrivate() { - return HomesteadSigner{}.Sender(tx) + return QuorumPrivateTxSigner{}.Sender(tx) } if !tx.Protected() { return HomesteadSigner{}.Sender(tx) @@ -136,16 +136,12 @@ func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) { } 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, tx.IsPrivate()) + return recoverPlain(s.Hash(tx), tx.data.R, tx.data.S, V, true) } // SignatureValues returns signature values. This signature // needs to be in the [R || S || V] format where V is 0 or 1. func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { - if tx.IsPrivate() { - return HomesteadSigner{}.SignatureValues(tx, sig) - } - R, S, V, err = HomesteadSigner{}.SignatureValues(tx, sig) if err != nil { return nil, nil, nil, err @@ -187,7 +183,7 @@ func (hs HomesteadSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v } func (hs HomesteadSigner) Sender(tx *Transaction) (common.Address, error) { - return recoverPlain(hs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, true, tx.IsPrivate()) + return recoverPlain(hs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, true) } type FrontierSigner struct{} @@ -205,11 +201,7 @@ func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v * } r = new(big.Int).SetBytes(sig[:32]) s = new(big.Int).SetBytes(sig[32:64]) - if tx.IsPrivate() { - v = new(big.Int).SetBytes([]byte{sig[64] + 37}) - } else { - v = new(big.Int).SetBytes([]byte{sig[64] + 27}) - } + v = new(big.Int).SetBytes([]byte{sig[64] + 27}) return r, s, v, nil } @@ -227,15 +219,16 @@ func (fs FrontierSigner) Hash(tx *Transaction) common.Hash { } func (fs FrontierSigner) Sender(tx *Transaction) (common.Address, error) { - return recoverPlain(fs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, false, tx.IsPrivate()) + return recoverPlain(fs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, false) } -func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool, isPrivate bool) (common.Address, error) { +func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (common.Address, error) { if Vb.BitLen() > 8 { return common.Address{}, ErrInvalidSig } var offset uint64 - if isPrivate { + // private transaction has a v value of 37 or 38 + if isPrivate(Vb) { offset = 37 } else { offset = 27 diff --git a/core/types/transaction_signing_private.go b/core/types/transaction_signing_private.go new file mode 100644 index 000000000..fef99a458 --- /dev/null +++ b/core/types/transaction_signing_private.go @@ -0,0 +1,61 @@ +// Copyright 2016 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 . + +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// Signs with Homestead +// obtains sender from EIP55Signer +type QuorumPrivateTxSigner struct{ HomesteadSigner } + +func (s QuorumPrivateTxSigner) Sender(tx *Transaction) (common.Address, error) { + return HomesteadSigner{}.Sender(tx) +} + +// SignatureValues returns signature values. This signature +// needs to be in the [R || S || V] format where V is 0 or 1. +func (qs QuorumPrivateTxSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + r, s, v, err := HomesteadSigner{}.SignatureValues(tx, sig) + // update v for private transaction marker: needs to be 37 (0+37) or 38 (1+37) for a private transaction. + v = new(big.Int).SetBytes([]byte{sig[64] + 37}) + return r, s, v, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s QuorumPrivateTxSigner) Hash(tx *Transaction) common.Hash { + return s.HomesteadSigner.Hash(tx) +} + +func (s QuorumPrivateTxSigner) Equal(s2 Signer) bool { + _, ok := s2.(QuorumPrivateTxSigner) + return ok +} + +/* + * If v is `37` or `38` that marks the transaction as private in Quorum. + * Note: this means quorum chains cannot have a public ethereum chainId == 1, as the EIP155 v + * param is `37` and `38` for the public Ethereum chain. Having a private chain with a chainId ==1 + * is discouraged in the general Ethereum ecosystem. + */ +func isPrivate(v *big.Int) bool { + return v.Cmp(big.NewInt(37)) == 0 || v.Cmp(big.NewInt(38)) == 0 +} diff --git a/core/types/transaction_signing_quorum_private_test.go b/core/types/transaction_signing_quorum_private_test.go new file mode 100644 index 000000000..13ff83020 --- /dev/null +++ b/core/types/transaction_signing_quorum_private_test.go @@ -0,0 +1,63 @@ +// Copyright 2014 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 . + +package types + +import ( + "crypto/ecdsa" + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + testifyassert "github.com/stretchr/testify/assert" +) + +func signTxWithSigner(signer Signer, key *ecdsa.PrivateKey) (*Transaction, common.Address, error) { + addr := crypto.PubkeyToAddress(key.PublicKey) + tx := NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil) + signedTx, err := SignTx(tx, signer, key) + return signedTx, addr, err +} + +// run all the tests in this file +// $> go test $(go list ./...) -run QuorumSignPrivate + +// test with QuorumPrivateSigner +/* +* $> go test -run TestQuorumSignPrivateQuorum + */ +func TestQuorumSignPrivateQuorum(t *testing.T) { + + assert := testifyassert.New(t) + keys := []*big.Int{k0v, k1v} + + for i := 0; i < len(keys); i++ { + key, _ := createKey(crypto.S256(), keys[i]) + qpPrivateSigner := QuorumPrivateTxSigner{HomesteadSigner{}} + + signedTx, addr, err := signTxWithSigner(qpPrivateSigner, key) + assert.Nil(err, err) + assert.True(signedTx.IsPrivate(), + fmt.Sprintf("The signed transaction is not private, signedTx.data.V is [%v]", signedTx.data.V)) + from, err := Sender(qpPrivateSigner, signedTx) + assert.Nil(err, err) + assert.True(from == addr, fmt.Sprintf("Expected from == address, [%x] == [%x]", from, addr)) + } + +} + diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 4aa5a84a9..c66dd49df 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -392,7 +392,14 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs defer s.nonceLock.UnlockAddr(args.From) } - isPrivate := args.PrivateFor != nil + // Set some sanity defaults and terminate on failure + if err := args.setDefaults(ctx, s.b); err != nil { + return common.Hash{}, err + } + // Assemble the transaction and sign with the wallet + tx := args.toTransaction() + + isPrivate := args.IsPrivate() if isPrivate { data := []byte(*args.Data) @@ -407,25 +414,18 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs // zekun: HACK d := hexutil.Bytes(data) args.Data = &d + tx = args.toTransaction() + // set to private before submitting to signer + // this sets the v value to 37 temporarily to indicate a private tx, and to choose the correct signer. + tx.SetPrivate() } - // Set some sanity defaults and terminate on failure - if err := args.setDefaults(ctx, s.b); err != nil { - return common.Hash{}, 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()) && !isPrivate { - chainID = config.ChainID - } - signed, err := wallet.SignTxWithPassphrase(account, passwd, tx, chainID) + signed, err := wallet.SignTxWithPassphrase(account, passwd, tx, s.b.ChainConfig().ChainID) 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, s.b, signed, isPrivate) + return submitTransaction(ctx, s.b, signed) } // SignTransaction will create a transaction from the given arguments and @@ -1238,6 +1238,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha return fields, nil } +// quorum: if signing a private TX set with tx.SetPrivate() before calling this method. // sign is a helper function that signs a transaction with the private key of the given address. func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { // Look up the wallet containing the requested signer @@ -1249,11 +1250,10 @@ func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transacti } // Request the wallet to sign the transaction var chainID *big.Int - isQuorum := tx.IsPrivate() if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) && !tx.IsPrivate() { chainID = config.ChainID } - return wallet.SignTx(account, tx, chainID, isQuorum) + return wallet.SignTx(account, tx, chainID) } // SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool. @@ -1276,6 +1276,10 @@ type SendTxArgs struct { //End-Quorum } +func (s SendTxArgs) IsPrivate() bool { + return s.PrivateFor != nil +} + // SendRawTxArgs represents the arguments to submit a new signed private transaction into the transaction pool. type SendRawTxArgs struct { PrivateFor []string `json:"privateFor"` @@ -1325,17 +1329,19 @@ func (args *SendTxArgs) toTransaction() *types.Transaction { return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input) } +// TODO: this submits a signed transaction, if it is a signed private transaction that should already be recorded in the tx. // submitTransaction is a helper function that submits tx to txPool and logs a message. -func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction, isPrivate bool) (common.Hash, error) { - if isPrivate { - tx.SetPrivate() - } - +func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) { if err := b.SendTx(ctx, tx); err != nil { return common.Hash{}, err } if tx.To() == nil { - signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number()) + var signer types.Signer + if tx.IsPrivate() { + signer = types.QuorumPrivateTxSigner{} + } else { + signer = types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number()) + } from, err := types.Sender(signer, tx) if err != nil { return common.Hash{}, err @@ -1369,7 +1375,7 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen defer s.nonceLock.UnlockAddr(args.From) } - isPrivate := args.PrivateFor != nil + isPrivate := args.IsPrivate() var data []byte if isPrivate { if args.Data != nil { @@ -1401,15 +1407,19 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen tx := args.toTransaction() var chainID *big.Int - isQuorum := tx.IsPrivate() if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) && !isPrivate { chainID = config.ChainID } - signed, err := wallet.SignTx(account, tx, chainID, isQuorum) + + if isPrivate { + tx.SetPrivate() + } + signed, err := wallet.SignTx(account, tx, chainID) if err != nil { return common.Hash{}, err } - return submitTransaction(ctx, s.b, signed, isPrivate) + return submitTransaction(ctx, s.b, signed) + } // SendRawTransaction will add the signed transaction to the transaction pool. @@ -1419,7 +1429,7 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod if err := rlp.DecodeBytes(encodedTx, tx); err != nil { return common.Hash{}, err } - return submitTransaction(ctx, s.b, tx, tx.IsPrivate()) + return submitTransaction(ctx, s.b, tx) } // SendRawPrivateTransaction will add the signed transaction to the transaction pool. @@ -1432,7 +1442,7 @@ func (s *PublicTransactionPoolAPI) SendRawPrivateTransaction(ctx context.Context } txHash := []byte(tx.Data()) - isPrivate := args.PrivateFor != nil + isPrivate := (args.PrivateFor != nil) && tx.IsPrivate() if isPrivate { if len(txHash) > 0 { @@ -1447,8 +1457,7 @@ func (s *PublicTransactionPoolAPI) SendRawPrivateTransaction(ctx context.Context } else { return common.Hash{}, fmt.Errorf("transaction is not private") } - - return submitTransaction(ctx, s.b, tx, isPrivate) + return submitTransaction(ctx, s.b, tx) } // Sign calculates an ECDSA signature for: @@ -1556,7 +1565,9 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr for _, p := range pending { var signer types.Signer = types.HomesteadSigner{} - if p.Protected() && !p.IsPrivate() { + if p.IsPrivate() { + signer = types.QuorumPrivateTxSigner{} + } else if p.Protected() { signer = types.NewEIP155Signer(p.ChainId()) } wantSigHash := signer.Hash(matchTx) @@ -1570,10 +1581,10 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr sendArgs.Gas = gasLimit } newTx := sendArgs.toTransaction() + // set v param to 37 to indicate private tx before submitting to the signer. if len(sendArgs.PrivateFor) > 0 { newTx.SetPrivate() } - signedTx, err := s.sign(sendArgs.From, newTx) if err != nil { return common.Hash{}, err diff --git a/internal/guide/guide_test.go b/internal/guide/guide_test.go index 146750dac..9c7ad16d1 100644 --- a/internal/guide/guide_test.go +++ b/internal/guide/guide_test.go @@ -85,7 +85,7 @@ func TestAccountManagement(t *testing.T) { if err := ks.Unlock(signer, "Signer password"); err != nil { t.Fatalf("Failed to unlock account: %v", err) } - if _, err := ks.SignTx(signer, tx, chain, false); err != nil { + if _, err := ks.SignTx(signer, tx, chain); err != nil { t.Fatalf("Failed to sign with unlocked account: %v", err) } if err := ks.Lock(signer.Address); err != nil { @@ -95,7 +95,7 @@ func TestAccountManagement(t *testing.T) { if err := ks.TimedUnlock(signer, "Signer password", time.Second); err != nil { t.Fatalf("Failed to time unlock account: %v", err) } - if _, err := ks.SignTx(signer, tx, chain, false); err != nil { + if _, err := ks.SignTx(signer, tx, chain); err != nil { t.Fatalf("Failed to sign with time unlocked account: %v", err) } } diff --git a/mobile/accounts.go b/mobile/accounts.go index 2ddff93e6..4d979bfff 100644 --- a/mobile/accounts.go +++ b/mobile/accounts.go @@ -120,7 +120,7 @@ func (ks *KeyStore) SignTx(account *Account, tx *Transaction, chainID *BigInt) ( if chainID == nil { // Null passed from mobile app chainID = new(BigInt) } - signed, err := ks.keystore.SignTx(account.account, tx.tx, chainID.bigint, false) + signed, err := ks.keystore.SignTx(account.account, tx.tx, chainID.bigint) if err != nil { return nil, err }