Skip to content

Commit fd6975d

Browse files
committed
codex32: implement encoding and decoding
1 parent 7a57cda commit fd6975d

File tree

5 files changed

+512
-0
lines changed

5 files changed

+512
-0
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ BITCOIN_CORE_H = \
134134
chainparamsseeds.h \
135135
checkqueue.h \
136136
clientversion.h \
137+
codex32.h \
137138
coins.h \
138139
common/args.h \
139140
common/bloom.h \
@@ -664,6 +665,7 @@ libbitcoin_common_a_SOURCES = \
664665
base58.cpp \
665666
bech32.cpp \
666667
chainparams.cpp \
668+
codex32.cpp \
667669
coins.cpp \
668670
common/args.cpp \
669671
common/bloom.cpp \

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ BITCOIN_TESTS =\
8383
test/bloom_tests.cpp \
8484
test/bswap_tests.cpp \
8585
test/checkqueue_tests.cpp \
86+
test/codex32_tests.cpp \
8687
test/coins_tests.cpp \
8788
test/coinstatsindex_tests.cpp \
8889
test/compilerbug_tests.cpp \

src/codex32.cpp

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
// Copyright (c) 2017, 2021 Pieter Wuille
2+
// Copyright (c) 2021-2022 The Bitcoin Core developers
3+
// Distributed under the MIT software license, see the accompanying
4+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
#include <bech32.h>
7+
#include <codex32.h>
8+
#include <util/vector.h>
9+
10+
#include <array>
11+
#include <assert.h>
12+
#include <numeric>
13+
#include <optional>
14+
15+
namespace codex32
16+
{
17+
18+
namespace
19+
{
20+
21+
typedef bech32::internal::data data;
22+
23+
// Build multiplication and logarithm tables for GF(32).
24+
//
25+
// We represent GF(32) as an extension of GF(2) by appending a root, alpha, of the
26+
// polynomial x^5 + x^3 + 1. All elements of GF(32) can be represented as degree-4
27+
// polynomials in alpha. So e.g. 1 is represented by 1, alpha by 2, alpha^2 by 4,
28+
// and so on.
29+
//
30+
// alpha is also a generator of the multiplicative group of the field. So every nonzero
31+
// element in GF(32) can be represented as alpha^i, for some i in {0, 1, ..., 31}.
32+
// This representation makes multiplication and division very easy, since it is just
33+
// addition and subtraction in the exponent.
34+
//
35+
// These tables allow converting from the normal binary representation of GF(32) elements
36+
// to the power-of-alpha one.
37+
constexpr std::pair<std::array<int8_t, 31>, std::array<int8_t, 32>> GenerateGF32Tables() {
38+
// We use these tables to perform arithmetic in GF(32) below, when constructing the
39+
// tables for GF(1024).
40+
std::array<int8_t, 31> GF32_EXP{};
41+
std::array<int8_t, 32> GF32_LOG{};
42+
43+
// fmod encodes the defining polynomial of GF(32) over GF(2), x^5 + x^3 + 1.
44+
// Because coefficients in GF(2) are binary digits, the coefficients are packed as 101001.
45+
const int fmod = 41;
46+
47+
// Elements of GF(32) are encoded as vectors of length 5 over GF(2), that is,
48+
// 5 binary digits. Each element (b_4, b_3, b_2, b_1, b_0) encodes a polynomial
49+
// b_4*x^4 + b_3*x^3 + b_2*x^2 + b_1*x^1 + b_0 (modulo fmod).
50+
// For example, 00001 = 1 is the multiplicative identity.
51+
GF32_EXP[0] = 1;
52+
GF32_LOG[0] = -1;
53+
GF32_LOG[1] = 0;
54+
int v = 1;
55+
for (int i = 1; i < 31; ++i) {
56+
// Multiplication by x is the same as shifting left by 1, as
57+
// every coefficient of the polynomial is moved up one place.
58+
v = v << 1;
59+
// If the polynomial now has an x^5 term, we subtract fmod from it
60+
// to remain working modulo fmod. Subtraction is the same as XOR in characteristic
61+
// 2 fields.
62+
if (v & 32) v ^= fmod;
63+
GF32_EXP[i] = v;
64+
GF32_LOG[v] = i;
65+
}
66+
67+
return std::make_pair(GF32_EXP, GF32_LOG);
68+
}
69+
70+
constexpr auto tables32 = GenerateGF32Tables();
71+
constexpr const std::array<int8_t, 31>& GF32_EXP = tables32.first;
72+
constexpr const std::array<int8_t, 32>& GF32_LOG = tables32.second;
73+
74+
uint8_t gf32_mul(uint8_t x, uint8_t y) {
75+
if (x == 0 || y == 0) {
76+
return 0;
77+
}
78+
return GF32_EXP[(GF32_LOG[x] + GF32_LOG[y]) % 31];
79+
}
80+
81+
// The bech32 string "secretshare32"
82+
constexpr const std::array<uint8_t, 13> CODEX32_M = {
83+
16, 25, 24, 3, 25, 11, 16, 23, 29, 3, 25, 17, 10
84+
};
85+
86+
// The bech32 string "secretshare32ex"
87+
constexpr const std::array<uint8_t, 15> CODEX32_LONG_M = {
88+
16, 25, 24, 3, 25, 11, 16, 23, 29, 3, 25, 17, 10, 25, 6,
89+
};
90+
91+
// The generator for the codex32 checksum, not including the leading x^13 term
92+
constexpr const std::array<uint8_t, 13> CODEX32_GEN = {
93+
25, 27, 17, 8, 0, 25, 25, 25, 31, 27, 24, 16, 16,
94+
};
95+
96+
// The generator for the long codex32 checksum, not including the leading x^15 term
97+
constexpr const std::array<uint8_t, 15> CODEX32_LONG_GEN = {
98+
15, 10, 25, 26, 9, 25, 21, 6, 23, 21, 6, 5, 22, 4, 23,
99+
};
100+
101+
/** This function will compute what 5-bit values to XOR into the last <checksum length>
102+
* input values, in order to make the checksum 0. These values are returned in an array
103+
* whose length is implied by the type of the generator polynomial (`CODEX32_GEN` or
104+
* `CODEX32_LONG_GEN`) that is passed in. The result should be xored with the target
105+
* residue ("secretshare32" or "secretshare32ex". */
106+
template <typename Residue>
107+
Residue PolyMod(const data& v, const Residue& gen)
108+
{
109+
// The input is interpreted as a list of coefficients of a polynomial over F = GF(32),
110+
// in the same way as in bech32. The block comment in bech32::<anonymous>::PolyMod
111+
// provides more details.
112+
//
113+
// Unlike bech32, the output consists of 13 5-bit values, rather than 6, so they cannot
114+
// be packed into a uint32_t, or even a uint64_t.
115+
//
116+
// Like bech32 we have a generator polynomial which defines the BCH code. For "short"
117+
// strings, whose data part is 93 characters or less, we use
118+
// g(x) = x^13 + {25}x^12 + {27}x^11 + {17}x^10 + {8}x^9 + {0}x^8 + {25}x^7
119+
// + {25}x^6 + {25}x^5 + {31}x^4 + {27}x^3 + {24}x^2 + {16}x + {16}
120+
//
121+
// For long strings, whose data part is more than 93 characters, we use
122+
// g(x) = x^15 + {15}x^14 + {10}x^13 + {25}x^12 + {26}x^11 + {9}x^10
123+
// + {25}x^9 + {21}x^8 + {6}x^7 + {23}x^6 + {21}x^5 + {6}x^4
124+
// + {5}x^3 + {22}x^2 + {4}x^1 + {23}
125+
//
126+
// In both cases g is chosen in such a way that the resulting code is a BCH code which
127+
// can detect up to 8 errors in a window of 93 characters. Unlike bech32, no further
128+
// optimization was done to achieve more detection capability than the design parameters.
129+
//
130+
// For information about the {n} encoding of GF32 elements, see the block comment in
131+
// bech32::<anonymous>::PolyMod.
132+
Residue res{};
133+
res[gen.size() - 1] = 1;
134+
for (const auto v_i : v) {
135+
// We want to update `res` to correspond to a polynomial with one extra term. That is,
136+
// we first multiply it by x and add the next character, which is done by left-shifting
137+
// the entire array and adding the next character to the open slot.
138+
//
139+
// We then reduce it module g, which involves taking the shifted-off character, multiplying
140+
// it by g, and adding it to the result of the previous step. This makes sense because after
141+
// multiplying by x, `res` has the same degree as g, so reduction by g simply requires
142+
// dividing the most significant coefficient of `res` by the most significant coefficient of
143+
// g (which is 1), then subtracting that multiple of g.
144+
//
145+
// Recall that we are working in a characteristic-2 field, so that subtraction is the same
146+
// thing as addition.
147+
148+
// Multiply by x
149+
uint8_t shift = res[0];
150+
for (size_t i = 1; i < res.size(); ++i) {
151+
res[i - 1] = res[i];
152+
}
153+
// Add the next value
154+
res[res.size() - 1] = v_i;
155+
// Reduce
156+
if (shift != 0) {
157+
for(size_t i = 0; i < res.size(); ++i) {
158+
if (gen[i] != 0) {
159+
res[i] ^= gf32_mul(gen[i], shift);
160+
}
161+
}
162+
}
163+
}
164+
return res;
165+
}
166+
167+
/** Verify a checksum. */
168+
template <typename Residue>
169+
bool VerifyChecksum(const std::string& hrp, const data& values, const Residue& gen, const Residue& target)
170+
{
171+
auto res = PolyMod(Cat(bech32::internal::ExpandHRP(hrp), values), gen);
172+
for (size_t i = 0; i < res.size(); ++i) {
173+
if (res[i] != target[i]) {
174+
return 0;
175+
}
176+
}
177+
return 1;
178+
}
179+
180+
/** Create a checksum. */
181+
template <typename Residue>
182+
data CreateChecksum(const std::string& hrp, const data& values, const Residue& gen, const Residue& target)
183+
{
184+
data enc = Cat(bech32::internal::ExpandHRP(hrp), values);
185+
enc.resize(enc.size() + gen.size());
186+
const auto checksum = PolyMod(enc, gen);
187+
data ret(gen.size());
188+
for (size_t i = 0; i < checksum.size(); ++i) {
189+
ret[i] = checksum[i] ^ target[i];
190+
}
191+
return ret;
192+
}
193+
194+
} // namespace
195+
196+
/** Encode a codex32 string. */
197+
std::string Result::Encode() const {
198+
assert(IsValid());
199+
200+
const data checksum = m_data.size() <= 80
201+
? CreateChecksum(m_hrp, m_data, CODEX32_GEN, CODEX32_M)
202+
: CreateChecksum(m_hrp, m_data, CODEX32_LONG_GEN, CODEX32_LONG_M);
203+
return bech32::internal::Encode(m_hrp, m_data, checksum);
204+
}
205+
206+
/** Decode a codex32 string */
207+
Result::Result(const std::string& str) {
208+
m_valid = OK;
209+
210+
auto res = bech32::internal::Decode(str, 127, 6);
211+
212+
if (str.size() > 127) {
213+
m_valid = INVALID_LENGTH;
214+
// Early return since if we failed the max size check, Decode did not give us any data.
215+
return;
216+
} else if (res.first.empty() && res.second.empty()) {
217+
m_valid = BECH32_DECODE;
218+
return;
219+
} else if (res.first != "ms") {
220+
m_valid = INVALID_HRP;
221+
// Early return since if the HRP is wrong, all bets are off and no point continuing
222+
return;
223+
}
224+
m_hrp = std::move(res.first);
225+
226+
if (res.second.size() >= 45 && res.second.size() <= 90) {
227+
// If, after converting back to base-256, we have 5 or more bits of data
228+
// remaining, it means that we had an entire character of useless data,
229+
// which shouldn't have been included.
230+
if (((res.second.size() - 6 - 13) * 5) % 8 > 4) {
231+
m_valid = INVALID_LENGTH;
232+
} else if (VerifyChecksum(m_hrp, res.second, CODEX32_GEN, CODEX32_M)) {
233+
m_data = data(res.second.begin(), res.second.end() - 13);
234+
} else {
235+
m_valid = BAD_CHECKSUM;
236+
}
237+
} else if (res.second.size() >= 96 && res.second.size() <= 124) {
238+
if (((res.second.size() - 6 - 15) * 5) % 8 > 4) {
239+
m_valid = INVALID_LENGTH;
240+
} else if (VerifyChecksum(m_hrp, res.second, CODEX32_LONG_GEN, CODEX32_LONG_M)) {
241+
m_data = data(res.second.begin(), res.second.end() - 15);
242+
} else {
243+
m_valid = BAD_CHECKSUM;
244+
}
245+
} else {
246+
m_valid = INVALID_LENGTH;
247+
}
248+
249+
if (m_valid == OK) {
250+
auto k = bech32::internal::CHARSET[res.second[0]];
251+
if (k < '0' || k == '1' || k > '9') {
252+
m_valid = INVALID_K;
253+
}
254+
if (k == '0' && m_data[5] != 16) {
255+
// If the threshold is 0, the only allowable share is S
256+
m_valid = INVALID_SHARE_IDX;
257+
}
258+
}
259+
}
260+
261+
Result::Result(std::string&& hrp, size_t k, const std::string& id, char share_idx, const std::vector<unsigned char>& data) {
262+
m_valid = OK;
263+
if (hrp != "ms") {
264+
m_valid = INVALID_HRP;
265+
}
266+
m_hrp = hrp;
267+
if (k == 1 || k > 9) {
268+
m_valid = INVALID_K;
269+
}
270+
if (id.size() != 4) {
271+
m_valid = INVALID_ID_LEN;
272+
}
273+
int8_t sidx = bech32::internal::CHARSET_REV[(unsigned char) share_idx];
274+
if (sidx == -1) {
275+
m_valid = INVALID_SHARE_IDX;
276+
}
277+
if (k == 0 && sidx != 16) {
278+
// If the threshold is 0, the only allowable share is S
279+
m_valid = INVALID_SHARE_IDX;
280+
}
281+
for (size_t i = 0; i < id.size(); ++i) {
282+
if (bech32::internal::CHARSET_REV[(unsigned char) id[i]] == -1) {
283+
m_valid = INVALID_ID_CHAR;
284+
}
285+
}
286+
287+
if (m_valid != OK) {
288+
// early bail before allocating memory
289+
return;
290+
}
291+
292+
m_data.reserve(6 + ((data.size() * 8) + 4) / 5);
293+
m_data.push_back(bech32::internal::CHARSET_REV['0' + k]);
294+
m_data.push_back(bech32::internal::CHARSET_REV[(unsigned char) id[0]]);
295+
m_data.push_back(bech32::internal::CHARSET_REV[(unsigned char) id[1]]);
296+
m_data.push_back(bech32::internal::CHARSET_REV[(unsigned char) id[2]]);
297+
m_data.push_back(bech32::internal::CHARSET_REV[(unsigned char) id[3]]);
298+
m_data.push_back(sidx);
299+
ConvertBits<8, 5, true>([&](unsigned char c) { m_data.push_back(c); }, data.begin(), data.end());
300+
}
301+
302+
std::string Result::GetIdString() const {
303+
assert(IsValid());
304+
305+
std::string ret;
306+
ret.reserve(4);
307+
ret.push_back(bech32::internal::CHARSET[m_data[1]]);
308+
ret.push_back(bech32::internal::CHARSET[m_data[2]]);
309+
ret.push_back(bech32::internal::CHARSET[m_data[3]]);
310+
ret.push_back(bech32::internal::CHARSET[m_data[4]]);
311+
return ret;
312+
}
313+
314+
size_t Result::GetK() const {
315+
assert(IsValid());
316+
return bech32::internal::CHARSET[m_data[0]] - '0';
317+
}
318+
319+
} // namespace codex32

0 commit comments

Comments
 (0)