diff --git a/doc/release-notes.md b/doc/release-notes.md index a29094b5174..584ef9f6e43 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -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. + diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index 8b65b195b25..96ccf18d5b2 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -153,6 +153,7 @@ 'threeofthreerestore.py', 'show_help.py', 'errors.py', + 'converttex.py', ] ZMQ_SCRIPTS = [ diff --git a/qa/rpc-tests/converttex.py b/qa/rpc-tests/converttex.py new file mode 100755 index 00000000000..ec339404baf --- /dev/null +++ b/qa/rpc-tests/converttex.py @@ -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() diff --git a/src/Makefile.am b/src/Makefile.am index 09d3d07e23e..89711052926 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/bech32.cpp b/src/bech32.cpp index 8910d034515..352bacc7138 100644 --- a/src/bech32.cpp +++ b/src/bech32.cpp @@ -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 +#include + +#include + +namespace bech32 +{ namespace { typedef std::vector 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, @@ -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 @@ -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[] # Polynomials over the binary field + // F_mod = b**5 + b**3 + 1 + // F. = GF(32, modulus=F_mod, repr='int') # GF(32) definition + // FP. = F[] # Polynomials over GF(32) + // E_mod = x**2 + F.fetch_int(9)*x + F.fetch_int(23) + // 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 @@ -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; } @@ -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. @@ -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 ""; } @@ -158,14 +197,14 @@ std::string Encode(const std::string& hrp, const data& values) { return ret; } -/** Decode a Bech32 string. */ -std::pair 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'); @@ -175,7 +214,8 @@ std::pair 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 {}; } @@ -185,10 +225,9 @@ std::pair 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 diff --git a/src/bech32.h b/src/bech32.h index eecc6e29f5d..b1c1dc77147 100644 --- a/src/bech32.h +++ b/src/bech32.h @@ -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 @@ -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& values); +enum class Encoding { + INVALID, //!< Failed decoding -/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */ -std::pair> 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& 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 data; //!< The payload (excluding checksum) + + DecodeResult() : encoding(Encoding::INVALID) {} + DecodeResult(Encoding enc, std::string&& h, std::vector&& 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 diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 4fdf81dbf1f..4c1b68a5e04 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -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 ecc_addresses = { "t3LmX1cxWPPPqL4TZHx42HU3U5ghbFjRiif", @@ -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 ecc_addresses = { @@ -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"); diff --git a/src/chainparams.h b/src/chainparams.h index 0286c2c3b1a..5f3073e92b2 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -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& FixedSeeds() const { return vFixedSeeds; } const CCheckpointData& Checkpoints() const { return checkpointData; } /** Return the founder's reward address and script for a given block height */ diff --git a/src/key_constants.h b/src/key_constants.h index 567c27d68d3..e6564f51ca5 100644 --- a/src/key_constants.h +++ b/src/key_constants.h @@ -35,10 +35,17 @@ class KeyConstants MAX_BECH32_TYPES }; + enum Bech32mType { + TEX_ADDRESS, + + MAX_BECH32M_TYPES + }; + virtual std::string NetworkIDString() const =0; virtual uint32_t BIP44CoinType() const =0; virtual const std::vector& Base58Prefix(Base58Type type) const =0; virtual const std::string& Bech32HRP(Bech32Type type) const =0; + virtual const std::string& Bech32mHRP(Bech32mType type) const =0; }; class CBaseKeyConstants : public KeyConstants { @@ -47,11 +54,13 @@ class CBaseKeyConstants : public KeyConstants { uint32_t bip44CoinType; std::vector base58Prefixes[KeyConstants::MAX_BASE58_TYPES]; std::string bech32HRPs[KeyConstants::MAX_BECH32_TYPES]; + std::string bech32mHRPs[KeyConstants::MAX_BECH32M_TYPES]; std::string NetworkIDString() const { return strNetworkID; } uint32_t BIP44CoinType() const { return bip44CoinType; } const std::vector& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } const std::string& Bech32HRP(Bech32Type type) const { return bech32HRPs[type]; } + const std::string& Bech32mHRP(Bech32mType type) const { return bech32mHRPs[type]; } }; #endif // ZCASH_KEY_CONSTANTS_H diff --git a/src/key_io.cpp b/src/key_io.cpp index 524028d2db9..47fa0b182c4 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -149,7 +149,7 @@ class PaymentAddressEncoder // See calculation comment below data.reserve((seraddr.size() * 8 + 4) / 5); ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, seraddr.begin(), seraddr.end()); - return bech32::Encode(keyConstants.Bech32HRP(KeyConstants::SAPLING_PAYMENT_ADDRESS), data); + return bech32::Encode(bech32::Encoding::BECH32, keyConstants.Bech32HRP(KeyConstants::SAPLING_PAYMENT_ADDRESS), data); } std::string operator()(const libzcash::UnifiedAddress& uaddr) const @@ -199,7 +199,7 @@ class ViewingKeyEncoder // See calculation comment below data.reserve((serkey.size() * 8 + 4) / 5); ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, serkey.begin(), serkey.end()); - std::string ret = bech32::Encode(keyConstants.Bech32HRP(KeyConstants::SAPLING_EXTENDED_FVK), data); + std::string ret = bech32::Encode(bech32::Encoding::BECH32, keyConstants.Bech32HRP(KeyConstants::SAPLING_EXTENDED_FVK), data); memory_cleanse(serkey.data(), serkey.size()); memory_cleanse(data.data(), data.size()); return ret; @@ -239,7 +239,7 @@ class SpendingKeyEncoder // See calculation comment below data.reserve((serkey.size() * 8 + 4) / 5); ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, serkey.begin(), serkey.end()); - std::string ret = bech32::Encode(keyConstants.Bech32HRP(KeyConstants::SAPLING_EXTENDED_SPEND_KEY), data); + std::string ret = bech32::Encode(bech32::Encoding::BECH32, keyConstants.Bech32HRP(KeyConstants::SAPLING_EXTENDED_SPEND_KEY), data); memory_cleanse(serkey.data(), serkey.size()); memory_cleanse(data.data(), data.size()); return ret; @@ -370,6 +370,19 @@ std::string KeyIO::EncodePaymentAddress(const libzcash::PaymentAddress& zaddr) c return std::visit(PaymentAddressEncoder(keyConstants), zaddr); } +std::string KeyIO::EncodeTexAddress(const CKeyID& p2pkhAddr) const +{ + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << p2pkhAddr; + // ConvertBits requires unsigned char, but CDataStream uses char + std::vector seraddr(ss.begin(), ss.end()); + std::vector data; + // See calculation comment above + data.reserve((seraddr.size() * 8 + 4) / 5); + ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, seraddr.begin(), seraddr.end()); + return bech32::Encode(bech32::Encoding::BECH32M, keyConstants.Bech32mHRP(KeyConstants::TEX_ADDRESS), data); +} + template std::optional DecodeSprout( const KeyConstants& keyConstants, @@ -404,11 +417,12 @@ std::optional DecodeSapling( std::vector data; auto bech = bech32::Decode(str); - if (bech.first == keyConstants.Bech32HRP(keyMeta.first) && - bech.second.size() == keyMeta.second) { + if (bech.hrp == keyConstants.Bech32HRP(keyMeta.first) && + bech.data.size() == keyMeta.second && + bech.encoding == bech32::Encoding::BECH32) { // Bech32 decoding - data.reserve((bech.second.size() * 5) / 8); - if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, bech.second.begin(), bech.second.end())) { + data.reserve((bech.data.size() * 5) / 8); + if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, bech.data.begin(), bech.data.end())) { CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION); T2 ret; ss >> ret; diff --git a/src/key_io.h b/src/key_io.h index eb4e084a208..e950986a362 100644 --- a/src/key_io.h +++ b/src/key_io.h @@ -39,12 +39,14 @@ class KeyIO { std::string EncodePaymentAddress(const libzcash::PaymentAddress& zaddr) const; std::optional DecodePaymentAddress(const std::string& str) const; bool IsValidPaymentAddressString(const std::string& str) const; + std::string EncodeTexAddress(const CKeyID& p2pkhAddr) const; std::string EncodeViewingKey(const libzcash::ViewingKey& vk) const; std::optional DecodeViewingKey(const std::string& str) const; std::string EncodeSpendingKey(const libzcash::SpendingKey& zkey) const; std::optional DecodeSpendingKey(const std::string& str) const; + }; #endif // BITCOIN_KEY_IO_H diff --git a/src/rpc/common.h b/src/rpc/common.h index b4278b52db6..9d594cb99dd 100644 --- a/src/rpc/common.h +++ b/src/rpc/common.h @@ -130,6 +130,7 @@ static const CRPCConvertTable rpcCvtTable = { "z_exportkey", {{s}, {}} }, { "z_exportviewingkey", {{s}, {}} }, // rpcwallet + { "z_converttex", {{s}, {}} }, { "getnewaddress", {{}, {s}} }, { "getrawchangeaddress", {{}, {}} }, { "sendtoaddress", {{s, o}, {s, s, o}} }, diff --git a/src/test/bech32_tests.cpp b/src/test/bech32_tests.cpp index 2a48ce082e0..5ce8c42d97d 100644 --- a/src/test/bech32_tests.cpp +++ b/src/test/bech32_tests.cpp @@ -52,17 +52,18 @@ BOOST_AUTO_TEST_CASE(bip173_testvectors_valid) {"?1ezyfcl", "?", ""}, }; for (const auto& [str, hrp, data] : CASES) { - auto ret = bech32::Decode(str); - BOOST_CHECK(!ret.first.empty()); - BOOST_CHECK_EQUAL(ret.first, hrp); + const auto dec = bech32::Decode(str); + BOOST_CHECK(!dec.hrp.empty()); + BOOST_CHECK_EQUAL(dec.hrp, hrp); std::vector decoded; - decoded.reserve((ret.second.size() * 5) / 8); - auto success = ConvertBits<5, 8, false>([&](unsigned char c) { decoded.push_back(c); }, ret.second.begin(), ret.second.end()); + decoded.reserve((dec.data.size() * 5) / 8); + auto success = ConvertBits<5, 8, false>([&](unsigned char c) { decoded.push_back(c); }, dec.data.begin(), dec.data.end()); BOOST_CHECK(success); BOOST_CHECK_EQUAL(HexStr(decoded.begin(), decoded.end()), data); - std::string recode = bech32::Encode(ret.first, ret.second); + BOOST_CHECK(dec.encoding == bech32::Encoding::BECH32); + std::string recode = bech32::Encode(bech32::Encoding::BECH32, dec.hrp, dec.data); BOOST_CHECK(!recode.empty()); BOOST_CHECK(CaseInsensitiveEqual(str, recode)); } @@ -82,10 +83,12 @@ BOOST_AUTO_TEST_CASE(bip173_testvectors_invalid) "A1G7SGD8", "10a06t8", "1qzzfhee", + "a12UEL5L", + "A12uEL5L", }; for (const std::string& str : CASES) { - auto ret = bech32::Decode(str); - BOOST_CHECK(ret.first.empty()); + const auto dec = bech32::Decode(str); + BOOST_CHECK(dec.encoding != bech32::Encoding::BECH32); } } @@ -93,13 +96,13 @@ BOOST_AUTO_TEST_CASE(bech32_deterministic_valid) { for (size_t i = 0; i < 255; i++) { std::vector input(32, i); - auto encoded = bech32::Encode("a", input); + auto encoded = bech32::Encode(bech32::Encoding::BECH32, "a", input); if (i < 32) { // Valid input BOOST_CHECK(!encoded.empty()); auto ret = bech32::Decode(encoded); - BOOST_CHECK(ret.first == "a"); - BOOST_CHECK(ret.second == input); + BOOST_CHECK(ret.hrp == "a"); + BOOST_CHECK(ret.data == input); } else { // Invalid input BOOST_CHECK(encoded.empty()); @@ -108,13 +111,13 @@ BOOST_AUTO_TEST_CASE(bech32_deterministic_valid) for (size_t i = 0; i < 255; i++) { std::vector input(43, i); - auto encoded = bech32::Encode("a", input); + auto encoded = bech32::Encode(bech32::Encoding::BECH32, "a", input); if (i < 32) { // Valid input BOOST_CHECK(!encoded.empty()); auto ret = bech32::Decode(encoded); - BOOST_CHECK(ret.first == "a"); - BOOST_CHECK(ret.second == input); + BOOST_CHECK(ret.hrp == "a"); + BOOST_CHECK(ret.data == input); } else { // Invalid input BOOST_CHECK(encoded.empty()); diff --git a/src/util/vector.h b/src/util/vector.h new file mode 100644 index 00000000000..dab65ded2ac --- /dev/null +++ b/src/util/vector.h @@ -0,0 +1,51 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_VECTOR_H +#define BITCOIN_UTIL_VECTOR_H + +#include +#include +#include + +/** Construct a vector with the specified elements. + * + * This is preferable over the list initializing constructor of std::vector: + * - It automatically infers the element type from its arguments. + * - If any arguments are rvalue references, they will be moved into the vector + * (list initialization always copies). + */ +template +inline std::vector::type> Vector(Args&&... args) +{ + std::vector::type> ret; + ret.reserve(sizeof...(args)); + // The line below uses the trick from https://www.experts-exchange.com/articles/32502/None-recursive-variadic-templates-with-std-initializer-list.html + (void)std::initializer_list{(ret.emplace_back(std::forward(args)), 0)...}; + return ret; +} + +/** Concatenate two vectors, moving elements. */ +template +inline V Cat(V v1, V&& v2) +{ + v1.reserve(v1.size() + v2.size()); + for (auto& arg : v2) { + v1.push_back(std::move(arg)); + } + return v1; +} + +/** Concatenate two vectors. */ +template +inline V Cat(V v1, const V& v2) +{ + v1.reserve(v1.size() + v2.size()); + for (const auto& arg : v2) { + v1.push_back(arg); + } + return v1; +} + +#endif // BITCOIN_UTIL_VECTOR_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 911150ac8f3..cf3f0307e5b 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -215,6 +215,36 @@ UniValue getnewaddress(const UniValue& params, bool fHelp) return keyIO.EncodeDestination(keyID); } +UniValue z_converttex(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 1) + throw runtime_error( + "z_converttex ( \"transparentaddress\" )\n" + "\nConverts a transparent Zcash address to a TEX address.\n" + + "\nArguments:\n" + "1. \"transparentaddress\" (string, required) \n" + + "\nResult:\n" + "\"texaddress\" (string) The converted ZIP 320 (TEX) address\n" + + "\nExamples:\n" + + HelpExampleCli("z_converttex", "\"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"") + ); + + KeyIO keyIO(Params()); + auto decoded = keyIO.DecodePaymentAddress(params[0].get_str()); + if (!decoded.has_value()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + if (!std::holds_alternative(decoded.value())) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Address is not a transparent p2pkh address"); + } + auto p2pkhKey = std::get(decoded.value()); + + return keyIO.EncodeTexAddress(p2pkhKey); +} + UniValue getrawchangeaddress(const UniValue& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp)) @@ -5964,6 +5994,7 @@ static const CRPCCommand commands[] = { "wallet", "dumpprivkey", &dumpprivkey, true }, { "hidden", "dumpwallet", &dumpwallet, true }, { "wallet", "encryptwallet", &encryptwallet, true }, + { "wallet", "z_converttex", &z_converttex, true }, { "wallet", "getbalance", &getbalance, false }, { "wallet", "getnewaddress", &getnewaddress, true }, { "wallet", "getrawchangeaddress", &getrawchangeaddress, true },