Skip to content

Commit

Permalink
Add blech32 implementation and API
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenroose committed Mar 20, 2019
1 parent 9cb2fa0 commit 0f28780
Show file tree
Hide file tree
Showing 9 changed files with 401 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ BITCOIN_CORE_H = \
addrman.h \
base58.h \
bech32.h \
blech32.h \
bloom.h \
blockencodings.h \
blockfilter.h \
Expand Down Expand Up @@ -403,6 +404,7 @@ libbitcoin_common_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_common_a_SOURCES = \
base58.cpp \
bech32.cpp \
blech32.cpp \
chainparams.cpp \
coins.cpp \
compressor.cpp \
Expand Down
199 changes: 199 additions & 0 deletions src/blech32.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// Copyright (c) 2017 Pieter Wuille
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <blech32.h>

/*
* IMPORTANT NOTE: Comments below may largely pertain for bech32, not blech32.
* Some of these magic constants have changes.
* See liquid_addr.py for compact difference from bech32
* TODO: Update comments
*/

namespace
{

typedef std::vector<uint8_t> data;

/** The Blech32 character set for encoding. */
const char* CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";

/** The Blech32 character set for decoding. */
const int8_t CHARSET_REV[128] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1,
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1,
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
};

/** Concatenate two byte arrays. */
data Cat(data x, const data& y)
{
x.insert(x.end(), y.begin(), y.end());
return x;
}

/** This function will compute what 6 5-bit values to XOR into the last 6 input values, in order to
* make the checksum 0. These 6 values are packed together in a single 30-bit integer. The higher
* bits correspond to earlier values. */
uint32_t PolyMod(const data& v)
{
// The input is interpreted as a list of coefficients of a polynomial over F = GF(32), with an
// implicit 1 in front. If the input is [v0,v1,v2,v3,v4], that polynomial is v(x) =
// 1*x^5 + v0*x^4 + v1*x^3 + v2*x^2 + v3*x + v4. The implicit 1 guarantees that
// [v0,v1,v2,...] has a distinct checksum from [0,v0,v1,v2,...].

// The output is a 30-bit integer whose 5-bit groups are the coefficients of the remainder of
// v(x) mod g(x), where g(x) is the Blech32 generator,
// x^6 + {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18}. g(x) is chosen in such a way
// that the resulting code is a BCH code, guaranteeing detection of up to 3 errors within a
// window of 1023 characters. Among the various possible BCH codes, one was selected to in
// fact guarantee detection of up to 4 errors within a window of 89 characters.

// Note that the coefficients are elements of GF(32), here represented as decimal numbers
// between {}. In this finite field, addition is just XOR of the corresponding numbers. For
// example, {27} + {13} = {27 ^ 13} = {22}. Multiplication is more complicated, and requires
// treating the bits of values themselves as coefficients of a polynomial over a smaller field,
// GF(2), and multiplying those polynomials mod a^5 + a^3 + 1. For example, {5} * {26} =
// (a^2 + 1) * (a^4 + a^3 + a) = (a^4 + a^3 + a) * a^2 + (a^4 + a^3 + a) = a^6 + a^5 + a^4 + a
// = a^3 + 1 (mod a^5 + a^3 + 1) = {9}.

// During the course of the loop below, `c` contains the bitpacked coefficients of the
// polynomial constructed from just the values of v that were processed so far, mod g(x). In
// the above example, `c` initially corresponds to 1 mod (x), and after processing 2 inputs of
// v, it corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the starting value
// for `c`.
uint64_t c = 1;
for (const auto v_i : v) {
// We want to update `c` to correspond to a polynomial with one extra term. If the initial
// value of `c` consists of the coefficients of c(x) = f(x) mod g(x), we modify it to
// correspond to c'(x) = (f(x) * x + v_i) mod g(x), where v_i is the next input to
// process. Simplifying:
// c'(x) = (f(x) * x + v_i) mod g(x)
// ((f(x) mod g(x)) * x + v_i) mod g(x)
// (c(x) * x + v_i) mod g(x)
// If c(x) = c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5, we want to compute
// c'(x) = (c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5) * x + v_i mod g(x)
// = c0*x^6 + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i mod g(x)
// = c0*(x^6 mod g(x)) + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i
// If we call (x^6 mod g(x)) = k(x), this can be written as
// c'(x) = (c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i) + c0*k(x)

// First, determine the value of c0:
uint8_t c0 = c >> 55; // ELEMENTS: 25->55

// Then compute c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i:
c = ((c & 0x7fffffffffffff) << 5) ^ v_i; // ELEMENTS 0x1ffffff->0x7fffffffffffff

// Finally, for each set bit n in c0, conditionally add {2^n}k(x):
if (c0 & 1) c ^= 0x7d52fba40bd886; // ELEMENTS
if (c0 & 2) c ^= 0x5e8dbf1a03950c; // ELEMENTS
if (c0 & 4) c ^= 0x1c3a3c74072a18; // ELEMENTS
if (c0 & 8) c ^= 0x385d72fa0e5139; // ELEMENTS
if (c0 & 16) c ^= 0x7093e5a608865b; // ELEMENTS
}
return c;
}

/** Convert to lower case. */
inline unsigned char LowerCase(unsigned char c)
{
return (c >= 'A' && c <= 'Z') ? (c - 'A') + 'a' : c;
}

/** Expand a HRP for use in checksum computation. */
data ExpandHRP(const std::string& hrp)
{
data ret;
ret.reserve(hrp.size() + 90);
ret.resize(hrp.size() * 2 + 1);
for (size_t i = 0; i < hrp.size(); ++i) {
unsigned char c = hrp[i];
ret[i] = c >> 5;
ret[i + hrp.size() + 1] = c & 0x1f;
}
ret[hrp.size()] = 0;
return ret;
}

/** Verify a checksum. */
bool VerifyChecksum(const std::string& hrp, const data& values)
{
// PolyMod computes what value to xor into the final values to make the checksum 0. However,
// if we required that the checksum was 0, it would be the case that appending a 0 to a valid
// list of values would result in a new valid list. For that reason, Blech32 requires the
// resulting checksum to be 1 instead.
return PolyMod(Cat(ExpandHRP(hrp), values)) == 1;
}

/** Create a checksum. */
data CreateChecksum(const std::string& hrp, const data& values)
{
data enc = Cat(ExpandHRP(hrp), values);
enc.resize(enc.size() + 12); // ELEMENTS: Append 6->12 zeroes
uint32_t mod = PolyMod(enc) ^ 1; // Determine what to XOR into those 6 zeroes.
data ret(12); // ELEMENTS: 6->12
for (size_t i = 0; i < 12; ++i) { // ELEMENTS: 6->12
// Convert the 5-bit groups in mod to checksum values.
ret[i] = (mod >> (5 * (11 - i))) & 31; // ELEMENTS: 5->11
}
return ret;
}

} // namespace

namespace blech32
{

/** Encode a Blech32 string. */
std::string Encode(const std::string& hrp, const data& values) {
data checksum = CreateChecksum(hrp, values);
data combined = Cat(values, checksum);
std::string ret = hrp + '1';
ret.reserve(ret.size() + combined.size());
for (const auto c : combined) {
ret += CHARSET[c];
}
return ret;
}

/** Decode a Blech32 string. */
std::pair<std::string, data> Decode(const std::string& str) {
bool lower = false, upper = false;
for (size_t i = 0; i < str.size(); ++i) {
unsigned char c = str[i];
if (c >= 'a' && c <= 'z') lower = true;
else if (c >= 'A' && c <= 'Z') upper = true;
else if (c < 33 || c > 126) return {};
}
if (lower && upper) return {};
size_t pos = str.rfind('1');
if (str.size() > 1000 || pos == str.npos || pos == 0 || pos + 13 > str.size()) { // ELEMENTS: 90->1000, 7->13
return {};
}
data values(str.size() - 1 - pos);
for (size_t i = 0; i < str.size() - 1 - pos; ++i) {
unsigned char c = str[i + pos + 1];
int8_t rev = CHARSET_REV[c];

if (rev == -1) {
return {};
}
values[i] = rev;
}
std::string hrp;
for (size_t i = 0; i < pos; ++i) {
hrp += LowerCase(str[i]);
}
if (!VerifyChecksum(hrp, values)) {
return {};
}
return {hrp, data(values.begin(), values.end() - 12)};
}

} // namespace blech32
30 changes: 30 additions & 0 deletions src/blech32.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2017 Pieter Wuille
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

// Bech32 is a string encoding format used in newer address types.
// The output consists of a human-readable part (alphanumeric), a
// separator character (1), and a base32 data section, the last
// 6 characters of which are a checksum.
//
// For more information, see BIP 173.

#ifndef BITCOIN_BLECH32_H
#define BITCOIN_BLECH32_H

#include <stdint.h>
#include <string>
#include <vector>

namespace blech32
{

/** Encode a Bech32 string. Returns the empty string in case of failure. */
std::string Encode(const std::string& hrp, const std::vector<uint8_t>& values);

/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */
std::pair<std::string, std::vector<uint8_t>> Decode(const std::string& str);

} // namespace blech32

#endif // BITCOIN_BLECH32_H
5 changes: 5 additions & 0 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ class CMainParams : public CChainParams {
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4};

bech32_hrp = "bc";
blech32_hrp = blech32_hrp;

vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main));

Expand Down Expand Up @@ -298,6 +299,7 @@ class CTestNetParams : public CChainParams {
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};

bech32_hrp = "tb";
blech32_hrp = blech32_hrp;

vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test));

Expand Down Expand Up @@ -407,6 +409,7 @@ class CRegTestParams : public CChainParams {
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};

bech32_hrp = "bcrt";
blech32_hrp = blech32_hrp;

/* enable fallback fee on regtest */
m_fallback_fee_enabled = true;
Expand Down Expand Up @@ -486,6 +489,7 @@ class CCustomParams : public CRegTestParams {
m_fallback_fee_enabled = args.GetBoolArg("-fallback_fee_enabled", m_fallback_fee_enabled);

bech32_hrp = args.GetArg("-bech32_hrp", bech32_hrp);
blech32_hrp = args.GetArg("-blech32_hrp", "el");
base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1, args.GetArg("-pubkeyprefix", 111));
base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1, args.GetArg("-scriptprefix", 196));
base58Prefixes[SECRET_KEY] = std::vector<unsigned char>(1, args.GetArg("-secretprefix", 239));
Expand Down Expand Up @@ -565,6 +569,7 @@ class CCustomParams : public CRegTestParams {
base58Prefixes[PARENT_PUBKEY_ADDRESS] = std::vector<unsigned char>(1, args.GetArg("-parentpubkeyprefix", 111));
base58Prefixes[PARENT_SCRIPT_ADDRESS] = std::vector<unsigned char>(1, args.GetArg("-parentscriptprefix", 196));
parent_bech32_hrp = args.GetArg("-parent_bech32_hrp", "bcrt");
parent_blech32_hrp = args.GetArg("-parent_bech32_hrp", "bcrt");

base58Prefixes[BLINDED_ADDRESS] = std::vector<unsigned char>(1, args.GetArg("-blindedprefix", 4));

Expand Down
4 changes: 4 additions & 0 deletions src/chainparams.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,15 @@ class CChainParams
const std::vector<std::string>& DNSSeeds() const { return vSeeds; }
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; }
const std::string& Bech32HRP() const { return bech32_hrp; }
const std::string& Blech32HRP() const { return blech32_hrp; }
const std::vector<SeedSpec6>& FixedSeeds() const { return vFixedSeeds; }
const CCheckpointData& Checkpoints() const { return checkpointData; }
const ChainTxData& TxData() const { return chainTxData; }
// ELEMENTS extra fields:
const uint256 ParentGenesisBlockHash() const { return parentGenesisBlockHash; }
bool anyonecanspend_aremine;
const std::string& ParentBech32HRP() const { return parent_bech32_hrp; }
const std::string& ParentBlech32HRP() const { return parent_blech32_hrp; }
bool GetEnforcePak() const { return enforce_pak; }
bool GetMultiDataPermitted() const { return multi_data_permitted; }

Expand All @@ -101,6 +103,7 @@ class CChainParams
std::vector<std::string> vSeeds;
std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES];
std::string bech32_hrp;
std::string blech32_hrp;
std::string strNetworkID;
CBlock genesis;
CAmount initialFreeCoins;
Expand All @@ -114,6 +117,7 @@ class CChainParams
// ELEMENTS extra fields:
uint256 parentGenesisBlockHash;
std::string parent_bech32_hrp;
std::string parent_blech32_hrp;
bool enforce_pak;
bool multi_data_permitted;
};
Expand Down
2 changes: 2 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,7 @@ void SetupServerArgs()
gArgs.AddArg("-extpubkeyprefix", strprintf("The 4-byte prefix, in hex, of the chain's base58 extended public key encoding. (default: %s)", HexStr(defaultChainParams->Base58Prefix(CChainParams::EXT_PUBLIC_KEY))), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-extprvkeyprefix", strprintf("The 4-byte prefix, in hex, of the chain's base58 extended private key encoding. (default: %s)", HexStr(defaultChainParams->Base58Prefix(CChainParams::EXT_SECRET_KEY))), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-bech32_hrp", strprintf("The human-readable part of the chain's bech32 encoding. (default: %s)", defaultChainParams->Bech32HRP()), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-blech32_hrp", strprintf("The human-readable part of the chain's blech32 encoding. Used in confidential addresses.(default: %s)", defaultChainParams->Blech32HRP()), false, OptionsCategory::CHAINPARAMS);

#if HAVE_DECL_DAEMON
gArgs.AddArg("-daemon", "Run in the background as a daemon and accept commands", false, OptionsCategory::OPTIONS);
Expand All @@ -534,6 +535,7 @@ void SetupServerArgs()
gArgs.AddArg("-parentpubkeyprefix", strprintf("The byte prefix, in decimal, of the parent chain's base58 pubkey address. (default: %d)", 111), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-parentscriptprefix", strprintf("The byte prefix, in decimal, of the parent chain's base58 script address. (default: %d)", 196), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-parent_bech32_hrp", strprintf("The human-readable part of the parent chain's bech32 encoding. (default: %s)", "bc"), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-parent_blech32_hrp", strprintf("The human-readable part of the parent chain's blech32 encoding. (default: %s)", "bc"), false, OptionsCategory::CHAINPARAMS);

// Add the hidden options
gArgs.AddHiddenArgs(hidden_args);
Expand Down
19 changes: 19 additions & 0 deletions src/key_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <base58.h>
#include <bech32.h>
#include <blech32.h>
#include <chainparams.h>
#include <script/script.h>
#include <utilstrencodings.h>
Expand Down Expand Up @@ -72,6 +73,12 @@ class DestinationEncoder : public boost::static_visitor<std::string>
std::vector<unsigned char> data = {0};
data.reserve(33);
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.begin(), id.end());
if (id.blinding_pubkey.IsFullyValid()) {
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.blinding_pubkey.begin(), id.blinding_pubkey.end());
const std::string& hrp = for_parent ? m_params.ParentBlech32HRP() : m_params.Blech32HRP();
return blech32::Encode(hrp, data);
}

const std::string& hrp = for_parent ? m_params.ParentBech32HRP() : m_params.Bech32HRP();
return bech32::Encode(hrp, data);
}
Expand All @@ -81,6 +88,12 @@ class DestinationEncoder : public boost::static_visitor<std::string>
std::vector<unsigned char> data = {0};
data.reserve(53);
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.begin(), id.end());
if (id.blinding_pubkey.IsFullyValid()) {
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.blinding_pubkey.begin(), id.blinding_pubkey.end());
const std::string& hrp = for_parent ? m_params.ParentBlech32HRP() : m_params.Blech32HRP();
return blech32::Encode(hrp, data);
}

const std::string& hrp = for_parent ? m_params.ParentBech32HRP() : m_params.Bech32HRP();
return bech32::Encode(hrp, data);
}
Expand All @@ -93,6 +106,12 @@ class DestinationEncoder : public boost::static_visitor<std::string>
std::vector<unsigned char> data = {(unsigned char)id.version};
data.reserve(1 + (id.length * 8 + 4) / 5);
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.program, id.program + id.length);
if (id.blinding_pubkey.IsFullyValid()) {
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.blinding_pubkey.begin(), id.blinding_pubkey.end());
const std::string& hrp = for_parent ? m_params.ParentBlech32HRP() : m_params.Blech32HRP();
return blech32::Encode(hrp, data);
}

const std::string& hrp = for_parent ? m_params.ParentBech32HRP() : m_params.Bech32HRP();
return bech32::Encode(hrp, data);
}
Expand Down

0 comments on commit 0f28780

Please sign in to comment.