Skip to content

Commit 385b4c8

Browse files
apoelstraroconnor-blockstream
authored andcommitted
codex32: implement encoding and decoding
1 parent f76be05 commit 385b4c8

File tree

5 files changed

+512
-0
lines changed

5 files changed

+512
-0
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL
137137
chain.cpp
138138
chainparams.cpp
139139
chainparamsbase.cpp
140+
codex32.cpp
140141
coins.cpp
141142
common/args.cpp
142143
common/bloom.cpp

src/codex32.cpp

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
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 enc = bech32::internal::PreparePolynomialCoefficients(hrp, values, 0);
172+
auto res = PolyMod(enc, gen);
173+
for (size_t i = 0; i < res.size(); ++i) {
174+
if (res[i] != target[i]) {
175+
return 0;
176+
}
177+
}
178+
return 1;
179+
}
180+
181+
/** Create a checksum. */
182+
template <typename Residue>
183+
data CreateChecksum(const std::string& hrp, const data& values, const Residue& gen, const Residue& target)
184+
{
185+
auto enc = bech32::internal::PreparePolynomialCoefficients(hrp, values, gen.size());
186+
enc.insert(enc.end(), gen.size(), 0x00);
187+
const auto checksum = PolyMod(enc, gen);
188+
data ret(gen.size());
189+
for (size_t i = 0; i < checksum.size(); ++i) {
190+
ret[i] = checksum[i] ^ target[i];
191+
}
192+
return ret;
193+
}
194+
195+
} // namespace
196+
197+
/** Encode a codex32 string. */
198+
std::string Result::Encode() const {
199+
assert(IsValid());
200+
201+
const data checksum = m_data.size() <= 80
202+
? CreateChecksum(m_hrp, m_data, CODEX32_GEN, CODEX32_M)
203+
: CreateChecksum(m_hrp, m_data, CODEX32_LONG_GEN, CODEX32_LONG_M);
204+
return bech32::internal::Encode(m_hrp, m_data, checksum);
205+
}
206+
207+
/** Decode a codex32 string */
208+
Result::Result(const std::string& str) {
209+
m_valid = OK;
210+
211+
auto res = bech32::internal::Decode(str, 127, 6);
212+
213+
if (str.size() > 127) {
214+
m_valid = INVALID_LENGTH;
215+
// Early return since if we failed the max size check, Decode did not give us any data.
216+
return;
217+
} else if (res.first.empty() && res.second.empty()) {
218+
m_valid = BECH32_DECODE;
219+
return;
220+
} else if (res.first != "ms") {
221+
m_valid = INVALID_HRP;
222+
// Early return since if the HRP is wrong, all bets are off and no point continuing
223+
return;
224+
}
225+
m_hrp = std::move(res.first);
226+
227+
if (res.second.size() >= 45 && res.second.size() <= 90) {
228+
// If, after converting back to base-256, we have 5 or more bits of data
229+
// remaining, it means that we had an entire character of useless data,
230+
// which shouldn't have been included.
231+
if (((res.second.size() - 6 - 13) * 5) % 8 > 4) {
232+
m_valid = INVALID_LENGTH;
233+
} else if (VerifyChecksum(m_hrp, res.second, CODEX32_GEN, CODEX32_M)) {
234+
m_data = data(res.second.begin(), res.second.end() - 13);
235+
} else {
236+
m_valid = BAD_CHECKSUM;
237+
}
238+
} else if (res.second.size() >= 96 && res.second.size() <= 124) {
239+
if (((res.second.size() - 6 - 15) * 5) % 8 > 4) {
240+
m_valid = INVALID_LENGTH;
241+
} else if (VerifyChecksum(m_hrp, res.second, CODEX32_LONG_GEN, CODEX32_LONG_M)) {
242+
m_data = data(res.second.begin(), res.second.end() - 15);
243+
} else {
244+
m_valid = BAD_CHECKSUM;
245+
}
246+
} else {
247+
m_valid = INVALID_LENGTH;
248+
}
249+
250+
if (m_valid == OK) {
251+
auto k = bech32::internal::CHARSET[res.second[0]];
252+
if (k < '0' || k == '1' || k > '9') {
253+
m_valid = INVALID_K;
254+
}
255+
if (k == '0' && m_data[5] != 16) {
256+
// If the threshold is 0, the only allowable share is S
257+
m_valid = INVALID_SHARE_IDX;
258+
}
259+
}
260+
}
261+
262+
Result::Result(std::string&& hrp, size_t k, const std::string& id, char share_idx, const std::vector<unsigned char>& data) {
263+
m_valid = OK;
264+
if (hrp != "ms") {
265+
m_valid = INVALID_HRP;
266+
}
267+
m_hrp = hrp;
268+
if (k == 1 || k > 9) {
269+
m_valid = INVALID_K;
270+
}
271+
if (id.size() != 4) {
272+
m_valid = INVALID_ID_LEN;
273+
}
274+
int8_t sidx = bech32::internal::CHARSET_REV[(unsigned char) share_idx];
275+
if (sidx == -1) {
276+
m_valid = INVALID_SHARE_IDX;
277+
}
278+
if (k == 0 && sidx != 16) {
279+
// If the threshold is 0, the only allowable share is S
280+
m_valid = INVALID_SHARE_IDX;
281+
}
282+
for (size_t i = 0; i < id.size(); ++i) {
283+
if (bech32::internal::CHARSET_REV[(unsigned char) id[i]] == -1) {
284+
m_valid = INVALID_ID_CHAR;
285+
}
286+
}
287+
288+
if (m_valid != OK) {
289+
// early bail before allocating memory
290+
return;
291+
}
292+
293+
m_data.reserve(6 + ((data.size() * 8) + 4) / 5);
294+
m_data.push_back(bech32::internal::CHARSET_REV['0' + k]);
295+
m_data.push_back(bech32::internal::CHARSET_REV[(unsigned char) id[0]]);
296+
m_data.push_back(bech32::internal::CHARSET_REV[(unsigned char) id[1]]);
297+
m_data.push_back(bech32::internal::CHARSET_REV[(unsigned char) id[2]]);
298+
m_data.push_back(bech32::internal::CHARSET_REV[(unsigned char) id[3]]);
299+
m_data.push_back(sidx);
300+
ConvertBits<8, 5, true>([&](unsigned char c) { m_data.push_back(c); }, data.begin(), data.end());
301+
}
302+
303+
std::string Result::GetIdString() const {
304+
assert(IsValid());
305+
306+
std::string ret;
307+
ret.reserve(4);
308+
ret.push_back(bech32::internal::CHARSET[m_data[1]]);
309+
ret.push_back(bech32::internal::CHARSET[m_data[2]]);
310+
ret.push_back(bech32::internal::CHARSET[m_data[3]]);
311+
ret.push_back(bech32::internal::CHARSET[m_data[4]]);
312+
return ret;
313+
}
314+
315+
size_t Result::GetK() const {
316+
assert(IsValid());
317+
return bech32::internal::CHARSET[m_data[0]] - '0';
318+
}
319+
320+
} // namespace codex32

0 commit comments

Comments
 (0)