Skip to content

Commit 70e2c87

Browse files
committed
refactor: add uint256::FromUserHex helper
FromUserHex will be used in future commits to construct uint256 instances from user hex input without being unnecessarily restrictive on formatting by allowing 0x-prefixed input that is shorter than 64 characters.
1 parent 85b7cbf commit 70e2c87

File tree

3 files changed

+55
-0
lines changed

3 files changed

+55
-0
lines changed

src/test/fuzz/hex.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ FUZZ_TARGET(hex)
3232
assert(random_hex_string.length() == 64);
3333
assert(Txid::FromHex(random_hex_string));
3434
assert(Wtxid::FromHex(random_hex_string));
35+
assert(uint256::FromUserHex(random_hex_string));
36+
}
37+
if (const auto result{uint256::FromUserHex(random_hex_string)}) {
38+
assert(uint256::FromHex(result->ToString()));
3539
}
3640
(void)uint256S(random_hex_string);
3741
try {

src/test/uint256_tests.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,35 @@ BOOST_AUTO_TEST_CASE(from_hex)
386386
TestFromHex<Wtxid>();
387387
}
388388

389+
BOOST_AUTO_TEST_CASE(from_user_hex)
390+
{
391+
BOOST_CHECK_EQUAL(uint256::FromUserHex("").value(), uint256::ZERO);
392+
BOOST_CHECK_EQUAL(uint256::FromUserHex("0x").value(), uint256::ZERO);
393+
BOOST_CHECK_EQUAL(uint256::FromUserHex("0").value(), uint256::ZERO);
394+
BOOST_CHECK_EQUAL(uint256::FromUserHex("00").value(), uint256::ZERO);
395+
BOOST_CHECK_EQUAL(uint256::FromUserHex("1").value(), uint256::ONE);
396+
BOOST_CHECK_EQUAL(uint256::FromUserHex("0x10").value(), uint256{0x10});
397+
BOOST_CHECK_EQUAL(uint256::FromUserHex("10").value(), uint256{0x10});
398+
BOOST_CHECK_EQUAL(uint256::FromUserHex("0xFf").value(), uint256{0xff});
399+
BOOST_CHECK_EQUAL(uint256::FromUserHex("Ff").value(), uint256{0xff});
400+
const std::string valid_hex_64{"0x0123456789abcdef0123456789abcdef0123456789ABDCEF0123456789ABCDEF"};
401+
BOOST_REQUIRE_EQUAL(valid_hex_64.size(), 2 + 64); // 0x prefix and 64 hex digits
402+
BOOST_CHECK_EQUAL(uint256::FromUserHex(valid_hex_64.substr(2)).value().ToString(), ToLower(valid_hex_64.substr(2)));
403+
BOOST_CHECK_EQUAL(uint256::FromUserHex(valid_hex_64.substr(0)).value().ToString(), ToLower(valid_hex_64.substr(2)));
404+
405+
BOOST_CHECK(!uint256::FromUserHex("0x0 ")); // no spaces at end,
406+
BOOST_CHECK(!uint256::FromUserHex(" 0x0")); // or beginning,
407+
BOOST_CHECK(!uint256::FromUserHex("0x 0")); // or middle,
408+
BOOST_CHECK(!uint256::FromUserHex(" ")); // etc.
409+
BOOST_CHECK(!uint256::FromUserHex("0x0ga")); // invalid character
410+
BOOST_CHECK(!uint256::FromUserHex("x0")); // broken prefix
411+
BOOST_CHECK(!uint256::FromUserHex("0x0x00")); // two prefixes not allowed
412+
BOOST_CHECK(!uint256::FromUserHex(valid_hex_64.substr(2) + "0")); // 1 hex digit too many
413+
BOOST_CHECK(!uint256::FromUserHex(valid_hex_64 + "a")); // 1 hex digit too many
414+
BOOST_CHECK(!uint256::FromUserHex(valid_hex_64 + " ")); // whitespace after max length
415+
BOOST_CHECK(!uint256::FromUserHex(valid_hex_64 + "z")); // invalid character after max length
416+
}
417+
389418
BOOST_AUTO_TEST_CASE( check_ONE )
390419
{
391420
uint256 one = uint256S("0000000000000000000000000000000000000000000000000000000000000001");

src/uint256.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <crypto/common.h>
1010
#include <span.h>
1111
#include <util/strencodings.h>
12+
#include <util/string.h>
1213

1314
#include <algorithm>
1415
#include <array>
@@ -17,6 +18,7 @@
1718
#include <cstring>
1819
#include <optional>
1920
#include <string>
21+
#include <string_view>
2022

2123
/** Template base class for fixed-sized opaque blobs. */
2224
template<unsigned int BITS>
@@ -106,6 +108,25 @@ std::optional<uintN_t> FromHex(std::string_view str)
106108
rv.SetHexDeprecated(str);
107109
return rv;
108110
}
111+
/**
112+
* @brief Like FromHex(std::string_view str), but allows an "0x" prefix
113+
* and pads the input with leading zeroes if it is shorter than
114+
* the expected length of uintN_t::size()*2.
115+
*
116+
* Designed to be used when dealing with user input.
117+
*/
118+
template <class uintN_t>
119+
std::optional<uintN_t> FromUserHex(std::string_view input)
120+
{
121+
input = util::RemovePrefixView(input, "0x");
122+
constexpr auto expected_size{uintN_t::size() * 2};
123+
if (input.size() < expected_size) {
124+
auto padded = std::string(expected_size, '0');
125+
std::copy(input.begin(), input.end(), padded.begin() + expected_size - input.size());
126+
return FromHex<uintN_t>(padded);
127+
}
128+
return FromHex<uintN_t>(input);
129+
}
109130
} // namespace detail
110131

111132
/** 160-bit opaque blob.
@@ -127,6 +148,7 @@ class uint160 : public base_blob<160> {
127148
class uint256 : public base_blob<256> {
128149
public:
129150
static std::optional<uint256> FromHex(std::string_view str) { return detail::FromHex<uint256>(str); }
151+
static std::optional<uint256> FromUserHex(std::string_view str) { return detail::FromUserHex<uint256>(str); }
130152
constexpr uint256() = default;
131153
constexpr explicit uint256(uint8_t v) : base_blob<256>(v) {}
132154
constexpr explicit uint256(Span<const unsigned char> vch) : base_blob<256>(vch) {}

0 commit comments

Comments
 (0)