Skip to content

Commit 73e3fa1

Browse files
committed
doc + test: Correct uint256 hex string endianness
Follow-up to bitcoin#30436. uint256 string representation was wrongfully documented as little-endian due to them being reversed by GetHex() etc, and base_blob::Compare() giving most significance to the beginning of the internal array. They are closer to "big-endian", but this commit tries to be even more precise than that. uint256_tests.cpp - Avoid using variable from the left side of the condition in the right side. setup_common.cpp - Skip needless ArithToUint256-conversion.
1 parent 5d28013 commit 73e3fa1

File tree

5 files changed

+70
-23
lines changed

5 files changed

+70
-23
lines changed

src/arith_uint256.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ class base_uint
196196
return ret;
197197
}
198198

199+
/** Numeric ordering (unlike \ref base_blob::Compare) */
199200
int CompareTo(const base_uint& b) const;
200201
bool EqualTo(uint64_t b) const;
201202

@@ -218,6 +219,7 @@ class base_uint
218219
friend inline bool operator==(const base_uint& a, uint64_t b) { return a.EqualTo(b); }
219220
friend inline bool operator!=(const base_uint& a, uint64_t b) { return !a.EqualTo(b); }
220221

222+
/** Hex encoding of the number (with the most significant digits first). */
221223
std::string GetHex() const;
222224
std::string ToString() const;
223225

src/test/arith_uint256_tests.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <arith_uint256.h>
6+
#include <test/util/setup_common.h>
67
#include <uint256.h>
78

89
#include <boost/test/unit_test.hpp>
@@ -22,7 +23,8 @@ static inline arith_uint256 arith_uint256V(const std::vector<unsigned char>& vch
2223
{
2324
return UintToArith256(uint256(vch));
2425
}
25-
static inline arith_uint256 arith_uint256S(const std::string& str) { return UintToArith256(uint256S(str)); }
26+
// Takes a number written in hex (with most significant digits first).
27+
static inline arith_uint256 arith_uint256S(std::string_view str) { return UintToArith256(uint256S(str)); }
2628

2729
const unsigned char R1Array[] =
2830
"\x9c\x52\x4a\xdb\xcf\x56\x11\x12\x2b\x29\x12\x5e\x5d\x35\xd2\xd2"
@@ -104,6 +106,7 @@ BOOST_AUTO_TEST_CASE( basics ) // constructors, equality, inequality
104106
BOOST_CHECK(arith_uint256S(R1L.ToString()) == R1L);
105107
BOOST_CHECK(arith_uint256S(" 0x" + R1L.ToString() + " ") == R1L);
106108
BOOST_CHECK(arith_uint256S("") == ZeroL);
109+
BOOST_CHECK(arith_uint256S("1") == OneL);
107110
BOOST_CHECK(R1L == arith_uint256S(R1ArrayHex));
108111
BOOST_CHECK(arith_uint256(R1L) == R1L);
109112
BOOST_CHECK((arith_uint256(R1L^R2L)^R2L) == R1L);
@@ -278,6 +281,12 @@ BOOST_AUTO_TEST_CASE( comparison ) // <= >= < >
278281
BOOST_CHECK( R1L <= TmpL ); BOOST_CHECK( (R1L == TmpL) != (R1L < TmpL)); BOOST_CHECK( (TmpL == R1L) || !( R1L >= TmpL));
279282
BOOST_CHECK(! (TmpL < R1L)); BOOST_CHECK(! (R1L > TmpL));
280283
}
284+
285+
BOOST_CHECK_LT(ZeroL,
286+
OneL);
287+
// Verify hex number representation has the most significant digits first.
288+
BOOST_CHECK_LT(arith_uint256S("0000000000000000000000000000000000000000000000000000000000000001"),
289+
arith_uint256S("1000000000000000000000000000000000000000000000000000000000000000"));
281290
}
282291

283292
BOOST_AUTO_TEST_CASE( plusMinus )

src/test/uint256_tests.cpp

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ static std::string ArrayToString(const unsigned char A[], unsigned int width)
5858
return Stream.str();
5959
}
6060

61-
// Input is treated as little-endian.
61+
// Takes hex string in reverse byte order.
6262
inline uint160 uint160S(std::string_view str)
6363
{
6464
uint160 rv;
@@ -99,6 +99,7 @@ BOOST_AUTO_TEST_CASE( basics ) // constructors, equality, inequality
9999
BOOST_CHECK_EQUAL(uint256S(" 0x"+R1L.ToString()+"-trash;%^& "), R1L);
100100
BOOST_CHECK_EQUAL(uint256S("\t \n \n \f\n\r\t\v\t 0x"+R1L.ToString()+" \t \n \n \f\n\r\t\v\t "), R1L);
101101
BOOST_CHECK_EQUAL(uint256S(""), ZeroL);
102+
BOOST_CHECK_EQUAL(uint256S("1"), OneL);
102103
BOOST_CHECK_EQUAL(R1L, uint256S(R1ArrayHex));
103104
BOOST_CHECK_EQUAL(uint256(R1L), R1L);
104105
BOOST_CHECK_EQUAL(uint256(ZeroL), ZeroL);
@@ -152,9 +153,15 @@ BOOST_AUTO_TEST_CASE( comparison ) // <= >= < >
152153
BOOST_CHECK_LT(R1S, MaxS);
153154
BOOST_CHECK_LT(R2S, MaxS);
154155

155-
// Verify hex strings are little-endian
156-
BOOST_CHECK_LT(uint256S("2000000000000000000000000000000000000000000000000000000000000001"),
157-
uint256S("1000000000000000000000000000000000000000000000000000000000000002"));
156+
// Non-arithmetic uint256s compare from the beginning of their inner arrays:
157+
BOOST_CHECK_LT(R2L, R1L);
158+
// Ensure first element comparisons give the same order as above:
159+
BOOST_CHECK_LT(*R2L.begin(), *R1L.begin());
160+
// Ensure last element comparisons give a different result (swapped params):
161+
BOOST_CHECK_LT(*(R1L.end()-1), *(R2L.end()-1));
162+
// Hex strings represent reverse-encoded bytes, with lexicographic ordering:
163+
BOOST_CHECK_LT(uint256S("1000000000000000000000000000000000000000000000000000000000000000"),
164+
uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
158165
}
159166

160167
BOOST_AUTO_TEST_CASE(methods) // GetHex SetHexDeprecated FromHex begin() end() size() GetLow64 GetSerializeSize, Serialize, Unserialize
@@ -172,11 +179,11 @@ BOOST_AUTO_TEST_CASE(methods) // GetHex SetHexDeprecated FromHex begin() end() s
172179
BOOST_CHECK_EQUAL(uint256::FromHex(ZeroL.ToString()).value(), uint256());
173180

174181
TmpL = uint256::FromHex(R1L.ToString()).value();
175-
BOOST_CHECK_EQUAL_COLLECTIONS(R1L.begin(), R1L.end(), R1Array, R1Array + R1L.size());
176-
BOOST_CHECK_EQUAL_COLLECTIONS(TmpL.begin(), TmpL.end(), R1Array, R1Array + TmpL.size());
177-
BOOST_CHECK_EQUAL_COLLECTIONS(R2L.begin(), R2L.end(), R2Array, R2Array + R2L.size());
178-
BOOST_CHECK_EQUAL_COLLECTIONS(ZeroL.begin(), ZeroL.end(), ZeroArray, ZeroArray + ZeroL.size());
179-
BOOST_CHECK_EQUAL_COLLECTIONS(OneL.begin(), OneL.end(), OneArray, OneArray + OneL.size());
182+
BOOST_CHECK_EQUAL_COLLECTIONS(R1L.begin(), R1L.end(), R1Array, R1Array + uint256::size());
183+
BOOST_CHECK_EQUAL_COLLECTIONS(TmpL.begin(), TmpL.end(), R1Array, R1Array + uint256::size());
184+
BOOST_CHECK_EQUAL_COLLECTIONS(R2L.begin(), R2L.end(), R2Array, R2Array + uint256::size());
185+
BOOST_CHECK_EQUAL_COLLECTIONS(ZeroL.begin(), ZeroL.end(), ZeroArray, ZeroArray + uint256::size());
186+
BOOST_CHECK_EQUAL_COLLECTIONS(OneL.begin(), OneL.end(), OneArray, OneArray + uint256::size());
180187
BOOST_CHECK_EQUAL(R1L.size(), sizeof(R1L));
181188
BOOST_CHECK_EQUAL(sizeof(R1L), 32);
182189
BOOST_CHECK_EQUAL(R1L.size(), 32);
@@ -218,11 +225,11 @@ BOOST_AUTO_TEST_CASE(methods) // GetHex SetHexDeprecated FromHex begin() end() s
218225
BOOST_CHECK_EQUAL(uint160::FromHex(ZeroS.ToString()).value(), uint160());
219226

220227
TmpS = uint160::FromHex(R1S.ToString()).value();
221-
BOOST_CHECK_EQUAL_COLLECTIONS(R1S.begin(), R1S.end(), R1Array, R1Array + R1S.size());
222-
BOOST_CHECK_EQUAL_COLLECTIONS(TmpS.begin(), TmpS.end(), R1Array, R1Array + TmpS.size());
223-
BOOST_CHECK_EQUAL_COLLECTIONS(R2S.begin(), R2S.end(), R2Array, R2Array + R2S.size());
224-
BOOST_CHECK_EQUAL_COLLECTIONS(ZeroS.begin(), ZeroS.end(), ZeroArray, ZeroArray + ZeroS.size());
225-
BOOST_CHECK_EQUAL_COLLECTIONS(OneS.begin(), OneS.end(), OneArray, OneArray + OneS.size());
228+
BOOST_CHECK_EQUAL_COLLECTIONS(R1S.begin(), R1S.end(), R1Array, R1Array + uint160::size());
229+
BOOST_CHECK_EQUAL_COLLECTIONS(TmpS.begin(), TmpS.end(), R1Array, R1Array + uint160::size());
230+
BOOST_CHECK_EQUAL_COLLECTIONS(R2S.begin(), R2S.end(), R2Array, R2Array + uint160::size());
231+
BOOST_CHECK_EQUAL_COLLECTIONS(ZeroS.begin(), ZeroS.end(), ZeroArray, ZeroArray + uint160::size());
232+
BOOST_CHECK_EQUAL_COLLECTIONS(OneS.begin(), OneS.end(), OneArray, OneArray + uint160::size());
226233
BOOST_CHECK_EQUAL(R1S.size(), sizeof(R1S));
227234
BOOST_CHECK_EQUAL(sizeof(R1S), 20);
228235
BOOST_CHECK_EQUAL(R1S.size(), 20);
@@ -307,23 +314,23 @@ BOOST_AUTO_TEST_CASE(parse)
307314
{
308315
std::string s_12{"0000000000000000000000000000000000000000000000000000000000000012"};
309316
BOOST_CHECK_EQUAL(uint256S("12\0").GetHex(), s_12);
310-
BOOST_CHECK_EQUAL(uint256S(std::string{"12\0", 3}).GetHex(), s_12);
317+
BOOST_CHECK_EQUAL(uint256S(std::string_view{"12\0", 3}).GetHex(), s_12);
311318
BOOST_CHECK_EQUAL(uint256S("0x12").GetHex(), s_12);
312319
BOOST_CHECK_EQUAL(uint256S(" 0x12").GetHex(), s_12);
313320
BOOST_CHECK_EQUAL(uint256S(" 12").GetHex(), s_12);
314321
}
315322
{
316323
std::string s_1{uint256::ONE.GetHex()};
317324
BOOST_CHECK_EQUAL(uint256S("1\0").GetHex(), s_1);
318-
BOOST_CHECK_EQUAL(uint256S(std::string{"1\0", 2}).GetHex(), s_1);
325+
BOOST_CHECK_EQUAL(uint256S(std::string_view{"1\0", 2}).GetHex(), s_1);
319326
BOOST_CHECK_EQUAL(uint256S("0x1").GetHex(), s_1);
320327
BOOST_CHECK_EQUAL(uint256S(" 0x1").GetHex(), s_1);
321328
BOOST_CHECK_EQUAL(uint256S(" 1").GetHex(), s_1);
322329
}
323330
{
324331
std::string s_0{uint256::ZERO.GetHex()};
325332
BOOST_CHECK_EQUAL(uint256S("\0").GetHex(), s_0);
326-
BOOST_CHECK_EQUAL(uint256S(std::string{"\0", 1}).GetHex(), s_0);
333+
BOOST_CHECK_EQUAL(uint256S(std::string_view{"\0", 1}).GetHex(), s_0);
327334
BOOST_CHECK_EQUAL(uint256S("0x").GetHex(), s_0);
328335
BOOST_CHECK_EQUAL(uint256S(" 0x").GetHex(), s_0);
329336
BOOST_CHECK_EQUAL(uint256S(" ").GetHex(), s_0);

src/test/util/setup_common.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ static FastRandomContext g_insecure_rand_ctx_temp_path;
8181

8282
std::ostream& operator<<(std::ostream& os, const arith_uint256& num)
8383
{
84-
os << ArithToUint256(num).ToString();
84+
os << num.ToString();
8585
return os;
8686
}
8787

src/uint256.h

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,46 @@ class base_blob
5353
std::fill(m_data.begin(), m_data.end(), 0);
5454
}
5555

56+
/** Lexicographic ordering
57+
* @note Does NOT match the ordering on the corresponding \ref
58+
* base_uint::CompareTo, which starts comparing from the end.
59+
*/
5660
constexpr int Compare(const base_blob& other) const { return std::memcmp(m_data.data(), other.m_data.data(), WIDTH); }
5761

5862
friend constexpr bool operator==(const base_blob& a, const base_blob& b) { return a.Compare(b) == 0; }
5963
friend constexpr bool operator!=(const base_blob& a, const base_blob& b) { return a.Compare(b) != 0; }
6064
friend constexpr bool operator<(const base_blob& a, const base_blob& b) { return a.Compare(b) < 0; }
6165

62-
// Hex string representations are little-endian.
66+
/** @name Hex representation
67+
*
68+
* The reverse-byte hex representation is a convenient way to view the blob
69+
* as a number, because it is consistent with the way the base_uint class
70+
* converts blobs to numbers.
71+
*
72+
* @note base_uint treats the blob as an array of bytes with the numerically
73+
* least significant byte first and the most significant byte last. Because
74+
* numbers are typically written with the most significant digit first and
75+
* the least significant digit last, the reverse hex display of the blob
76+
* corresponds to the same numeric value that base_uint interprets from the
77+
* blob.
78+
* @{*/
6379
std::string GetHex() const;
64-
/** Unlike FromHex this accepts any invalid input, thus it is fragile and deprecated */
80+
/** Unlike FromHex this accepts any invalid input, thus it is fragile and deprecated!
81+
*
82+
* - Hex numbers that don't specify enough bytes to fill the internal array
83+
* will be treated as setting the beginning of it, which corresponds to
84+
* the least significant bytes when converted to base_uint.
85+
*
86+
* - Hex numbers specifying too many bytes will have the numerically most
87+
* significant bytes (the beginning of the string) narrowed away.
88+
*
89+
* - An odd count of hex digits will result in the high bits of the leftmost
90+
* byte being zero.
91+
* "0x123" => {0x23, 0x1, 0x0, ..., 0x0}
92+
*/
6593
void SetHexDeprecated(std::string_view str);
6694
std::string ToString() const;
95+
/**@}*/
6796

6897
constexpr const unsigned char* data() const { return m_data.data(); }
6998
constexpr unsigned char* data() { return m_data.data(); }
@@ -93,7 +122,7 @@ class base_blob
93122

94123
namespace detail {
95124
/**
96-
* Writes the hex string (treated as little-endian) into a new uintN_t object
125+
* Writes the hex string (in reverse byte order) into a new uintN_t object
97126
* and only returns a value iff all of the checks pass:
98127
* - Input length is uintN_t::size()*2
99128
* - All characters are hex
@@ -134,7 +163,7 @@ class uint256 : public base_blob<256> {
134163
static const uint256 ONE;
135164
};
136165

137-
/* uint256 from std::string_view, treated as little-endian.
166+
/* uint256 from std::string_view, containing byte-reversed hex encoding.
138167
* DEPRECATED. Unlike FromHex this accepts any invalid input, thus it is fragile and deprecated!
139168
*/
140169
inline uint256 uint256S(std::string_view str)

0 commit comments

Comments
 (0)