From e6cfbe582454d5a4934ec37b721d8554275ec4f7 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 2 Jul 2015 10:38:02 +0200 Subject: [PATCH] ethkey can sign & recover transaction information. AZ can have sentinel set. Split up Transaction and move most to ethcore. --- alethzero/Main.ui | 22 +++-- alethzero/MainWin.cpp | 8 ++ alethzero/MainWin.h | 1 + ethkey/KeyAux.h | 171 +++++++++++++++++++++++++++++++--- libethcore/CMakeLists.txt | 2 +- libethcore/Transaction.cpp | 118 ++++++++++++++++++++++++ libethcore/Transaction.h | 177 ++++++++++++++++++++++++++++++++++++ libethereum/Client.cpp | 3 +- libethereum/Client.h | 2 + libethereum/Transaction.cpp | 87 +----------------- libethereum/Transaction.h | 126 +------------------------ 11 files changed, 484 insertions(+), 233 deletions(-) create mode 100644 libethcore/Transaction.cpp create mode 100644 libethcore/Transaction.h diff --git a/alethzero/Main.ui b/alethzero/Main.ui index f798437e6..e12756d6e 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -44,14 +44,14 @@ 0 bytes used - - - - - - - - + + + + + + + + @@ -224,6 +224,7 @@ &Config + @@ -1782,6 +1783,11 @@ font-size: 14pt &Gas Prices... + + + &Sentinel... + + diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 821022abc..445b5fdac 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -296,6 +296,14 @@ void Main::on_gasPrices_triggered() } } +void Main::on_sentinel_triggered() +{ + bool ok; + QString sentinel = QInputDialog::getText(nullptr, "Enter sentinel address", "Enter the sentinel address for bad block reporting (e.g. http://badblockserver.com:8080). Enter nothing to disable.", QLineEdit::Normal, QString::fromStdString(ethereum()->sentinel()), &ok); + if (ok) + ethereum()->setSentinel(sentinel.toStdString()); +} + void Main::on_newIdentity_triggered() { KeyPair kp = KeyPair::create(); diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index f8a6fa6c7..a3f6328cd 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -196,6 +196,7 @@ private slots: // Config void on_gasPrices_triggered(); + void on_sentinel_triggered(); void refreshWhisper(); void refreshBlockChain(); diff --git a/ethkey/KeyAux.h b/ethkey/KeyAux.h index ae8eaed92..af7d8e048 100644 --- a/ethkey/KeyAux.h +++ b/ethkey/KeyAux.h @@ -32,6 +32,7 @@ #include #include #include +#include #include "BuildInfo.h" using namespace std; using namespace dev; @@ -105,7 +106,9 @@ public: ImportWithAddress, Export, Recode, - Kill + Kill, + SignTx, + DecodeTx, }; KeyCLI(OperationMode _mode = OperationMode::None): m_mode(_mode) {} @@ -131,8 +134,13 @@ public: auto v = argv[++i]; m_kdfParams[n] = v; } - else if (arg == "--new-bare") - m_mode = OperationMode::NewBare; + else if (arg == "--sign-tx" && i + 1 < argc) + { + m_mode = OperationMode::SignTx; + m_signKey = argv[++i]; + } + else if (arg == "--decode-tx") + m_mode = OperationMode::DecodeTx; else if (arg == "--import-bare") m_mode = OperationMode::ImportBare; else if (arg == "--list-bare") @@ -173,7 +181,7 @@ public: m_mode = OperationMode::Recode; else if (arg == "--no-icap") m_icap = false; - else if (m_mode == OperationMode::ImportBare || m_mode == OperationMode::InspectBare || m_mode == OperationMode::KillBare || m_mode == OperationMode::Recode || m_mode == OperationMode::Export || m_mode == OperationMode::RecodeBare || m_mode == OperationMode::ExportBare) + else if (m_mode == OperationMode::DecodeTx || m_mode == OperationMode::SignTx || m_mode == OperationMode::ImportBare || m_mode == OperationMode::InspectBare || m_mode == OperationMode::KillBare || m_mode == OperationMode::Recode || m_mode == OperationMode::Export || m_mode == OperationMode::RecodeBare || m_mode == OperationMode::ExportBare) m_inputs.push_back(arg); else return false; @@ -209,6 +217,127 @@ public: } } } + else if (m_mode == OperationMode::DecodeTx) + { + string const& i = m_inputs[0]; + bytes b = fromHex(i); + if (b.empty()) + { + std::string s = contentsString(i); + b = fromHex(s); + if (b.empty()) + b = asBytes(s); + } + if (b.empty()) + cerr << "Unknown file or bad hex: " << i << endl; + else + try + { + TransactionBase t(b, CheckTransaction::Everything); + cout << "Transaction " << t.sha3().hex() << endl; + if (t.isCreation()) + { + cout << " type: creation" << endl; + cout << " code: " << toHex(t.data()) << endl; + } + else + { + cout << " type: message" << endl; + cout << " to: " << t.to().hex() << endl; + cout << " data: " << (t.data().empty() ? "none" : toHex(t.data())) << endl; + } + cout << " from: " << t.from().hex() << endl; + cout << " value: " << formatBalance(t.value()) << " (" << t.value() << " wei)" << endl; + cout << " nonce: " << t.nonce() << endl; + cout << " gas: " << t.gas() << endl; + cout << " gas price: " << formatBalance(t.gasPrice()) << " (" << t.gasPrice() << " wei)" << endl; + cout << " signing hash: " << t.sha3(WithoutSignature).hex() << endl; + cout << " v: " << (int)t.signature().v << endl; + cout << " r: " << t.signature().r << endl; + cout << " s: " << t.signature().s << endl; + } + catch (Exception& ex) + { + cerr << "Invalid transaction: " << ex.what() << endl; + } + } + else if (m_mode == OperationMode::SignTx) + { + Secret s; + + string json = contentsString(m_signKey); + if (!json.empty()) + { + SecretStore store(m_secretsPath); + s = Secret(store.secret(store.readKeyContent(json), [&](){ return getPassword("Enter password for key: "); })); + } + else + { + if (h128 u = fromUUID(m_signKey)) + { + SecretStore store(m_secretsPath); + s = Secret(store.secret(u, [&](){ return getPassword("Enter password for key: "); })); + } + else if (Address a = Address(m_signKey)) + { + KeyManager wallet(m_walletPath, m_secretsPath); + if (wallet.exists()) + { + openWallet(wallet); + s = wallet.secret(a, [&](){ return getPassword("Enter password for key: "); }); + } + else + { + cerr << "Wallet doesn't exist." << endl; + exit(-1); + } + } + else + { + cerr << "Bad file, UUID and address: " << m_signKey << endl; + exit(-1); + } + } + if (!s) + { + cerr << "UUID/address not found: " << m_signKey << endl; + exit(-1); + } + + for (string const& i: m_inputs) + { + bytes b = fromHex(i); + bool isFile = false; + if (b.empty()) + { + isFile = true; + std::string s = contentsString(i); + b = fromHex(s); + if (b.empty()) + b = asBytes(s); + } + if (b.empty()) + cerr << "Unknown file or bad hex: " << i << endl; + else + try + { + TransactionBase t(b, CheckTransaction::None); + t.sign(s); + cout << t.sha3() << ": "; + if (isFile) + { + writeFile(i + ".signed", t.data()); + cout << i + ".signed" << endl; + } + else + cout << toHex(t.data()) << endl; + } + catch (Exception& ex) + { + cerr << "Invalid transaction: " << ex.what() << endl; + } + } + } else if (m_mode < OperationMode::CreateWallet) { SecretStore store(m_secretsPath); @@ -297,17 +426,7 @@ public: { KeyManager wallet(m_walletPath, m_secretsPath); if (wallet.exists()) - while (true) - { - if (wallet.load(m_masterPassword)) - break; - if (!m_masterPassword.empty()) - { - cout << "Password invalid. Try again." << endl; - m_masterPassword.clear(); - } - m_masterPassword = getPassword("Please enter your MASTER password: "); - } + openWallet(wallet); else { cerr << "Couldn't open wallet. Does it exist?" << endl; @@ -419,6 +538,10 @@ public: << " --wallet-path Specify Ethereum wallet path (default: " << KeyManager::defaultPath() << ")" << endl << " -m, --master Specify wallet (master) password." << endl << endl + << "Transaction operating modes:" << endl + << " -d,--decode-tx [|] Decode given transaction." << endl + << " -s,--sign-tx [
|| ] [ | , ... ] (Re-)Sign given transaction." << endl + << endl << "Encryption configuration:" << endl << " --kdf Specify KDF to use when encrypting (default: sc rypt)" << endl << " --kdf-param Specify a parameter for the KDF." << endl @@ -445,6 +568,21 @@ public: } private: + void openWallet(KeyManager& _w) + { + while (true) + { + if (_w.load(m_masterPassword)) + break; + if (!m_masterPassword.empty()) + { + cout << "Password invalid. Try again." << endl; + m_masterPassword.clear(); + } + m_masterPassword = getPassword("Please enter your MASTER password: "); + } + } + KDF kdf() const { return m_kdf == "pbkdf2" ? KDF::PBKDF2_SHA256 : KDF::Scrypt; } /// Operating mode. @@ -468,6 +606,9 @@ private: /// Importing strings m_inputs; + /// Signing + string m_signKey; + string m_kdf = "scrypt"; map m_kdfParams; // string m_cipher; diff --git a/libethcore/CMakeLists.txt b/libethcore/CMakeLists.txt index a527ad1f4..4dd626642 100644 --- a/libethcore/CMakeLists.txt +++ b/libethcore/CMakeLists.txt @@ -28,7 +28,7 @@ add_library(${EXECUTABLE} ${SRC_LIST} ${HEADERS}) target_link_libraries(${EXECUTABLE} ethash) target_link_libraries(${EXECUTABLE} devcrypto) -#target_link_libraries(${EXECUTABLE} evmcore) +target_link_libraries(${EXECUTABLE} evmcore) if (ETHASHCL) target_link_libraries(${EXECUTABLE} ethash-cl) diff --git a/libethcore/Transaction.cpp b/libethcore/Transaction.cpp new file mode 100644 index 000000000..5e08acd4d --- /dev/null +++ b/libethcore/Transaction.cpp @@ -0,0 +1,118 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file TransactionBase.cpp + * @author Gav Wood + * @date 2014 + */ + +#include +#include +#include +#include +#include +#include "Transaction.h" +using namespace std; +using namespace dev; +using namespace dev::eth; + +TransactionBase::TransactionBase(bytesConstRef _rlpData, CheckTransaction _checkSig) +{ + int field = 0; + RLP rlp(_rlpData); + try + { + if (!rlp.isList()) + BOOST_THROW_EXCEPTION(InvalidTransactionFormat() << errinfo_comment("transaction RLP must be a list")); + + m_nonce = rlp[field = 0].toInt(); + m_gasPrice = rlp[field = 1].toInt(); + m_gas = rlp[field = 2].toInt(); + m_type = rlp[field = 3].isEmpty() ? ContractCreation : MessageCall; + m_receiveAddress = rlp[field = 3].isEmpty() ? Address() : rlp[field = 3].toHash
(RLP::VeryStrict); + m_value = rlp[field = 4].toInt(); + + if (!rlp[field = 5].isData()) + BOOST_THROW_EXCEPTION(InvalidTransactionFormat() << errinfo_comment("transaction data RLP must be an array")); + + m_data = rlp[field = 5].toBytes(); + byte v = rlp[field = 6].toInt() - 27; + h256 r = rlp[field = 7].toInt(); + h256 s = rlp[field = 8].toInt(); + + if (rlp.itemCount() > 9) + BOOST_THROW_EXCEPTION(InvalidTransactionFormat() << errinfo_comment("to many fields in the transaction RLP")); + + m_vrs = SignatureStruct{ r, s, v }; + if (_checkSig >= CheckTransaction::Cheap && !m_vrs.isValid()) + BOOST_THROW_EXCEPTION(InvalidSignature()); + if (_checkSig == CheckTransaction::Everything) + m_sender = sender(); + } + catch (Exception& _e) + { + _e << errinfo_name("invalid transaction format") << BadFieldError(field, toHex(rlp[field].data().toBytes())); + throw; + } +} + +Address const& TransactionBase::safeSender() const noexcept +{ + try + { + return sender(); + } + catch (...) + { + cwarn << "safeSender() did throw an exception: " << boost::current_exception_diagnostic_information(); + return ZeroAddress; + } +} + +Address const& TransactionBase::sender() const +{ + if (!m_sender) + { + auto p = recover(m_vrs, sha3(WithoutSignature)); + if (!p) + BOOST_THROW_EXCEPTION(InvalidSignature()); + m_sender = right160(dev::sha3(bytesConstRef(p.data(), sizeof(p)))); + } + return m_sender; +} + +void TransactionBase::sign(Secret const& _priv) +{ + auto sig = dev::sign(_priv, sha3(WithoutSignature)); + SignatureStruct sigStruct = *(SignatureStruct const*)&sig; + if (sigStruct.isValid()) + m_vrs = sigStruct; +} + +void TransactionBase::streamRLP(RLPStream& _s, IncludeSignature _sig) const +{ + if (m_type == NullTransaction) + return; + _s.appendList((_sig ? 3 : 0) + 6); + _s << m_nonce << m_gasPrice << m_gas; + if (m_type == MessageCall) + _s << m_receiveAddress; + else + _s << ""; + _s << m_value << m_data; + if (_sig) + _s << (m_vrs.v + 27) << (u256)m_vrs.r << (u256)m_vrs.s; +} diff --git a/libethcore/Transaction.h b/libethcore/Transaction.h new file mode 100644 index 000000000..e5eeb74b4 --- /dev/null +++ b/libethcore/Transaction.h @@ -0,0 +1,177 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file TransactionBase.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include + +namespace dev +{ +namespace eth +{ + +/// Named-boolean type to encode whether a signature be included in the serialisation process. +enum IncludeSignature +{ + WithoutSignature = 0, ///< Do not include a signature. + WithSignature = 1, ///< Do include a signature. +}; + +enum class CheckTransaction +{ + None, + Cheap, + Everything +}; + +/// Encodes a transaction, ready to be exported to or freshly imported from RLP. +class TransactionBase +{ +public: + /// Constructs a null transaction. + TransactionBase() {} + + /// Constructs a signed message-call transaction. + TransactionBase(u256 const& _value, u256 const& _gasPrice, u256 const& _gas, Address const& _dest, bytes const& _data, u256 const& _nonce, Secret const& _secret): m_type(MessageCall), m_nonce(_nonce), m_value(_value), m_receiveAddress(_dest), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) { sign(_secret); } + + /// Constructs a signed contract-creation transaction. + TransactionBase(u256 const& _value, u256 const& _gasPrice, u256 const& _gas, bytes const& _data, u256 const& _nonce, Secret const& _secret): m_type(ContractCreation), m_nonce(_nonce), m_value(_value), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) { sign(_secret); } + + /// Constructs an unsigned message-call transaction. + TransactionBase(u256 const& _value, u256 const& _gasPrice, u256 const& _gas, Address const& _dest, bytes const& _data, u256 const& _nonce = 0): m_type(MessageCall), m_nonce(_nonce), m_value(_value), m_receiveAddress(_dest), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) {} + + /// Constructs an unsigned contract-creation transaction. + TransactionBase(u256 const& _value, u256 const& _gasPrice, u256 const& _gas, bytes const& _data, u256 const& _nonce = 0): m_type(ContractCreation), m_nonce(_nonce), m_value(_value), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) {} + + /// Constructs a transaction from the given RLP. + explicit TransactionBase(bytesConstRef _rlp, CheckTransaction _checkSig); + + /// Constructs a transaction from the given RLP. + explicit TransactionBase(bytes const& _rlp, CheckTransaction _checkSig): TransactionBase(&_rlp, _checkSig) {} + + + /// Checks equality of transactions. + bool operator==(TransactionBase const& _c) const { return m_type == _c.m_type && (m_type == ContractCreation || m_receiveAddress == _c.m_receiveAddress) && m_value == _c.m_value && m_data == _c.m_data; } + /// Checks inequality of transactions. + bool operator!=(TransactionBase const& _c) const { return !operator==(_c); } + + /// @returns sender of the transaction from the signature (and hash). + Address const& sender() const; + /// Like sender() but will never throw. @returns a null Address if the signature is invalid. + Address const& safeSender() const noexcept; + /// Force the sender to a particular value. This will result in an invalid transaction RLP. + void forceSender(Address const& _a) { m_sender = _a; } + + /// @returns true if transaction is non-null. + explicit operator bool() const { return m_type != NullTransaction; } + + /// @returns true if transaction is contract-creation. + bool isCreation() const { return m_type == ContractCreation; } + + /// @returns true if transaction is message-call. + bool isMessageCall() const { return m_type == MessageCall; } + + /// Serialises this transaction to an RLPStream. + void streamRLP(RLPStream& _s, IncludeSignature _sig = WithSignature) const; + + /// @returns the RLP serialisation of this transaction. + bytes rlp(IncludeSignature _sig = WithSignature) const { RLPStream s; streamRLP(s, _sig); return s.out(); } + + /// @returns the SHA3 hash of the RLP serialisation of this transaction. + h256 sha3(IncludeSignature _sig = WithSignature) const { if (_sig == WithSignature && m_hashWith) return m_hashWith; RLPStream s; streamRLP(s, _sig); auto ret = dev::sha3(s.out()); if (_sig == WithSignature) m_hashWith = ret; return ret; } + + /// @returns the amount of ETH to be transferred by this (message-call) transaction, in Wei. Synonym for endowment(). + u256 value() const { return m_value; } + /// @returns the amount of ETH to be endowed by this (contract-creation) transaction, in Wei. Synonym for value(). + u256 endowment() const { return m_value; } + + /// @returns the base fee and thus the implied exchange rate of ETH to GAS. + u256 gasPrice() const { return m_gasPrice; } + + /// @returns the total gas to convert, paid for from sender's account. Any unused gas gets refunded once the contract is ended. + u256 gas() const { return m_gas; } + + /// @returns the receiving address of the message-call transaction (undefined for contract-creation transactions). + Address receiveAddress() const { return m_receiveAddress; } + + /// Synonym for receiveAddress(). + Address to() const { return m_receiveAddress; } + + /// Synonym for safeSender(). + Address from() const { return safeSender(); } + + /// @returns the data associated with this (message-call) transaction. Synonym for initCode(). + bytes const& data() const { return m_data; } + /// @returns the initialisation code associated with this (contract-creation) transaction. Synonym for data(). + bytes const& initCode() const { return m_data; } + + /// @returns the transaction-count of the sender. + u256 nonce() const { return m_nonce; } + + /// @returns the signature of the transaction. Encodes the sender. + SignatureStruct const& signature() const { return m_vrs; } + + void sign(Secret const& _priv); ///< Sign the transaction. + +protected: + /// Type of transaction. + enum Type + { + NullTransaction, ///< Null transaction. + ContractCreation, ///< Transaction to create contracts - receiveAddress() is ignored. + MessageCall ///< Transaction to invoke a message call - receiveAddress() is used. + }; + + Type m_type = NullTransaction; ///< Is this a contract-creation transaction or a message-call transaction? + u256 m_nonce; ///< The transaction-count of the sender. + u256 m_value; ///< The amount of ETH to be transferred by this transaction. Called 'endowment' for contract-creation transactions. + Address m_receiveAddress; ///< The receiving address of the transaction. + u256 m_gasPrice; ///< The base fee and thus the implied exchange rate of ETH to GAS. + u256 m_gas; ///< The total gas to convert, paid for from sender's account. Any unused gas gets refunded once the contract is ended. + bytes m_data; ///< The data associated with the transaction, or the initialiser if it's a creation transaction. + SignatureStruct m_vrs; ///< The signature of the transaction. Encodes the sender. + + mutable h256 m_hashWith; ///< Cached hash of transaction with signature. + mutable Address m_sender; ///< Cached sender, determined from signature. + mutable bigint m_gasRequired = 0; ///< Memoised amount required for the transaction to run. +}; + +/// Nice name for vector of Transaction. +using TransactionBases = std::vector; + +/// Simple human-readable stream-shift operator. +inline std::ostream& operator<<(std::ostream& _out, TransactionBase const& _t) +{ + _out << _t.sha3().abridged() << "{"; + if (_t.receiveAddress()) + _out << _t.receiveAddress().abridged(); + else + _out << "[CREATE]"; + + _out << "/" << _t.data().size() << "$" << _t.value() << "+" << _t.gas() << "@" << _t.gasPrice(); + _out << "<-" << _t.safeSender().abridged() << " #" << _t.nonce() << "}"; + return _out; +} + +} +} diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index c7d7c60c4..1e2cbf940 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -339,7 +339,8 @@ Client::~Client() stopWorking(); } -static const Address c_canary("0x"); +static const Address c_canary("0x73c054b5865c427064641725a51032458d1d59b5"); +static const Addresses c_canaries = {}; bool Client::isChainBad() const { diff --git a/libethereum/Client.h b/libethereum/Client.h index f38c7c099..98ea253ce 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -240,6 +240,8 @@ public: ActivityReport activityReport() { ActivityReport ret; std::swap(m_report, ret); return ret; } /// Set a JSONRPC server to which we can report bad blocks. void setSentinel(std::string const& _server) { m_sentinel = _server; } + /// Get the JSONRPC server to which we report bad blocks. + std::string const& sentinel() const { return m_sentinel; } protected: /// InterfaceStub methods diff --git a/libethereum/Transaction.cpp b/libethereum/Transaction.cpp index 40a7914d3..70f82c6d2 100644 --- a/libethereum/Transaction.cpp +++ b/libethereum/Transaction.cpp @@ -94,99 +94,16 @@ std::ostream& dev::eth::operator<<(std::ostream& _out, TransactionException cons return _out; } -Transaction::Transaction(bytesConstRef _rlpData, CheckTransaction _checkSig) +Transaction::Transaction(bytesConstRef _rlpData, CheckTransaction _checkSig): + TransactionBase(_rlpData, _checkSig) { - int field = 0; - RLP rlp(_rlpData); - try - { - if (!rlp.isList()) - BOOST_THROW_EXCEPTION(InvalidTransactionFormat() << errinfo_comment("transaction RLP must be a list")); - - m_nonce = rlp[field = 0].toInt(); - m_gasPrice = rlp[field = 1].toInt(); - m_gas = rlp[field = 2].toInt(); - m_type = rlp[field = 3].isEmpty() ? ContractCreation : MessageCall; - m_receiveAddress = rlp[field = 3].isEmpty() ? Address() : rlp[field = 3].toHash
(RLP::VeryStrict); - m_value = rlp[field = 4].toInt(); - - if (!rlp[field = 5].isData()) - BOOST_THROW_EXCEPTION(InvalidTransactionFormat() << errinfo_comment("transaction data RLP must be an array")); - - m_data = rlp[field = 5].toBytes(); - byte v = rlp[field = 6].toInt() - 27; - h256 r = rlp[field = 7].toInt(); - h256 s = rlp[field = 8].toInt(); - - if (rlp.itemCount() > 9) - BOOST_THROW_EXCEPTION(InvalidTransactionFormat() << errinfo_comment("to many fields in the transaction RLP")); - - m_vrs = SignatureStruct{ r, s, v }; - if (_checkSig >= CheckTransaction::Cheap && !m_vrs.isValid()) - BOOST_THROW_EXCEPTION(InvalidSignature()); - if (_checkSig == CheckTransaction::Everything) - m_sender = sender(); - } - catch (Exception& _e) - { - _e << errinfo_name("invalid transaction format") << BadFieldError(field, toHex(rlp[field].data().toBytes())); - throw; - } if (_checkSig >= CheckTransaction::Cheap && !checkPayment()) BOOST_THROW_EXCEPTION(OutOfGasIntrinsic() << RequirementError(gasRequired(), (bigint)gas())); } -Address const& Transaction::safeSender() const noexcept -{ - try - { - return sender(); - } - catch (...) - { - cwarn << "safeSender() did throw an exception: " << boost::current_exception_diagnostic_information(); - return ZeroAddress; - } -} - -Address const& Transaction::sender() const -{ - if (!m_sender) - { - auto p = recover(m_vrs, sha3(WithoutSignature)); - if (!p) - BOOST_THROW_EXCEPTION(InvalidSignature()); - m_sender = right160(dev::sha3(bytesConstRef(p.data(), sizeof(p)))); - } - return m_sender; -} - bigint Transaction::gasRequired() const { if (!m_gasRequired) m_gasRequired = Transaction::gasRequired(m_data); return m_gasRequired; } - -void Transaction::sign(Secret _priv) -{ - auto sig = dev::sign(_priv, sha3(WithoutSignature)); - SignatureStruct sigStruct = *(SignatureStruct const*)&sig; - if (sigStruct.isValid()) - m_vrs = sigStruct; -} - -void Transaction::streamRLP(RLPStream& _s, IncludeSignature _sig) const -{ - if (m_type == NullTransaction) - return; - _s.appendList((_sig ? 3 : 0) + 6); - _s << m_nonce << m_gasPrice << m_gas; - if (m_type == MessageCall) - _s << m_receiveAddress; - else - _s << ""; - _s << m_value << m_data; - if (_sig) - _s << (m_vrs.v + 27) << (u256)m_vrs.r << (u256)m_vrs.s; -} diff --git a/libethereum/Transaction.h b/libethereum/Transaction.h index 4de9d7e92..95e1619fe 100644 --- a/libethereum/Transaction.h +++ b/libethereum/Transaction.h @@ -24,6 +24,7 @@ #include #include #include +#include #include namespace dev @@ -31,20 +32,6 @@ namespace dev namespace eth { -/// Named-boolean type to encode whether a signature be included in the serialisation process. -enum IncludeSignature -{ - WithoutSignature = 0, ///< Do not include a signature. - WithSignature = 1, ///< Do include a signature. -}; - -enum class CheckTransaction -{ - None, - Cheap, - Everything -}; - enum class TransactionException { None = 0, @@ -92,23 +79,13 @@ struct ExecutionResult std::ostream& operator<<(std::ostream& _out, ExecutionResult const& _er); /// Encodes a transaction, ready to be exported to or freshly imported from RLP. -class Transaction +class Transaction: public TransactionBase { public: /// Constructs a null transaction. Transaction() {} - /// Constructs a signed message-call transaction. - Transaction(u256 const& _value, u256 const& _gasPrice, u256 const& _gas, Address const& _dest, bytes const& _data, u256 const& _nonce, Secret const& _secret): m_type(MessageCall), m_nonce(_nonce), m_value(_value), m_receiveAddress(_dest), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) { sign(_secret); } - - /// Constructs a signed contract-creation transaction. - Transaction(u256 const& _value, u256 const& _gasPrice, u256 const& _gas, bytes const& _data, u256 const& _nonce, Secret const& _secret): m_type(ContractCreation), m_nonce(_nonce), m_value(_value), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) { sign(_secret); } - - /// Constructs an unsigned message-call transaction. - Transaction(u256 const& _value, u256 const& _gasPrice, u256 const& _gas, Address const& _dest, bytes const& _data, u256 const& _nonce = 0): m_type(MessageCall), m_nonce(_nonce), m_value(_value), m_receiveAddress(_dest), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) {} - - /// Constructs an unsigned contract-creation transaction. - Transaction(u256 const& _value, u256 const& _gasPrice, u256 const& _gas, bytes const& _data, u256 const& _nonce = 0): m_type(ContractCreation), m_nonce(_nonce), m_value(_value), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) {} + using TransactionBase::TransactionBase; /// Constructs a transaction from the given RLP. explicit Transaction(bytesConstRef _rlp, CheckTransaction _checkSig); @@ -116,68 +93,6 @@ public: /// Constructs a transaction from the given RLP. explicit Transaction(bytes const& _rlp, CheckTransaction _checkSig): Transaction(&_rlp, _checkSig) {} - - /// Checks equality of transactions. - bool operator==(Transaction const& _c) const { return m_type == _c.m_type && (m_type == ContractCreation || m_receiveAddress == _c.m_receiveAddress) && m_value == _c.m_value && m_data == _c.m_data; } - /// Checks inequality of transactions. - bool operator!=(Transaction const& _c) const { return !operator==(_c); } - - /// @returns sender of the transaction from the signature (and hash). - Address const& sender() const; - /// Like sender() but will never throw. @returns a null Address if the signature is invalid. - Address const& safeSender() const noexcept; - /// Force the sender to a particular value. This will result in an invalid transaction RLP. - void forceSender(Address const& _a) { m_sender = _a; } - - /// @returns true if transaction is non-null. - explicit operator bool() const { return m_type != NullTransaction; } - - /// @returns true if transaction is contract-creation. - bool isCreation() const { return m_type == ContractCreation; } - - /// @returns true if transaction is message-call. - bool isMessageCall() const { return m_type == MessageCall; } - - /// Serialises this transaction to an RLPStream. - void streamRLP(RLPStream& _s, IncludeSignature _sig = WithSignature) const; - - /// @returns the RLP serialisation of this transaction. - bytes rlp(IncludeSignature _sig = WithSignature) const { RLPStream s; streamRLP(s, _sig); return s.out(); } - - /// @returns the SHA3 hash of the RLP serialisation of this transaction. - h256 sha3(IncludeSignature _sig = WithSignature) const { if (_sig == WithSignature && m_hashWith) return m_hashWith; RLPStream s; streamRLP(s, _sig); auto ret = dev::sha3(s.out()); if (_sig == WithSignature) m_hashWith = ret; return ret; } - - /// @returns the amount of ETH to be transferred by this (message-call) transaction, in Wei. Synonym for endowment(). - u256 value() const { return m_value; } - /// @returns the amount of ETH to be endowed by this (contract-creation) transaction, in Wei. Synonym for value(). - u256 endowment() const { return m_value; } - - /// @returns the base fee and thus the implied exchange rate of ETH to GAS. - u256 gasPrice() const { return m_gasPrice; } - - /// @returns the total gas to convert, paid for from sender's account. Any unused gas gets refunded once the contract is ended. - u256 gas() const { return m_gas; } - - /// @returns the receiving address of the message-call transaction (undefined for contract-creation transactions). - Address receiveAddress() const { return m_receiveAddress; } - - /// Synonym for receiveAddress(). - Address to() const { return m_receiveAddress; } - - /// Synonym for safeSender(). - Address from() const { return safeSender(); } - - /// @returns the data associated with this (message-call) transaction. Synonym for initCode(). - bytes const& data() const { return m_data; } - /// @returns the initialisation code associated with this (contract-creation) transaction. Synonym for data(). - bytes const& initCode() const { return m_data; } - - /// @returns the transaction-count of the sender. - u256 nonce() const { return m_nonce; } - - /// @returns the signature of the transaction. Encodes the sender. - SignatureStruct const& signature() const { return m_vrs; } - /// @returns true if the transaction contains enough gas for the basic payment. bool checkPayment() const { return m_gas >= gasRequired(); } @@ -188,46 +103,11 @@ public: template static bigint gasRequired(T const& _data, u256 _gas = 0) { bigint ret = c_txGas + _gas; for (auto i: _data) ret += i ? c_txDataNonZeroGas : c_txDataZeroGas; return ret; } private: - /// Type of transaction. - enum Type - { - NullTransaction, ///< Null transaction. - ContractCreation, ///< Transaction to create contracts - receiveAddress() is ignored. - MessageCall ///< Transaction to invoke a message call - receiveAddress() is used. - }; - - void sign(Secret _priv); ///< Sign the transaction. - - Type m_type = NullTransaction; ///< Is this a contract-creation transaction or a message-call transaction? - u256 m_nonce; ///< The transaction-count of the sender. - u256 m_value; ///< The amount of ETH to be transferred by this transaction. Called 'endowment' for contract-creation transactions. - Address m_receiveAddress; ///< The receiving address of the transaction. - u256 m_gasPrice; ///< The base fee and thus the implied exchange rate of ETH to GAS. - u256 m_gas; ///< The total gas to convert, paid for from sender's account. Any unused gas gets refunded once the contract is ended. - bytes m_data; ///< The data associated with the transaction, or the initialiser if it's a creation transaction. - SignatureStruct m_vrs; ///< The signature of the transaction. Encodes the sender. - - mutable h256 m_hashWith; ///< Cached hash of transaction with signature. - mutable Address m_sender; ///< Cached sender, determined from signature. mutable bigint m_gasRequired = 0; ///< Memoised amount required for the transaction to run. }; /// Nice name for vector of Transaction. using Transactions = std::vector; -/// Simple human-readable stream-shift operator. -inline std::ostream& operator<<(std::ostream& _out, Transaction const& _t) -{ - _out << _t.sha3().abridged() << "{"; - if (_t.receiveAddress()) - _out << _t.receiveAddress().abridged(); - else - _out << "[CREATE]"; - - _out << "/" << _t.data().size() << "$" << _t.value() << "+" << _t.gas() << "@" << _t.gasPrice(); - _out << "<-" << _t.safeSender().abridged() << " #" << _t.nonce() << "}"; - return _out; -} - } }