Skip to content

Commit

Permalink
Merge pull request zcash#6891 from nuttycom/converttex
Browse files Browse the repository at this point in the history
Backport upstream bech32m implementation & implement `converttex` RPC method.
  • Loading branch information
nuttycom authored May 16, 2024
2 parents 402d303 + fa3af5f commit 77c5cfe
Show file tree
Hide file tree
Showing 15 changed files with 301 additions and 64 deletions.
3 changes: 3 additions & 0 deletions doc/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ release-notes at release time)
Notable changes
===============

- Added a `z_converttex` RPC method to support conversion of transparent
p2pkh addresses to the ZIP 320 (TEX) format.

1 change: 1 addition & 0 deletions qa/pull-tester/rpc-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
'threeofthreerestore.py',
'show_help.py',
'errors.py',
'converttex.py',
]

ZMQ_SCRIPTS = [
Expand Down
55 changes: 55 additions & 0 deletions qa/rpc-tests/converttex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
# Copyright (c) 2021 The Zcash developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php .

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
start_nodes,
)


class ConvertTEXTest(BitcoinTestFramework):
'''
Test that the `z_converttex` RPC method correctly converts transparent
addresses to ZIP 320 TEX addresses.
'''

def __init__(self):
super().__init__()
self.num_nodes = 1

def setup_network(self, split=False):
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
self.is_network_split = False

def run_test(self):
node = self.nodes[0]

# From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/zcash_test_vectors/transparent/zip_0320.py
# ["t_addr, tex_addr"],
test_vectors = [
["tmQqjg2hqn5XMK9v1wtueg1CpzGbgTNGZQu", "texregtest15hhh9uprfp6krumprglg6qpx3928ulrnlxt9na"],
["tmGiqpWKPJdraF2PqBzPojzkRbDE4fPTyAF", "texregtest1fns2jk8xpjr7rqtaggn2zpmcdtfyj2jer8arm0"],
["tmEkTF6UovNsEQM9h1ehnA3byw6yhFCJWor", "texregtest1xulx2a0pgc84phkdtue67zwe26axtcvvyaf6yu"],
["tmGoyC4XZ1GNCdJGk96K6mT8jxDQEhzVbfR", "texregtest1fhvw29vvg37mep5kkhyew47rrqadjtyk4xzx8n"],
["tmG4gSmUZzCcyR6S5nBhEFrfmodmUjXXAZG", "texregtest1gk5swlnzf8m5hc9x82344aqv5s90k5x2dvqyvh"],
["tmKgnRCv6SjwEFgXhqPoADKp3HLFF67Seww", "texregtest1d4j4uz8wnl5zmuzdl7y3fykrkk2zarnccfs578"],
["tmTymg9bGECw8tR8WHepE45c4joNTUt1zth", "texregtest1epwdxsm94e9wh2zwad7j885gelxly2f8d4mqry"],
["tmMGGBngBJwgTYWCx23zWDY7QvLateZNqCC", "texregtest106exvc6ufugdwppy7vnuf2vwztf5qh9r4tpsfa"],
["tmUyukTGWjTM7Nw4j8zgbZZwPfM8enu9NvZ", "texregtest16ddmp690el6vrzajhftc3fqpmx4a3cgfqf97yn"],
["tmDsJSojZxU3sb3LGMs6nMC1SVSQhKD99My", "texregtest19kgadp7hwu08xlufnvr2r0p5aygw3ss60px28u"],
["tmWezycaJjPoXNJxK2zvzELjS65mzExnvVE", "texregtest1ukuxwrfrj20q65c25ssk3nkathsy8qneehv5vl"],
["tmMUEbcXX7tVwtRjaSaMVfzXu6PCeyMnsCH", "texregtest1srmppx752ux07mntsjmthpddy2enunfuh6mlej"],
["tmDjbRj8go7BrS8AxSjfjTBsCcHw7J45SCi", "texregtest19sw2lplpdswc4zvdtz3z8yt42wtz4recz5plym"],
["tmSSJADGK9bgafaY9eih17WRazi3KzNL7RT", "texregtest1kacdt4amhphqx6tf4gla7a4qg8h9ey5tauz24v"],
["tmSJ3JSRx2R71MfBksWzHNEfMxUgzMxDMxy", "texregtest1khs34j6m944un75ula8xxgvgdnjc4m2l0kfgpy"]
];

for tv in test_vectors:
tex = node.z_converttex(tv[0])
assert_equal(tex, tv[1])

if __name__ == '__main__':
ConvertTEXTest().main()
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ BITCOIN_CORE_H = \
util/string.h \
util/test.h \
util/time.h \
util/vector.h \
validationinterface.h \
wallet/asyncrpcoperation_common.h \
wallet/asyncrpcoperation_mergetoaddress.h \
Expand Down
105 changes: 72 additions & 33 deletions src/bech32.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
// Copyright (c) 2017 Pieter Wuille
// Copyright (c) 2017, 2021 Pieter Wuille
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .

#include "bech32.h"
#include <bech32.h>
#include <util/vector.h>

#include <assert.h>

namespace bech32
{

namespace
{

typedef std::vector<uint8_t> data;

/** The Bech32 character set for encoding. */
/** The Bech32 and Bech32m character set for encoding. */
const char* CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";

/** The Bech32 character set for decoding. */
/** The Bech32 and Bech32m 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,
Expand All @@ -24,11 +30,10 @@ const int8_t CHARSET_REV[128] = {
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;
/* Determine the final constant to use for the specified encoding. */
uint32_t EncodingConstant(Encoding encoding) {
assert(encoding == Encoding::BECH32 || encoding == Encoding::BECH32M);
return encoding == Encoding::BECH32 ? 1 : 0x2bc830a3;
}

/** This function will compute what 6 5-bit values to XOR into the last 6 input values, in order to
Expand Down Expand Up @@ -58,11 +63,31 @@ uint32_t PolyMod(const data& v)

// 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
// the above example, `c` initially corresponds to 1 mod g(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`.

// The following Sage code constructs the generator used:
//
// B = GF(2) # Binary field
// BP.<b> = B[] # Polynomials over the binary field
// F_mod = b**5 + b**3 + 1
// F.<f> = GF(32, modulus=F_mod, repr='int') # GF(32) definition
// FP.<x> = F[] # Polynomials over GF(32)
// E_mod = x**2 + F.fetch_int(9)*x + F.fetch_int(23)
// E.<e> = F.extension(E_mod) # GF(1024) extension field definition
// for p in divisors(E.order() - 1): # Verify e has order 1023.
// assert((e**p == 1) == (p % 1023 == 0))
// G = lcm([(e**i).minpoly() for i in range(997,1000)])
// print(G) # Print out the generator
//
// It demonstrates that g(x) is the least common multiple of the minimal polynomials
// of 3 consecutive powers (997,998,999) of a primitive element (e) of GF(1024).
// That guarantees it is, in fact, the generator of a primitive BCH code with cycle
// length 1023 and distance 4. See https://en.wikipedia.org/wiki/BCH_code for more details.

uint32_t c = 1;
for (auto v_i : v) {
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
Expand All @@ -83,12 +108,21 @@ uint32_t PolyMod(const data& v)
// Then compute c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i:
c = ((c & 0x1ffffff) << 5) ^ v_i;

// Finally, for each set bit n in c0, conditionally add {2^n}k(x):
// Finally, for each set bit n in c0, conditionally add {2^n}k(x). These constants can be
// computed using the following Sage code (continuing the code above):
//
// for i in [1,2,4,8,16]: # Print out {1,2,4,8,16}*(g(x) mod x^6), packed in hex integers.
// v = 0
// for coef in reversed((F.fetch_int(i)*(G % x**6)).coefficients(sparse=True)):
// v = v*32 + coef.integer_representation()
// print("0x%x" % v)
//
if (c0 & 1) c ^= 0x3b6a57b2; // k(x) = {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18}
if (c0 & 2) c ^= 0x26508e6d; // {2}k(x) = {19}x^5 + {5}x^4 + x^3 + {3}x^2 + {19}x + {13}
if (c0 & 4) c ^= 0x1ea119fa; // {4}k(x) = {15}x^5 + {10}x^4 + {2}x^3 + {6}x^2 + {15}x + {26}
if (c0 & 8) c ^= 0x3d4233dd; // {8}k(x) = {30}x^5 + {20}x^4 + {4}x^3 + {12}x^2 + {30}x + {29}
if (c0 & 16) c ^= 0x2a1462b3; // {16}k(x) = {21}x^5 + x^4 + {8}x^3 + {24}x^2 + {21}x + {19}

}
return c;
}
Expand All @@ -115,21 +149,25 @@ data ExpandHRP(const std::string& hrp)
}

/** Verify a checksum. */
bool VerifyChecksum(const std::string& hrp, const data& values)
Encoding 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, Bech32 requires the
// resulting checksum to be 1 instead.
return PolyMod(Cat(ExpandHRP(hrp), values)) == 1;
// resulting checksum to be 1 instead. In Bech32m, this constant was amended. See
// https://gist.github.com/sipa/14c248c288c3880a3b191f978a34508e for details.
const uint32_t check = PolyMod(Cat(ExpandHRP(hrp), values));
if (check == EncodingConstant(Encoding::BECH32)) return Encoding::BECH32;
if (check == EncodingConstant(Encoding::BECH32M)) return Encoding::BECH32M;
return Encoding::INVALID;
}

/** Create a checksum. */
data CreateChecksum(const std::string& hrp, const data& values)
data CreateChecksum(Encoding encoding, const std::string& hrp, const data& values)
{
data enc = Cat(ExpandHRP(hrp), values);
enc.resize(enc.size() + 6); // Append 6 zeroes
uint32_t mod = PolyMod(enc) ^ 1; // Determine what to XOR into those 6 zeroes.
uint32_t mod = PolyMod(enc) ^ EncodingConstant(encoding); // Determine what to XOR into those 6 zeroes.
data ret(6);
for (size_t i = 0; i < 6; ++i) {
// Convert the 5-bit groups in mod to checksum values.
Expand All @@ -140,16 +178,17 @@ data CreateChecksum(const std::string& hrp, const data& values)

} // namespace

namespace bech32
{

/** Encode a Bech32 string. */
std::string Encode(const std::string& hrp, const data& values) {
data checksum = CreateChecksum(hrp, values);
/** Encode a Bech32 or Bech32m string. */
std::string Encode(Encoding encoding, const std::string& hrp, const data& values) {
// First ensure that the HRP is all lowercase. BIP-173 and BIP350 require an encoder
// to return a lowercase Bech32/Bech32m string, but if given an uppercase HRP, the
// result will always be invalid.
for (const char& c : hrp) assert(c < 'A' || c > 'Z');
data checksum = CreateChecksum(encoding, hrp, values);
data combined = Cat(values, checksum);
std::string ret = hrp + '1';
ret.reserve(ret.size() + combined.size());
for (auto c : combined) {
for (const auto c : combined) {
if (c >= 32) {
return "";
}
Expand All @@ -158,14 +197,14 @@ std::string Encode(const std::string& hrp, const data& values) {
return ret;
}

/** Decode a Bech32 string. */
std::pair<std::string, data> Decode(const std::string& str) {
/** Decode a Bech32 or Bech32m string. */
DecodeResult 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 < 33 || c > 126) return {};
if (c >= 'a' && c <= 'z') lower = true;
if (c >= 'A' && c <= 'Z') upper = 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');
Expand All @@ -175,7 +214,8 @@ std::pair<std::string, data> Decode(const std::string& str) {
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 = (c < 33 || c > 126) ? -1 : CHARSET_REV[c];
int8_t rev = CHARSET_REV[c];

if (rev == -1) {
return {};
}
Expand All @@ -185,10 +225,9 @@ std::pair<std::string, data> Decode(const std::string& str) {
for (size_t i = 0; i < pos; ++i) {
hrp += LowerCase(str[i]);
}
if (!VerifyChecksum(hrp, values)) {
return {};
}
return {hrp, data(values.begin(), values.end() - 6)};
Encoding result = VerifyChecksum(hrp, values);
if (result == Encoding::INVALID) return {};
return {result, std::move(hrp), data(values.begin(), values.end() - 6)};
}

} // namespace bech32
39 changes: 29 additions & 10 deletions src/bech32.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// Copyright (c) 2017 Pieter Wuille
// Copyright (c) 2017, 2021 Pieter Wuille
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://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.
// Bech32 and Bech32m are string encoding formats used in newer
// address types. The outputs consist of a human-readable part
// (alphanumeric), a separator character (1), and a base32 data
// section, the last 6 characters of which are a checksum. The
// module is namespaced under bech32 for historical reasons.
//
// For more information, see BIP 173.
// For more information, see BIP 173 and BIP 350.

#ifndef BITCOIN_BECH32_H
#define BITCOIN_BECH32_H
Expand All @@ -19,11 +20,29 @@
namespace bech32
{

/** 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);
enum class Encoding {
INVALID, //!< Failed decoding

/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */
std::pair<std::string, std::vector<uint8_t>> Decode(const std::string& str);
BECH32, //!< Bech32 encoding as defined in BIP173
BECH32M, //!< Bech32m encoding as defined in BIP350
};

/** Encode a Bech32 or Bech32m string. If hrp contains uppercase characters, this will cause an
* assertion error. Encoding must be one of BECH32 or BECH32M. */
std::string Encode(Encoding encoding, const std::string& hrp, const std::vector<uint8_t>& values);

struct DecodeResult
{
Encoding encoding; //!< What encoding was detected in the result; Encoding::INVALID if failed.
std::string hrp; //!< The human readable part
std::vector<uint8_t> data; //!< The payload (excluding checksum)

DecodeResult() : encoding(Encoding::INVALID) {}
DecodeResult(Encoding enc, std::string&& h, std::vector<uint8_t>&& d) : encoding(enc), hrp(std::move(h)), data(std::move(d)) {}
};

/** Decode a Bech32 or Bech32m string. */
DecodeResult Decode(const std::string& str);

} // namespace bech32

Expand Down
5 changes: 5 additions & 0 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ class CMainParams : public CChainParams {
keyConstants.bech32HRPs[SAPLING_EXTENDED_SPEND_KEY] = "secret-extended-key-main";
keyConstants.bech32HRPs[SAPLING_EXTENDED_FVK] = "zxviews";

keyConstants.bech32mHRPs[TEX_ADDRESS] = "tex";
{
std::vector<std::string> ecc_addresses = {
"t3LmX1cxWPPPqL4TZHx42HU3U5ghbFjRiif",
Expand Down Expand Up @@ -462,6 +463,8 @@ class CTestNetParams : public CChainParams {
keyConstants.bech32HRPs[SAPLING_EXTENDED_SPEND_KEY] = "secret-extended-key-test";
keyConstants.bech32HRPs[SAPLING_EXTENDED_FVK] = "zxviewtestsapling";

keyConstants.bech32mHRPs[TEX_ADDRESS] = "textest";

// Testnet funding streams
{
std::vector<std::string> ecc_addresses = {
Expand Down Expand Up @@ -705,6 +708,8 @@ class CRegTestParams : public CChainParams {
keyConstants.bech32HRPs[SAPLING_EXTENDED_SPEND_KEY] = "secret-extended-key-regtest";
keyConstants.bech32HRPs[SAPLING_EXTENDED_FVK] = "zxviewregtestsapling";

keyConstants.bech32mHRPs[TEX_ADDRESS] = "texregtest";

// The best chain should have at least this much work.
consensus.nMinimumChainWork = uint256S("0x00");

Expand Down
3 changes: 3 additions & 0 deletions src/chainparams.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ class CChainParams: public KeyConstants
const std::string& Bech32HRP(Bech32Type type) const {
return keyConstants.Bech32HRP(type);
}
const std::string& Bech32mHRP(Bech32mType type) const {
return keyConstants.Bech32mHRP(type);
}
const std::vector<SeedSpec6>& FixedSeeds() const { return vFixedSeeds; }
const CCheckpointData& Checkpoints() const { return checkpointData; }
/** Return the founder's reward address and script for a given block height */
Expand Down
Loading

0 comments on commit 77c5cfe

Please sign in to comment.