mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-08-04 14:04:49 +02:00
codex32: implement encoding and decoding
Github-Pull: #27351 Rebased-From: fd6975d631f12ff7947db9abfdda4147ddc6cc24
This commit is contained in:
parent
55a5d2e898
commit
82a16425ef
@ -134,6 +134,7 @@ BITCOIN_CORE_H = \
|
|||||||
chainparamsseeds.h \
|
chainparamsseeds.h \
|
||||||
checkqueue.h \
|
checkqueue.h \
|
||||||
clientversion.h \
|
clientversion.h \
|
||||||
|
codex32.h \
|
||||||
coins.h \
|
coins.h \
|
||||||
common/args.h \
|
common/args.h \
|
||||||
common/bloom.h \
|
common/bloom.h \
|
||||||
@ -670,6 +671,7 @@ libbitcoin_common_a_SOURCES = \
|
|||||||
base58.cpp \
|
base58.cpp \
|
||||||
bech32.cpp \
|
bech32.cpp \
|
||||||
chainparams.cpp \
|
chainparams.cpp \
|
||||||
|
codex32.cpp \
|
||||||
coins.cpp \
|
coins.cpp \
|
||||||
common/args.cpp \
|
common/args.cpp \
|
||||||
common/bloom.cpp \
|
common/bloom.cpp \
|
||||||
|
@ -83,6 +83,7 @@ BITCOIN_TESTS =\
|
|||||||
test/bloom_tests.cpp \
|
test/bloom_tests.cpp \
|
||||||
test/bswap_tests.cpp \
|
test/bswap_tests.cpp \
|
||||||
test/checkqueue_tests.cpp \
|
test/checkqueue_tests.cpp \
|
||||||
|
test/codex32_tests.cpp \
|
||||||
test/coins_tests.cpp \
|
test/coins_tests.cpp \
|
||||||
test/coinstatsindex_tests.cpp \
|
test/coinstatsindex_tests.cpp \
|
||||||
test/compilerbug_tests.cpp \
|
test/compilerbug_tests.cpp \
|
||||||
|
319
src/codex32.cpp
Normal file
319
src/codex32.cpp
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
// Copyright (c) 2017, 2021 Pieter Wuille
|
||||||
|
// Copyright (c) 2021-2022 The Bitcoin Core developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include <bech32.h>
|
||||||
|
#include <codex32.h>
|
||||||
|
#include <util/vector.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <numeric>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace codex32
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
typedef bech32::internal::data data;
|
||||||
|
|
||||||
|
// Build multiplication and logarithm tables for GF(32).
|
||||||
|
//
|
||||||
|
// We represent GF(32) as an extension of GF(2) by appending a root, alpha, of the
|
||||||
|
// polynomial x^5 + x^3 + 1. All elements of GF(32) can be represented as degree-4
|
||||||
|
// polynomials in alpha. So e.g. 1 is represented by 1, alpha by 2, alpha^2 by 4,
|
||||||
|
// and so on.
|
||||||
|
//
|
||||||
|
// alpha is also a generator of the multiplicative group of the field. So every nonzero
|
||||||
|
// element in GF(32) can be represented as alpha^i, for some i in {0, 1, ..., 31}.
|
||||||
|
// This representation makes multiplication and division very easy, since it is just
|
||||||
|
// addition and subtraction in the exponent.
|
||||||
|
//
|
||||||
|
// These tables allow converting from the normal binary representation of GF(32) elements
|
||||||
|
// to the power-of-alpha one.
|
||||||
|
constexpr std::pair<std::array<int8_t, 31>, std::array<int8_t, 32>> GenerateGF32Tables() {
|
||||||
|
// We use these tables to perform arithmetic in GF(32) below, when constructing the
|
||||||
|
// tables for GF(1024).
|
||||||
|
std::array<int8_t, 31> GF32_EXP{};
|
||||||
|
std::array<int8_t, 32> GF32_LOG{};
|
||||||
|
|
||||||
|
// fmod encodes the defining polynomial of GF(32) over GF(2), x^5 + x^3 + 1.
|
||||||
|
// Because coefficients in GF(2) are binary digits, the coefficients are packed as 101001.
|
||||||
|
const int fmod = 41;
|
||||||
|
|
||||||
|
// Elements of GF(32) are encoded as vectors of length 5 over GF(2), that is,
|
||||||
|
// 5 binary digits. Each element (b_4, b_3, b_2, b_1, b_0) encodes a polynomial
|
||||||
|
// b_4*x^4 + b_3*x^3 + b_2*x^2 + b_1*x^1 + b_0 (modulo fmod).
|
||||||
|
// For example, 00001 = 1 is the multiplicative identity.
|
||||||
|
GF32_EXP[0] = 1;
|
||||||
|
GF32_LOG[0] = -1;
|
||||||
|
GF32_LOG[1] = 0;
|
||||||
|
int v = 1;
|
||||||
|
for (int i = 1; i < 31; ++i) {
|
||||||
|
// Multiplication by x is the same as shifting left by 1, as
|
||||||
|
// every coefficient of the polynomial is moved up one place.
|
||||||
|
v = v << 1;
|
||||||
|
// If the polynomial now has an x^5 term, we subtract fmod from it
|
||||||
|
// to remain working modulo fmod. Subtraction is the same as XOR in characteristic
|
||||||
|
// 2 fields.
|
||||||
|
if (v & 32) v ^= fmod;
|
||||||
|
GF32_EXP[i] = v;
|
||||||
|
GF32_LOG[v] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_pair(GF32_EXP, GF32_LOG);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto tables32 = GenerateGF32Tables();
|
||||||
|
constexpr const std::array<int8_t, 31>& GF32_EXP = tables32.first;
|
||||||
|
constexpr const std::array<int8_t, 32>& GF32_LOG = tables32.second;
|
||||||
|
|
||||||
|
uint8_t gf32_mul(uint8_t x, uint8_t y) {
|
||||||
|
if (x == 0 || y == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return GF32_EXP[(GF32_LOG[x] + GF32_LOG[y]) % 31];
|
||||||
|
}
|
||||||
|
|
||||||
|
// The bech32 string "secretshare32"
|
||||||
|
constexpr const std::array<uint8_t, 13> CODEX32_M = {
|
||||||
|
16, 25, 24, 3, 25, 11, 16, 23, 29, 3, 25, 17, 10
|
||||||
|
};
|
||||||
|
|
||||||
|
// The bech32 string "secretshare32ex"
|
||||||
|
constexpr const std::array<uint8_t, 15> CODEX32_LONG_M = {
|
||||||
|
16, 25, 24, 3, 25, 11, 16, 23, 29, 3, 25, 17, 10, 25, 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The generator for the codex32 checksum, not including the leading x^13 term
|
||||||
|
constexpr const std::array<uint8_t, 13> CODEX32_GEN = {
|
||||||
|
25, 27, 17, 8, 0, 25, 25, 25, 31, 27, 24, 16, 16,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The generator for the long codex32 checksum, not including the leading x^15 term
|
||||||
|
constexpr const std::array<uint8_t, 15> CODEX32_LONG_GEN = {
|
||||||
|
15, 10, 25, 26, 9, 25, 21, 6, 23, 21, 6, 5, 22, 4, 23,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** This function will compute what 5-bit values to XOR into the last <checksum length>
|
||||||
|
* input values, in order to make the checksum 0. These values are returned in an array
|
||||||
|
* whose length is implied by the type of the generator polynomial (`CODEX32_GEN` or
|
||||||
|
* `CODEX32_LONG_GEN`) that is passed in. The result should be xored with the target
|
||||||
|
* residue ("secretshare32" or "secretshare32ex". */
|
||||||
|
template <typename Residue>
|
||||||
|
Residue PolyMod(const data& v, const Residue& gen)
|
||||||
|
{
|
||||||
|
// The input is interpreted as a list of coefficients of a polynomial over F = GF(32),
|
||||||
|
// in the same way as in bech32. The block comment in bech32::<anonymous>::PolyMod
|
||||||
|
// provides more details.
|
||||||
|
//
|
||||||
|
// Unlike bech32, the output consists of 13 5-bit values, rather than 6, so they cannot
|
||||||
|
// be packed into a uint32_t, or even a uint64_t.
|
||||||
|
//
|
||||||
|
// Like bech32 we have a generator polynomial which defines the BCH code. For "short"
|
||||||
|
// strings, whose data part is 93 characters or less, we use
|
||||||
|
// g(x) = x^13 + {25}x^12 + {27}x^11 + {17}x^10 + {8}x^9 + {0}x^8 + {25}x^7
|
||||||
|
// + {25}x^6 + {25}x^5 + {31}x^4 + {27}x^3 + {24}x^2 + {16}x + {16}
|
||||||
|
//
|
||||||
|
// For long strings, whose data part is more than 93 characters, we use
|
||||||
|
// g(x) = x^15 + {15}x^14 + {10}x^13 + {25}x^12 + {26}x^11 + {9}x^10
|
||||||
|
// + {25}x^9 + {21}x^8 + {6}x^7 + {23}x^6 + {21}x^5 + {6}x^4
|
||||||
|
// + {5}x^3 + {22}x^2 + {4}x^1 + {23}
|
||||||
|
//
|
||||||
|
// In both cases g is chosen in such a way that the resulting code is a BCH code which
|
||||||
|
// can detect up to 8 errors in a window of 93 characters. Unlike bech32, no further
|
||||||
|
// optimization was done to achieve more detection capability than the design parameters.
|
||||||
|
//
|
||||||
|
// For information about the {n} encoding of GF32 elements, see the block comment in
|
||||||
|
// bech32::<anonymous>::PolyMod.
|
||||||
|
Residue res{};
|
||||||
|
res[gen.size() - 1] = 1;
|
||||||
|
for (const auto v_i : v) {
|
||||||
|
// We want to update `res` to correspond to a polynomial with one extra term. That is,
|
||||||
|
// we first multiply it by x and add the next character, which is done by left-shifting
|
||||||
|
// the entire array and adding the next character to the open slot.
|
||||||
|
//
|
||||||
|
// We then reduce it module g, which involves taking the shifted-off character, multiplying
|
||||||
|
// it by g, and adding it to the result of the previous step. This makes sense because after
|
||||||
|
// multiplying by x, `res` has the same degree as g, so reduction by g simply requires
|
||||||
|
// dividing the most significant coefficient of `res` by the most significant coefficient of
|
||||||
|
// g (which is 1), then subtracting that multiple of g.
|
||||||
|
//
|
||||||
|
// Recall that we are working in a characteristic-2 field, so that subtraction is the same
|
||||||
|
// thing as addition.
|
||||||
|
|
||||||
|
// Multiply by x
|
||||||
|
uint8_t shift = res[0];
|
||||||
|
for (size_t i = 1; i < res.size(); ++i) {
|
||||||
|
res[i - 1] = res[i];
|
||||||
|
}
|
||||||
|
// Add the next value
|
||||||
|
res[res.size() - 1] = v_i;
|
||||||
|
// Reduce
|
||||||
|
if (shift != 0) {
|
||||||
|
for(size_t i = 0; i < res.size(); ++i) {
|
||||||
|
if (gen[i] != 0) {
|
||||||
|
res[i] ^= gf32_mul(gen[i], shift);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Verify a checksum. */
|
||||||
|
template <typename Residue>
|
||||||
|
bool VerifyChecksum(const std::string& hrp, const data& values, const Residue& gen, const Residue& target)
|
||||||
|
{
|
||||||
|
auto res = PolyMod(Cat(bech32::internal::ExpandHRP(hrp), values), gen);
|
||||||
|
for (size_t i = 0; i < res.size(); ++i) {
|
||||||
|
if (res[i] != target[i]) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a checksum. */
|
||||||
|
template <typename Residue>
|
||||||
|
data CreateChecksum(const std::string& hrp, const data& values, const Residue& gen, const Residue& target)
|
||||||
|
{
|
||||||
|
data enc = Cat(bech32::internal::ExpandHRP(hrp), values);
|
||||||
|
enc.resize(enc.size() + gen.size());
|
||||||
|
const auto checksum = PolyMod(enc, gen);
|
||||||
|
data ret(gen.size());
|
||||||
|
for (size_t i = 0; i < checksum.size(); ++i) {
|
||||||
|
ret[i] = checksum[i] ^ target[i];
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
/** Encode a codex32 string. */
|
||||||
|
std::string Result::Encode() const {
|
||||||
|
assert(IsValid());
|
||||||
|
|
||||||
|
const data checksum = m_data.size() <= 80
|
||||||
|
? CreateChecksum(m_hrp, m_data, CODEX32_GEN, CODEX32_M)
|
||||||
|
: CreateChecksum(m_hrp, m_data, CODEX32_LONG_GEN, CODEX32_LONG_M);
|
||||||
|
return bech32::internal::Encode(m_hrp, m_data, checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Decode a codex32 string */
|
||||||
|
Result::Result(const std::string& str) {
|
||||||
|
m_valid = OK;
|
||||||
|
|
||||||
|
auto res = bech32::internal::Decode(str, 127, 6);
|
||||||
|
|
||||||
|
if (str.size() > 127) {
|
||||||
|
m_valid = INVALID_LENGTH;
|
||||||
|
// Early return since if we failed the max size check, Decode did not give us any data.
|
||||||
|
return;
|
||||||
|
} else if (res.first.empty() && res.second.empty()) {
|
||||||
|
m_valid = BECH32_DECODE;
|
||||||
|
return;
|
||||||
|
} else if (res.first != "ms") {
|
||||||
|
m_valid = INVALID_HRP;
|
||||||
|
// Early return since if the HRP is wrong, all bets are off and no point continuing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_hrp = std::move(res.first);
|
||||||
|
|
||||||
|
if (res.second.size() >= 45 && res.second.size() <= 90) {
|
||||||
|
// If, after converting back to base-256, we have 5 or more bits of data
|
||||||
|
// remaining, it means that we had an entire character of useless data,
|
||||||
|
// which shouldn't have been included.
|
||||||
|
if (((res.second.size() - 6 - 13) * 5) % 8 > 4) {
|
||||||
|
m_valid = INVALID_LENGTH;
|
||||||
|
} else if (VerifyChecksum(m_hrp, res.second, CODEX32_GEN, CODEX32_M)) {
|
||||||
|
m_data = data(res.second.begin(), res.second.end() - 13);
|
||||||
|
} else {
|
||||||
|
m_valid = BAD_CHECKSUM;
|
||||||
|
}
|
||||||
|
} else if (res.second.size() >= 96 && res.second.size() <= 124) {
|
||||||
|
if (((res.second.size() - 6 - 15) * 5) % 8 > 4) {
|
||||||
|
m_valid = INVALID_LENGTH;
|
||||||
|
} else if (VerifyChecksum(m_hrp, res.second, CODEX32_LONG_GEN, CODEX32_LONG_M)) {
|
||||||
|
m_data = data(res.second.begin(), res.second.end() - 15);
|
||||||
|
} else {
|
||||||
|
m_valid = BAD_CHECKSUM;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_valid = INVALID_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_valid == OK) {
|
||||||
|
auto k = bech32::internal::CHARSET[res.second[0]];
|
||||||
|
if (k < '0' || k == '1' || k > '9') {
|
||||||
|
m_valid = INVALID_K;
|
||||||
|
}
|
||||||
|
if (k == '0' && m_data[5] != 16) {
|
||||||
|
// If the threshold is 0, the only allowable share is S
|
||||||
|
m_valid = INVALID_SHARE_IDX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result::Result(std::string&& hrp, size_t k, const std::string& id, char share_idx, const std::vector<unsigned char>& data) {
|
||||||
|
m_valid = OK;
|
||||||
|
if (hrp != "ms") {
|
||||||
|
m_valid = INVALID_HRP;
|
||||||
|
}
|
||||||
|
m_hrp = hrp;
|
||||||
|
if (k == 1 || k > 9) {
|
||||||
|
m_valid = INVALID_K;
|
||||||
|
}
|
||||||
|
if (id.size() != 4) {
|
||||||
|
m_valid = INVALID_ID_LEN;
|
||||||
|
}
|
||||||
|
int8_t sidx = bech32::internal::CHARSET_REV[(unsigned char) share_idx];
|
||||||
|
if (sidx == -1) {
|
||||||
|
m_valid = INVALID_SHARE_IDX;
|
||||||
|
}
|
||||||
|
if (k == 0 && sidx != 16) {
|
||||||
|
// If the threshold is 0, the only allowable share is S
|
||||||
|
m_valid = INVALID_SHARE_IDX;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < id.size(); ++i) {
|
||||||
|
if (bech32::internal::CHARSET_REV[(unsigned char) id[i]] == -1) {
|
||||||
|
m_valid = INVALID_ID_CHAR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_valid != OK) {
|
||||||
|
// early bail before allocating memory
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_data.reserve(6 + ((data.size() * 8) + 4) / 5);
|
||||||
|
m_data.push_back(bech32::internal::CHARSET_REV['0' + k]);
|
||||||
|
m_data.push_back(bech32::internal::CHARSET_REV[(unsigned char) id[0]]);
|
||||||
|
m_data.push_back(bech32::internal::CHARSET_REV[(unsigned char) id[1]]);
|
||||||
|
m_data.push_back(bech32::internal::CHARSET_REV[(unsigned char) id[2]]);
|
||||||
|
m_data.push_back(bech32::internal::CHARSET_REV[(unsigned char) id[3]]);
|
||||||
|
m_data.push_back(sidx);
|
||||||
|
ConvertBits<8, 5, true>([&](unsigned char c) { m_data.push_back(c); }, data.begin(), data.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Result::GetIdString() const {
|
||||||
|
assert(IsValid());
|
||||||
|
|
||||||
|
std::string ret;
|
||||||
|
ret.reserve(4);
|
||||||
|
ret.push_back(bech32::internal::CHARSET[m_data[1]]);
|
||||||
|
ret.push_back(bech32::internal::CHARSET[m_data[2]]);
|
||||||
|
ret.push_back(bech32::internal::CHARSET[m_data[3]]);
|
||||||
|
ret.push_back(bech32::internal::CHARSET[m_data[4]]);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Result::GetK() const {
|
||||||
|
assert(IsValid());
|
||||||
|
return bech32::internal::CHARSET[m_data[0]] - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace codex32
|
106
src/codex32.h
Normal file
106
src/codex32.h
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// Copyright (c) 2023 The Bitcoin Core developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
// codex32 is a string encoding format for BIP-32 seeds. Like bech32 and
|
||||||
|
// bech32m, the outputs consist of a human-readable part (alphanumeric),
|
||||||
|
// a separator character (1), and a base32 data section. The final 13
|
||||||
|
// characters are a checksum.
|
||||||
|
//
|
||||||
|
// For more information, see BIP 93.
|
||||||
|
|
||||||
|
#ifndef BITCOIN_CODEX32_H
|
||||||
|
#define BITCOIN_CODEX32_H
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <bech32.h>
|
||||||
|
#include <util/strencodings.h>
|
||||||
|
|
||||||
|
namespace codex32
|
||||||
|
{
|
||||||
|
|
||||||
|
enum Error {
|
||||||
|
OK,
|
||||||
|
BAD_CHECKSUM,
|
||||||
|
BECH32_DECODE,
|
||||||
|
INVALID_HRP,
|
||||||
|
INVALID_ID_LEN,
|
||||||
|
INVALID_ID_CHAR,
|
||||||
|
INVALID_LENGTH,
|
||||||
|
INVALID_K,
|
||||||
|
INVALID_SHARE_IDX,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Result
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Construct a codex32 result by parsing a string */
|
||||||
|
Result(const std::string& str);
|
||||||
|
|
||||||
|
/** Construct a codex32 directly from a HRP, k, seed ID, share index and payload
|
||||||
|
*
|
||||||
|
* This constructor requires the hrp to be the lowercase string "ms", but will
|
||||||
|
* ignore the case of `id` and `share_idx`. */
|
||||||
|
Result(std::string&& hrp, size_t k, const std::string& id, char share_idx, const std::vector<unsigned char>& data);
|
||||||
|
|
||||||
|
/** Boolean indicating whether the data was successfully parsed.
|
||||||
|
*
|
||||||
|
* If this returns false, most of the other methods on this class will assert. */
|
||||||
|
bool IsValid() const {
|
||||||
|
return m_valid == OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Accessor for the specific parsing/construction error */
|
||||||
|
Error error() const {
|
||||||
|
return m_valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Accessor for the human-readable part of the codex32 string */
|
||||||
|
const std::string& GetHrp() const {
|
||||||
|
assert(IsValid());
|
||||||
|
return m_hrp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Accessor for the seed ID, in string form */
|
||||||
|
std::string GetIdString() const;
|
||||||
|
|
||||||
|
/** Accessor for the secret sharing threshold; 0 for a bare seed; (size_t)-1 if unavailable/invalid */
|
||||||
|
size_t GetK() const;
|
||||||
|
|
||||||
|
/** Accessor for the share index; (uint8_t)-1 if unavailable/invalid */
|
||||||
|
char GetShareIndex() const {
|
||||||
|
assert(IsValid());
|
||||||
|
return bech32::internal::CHARSET[m_data[5]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Accessor for the binary payload data (in base 256, not gf32) */
|
||||||
|
std::vector<unsigned char> GetPayload() const {
|
||||||
|
assert(IsValid());
|
||||||
|
|
||||||
|
std::vector<unsigned char> ret;
|
||||||
|
ret.reserve(((m_data.size() - 6) * 5) / 8);
|
||||||
|
// Note that `ConvertBits` returns a bool indicating whether or not nonzero bits
|
||||||
|
// were discarded. In BIP 93, we discard bits regardless of whether they are 0,
|
||||||
|
// so this is not an error and does not need to be checked.
|
||||||
|
ConvertBits<5, 8, false>([&](unsigned char c) { ret.push_back(c); }, m_data.begin() + 6, m_data.end());
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** (Re-)encode the codex32 data as a hrp string */
|
||||||
|
std::string Encode() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Error m_valid; //!< codex32::OK if the string was decoded correctly
|
||||||
|
|
||||||
|
std::string m_hrp; //!< The human readable part
|
||||||
|
std::vector<uint8_t> m_data; //!< The payload (remaining data, excluding checksum)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace codex32
|
||||||
|
|
||||||
|
#endif // BITCOIN_CODEX32_H
|
84
src/test/codex32_tests.cpp
Normal file
84
src/test/codex32_tests.cpp
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright (c) 2023 The Bitcoin Core developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include <bech32.h>
|
||||||
|
#include <codex32.h>
|
||||||
|
#include <test/util/str.h>
|
||||||
|
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE(codex32_tests)
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(codex32_bip93_misc_invalid)
|
||||||
|
{
|
||||||
|
// This example uses a "0" threshold with a non-"s" index
|
||||||
|
const std::string input1 = "ms10fauxxxxxxxxxxxxxxxxxxxxxxxxxxxx0z26tfn0ulw3p";
|
||||||
|
const auto dec1 = codex32::Result{input1};
|
||||||
|
BOOST_CHECK_EQUAL(dec1.error(), codex32::INVALID_SHARE_IDX);
|
||||||
|
|
||||||
|
// This example has a threshold that is not a digit.
|
||||||
|
const std::string input2 = "ms1fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxda3kr3s0s2swg";
|
||||||
|
const auto dec2 = codex32::Result{input2};
|
||||||
|
BOOST_CHECK_EQUAL(dec2.error(), codex32::INVALID_K);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(codex32_bip93_vector_1)
|
||||||
|
{
|
||||||
|
const std::string input = "ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw";
|
||||||
|
|
||||||
|
const auto dec = codex32::Result{input};
|
||||||
|
BOOST_CHECK(dec.IsValid());
|
||||||
|
BOOST_CHECK_EQUAL(dec.error(), codex32::OK);
|
||||||
|
BOOST_CHECK_EQUAL(dec.GetHrp(), "ms");
|
||||||
|
BOOST_CHECK_EQUAL(dec.GetK(), 0);
|
||||||
|
BOOST_CHECK_EQUAL(dec.GetIdString(), "test");
|
||||||
|
BOOST_CHECK_EQUAL(dec.GetShareIndex(), 's');
|
||||||
|
|
||||||
|
const auto payload = dec.GetPayload();
|
||||||
|
BOOST_CHECK_EQUAL(payload.size(), 16);
|
||||||
|
BOOST_CHECK_EQUAL(HexStr(payload), "318c6318c6318c6318c6318c6318c631");
|
||||||
|
|
||||||
|
// Try re-encoding
|
||||||
|
BOOST_CHECK_EQUAL(input, dec.Encode());
|
||||||
|
|
||||||
|
// Try directly constructing the share
|
||||||
|
const auto direct = codex32::Result("ms", 0, "test", 's', payload);
|
||||||
|
BOOST_CHECK(direct.IsValid());
|
||||||
|
BOOST_CHECK_EQUAL(direct.error(), codex32::OK);
|
||||||
|
BOOST_CHECK_EQUAL(direct.GetIdString(), "test");
|
||||||
|
BOOST_CHECK_EQUAL(direct.GetShareIndex(), 's');
|
||||||
|
|
||||||
|
// We cannot check that the codex32 string is exactly the same as the
|
||||||
|
// input, since it will not be -- the test vector has nonzero trailing
|
||||||
|
// bits while our code will always choose zero trailing bits. But we
|
||||||
|
// can at least check that the payloads are identical.
|
||||||
|
const auto payload_direct = direct.GetPayload();
|
||||||
|
BOOST_CHECK_EQUAL(HexStr(payload), HexStr(payload_direct));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(codex32_bip93_vector_2)
|
||||||
|
{
|
||||||
|
const std::string input_a = "MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM";
|
||||||
|
const auto dec_a = codex32::Result{input_a};
|
||||||
|
BOOST_CHECK(dec_a.IsValid());
|
||||||
|
BOOST_CHECK_EQUAL(dec_a.GetHrp(), "ms");
|
||||||
|
BOOST_CHECK_EQUAL(dec_a.GetK(), 2);
|
||||||
|
BOOST_CHECK_EQUAL(dec_a.GetIdString(), "name");
|
||||||
|
BOOST_CHECK_EQUAL(dec_a.GetShareIndex(), 'a');
|
||||||
|
BOOST_CHECK(CaseInsensitiveEqual(input_a, dec_a.Encode()));
|
||||||
|
|
||||||
|
const std::string input_c = "MS12NAMECACDEFGHJKLMNPQRSTUVWXYZ023FTR2GDZMPY6PN";
|
||||||
|
const auto dec_c = codex32::Result{input_c};
|
||||||
|
BOOST_CHECK(dec_c.IsValid());
|
||||||
|
BOOST_CHECK_EQUAL(dec_c.GetHrp(), "ms");
|
||||||
|
BOOST_CHECK_EQUAL(dec_c.GetK(), 2);
|
||||||
|
BOOST_CHECK_EQUAL(dec_c.GetIdString(), "name");
|
||||||
|
BOOST_CHECK_EQUAL(dec_c.GetShareIndex(), 'c');
|
||||||
|
BOOST_CHECK(CaseInsensitiveEqual(input_c, dec_c.Encode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END()
|
Loading…
Reference in New Issue
Block a user