Merge bitcoin/bitcoin#30482: rest: Reject truncated hex txid early in getutxos parsing

fac0c3d4bf doc: Add release notes for two pull requests (MarcoFalke)
fa7b57e5f5 refactor: Replace ParseHashStr with FromHex (MarcoFalke)
fa90777245 rest: Reject truncated hex txid early in getutxos parsing (MarcoFalke)
fab6ddbee6 refactor: Expose FromHex in transaction_identifier (MarcoFalke)
fad2991ba0 refactor: Implement strict uint256::FromHex() (MarcoFalke)
fa103db2bb scripted-diff: Rename SetHex to SetHexDeprecated (MarcoFalke)
fafe4b8051 test: refactor: Replace SetHex with uint256 constructor directly (MarcoFalke)

Pull request description:

  In `rest_getutxos` truncated txids such as `aa` or `ff` are accepted. This is brittle at best.

  Fix it by rejecting any truncated (or overlarge) input.

  ----

  Review note: This also starts a major refactor to rework hex parsing in Bitcoin Core, meaning that a few refactor commits are included as well. They are explained individually in the commit message and the work will be continued in the future.

ACKs for top commit:
  stickies-v:
    re-ACK fac0c3d4bf - only doc and test updates to address review comments, thanks!
  hodlinator:
    ACK fac0c3d4bf

Tree-SHA512: 473feb3fcf6118443435d1dd321006135b0b54689bfbbcb1697bb5811a449bef51f475c715de6911ff3c4ea3bdb75f601861ff93347bc4414d6b9e5298105dd7
This commit is contained in:
merge-script 2024-07-25 13:49:21 +01:00
commit 30e8a79aef
No known key found for this signature in database
GPG key ID: 2EEB9F5CC09526C1
16 changed files with 98 additions and 82 deletions

View file

@ -0,0 +1,6 @@
Updated REST APIs
-----------------
- Parameter validation for `/rest/getutxos` has been improved by rejecting
truncated or overly large txids and malformed outpoint indices by raising an
HTTP_BAD_REQUEST "Parse error". Previously, these malformed requests would be
silently handled. (#30482, #30444)

View file

@ -264,8 +264,8 @@ static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInpu
throw std::runtime_error("TX input missing separator"); throw std::runtime_error("TX input missing separator");
// extract and validate TXID // extract and validate TXID
uint256 txid; auto txid{Txid::FromHex(vStrInputParts[0])};
if (!ParseHashStr(vStrInputParts[0], txid)) { if (!txid) {
throw std::runtime_error("invalid TX input txid"); throw std::runtime_error("invalid TX input txid");
} }
@ -285,7 +285,7 @@ static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInpu
} }
// append to transaction input list // append to transaction input list
CTxIn txin(Txid::FromUint256(txid), vout, CScript(), nSequenceIn); CTxIn txin(*txid, vout, CScript(), nSequenceIn);
tx.vin.push_back(txin); tx.vin.push_back(txin);
} }
@ -625,8 +625,8 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
if (!prevOut.checkObject(types)) if (!prevOut.checkObject(types))
throw std::runtime_error("prevtxs internal object typecheck fail"); throw std::runtime_error("prevtxs internal object typecheck fail");
uint256 txid; auto txid{Txid::FromHex(prevOut["txid"].get_str())};
if (!ParseHashStr(prevOut["txid"].get_str(), txid)) { if (!txid) {
throw std::runtime_error("txid must be hexadecimal string (not '" + prevOut["txid"].get_str() + "')"); throw std::runtime_error("txid must be hexadecimal string (not '" + prevOut["txid"].get_str() + "')");
} }
@ -634,7 +634,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
if (nOut < 0) if (nOut < 0)
throw std::runtime_error("vout cannot be negative"); throw std::runtime_error("vout cannot be negative");
COutPoint out(Txid::FromUint256(txid), nOut); COutPoint out(*txid, nOut);
std::vector<unsigned char> pkData(ParseHexUV(prevOut["scriptPubKey"], "scriptPubKey")); std::vector<unsigned char> pkData(ParseHexUV(prevOut["scriptPubKey"], "scriptPubKey"));
CScript scriptPubKey(pkData.begin(), pkData.end()); CScript scriptPubKey(pkData.begin(), pkData.end());

View file

@ -37,15 +37,6 @@ std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDeco
[[nodiscard]] bool DecodeHexBlk(CBlock&, const std::string& strHexBlk); [[nodiscard]] bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header); bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header);
/**
* Parse a hex string into 256 bits
* @param[in] strHex a hex-formatted, 64-character string
* @param[out] result the result of the parsing
* @returns true if successful, false if not
*
* @see ParseHashV for an RPC-oriented version of this
*/
bool ParseHashStr(const std::string& strHex, uint256& result);
[[nodiscard]] util::Result<int> SighashFromStr(const std::string& sighash); [[nodiscard]] util::Result<int> SighashFromStr(const std::string& sighash);
// core_write.cpp // core_write.cpp

View file

@ -234,15 +234,6 @@ bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk)
return true; return true;
} }
bool ParseHashStr(const std::string& strHex, uint256& result)
{
if ((strHex.size() != 64) || !IsHex(strHex))
return false;
result.SetHex(strHex);
return true;
}
util::Result<int> SighashFromStr(const std::string& sighash) util::Result<int> SighashFromStr(const std::string& sighash)
{ {
static const std::map<std::string, int> map_sighash_values = { static const std::map<std::string, int> map_sighash_values = {

View file

@ -277,7 +277,7 @@ void TransactionTableModel::updateAmountColumnTitle()
void TransactionTableModel::updateTransaction(const QString &hash, int status, bool showTransaction) void TransactionTableModel::updateTransaction(const QString &hash, int status, bool showTransaction)
{ {
uint256 updated; uint256 updated;
updated.SetHex(hash.toStdString()); updated.SetHexDeprecated(hash.toStdString());
priv->updateWallet(walletModel->wallet(), updated, status, showTransaction); priv->updateWallet(walletModel->wallet(), updated, status, showTransaction);
} }

View file

@ -396,7 +396,7 @@ void TransactionView::contextualMenu(const QPoint &point)
// check if transaction can be abandoned, disable context menu action in case it doesn't // check if transaction can be abandoned, disable context menu action in case it doesn't
uint256 hash; uint256 hash;
hash.SetHex(selection.at(0).data(TransactionTableModel::TxHashRole).toString().toStdString()); hash.SetHexDeprecated(selection.at(0).data(TransactionTableModel::TxHashRole).toString().toStdString());
abandonAction->setEnabled(model->wallet().transactionCanBeAbandoned(hash)); abandonAction->setEnabled(model->wallet().transactionCanBeAbandoned(hash));
bumpFeeAction->setEnabled(model->wallet().transactionCanBeBumped(hash)); bumpFeeAction->setEnabled(model->wallet().transactionCanBeBumped(hash));
copyAddressAction->setEnabled(GUIUtil::hasEntryData(transactionView, 0, TransactionTableModel::AddressRole)); copyAddressAction->setEnabled(GUIUtil::hasEntryData(transactionView, 0, TransactionTableModel::AddressRole));
@ -416,7 +416,7 @@ void TransactionView::abandonTx()
// get the hash from the TxHashRole (QVariant / QString) // get the hash from the TxHashRole (QVariant / QString)
uint256 hash; uint256 hash;
QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString(); QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString();
hash.SetHex(hashQStr.toStdString()); hash.SetHexDeprecated(hashQStr.toStdString());
// Abandon the wallet transaction over the walletModel // Abandon the wallet transaction over the walletModel
model->wallet().abandonTransaction(hash); model->wallet().abandonTransaction(hash);
@ -431,7 +431,7 @@ void TransactionView::bumpFee([[maybe_unused]] bool checked)
// get the hash from the TxHashRole (QVariant / QString) // get the hash from the TxHashRole (QVariant / QString)
uint256 hash; uint256 hash;
QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString(); QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString();
hash.SetHex(hashQStr.toStdString()); hash.SetHexDeprecated(hashQStr.toStdString());
// Bump tx fee over the walletModel // Bump tx fee over the walletModel
uint256 newHash; uint256 newHash;

View file

@ -217,9 +217,10 @@ static bool rest_headers(const std::any& context,
return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count)); return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count));
} }
uint256 hash; auto hash{uint256::FromHex(hashStr)};
if (!ParseHashStr(hashStr, hash)) if (!hash) {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
}
const CBlockIndex* tip = nullptr; const CBlockIndex* tip = nullptr;
std::vector<const CBlockIndex*> headers; std::vector<const CBlockIndex*> headers;
@ -231,7 +232,7 @@ static bool rest_headers(const std::any& context,
LOCK(cs_main); LOCK(cs_main);
CChain& active_chain = chainman.ActiveChain(); CChain& active_chain = chainman.ActiveChain();
tip = active_chain.Tip(); tip = active_chain.Tip();
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash); const CBlockIndex* pindex{chainman.m_blockman.LookupBlockIndex(*hash)};
while (pindex != nullptr && active_chain.Contains(pindex)) { while (pindex != nullptr && active_chain.Contains(pindex)) {
headers.push_back(pindex); headers.push_back(pindex);
if (headers.size() == *parsed_count) { if (headers.size() == *parsed_count) {
@ -290,9 +291,10 @@ static bool rest_block(const std::any& context,
std::string hashStr; std::string hashStr;
const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart); const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart);
uint256 hash; auto hash{uint256::FromHex(hashStr)};
if (!ParseHashStr(hashStr, hash)) if (!hash) {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
}
FlatFilePos pos{}; FlatFilePos pos{};
const CBlockIndex* pblockindex = nullptr; const CBlockIndex* pblockindex = nullptr;
@ -303,7 +305,7 @@ static bool rest_block(const std::any& context,
{ {
LOCK(cs_main); LOCK(cs_main);
tip = chainman.ActiveChain().Tip(); tip = chainman.ActiveChain().Tip();
pblockindex = chainman.m_blockman.LookupBlockIndex(hash); pblockindex = chainman.m_blockman.LookupBlockIndex(*hash);
if (!pblockindex) { if (!pblockindex) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
} }
@ -390,8 +392,8 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count)); return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count));
} }
uint256 block_hash; auto block_hash{uint256::FromHex(raw_blockhash)};
if (!ParseHashStr(raw_blockhash, block_hash)) { if (!block_hash) {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + raw_blockhash); return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + raw_blockhash);
} }
@ -413,7 +415,7 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
ChainstateManager& chainman = *maybe_chainman; ChainstateManager& chainman = *maybe_chainman;
LOCK(cs_main); LOCK(cs_main);
CChain& active_chain = chainman.ActiveChain(); CChain& active_chain = chainman.ActiveChain();
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block_hash); const CBlockIndex* pindex{chainman.m_blockman.LookupBlockIndex(*block_hash)};
while (pindex != nullptr && active_chain.Contains(pindex)) { while (pindex != nullptr && active_chain.Contains(pindex)) {
headers.push_back(pindex); headers.push_back(pindex);
if (headers.size() == *parsed_count) if (headers.size() == *parsed_count)
@ -494,8 +496,8 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilter/<filtertype>/<blockhash>"); return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilter/<filtertype>/<blockhash>");
} }
uint256 block_hash; auto block_hash{uint256::FromHex(uri_parts[1])};
if (!ParseHashStr(uri_parts[1], block_hash)) { if (!block_hash) {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + uri_parts[1]); return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + uri_parts[1]);
} }
@ -516,7 +518,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s
if (!maybe_chainman) return false; if (!maybe_chainman) return false;
ChainstateManager& chainman = *maybe_chainman; ChainstateManager& chainman = *maybe_chainman;
LOCK(cs_main); LOCK(cs_main);
block_index = chainman.m_blockman.LookupBlockIndex(block_hash); block_index = chainman.m_blockman.LookupBlockIndex(*block_hash);
if (!block_index) { if (!block_index) {
return RESTERR(req, HTTP_NOT_FOUND, uri_parts[1] + " not found"); return RESTERR(req, HTTP_NOT_FOUND, uri_parts[1] + " not found");
} }
@ -616,14 +618,14 @@ static bool rest_deploymentinfo(const std::any& context, HTTPRequest* req, const
jsonRequest.params = UniValue(UniValue::VARR); jsonRequest.params = UniValue(UniValue::VARR);
if (!hash_str.empty()) { if (!hash_str.empty()) {
uint256 hash; auto hash{uint256::FromHex(hash_str)};
if (!ParseHashStr(hash_str, hash)) { if (!hash) {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hash_str); return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hash_str);
} }
const ChainstateManager* chainman = GetChainman(context, req); const ChainstateManager* chainman = GetChainman(context, req);
if (!chainman) return false; if (!chainman) return false;
if (!WITH_LOCK(::cs_main, return chainman->m_blockman.LookupBlockIndex(ParseHashV(hash_str, "blockhash")))) { if (!WITH_LOCK(::cs_main, return chainman->m_blockman.LookupBlockIndex(*hash))) {
return RESTERR(req, HTTP_BAD_REQUEST, "Block not found"); return RESTERR(req, HTTP_BAD_REQUEST, "Block not found");
} }
@ -704,9 +706,10 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string
std::string hashStr; std::string hashStr;
const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart); const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart);
uint256 hash; auto hash{uint256::FromHex(hashStr)};
if (!ParseHashStr(hashStr, hash)) if (!hash) {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
}
if (g_txindex) { if (g_txindex) {
g_txindex->BlockUntilSyncedToCurrentChain(); g_txindex->BlockUntilSyncedToCurrentChain();
@ -715,7 +718,7 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string
const NodeContext* const node = GetNodeContext(context, req); const NodeContext* const node = GetNodeContext(context, req);
if (!node) return false; if (!node) return false;
uint256 hashBlock = uint256(); uint256 hashBlock = uint256();
const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, node->mempool.get(), hash, hashBlock, node->chainman->m_blockman); const CTransactionRef tx{GetTransaction(/*block_index=*/nullptr, node->mempool.get(), *hash, hashBlock, node->chainman->m_blockman)};
if (!tx) { if (!tx) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
} }
@ -792,13 +795,14 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
if (txid_out.size() != 2) { if (txid_out.size() != 2) {
return RESTERR(req, HTTP_BAD_REQUEST, "Parse error"); return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
} }
auto txid{Txid::FromHex(txid_out.at(0))};
auto output{ToIntegral<uint32_t>(txid_out.at(1))}; auto output{ToIntegral<uint32_t>(txid_out.at(1))};
if (!output || !IsHex(txid_out.at(0))) { if (!txid || !output) {
return RESTERR(req, HTTP_BAD_REQUEST, "Parse error"); return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
} }
vOutPoints.emplace_back(TxidFromString(txid_out.at(0)), *output); vOutPoints.emplace_back(*txid, *output);
} }
if (vOutPoints.size() > 0) if (vOutPoints.size() > 0)

View file

@ -345,13 +345,11 @@ static RPCHelpMan generateblock()
std::vector<CTransactionRef> txs; std::vector<CTransactionRef> txs;
const auto raw_txs_or_txids = request.params[1].get_array(); const auto raw_txs_or_txids = request.params[1].get_array();
for (size_t i = 0; i < raw_txs_or_txids.size(); i++) { for (size_t i = 0; i < raw_txs_or_txids.size(); i++) {
const auto str(raw_txs_or_txids[i].get_str()); const auto& str{raw_txs_or_txids[i].get_str()};
uint256 hash;
CMutableTransaction mtx; CMutableTransaction mtx;
if (ParseHashStr(str, hash)) { if (auto hash{uint256::FromHex(str)}) {
const auto tx{mempool.get(*hash)};
const auto tx = mempool.get(hash);
if (!tx) { if (!tx) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Transaction %s not in mempool.", str)); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Transaction %s not in mempool.", str));
} }

View file

@ -149,8 +149,7 @@ BOOST_AUTO_TEST_CASE(blockfilters_json_test)
unsigned int pos = 0; unsigned int pos = 0;
/*int block_height =*/ test[pos++].getInt<int>(); /*int block_height =*/ test[pos++].getInt<int>();
uint256 block_hash; BOOST_CHECK(uint256::FromHex(test[pos++].get_str()));
BOOST_CHECK(ParseHashStr(test[pos++].get_str(), block_hash));
CBlock block; CBlock block;
BOOST_REQUIRE(DecodeHexBlk(block, test[pos++].get_str())); BOOST_REQUIRE(DecodeHexBlk(block, test[pos++].get_str()));
@ -165,11 +164,9 @@ BOOST_AUTO_TEST_CASE(blockfilters_json_test)
tx_undo.vprevout.emplace_back(txout, 0, false); tx_undo.vprevout.emplace_back(txout, 0, false);
} }
uint256 prev_filter_header_basic; uint256 prev_filter_header_basic{*Assert(uint256::FromHex(test[pos++].get_str()))};
BOOST_CHECK(ParseHashStr(test[pos++].get_str(), prev_filter_header_basic));
std::vector<unsigned char> filter_basic = ParseHex(test[pos++].get_str()); std::vector<unsigned char> filter_basic = ParseHex(test[pos++].get_str());
uint256 filter_header_basic; uint256 filter_header_basic{*Assert(uint256::FromHex(test[pos++].get_str()))};
BOOST_CHECK(ParseHashStr(test[pos++].get_str(), filter_header_basic));
BlockFilter computed_filter_basic(BlockFilterType::BASIC, block, block_undo); BlockFilter computed_filter_basic(BlockFilterType::BASIC, block, block_undo);
BOOST_CHECK(computed_filter_basic.GetFilter().GetEncoded() == filter_basic); BOOST_CHECK(computed_filter_basic.GetFilter().GetEncoded() == filter_basic);

View file

@ -27,8 +27,7 @@ FUZZ_TARGET(hex)
assert(ToLower(random_hex_string) == hex_data); assert(ToLower(random_hex_string) == hex_data);
} }
(void)IsHexNumber(random_hex_string); (void)IsHexNumber(random_hex_string);
uint256 result; (void)uint256::FromHex(random_hex_string);
(void)ParseHashStr(random_hex_string, result);
(void)uint256S(random_hex_string); (void)uint256S(random_hex_string);
try { try {
(void)HexToPubKey(random_hex_string); (void)HexToPubKey(random_hex_string);

View file

@ -86,7 +86,7 @@ BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_negative_target)
uint256 hash; uint256 hash;
unsigned int nBits; unsigned int nBits;
nBits = UintToArith256(consensus.powLimit).GetCompact(true); nBits = UintToArith256(consensus.powLimit).GetCompact(true);
hash.SetHex("0x1"); hash = uint256{1};
BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus)); BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
} }
@ -95,7 +95,7 @@ BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_overflow_target)
const auto consensus = CreateChainParams(*m_node.args, ChainType::MAIN)->GetConsensus(); const auto consensus = CreateChainParams(*m_node.args, ChainType::MAIN)->GetConsensus();
uint256 hash; uint256 hash;
unsigned int nBits{~0x00800000U}; unsigned int nBits{~0x00800000U};
hash.SetHex("0x1"); hash = uint256{1};
BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus)); BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
} }
@ -107,7 +107,7 @@ BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_too_easy_target)
arith_uint256 nBits_arith = UintToArith256(consensus.powLimit); arith_uint256 nBits_arith = UintToArith256(consensus.powLimit);
nBits_arith *= 2; nBits_arith *= 2;
nBits = nBits_arith.GetCompact(); nBits = nBits_arith.GetCompact();
hash.SetHex("0x1"); hash = uint256{1};
BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus)); BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
} }

View file

@ -62,7 +62,7 @@ static std::string ArrayToString(const unsigned char A[], unsigned int width)
inline uint160 uint160S(std::string_view str) inline uint160 uint160S(std::string_view str)
{ {
uint160 rv; uint160 rv;
rv.SetHex(str); rv.SetHexDeprecated(str);
return rv; return rv;
} }
@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE( comparison ) // <= >= < >
uint256S("1000000000000000000000000000000000000000000000000000000000000002")); uint256S("1000000000000000000000000000000000000000000000000000000000000002"));
} }
BOOST_AUTO_TEST_CASE( methods ) // GetHex SetHex begin() end() size() GetLow64 GetSerializeSize, Serialize, Unserialize BOOST_AUTO_TEST_CASE(methods) // GetHex SetHexDeprecated FromHex begin() end() size() GetLow64 GetSerializeSize, Serialize, Unserialize
{ {
BOOST_CHECK_EQUAL(R1L.GetHex(), R1L.ToString()); BOOST_CHECK_EQUAL(R1L.GetHex(), R1L.ToString());
BOOST_CHECK_EQUAL(R2L.GetHex(), R2L.ToString()); BOOST_CHECK_EQUAL(R2L.GetHex(), R2L.ToString());
@ -166,12 +166,12 @@ BOOST_AUTO_TEST_CASE( methods ) // GetHex SetHex begin() end() size() GetLow64 G
uint256 TmpL(R1L); uint256 TmpL(R1L);
BOOST_CHECK_EQUAL(TmpL, R1L); BOOST_CHECK_EQUAL(TmpL, R1L);
// Verify previous values don't persist when setting to truncated string. // Verify previous values don't persist when setting to truncated string.
TmpL.SetHex("21"); TmpL.SetHexDeprecated("21");
BOOST_CHECK_EQUAL(TmpL.ToString(), "0000000000000000000000000000000000000000000000000000000000000021"); BOOST_CHECK_EQUAL(TmpL.ToString(), "0000000000000000000000000000000000000000000000000000000000000021");
TmpL.SetHex(R2L.ToString()); BOOST_CHECK_EQUAL(TmpL, R2L); BOOST_CHECK_EQUAL(uint256::FromHex(R2L.ToString()).value(), R2L);
TmpL.SetHex(ZeroL.ToString()); BOOST_CHECK_EQUAL(TmpL, uint256()); BOOST_CHECK_EQUAL(uint256::FromHex(ZeroL.ToString()).value(), uint256());
TmpL.SetHex(R1L.ToString()); TmpL = uint256::FromHex(R1L.ToString()).value();
BOOST_CHECK_EQUAL_COLLECTIONS(R1L.begin(), R1L.end(), R1Array, R1Array + R1L.size()); BOOST_CHECK_EQUAL_COLLECTIONS(R1L.begin(), R1L.end(), R1Array, R1Array + R1L.size());
BOOST_CHECK_EQUAL_COLLECTIONS(TmpL.begin(), TmpL.end(), R1Array, R1Array + TmpL.size()); BOOST_CHECK_EQUAL_COLLECTIONS(TmpL.begin(), TmpL.end(), R1Array, R1Array + TmpL.size());
BOOST_CHECK_EQUAL_COLLECTIONS(R2L.begin(), R2L.end(), R2Array, R2Array + R2L.size()); BOOST_CHECK_EQUAL_COLLECTIONS(R2L.begin(), R2L.end(), R2Array, R2Array + R2L.size());
@ -214,10 +214,10 @@ BOOST_AUTO_TEST_CASE( methods ) // GetHex SetHex begin() end() size() GetLow64 G
BOOST_CHECK_EQUAL(MaxS.GetHex(), MaxS.ToString()); BOOST_CHECK_EQUAL(MaxS.GetHex(), MaxS.ToString());
uint160 TmpS(R1S); uint160 TmpS(R1S);
BOOST_CHECK_EQUAL(TmpS, R1S); BOOST_CHECK_EQUAL(TmpS, R1S);
TmpS.SetHex(R2S.ToString()); BOOST_CHECK_EQUAL(TmpS, R2S); BOOST_CHECK_EQUAL(uint160::FromHex(R2S.ToString()).value(), R2S);
TmpS.SetHex(ZeroS.ToString()); BOOST_CHECK_EQUAL(TmpS, uint160()); BOOST_CHECK_EQUAL(uint160::FromHex(ZeroS.ToString()).value(), uint160());
TmpS.SetHex(R1S.ToString()); TmpS = uint160::FromHex(R1S.ToString()).value();
BOOST_CHECK_EQUAL_COLLECTIONS(R1S.begin(), R1S.end(), R1Array, R1Array + R1S.size()); BOOST_CHECK_EQUAL_COLLECTIONS(R1S.begin(), R1S.end(), R1Array, R1Array + R1S.size());
BOOST_CHECK_EQUAL_COLLECTIONS(TmpS.begin(), TmpS.end(), R1Array, R1Array + TmpS.size()); BOOST_CHECK_EQUAL_COLLECTIONS(TmpS.begin(), TmpS.end(), R1Array, R1Array + TmpS.size());
BOOST_CHECK_EQUAL_COLLECTIONS(R2S.begin(), R2S.end(), R2Array, R2Array + R2S.size()); BOOST_CHECK_EQUAL_COLLECTIONS(R2S.begin(), R2S.end(), R2Array, R2Array + R2S.size());

View file

@ -18,7 +18,7 @@ std::string base_blob<BITS>::GetHex() const
} }
template <unsigned int BITS> template <unsigned int BITS>
void base_blob<BITS>::SetHex(const std::string_view str) void base_blob<BITS>::SetHexDeprecated(const std::string_view str)
{ {
std::fill(m_data.begin(), m_data.end(), 0); std::fill(m_data.begin(), m_data.end(), 0);
@ -52,12 +52,12 @@ std::string base_blob<BITS>::ToString() const
// Explicit instantiations for base_blob<160> // Explicit instantiations for base_blob<160>
template std::string base_blob<160>::GetHex() const; template std::string base_blob<160>::GetHex() const;
template std::string base_blob<160>::ToString() const; template std::string base_blob<160>::ToString() const;
template void base_blob<160>::SetHex(std::string_view); template void base_blob<160>::SetHexDeprecated(std::string_view);
// Explicit instantiations for base_blob<256> // Explicit instantiations for base_blob<256>
template std::string base_blob<256>::GetHex() const; template std::string base_blob<256>::GetHex() const;
template std::string base_blob<256>::ToString() const; template std::string base_blob<256>::ToString() const;
template void base_blob<256>::SetHex(std::string_view); template void base_blob<256>::SetHexDeprecated(std::string_view);
const uint256 uint256::ZERO(0); const uint256 uint256::ZERO(0);
const uint256 uint256::ONE(1); const uint256 uint256::ONE(1);

View file

@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2022 The Bitcoin Core developers // Copyright (c) 2009-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -8,12 +8,14 @@
#include <crypto/common.h> #include <crypto/common.h>
#include <span.h> #include <span.h>
#include <util/strencodings.h>
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cassert> #include <cassert>
#include <cstdint>
#include <cstring> #include <cstring>
#include <stdint.h> #include <optional>
#include <string> #include <string>
/** Template base class for fixed-sized opaque blobs. */ /** Template base class for fixed-sized opaque blobs. */
@ -59,7 +61,8 @@ public:
// Hex string representations are little-endian. // Hex string representations are little-endian.
std::string GetHex() const; std::string GetHex() const;
void SetHex(std::string_view str); /** Unlike FromHex this accepts any invalid input, thus it is fragile and deprecated */
void SetHexDeprecated(std::string_view str);
std::string ToString() const; std::string ToString() const;
constexpr const unsigned char* data() const { return m_data.data(); } constexpr const unsigned char* data() const { return m_data.data(); }
@ -88,12 +91,30 @@ public:
} }
}; };
namespace detail {
/**
* Writes the hex string (treated as little-endian) into a new uintN_t object
* and only returns a value iff all of the checks pass:
* - Input length is uintN_t::size()*2
* - All characters are hex
*/
template <class uintN_t>
std::optional<uintN_t> FromHex(std::string_view str)
{
if (uintN_t::size() * 2 != str.size() || !IsHex(str)) return std::nullopt;
uintN_t rv;
rv.SetHexDeprecated(str);
return rv;
}
} // namespace detail
/** 160-bit opaque blob. /** 160-bit opaque blob.
* @note This type is called uint160 for historical reasons only. It is an opaque * @note This type is called uint160 for historical reasons only. It is an opaque
* blob of 160 bits and has no integer operations. * blob of 160 bits and has no integer operations.
*/ */
class uint160 : public base_blob<160> { class uint160 : public base_blob<160> {
public: public:
static std::optional<uint160> FromHex(std::string_view str) { return detail::FromHex<uint160>(str); }
constexpr uint160() = default; constexpr uint160() = default;
constexpr explicit uint160(Span<const unsigned char> vch) : base_blob<160>(vch) {} constexpr explicit uint160(Span<const unsigned char> vch) : base_blob<160>(vch) {}
}; };
@ -105,6 +126,7 @@ public:
*/ */
class uint256 : public base_blob<256> { class uint256 : public base_blob<256> {
public: public:
static std::optional<uint256> FromHex(std::string_view str) { return detail::FromHex<uint256>(str); }
constexpr uint256() = default; constexpr uint256() = default;
constexpr explicit uint256(uint8_t v) : base_blob<256>(v) {} constexpr explicit uint256(uint8_t v) : base_blob<256>(v) {}
constexpr explicit uint256(Span<const unsigned char> vch) : base_blob<256>(vch) {} constexpr explicit uint256(Span<const unsigned char> vch) : base_blob<256>(vch) {}
@ -113,13 +135,12 @@ public:
}; };
/* uint256 from std::string_view, treated as little-endian. /* uint256 from std::string_view, treated as little-endian.
* This is not a uint256 constructor because of historical fears of uint256(0) * DEPRECATED. Unlike FromHex this accepts any invalid input, thus it is fragile and deprecated!
* resolving to a NULL string and crashing.
*/ */
inline uint256 uint256S(std::string_view str) inline uint256 uint256S(std::string_view str)
{ {
uint256 rv; uint256 rv;
rv.SetHex(str); rv.SetHexDeprecated(str);
return rv; return rv;
} }

View file

@ -42,6 +42,12 @@ public:
/** Wrapped `uint256` methods. */ /** Wrapped `uint256` methods. */
constexpr bool IsNull() const { return m_wrapped.IsNull(); } constexpr bool IsNull() const { return m_wrapped.IsNull(); }
constexpr void SetNull() { m_wrapped.SetNull(); } constexpr void SetNull() { m_wrapped.SetNull(); }
static std::optional<transaction_identifier> FromHex(std::string_view hex)
{
auto u{uint256::FromHex(hex)};
if (!u) return std::nullopt;
return FromUint256(*u);
}
std::string GetHex() const { return m_wrapped.GetHex(); } std::string GetHex() const { return m_wrapped.GetHex(); }
std::string ToString() const { return m_wrapped.ToString(); } std::string ToString() const { return m_wrapped.ToString(); }
static constexpr auto size() { return decltype(m_wrapped)::size(); } static constexpr auto size() { return decltype(m_wrapped)::size(); }
@ -66,6 +72,7 @@ using Txid = transaction_identifier<false>;
/** Wtxid commits to all transaction fields including the witness. */ /** Wtxid commits to all transaction fields including the witness. */
using Wtxid = transaction_identifier<true>; using Wtxid = transaction_identifier<true>;
/** DEPRECATED due to missing length-check and hex-check, please use the safer FromHex, or FromUint256 */
inline Txid TxidFromString(std::string_view str) inline Txid TxidFromString(std::string_view str)
{ {
return Txid::FromUint256(uint256S(str)); return Txid::FromUint256(uint256S(str));

View file

@ -208,6 +208,8 @@ class RESTTest (BitcoinTestFramework):
self.test_rest_request(f"/getutxos/{spending[0]}_+1", ret_type=RetType.OBJ, status=400) self.test_rest_request(f"/getutxos/{spending[0]}_+1", ret_type=RetType.OBJ, status=400)
self.test_rest_request(f"/getutxos/{spending[0]}-+1", ret_type=RetType.OBJ, status=400) self.test_rest_request(f"/getutxos/{spending[0]}-+1", ret_type=RetType.OBJ, status=400)
self.test_rest_request(f"/getutxos/{spending[0]}--1", ret_type=RetType.OBJ, status=400) self.test_rest_request(f"/getutxos/{spending[0]}--1", ret_type=RetType.OBJ, status=400)
self.test_rest_request(f"/getutxos/{spending[0]}aa-1234", ret_type=RetType.OBJ, status=400)
self.test_rest_request(f"/getutxos/aa-1234", ret_type=RetType.OBJ, status=400)
# Test limits # Test limits
long_uri = '/'.join([f"{txid}-{n_}" for n_ in range(20)]) long_uri = '/'.join([f"{txid}-{n_}" for n_ in range(20)])