Skip to content

Commit 5b74a84

Browse files
l0rinchodlinatorMarcoFalkeryanofskystickies-v
committed
util: Add consteval ""_hex[_v][_u8] literals
""_hex is a compile-time user-defined literal returning std::array<std::byte>, equivalent of ParseHex. Variants: - ""_hex_v returns std::vector<std::byte> - ""_hex_u8 returns std::array<uint8_t> - ""_hex_v_u8 returns std::vector<uint8_t> - Directly serializable as a size-prefixed OP_PUSH CScript payload using operator<<. Also extracts from_hex into shared util::ConstevalHexDigit function. Co-Authored-By: hodlinator <172445034+hodlinator@users.noreply.github.com> Co-Authored-By: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Co-Authored-By: Ryan Ofsky <ryan@ofsky.org> Co-Authored-By: stickies-v <stickies-v@protonmail.com>
1 parent dc5f6f6 commit 5b74a84

File tree

3 files changed

+108
-10
lines changed

3 files changed

+108
-10
lines changed

src/test/util_tests.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <hash.h> // For Hash()
88
#include <key.h> // For CKey
99
#include <script/parsing.h>
10+
#include <span.h>
1011
#include <sync.h>
1112
#include <test/util/random.h>
1213
#include <test/util/setup_common.h>
@@ -45,6 +46,8 @@
4546
#include <boost/test/unit_test.hpp>
4647

4748
using namespace std::literals;
49+
using namespace util::hex_literals;
50+
using util::ConstevalHexDigit;
4851
using util::Join;
4952
using util::RemovePrefix;
5053
using util::RemovePrefixView;
@@ -151,6 +154,20 @@ BOOST_AUTO_TEST_CASE(parse_hex)
151154

152155
// Basic test vector
153156
std::vector<unsigned char> expected(std::begin(HEX_PARSE_OUTPUT), std::end(HEX_PARSE_OUTPUT));
157+
constexpr std::array<std::byte, 65> hex_literal_array{operator""_hex<util::detail::Hex(HEX_PARSE_INPUT)>()};
158+
auto hex_literal_span{MakeUCharSpan(hex_literal_array)};
159+
BOOST_CHECK_EQUAL_COLLECTIONS(hex_literal_span.begin(), hex_literal_span.end(), expected.begin(), expected.end());
160+
161+
const std::vector<std::byte> hex_literal_vector{operator""_hex_v<util::detail::Hex(HEX_PARSE_INPUT)>()};
162+
hex_literal_span = MakeUCharSpan(hex_literal_vector);
163+
BOOST_CHECK_EQUAL_COLLECTIONS(hex_literal_span.begin(), hex_literal_span.end(), expected.begin(), expected.end());
164+
165+
constexpr std::array<uint8_t, 65> hex_literal_array_uint8{operator""_hex_u8<util::detail::Hex(HEX_PARSE_INPUT)>()};
166+
BOOST_CHECK_EQUAL_COLLECTIONS(hex_literal_array_uint8.begin(), hex_literal_array_uint8.end(), expected.begin(), expected.end());
167+
168+
result = operator""_hex_v_u8<util::detail::Hex(HEX_PARSE_INPUT)>();
169+
BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end());
170+
154171
result = ParseHex(HEX_PARSE_INPUT);
155172
BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end());
156173

@@ -179,6 +196,10 @@ BOOST_AUTO_TEST_CASE(parse_hex)
179196
BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end());
180197

181198
// Empty string is supported
199+
static_assert(""_hex.empty());
200+
static_assert(""_hex_u8.empty());
201+
BOOST_CHECK_EQUAL(""_hex_v.size(), 0);
202+
BOOST_CHECK_EQUAL(""_hex_v_u8.size(), 0);
182203
BOOST_CHECK_EQUAL(ParseHex("").size(), 0);
183204
BOOST_CHECK_EQUAL(TryParseHex<uint8_t>("").value().size(), 0);
184205

@@ -203,6 +224,14 @@ BOOST_AUTO_TEST_CASE(parse_hex)
203224
BOOST_CHECK(!TryParseHex("12 3").has_value());
204225
}
205226

227+
BOOST_AUTO_TEST_CASE(consteval_hex_digit)
228+
{
229+
BOOST_CHECK_EQUAL(ConstevalHexDigit('0'), 0);
230+
BOOST_CHECK_EQUAL(ConstevalHexDigit('9'), 9);
231+
BOOST_CHECK_EQUAL(ConstevalHexDigit('a'), 0xa);
232+
BOOST_CHECK_EQUAL(ConstevalHexDigit('f'), 0xf);
233+
}
234+
206235
BOOST_AUTO_TEST_CASE(util_HexStr)
207236
{
208237
BOOST_CHECK_EQUAL(HexStr(HEX_PARSE_OUTPUT), HEX_PARSE_INPUT);

src/uint256.h

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,19 +127,11 @@ class base_blob
127127
template <unsigned int BITS>
128128
consteval base_blob<BITS>::base_blob(std::string_view hex_str)
129129
{
130-
// Non-lookup table version of HexDigit().
131-
auto from_hex = [](const char c) -> int8_t {
132-
if (c >= '0' && c <= '9') return c - '0';
133-
if (c >= 'a' && c <= 'f') return c - 'a' + 0xa;
134-
135-
throw "Only lowercase hex digits are allowed, for consistency";
136-
};
137-
138130
if (hex_str.length() != m_data.size() * 2) throw "Hex string must fit exactly";
139131
auto str_it = hex_str.rbegin();
140132
for (auto& elem : m_data) {
141-
auto lo = from_hex(*(str_it++));
142-
elem = (from_hex(*(str_it++)) << 4) | lo;
133+
auto lo = util::ConstevalHexDigit(*(str_it++));
134+
elem = (util::ConstevalHexDigit(*(str_it++)) << 4) | lo;
143135
}
144136
}
145137

src/util/strencodings.h

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include <span.h>
1414
#include <util/string.h>
1515

16+
#include <array>
17+
#include <bit>
1618
#include <charconv>
1719
#include <cstddef>
1820
#include <cstdint>
@@ -365,4 +367,79 @@ std::string Capitalize(std::string str);
365367
*/
366368
std::optional<uint64_t> ParseByteUnits(std::string_view str, ByteUnit default_multiplier);
367369

370+
namespace util {
371+
/** consteval version of HexDigit() without the lookup table. */
372+
consteval uint8_t ConstevalHexDigit(const char c)
373+
{
374+
if (c >= '0' && c <= '9') return c - '0';
375+
if (c >= 'a' && c <= 'f') return c - 'a' + 0xa;
376+
377+
throw "Only lowercase hex digits are allowed, for consistency";
378+
}
379+
380+
/**
381+
* ""_hex is a compile-time user-defined literal returning a
382+
* `std::array<std::byte>`, equivalent to ParseHex(). Variants provided:
383+
*
384+
* - ""_hex_v: Returns `std::vector<std::byte>`, useful for heap allocation or
385+
* variable-length serialization.
386+
*
387+
* - ""_hex_u8: Returns `std::array<uint8_t>`, for cases where `std::byte` is
388+
* incompatible.
389+
*
390+
* - ""_hex_v_u8: Returns `std::vector<uint8_t>`, combining heap allocation with
391+
* `uint8_t`.
392+
*
393+
* @warning It could be necessary to use vector instead of array variants when
394+
* serializing, or vice versa, because vectors are assumed to be variable-
395+
* length and serialized with a size prefix, while arrays are considered fixed
396+
* length and serialized with no prefix.
397+
*
398+
* @warning It may be preferable to use vector variants to save stack space when
399+
* declaring local variables if hex strings are large. Alternatively variables
400+
* could be declared constexpr to avoid using stack space.
401+
*
402+
* @warning Avoid `uint8_t` variants when not necessary, as the codebase
403+
* migrates to use `std::byte` instead of `unsigned char` and `uint8_t`.
404+
*
405+
* @note One reason ""_hex uses `std::array` instead of `std::vector` like
406+
* ParseHex() does is because heap-based containers cannot cross the compile-
407+
* time/runtime barrier.
408+
*/
409+
inline namespace hex_literals {
410+
namespace detail {
411+
412+
template <size_t N>
413+
struct Hex {
414+
std::array<std::byte, N / 2> bytes{};
415+
consteval Hex(const char (&hex_str)[N])
416+
// 2 hex digits required per byte + implicit null terminator
417+
requires(N % 2 == 1)
418+
{
419+
if (hex_str[N - 1]) throw "null terminator required";
420+
for (std::size_t i = 0; i < bytes.size(); ++i) {
421+
bytes[i] = static_cast<std::byte>(
422+
(ConstevalHexDigit(hex_str[2 * i]) << 4) |
423+
ConstevalHexDigit(hex_str[2 * i + 1]));
424+
}
425+
}
426+
};
427+
428+
} // namespace detail
429+
430+
template <util::detail::Hex str>
431+
constexpr auto operator""_hex() { return str.bytes; }
432+
433+
template <util::detail::Hex str>
434+
constexpr auto operator""_hex_u8() { return std::bit_cast<std::array<uint8_t, str.bytes.size()>>(str.bytes); }
435+
436+
template <util::detail::Hex str>
437+
constexpr auto operator""_hex_v() { return std::vector<std::byte>{str.bytes.begin(), str.bytes.end()}; }
438+
439+
template <util::detail::Hex str>
440+
inline auto operator""_hex_v_u8() { return std::vector<uint8_t>{UCharCast(str.bytes.data()), UCharCast(str.bytes.data() + str.bytes.size())}; }
441+
442+
} // inline namespace hex_literals
443+
} // namespace util
444+
368445
#endif // BITCOIN_UTIL_STRENCODINGS_H

0 commit comments

Comments
 (0)