From 7e4563670726d1735100c6190b52a76458d7c7bc Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 26 Apr 2018 02:58:34 +0100 Subject: [PATCH 1/9] chainparams: Add Sapling Bech32 HRPs --- src/chainparams.cpp | 15 +++++++++++++++ src/chainparams.h | 11 +++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index aaad51639..701573bdb 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -153,6 +153,11 @@ public: // guarantees the first 2 characters, when base58 encoded, are "SK" base58Prefixes[ZCSPENDING_KEY] = {0xAB,0x36}; + bech32HRPs[SAPLING_PAYMENT_ADDRESS] = "zs"; + bech32HRPs[SAPLING_FULL_VIEWING_KEY] = "zviews"; + bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivks"; + bech32HRPs[SAPLING_SPENDING_KEY] = "secret-spending-key-main"; + vFixedSeeds = std::vector(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); fMiningRequiresPeers = true; @@ -310,6 +315,11 @@ public: // guarantees the first 2 characters, when base58 encoded, are "ST" base58Prefixes[ZCSPENDING_KEY] = {0xAC,0x08}; + bech32HRPs[SAPLING_PAYMENT_ADDRESS] = "ztestsapling"; + bech32HRPs[SAPLING_FULL_VIEWING_KEY] = "zviewtestsapling"; + bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivktestsapling"; + bech32HRPs[SAPLING_SPENDING_KEY] = "secret-spending-key-test"; + vFixedSeeds = std::vector(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); fMiningRequiresPeers = true; @@ -429,6 +439,11 @@ public: base58Prefixes[ZCVIEWING_KEY] = {0xA8,0xAC,0x0C}; base58Prefixes[ZCSPENDING_KEY] = {0xAC,0x08}; + bech32HRPs[SAPLING_PAYMENT_ADDRESS] = "zregtestsapling"; + bech32HRPs[SAPLING_FULL_VIEWING_KEY] = "zviewregtestsapling"; + bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivkregtestsapling"; + bech32HRPs[SAPLING_SPENDING_KEY] = "secret-spending-key-regtest"; + // Founders reward script expects a vector of 2-of-3 multisig addresses vFoundersRewardAddress = { "t2FwcEhFdNXuFMv1tcYwaBJtYVtMj8b1uTg" }; assert(vFoundersRewardAddress.size() <= consensus.GetLastFoundersRewardBlockHeight()); diff --git a/src/chainparams.h b/src/chainparams.h index f1d9b43c3..6ee41fc4b 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -56,6 +56,15 @@ public: MAX_BASE58_TYPES }; + enum Bech32Type { + SAPLING_PAYMENT_ADDRESS, + SAPLING_FULL_VIEWING_KEY, + SAPLING_INCOMING_VIEWING_KEY, + SAPLING_SPENDING_KEY, + + MAX_BECH32_TYPES + }; + const Consensus::Params& GetConsensus() const { return consensus; } const CMessageHeader::MessageStartChars& MessageStart() const { return pchMessageStart; } const std::vector& AlertKey() const { return vAlertPubKey; } @@ -81,6 +90,7 @@ public: std::string NetworkIDString() const { return strNetworkID; } const std::vector& DNSSeeds() const { return vSeeds; } const std::vector& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } + const std::string& Bech32HRP(Bech32Type type) const { return bech32HRPs[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 */ @@ -103,6 +113,7 @@ protected: unsigned int nEquihashK = 0; std::vector vSeeds; std::vector base58Prefixes[MAX_BASE58_TYPES]; + std::string bech32HRPs[MAX_BECH32_TYPES]; std::string strNetworkID; std::string strCurrencyUnits; CBlock genesis; From 6b759fb0921c964f72d84d6eff4a4a115b4d64dc Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 7 Jun 2018 12:41:05 +1200 Subject: [PATCH 2/9] ConvertBits() - convert from one power-of-2 number base to another. Function extracted from upstream: PR bitcoin/bitcoin#11167 Commit c091b99379b97cb314c9fa123beabdbc324cf7a4 --- src/Makefile.test.include | 1 + src/test/convertbits_tests.cpp | 52 ++++++++++++++++++++++++++++++++++ src/utilstrencodings.h | 24 ++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 src/test/convertbits_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 6aa2b1cbe..bbedd99ae 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -61,6 +61,7 @@ BITCOIN_TESTS =\ test/Checkpoints_tests.cpp \ test/coins_tests.cpp \ test/compress_tests.cpp \ + test/convertbits_tests.cpp \ test/crypto_tests.cpp \ test/DoS_tests.cpp \ test/equihash_tests.cpp \ diff --git a/src/test/convertbits_tests.cpp b/src/test/convertbits_tests.cpp new file mode 100644 index 000000000..3b288dba0 --- /dev/null +++ b/src/test/convertbits_tests.cpp @@ -0,0 +1,52 @@ +// Copyright (c) 2018 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include + +#include + +BOOST_FIXTURE_TEST_SUITE(convertbits_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(convertbits_deterministic) +{ + for (size_t i = 0; i < 256; i++) { + std::vector input(32, i); + std::vector data; + std::vector output; + ConvertBits<8, 5, true>(data, input.begin(), input.end()); + ConvertBits<5, 8, false>(output, data.begin(), data.end()); + BOOST_CHECK_EQUAL(data.size(), 52); + BOOST_CHECK_EQUAL(output.size(), 32); + BOOST_CHECK(input == output); + } + + for (size_t i = 0; i < 256; i++) { + std::vector input(43, i); + std::vector data; + std::vector output; + ConvertBits<8, 5, true>(data, input.begin(), input.end()); + ConvertBits<5, 8, false>(output, data.begin(), data.end()); + BOOST_CHECK_EQUAL(data.size(), 69); + BOOST_CHECK_EQUAL(output.size(), 43); + BOOST_CHECK(input == output); + } +} + +BOOST_AUTO_TEST_CASE(convertbits_random) +{ + for (size_t i = 0; i < 1000; i++) { + auto input = libzcash::random_uint256(); + std::vector data; + std::vector output; + ConvertBits<8, 5, true>(data, input.begin(), input.end()); + ConvertBits<5, 8, false>(output, data.begin(), data.end()); + BOOST_CHECK_EQUAL(data.size(), 52); + BOOST_CHECK_EQUAL(output.size(), 32); + BOOST_CHECK(input == uint256(output)); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/utilstrencodings.h b/src/utilstrencodings.h index 2375d0c4f..a5d0e3c8a 100644 --- a/src/utilstrencodings.h +++ b/src/utilstrencodings.h @@ -133,4 +133,28 @@ bool TimingResistantEqual(const T& a, const T& b) */ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); +/** Convert from one power-of-2 number base to another. */ +template +bool ConvertBits(O& out, I it, I end) { + size_t acc = 0; + size_t bits = 0; + constexpr size_t maxv = (1 << tobits) - 1; + constexpr size_t max_acc = (1 << (frombits + tobits - 1)) - 1; + while (it != end) { + acc = ((acc << frombits) | *it) & max_acc; + bits += frombits; + while (bits >= tobits) { + bits -= tobits; + out.push_back((acc >> bits) & maxv); + } + ++it; + } + if (pad) { + if (bits) out.push_back((acc << (tobits - bits)) & maxv); + } else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { + return false; + } + return true; +} + #endif // BITCOIN_UTILSTRENCODINGS_H From ac70f76c5d05d1b6779953c8c8a3a8cc9471199e Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 22 Nov 2017 17:04:48 -0800 Subject: [PATCH 3/9] Generalize ConvertBits --- src/test/convertbits_tests.cpp | 12 ++++++------ src/utilstrencodings.h | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/convertbits_tests.cpp b/src/test/convertbits_tests.cpp index 3b288dba0..a8908ebfc 100644 --- a/src/test/convertbits_tests.cpp +++ b/src/test/convertbits_tests.cpp @@ -16,8 +16,8 @@ BOOST_AUTO_TEST_CASE(convertbits_deterministic) std::vector input(32, i); std::vector data; std::vector output; - ConvertBits<8, 5, true>(data, input.begin(), input.end()); - ConvertBits<5, 8, false>(output, data.begin(), data.end()); + ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, input.begin(), input.end()); + ConvertBits<5, 8, false>([&](unsigned char c) { output.push_back(c); }, data.begin(), data.end()); BOOST_CHECK_EQUAL(data.size(), 52); BOOST_CHECK_EQUAL(output.size(), 32); BOOST_CHECK(input == output); @@ -27,8 +27,8 @@ BOOST_AUTO_TEST_CASE(convertbits_deterministic) std::vector input(43, i); std::vector data; std::vector output; - ConvertBits<8, 5, true>(data, input.begin(), input.end()); - ConvertBits<5, 8, false>(output, data.begin(), data.end()); + ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, input.begin(), input.end()); + ConvertBits<5, 8, false>([&](unsigned char c) { output.push_back(c); }, data.begin(), data.end()); BOOST_CHECK_EQUAL(data.size(), 69); BOOST_CHECK_EQUAL(output.size(), 43); BOOST_CHECK(input == output); @@ -41,8 +41,8 @@ BOOST_AUTO_TEST_CASE(convertbits_random) auto input = libzcash::random_uint256(); std::vector data; std::vector output; - ConvertBits<8, 5, true>(data, input.begin(), input.end()); - ConvertBits<5, 8, false>(output, data.begin(), data.end()); + ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, input.begin(), input.end()); + ConvertBits<5, 8, false>([&](unsigned char c) { output.push_back(c); }, data.begin(), data.end()); BOOST_CHECK_EQUAL(data.size(), 52); BOOST_CHECK_EQUAL(output.size(), 32); BOOST_CHECK(input == uint256(output)); diff --git a/src/utilstrencodings.h b/src/utilstrencodings.h index a5d0e3c8a..32c1baef6 100644 --- a/src/utilstrencodings.h +++ b/src/utilstrencodings.h @@ -135,7 +135,7 @@ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); /** Convert from one power-of-2 number base to another. */ template -bool ConvertBits(O& out, I it, I end) { +bool ConvertBits(const O& outfn, I it, I end) { size_t acc = 0; size_t bits = 0; constexpr size_t maxv = (1 << tobits) - 1; @@ -145,12 +145,12 @@ bool ConvertBits(O& out, I it, I end) { bits += frombits; while (bits >= tobits) { bits -= tobits; - out.push_back((acc >> bits) & maxv); + outfn((acc >> bits) & maxv); } ++it; } if (pad) { - if (bits) out.push_back((acc << (tobits - bits)) & maxv); + if (bits) outfn((acc << (tobits - bits)) & maxv); } else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { return false; } From 6a2cc8ddc04addc4e8a23a5d08ddb9ac54512bac Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 7 Nov 2017 14:24:30 -0800 Subject: [PATCH 4/9] Simplify Base32 and Base64 conversions --- src/test/base32_tests.cpp | 4 +- src/test/base64_tests.cpp | 4 +- src/utilstrencodings.cpp | 293 +++++++------------------------------- 3 files changed, 56 insertions(+), 245 deletions(-) diff --git a/src/test/base32_tests.cpp b/src/test/base32_tests.cpp index 8ec886142..0b883fbef 100644 --- a/src/test/base32_tests.cpp +++ b/src/test/base32_tests.cpp @@ -16,9 +16,9 @@ BOOST_AUTO_TEST_CASE(base32_testvectors) for (unsigned int i=0; i> 2]; - left = (enc & 3) << 4; - mode = 1; - break; - - case 1: // we have two bits - strRet += pbase64[left | (enc >> 4)]; - left = (enc & 15) << 2; - mode = 2; - break; - - case 2: // we have four bits - strRet += pbase64[left | (enc >> 6)]; - strRet += pbase64[enc & 63]; - mode = 0; - break; - } - } - - if (mode) - { - strRet += pbase64[left]; - strRet += '='; - if (mode == 1) - strRet += '='; - } - - return strRet; + std::string str; + str.reserve(((len + 2) / 3) * 4); + ConvertBits<8, 6, true>([&](int v) { str += pbase64[v]; }, pch, pch + len); + while (str.size() % 4) str += '='; + return str; } string EncodeBase64(const string& str) @@ -193,68 +158,32 @@ vector DecodeBase64(const char* p, bool* pfInvalid) -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; - if (pfInvalid) - *pfInvalid = false; - - vector vchRet; - vchRet.reserve(strlen(p)*3/4); - - int mode = 0; - int left = 0; - - while (1) - { - int dec = decode64_table[(unsigned char)*p]; - if (dec == -1) break; - p++; - switch (mode) - { - case 0: // we have no bits and get 6 - left = dec; - mode = 1; - break; - - case 1: // we have 6 bits and keep 4 - vchRet.push_back((left<<2) | (dec>>4)); - left = dec & 15; - mode = 2; - break; - - case 2: // we have 4 bits and get 6, we keep 2 - vchRet.push_back((left<<4) | (dec>>2)); - left = dec & 3; - mode = 3; - break; - - case 3: // we have 2 bits and get 6 - vchRet.push_back((left<<6) | dec); - mode = 0; - break; - } + const char* e = p; + std::vector val; + val.reserve(strlen(p)); + while (*p != 0) { + int x = decode64_table[(unsigned char)*p]; + if (x == -1) break; + val.push_back(x); + ++p; } - if (pfInvalid) - switch (mode) - { - case 0: // 4n base64 characters processed: ok - break; + std::vector ret; + ret.reserve((val.size() * 3) / 4); + bool valid = ConvertBits<6, 8, false>([&](unsigned char c) { ret.push_back(c); }, val.begin(), val.end()); - case 1: // 4n+1 base64 character processed: impossible - *pfInvalid = true; - break; - - case 2: // 4n+2 base64 characters processed: require '==' - if (left || p[0] != '=' || p[1] != '=' || decode64_table[(unsigned char)p[2]] != -1) - *pfInvalid = true; - break; - - case 3: // 4n+3 base64 characters processed: require '=' - if (left || p[0] != '=' || decode64_table[(unsigned char)p[1]] != -1) - *pfInvalid = true; - break; + const char* q = p; + while (valid && *p != 0) { + if (*p != '=') { + valid = false; + break; } + ++p; + } + valid = valid && (p - e) % 4 == 0 && p - q < 4; + if (pfInvalid) *pfInvalid = !valid; - return vchRet; + return ret; } string DecodeBase64(const string& str) @@ -267,59 +196,11 @@ string EncodeBase32(const unsigned char* pch, size_t len) { static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567"; - string strRet=""; - strRet.reserve((len+4)/5*8); - - int mode=0, left=0; - const unsigned char *pchEnd = pch+len; - - while (pch> 3]; - left = (enc & 7) << 2; - mode = 1; - break; - - case 1: // we have three bits - strRet += pbase32[left | (enc >> 6)]; - strRet += pbase32[(enc >> 1) & 31]; - left = (enc & 1) << 4; - mode = 2; - break; - - case 2: // we have one bit - strRet += pbase32[left | (enc >> 4)]; - left = (enc & 15) << 1; - mode = 3; - break; - - case 3: // we have four bits - strRet += pbase32[left | (enc >> 7)]; - strRet += pbase32[(enc >> 2) & 31]; - left = (enc & 3) << 3; - mode = 4; - break; - - case 4: // we have two bits - strRet += pbase32[left | (enc >> 5)]; - strRet += pbase32[enc & 31]; - mode = 0; - } - } - - static const int nPadding[5] = {0, 6, 4, 3, 1}; - if (mode) - { - strRet += pbase32[left]; - for (int n=0; n([&](int v) { str += pbase32[v]; }, pch, pch + len); + while (str.size() % 8) str += '='; + return str; } string EncodeBase32(const string& str) @@ -346,102 +227,32 @@ vector DecodeBase32(const char* p, bool* pfInvalid) -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; - if (pfInvalid) - *pfInvalid = false; - - vector vchRet; - vchRet.reserve((strlen(p))*5/8); - - int mode = 0; - int left = 0; - - while (1) - { - int dec = decode32_table[(unsigned char)*p]; - if (dec == -1) break; - p++; - switch (mode) - { - case 0: // we have no bits and get 5 - left = dec; - mode = 1; - break; - - case 1: // we have 5 bits and keep 2 - vchRet.push_back((left<<3) | (dec>>2)); - left = dec & 3; - mode = 2; - break; - - case 2: // we have 2 bits and keep 7 - left = left << 5 | dec; - mode = 3; - break; - - case 3: // we have 7 bits and keep 4 - vchRet.push_back((left<<1) | (dec>>4)); - left = dec & 15; - mode = 4; - break; - - case 4: // we have 4 bits, and keep 1 - vchRet.push_back((left<<4) | (dec>>1)); - left = dec & 1; - mode = 5; - break; - - case 5: // we have 1 bit, and keep 6 - left = left << 5 | dec; - mode = 6; - break; - - case 6: // we have 6 bits, and keep 3 - vchRet.push_back((left<<2) | (dec>>3)); - left = dec & 7; - mode = 7; - break; - - case 7: // we have 3 bits, and keep 0 - vchRet.push_back((left<<5) | dec); - mode = 0; - break; - } + const char* e = p; + std::vector val; + val.reserve(strlen(p)); + while (*p != 0) { + int x = decode32_table[(unsigned char)*p]; + if (x == -1) break; + val.push_back(x); + ++p; } - if (pfInvalid) - switch (mode) - { - case 0: // 8n base32 characters processed: ok - break; + std::vector ret; + ret.reserve((val.size() * 5) / 8); + bool valid = ConvertBits<5, 8, false>([&](unsigned char c) { ret.push_back(c); }, val.begin(), val.end()); - case 1: // 8n+1 base32 characters processed: impossible - case 3: // +3 - case 6: // +6 - *pfInvalid = true; - break; - - case 2: // 8n+2 base32 characters processed: require '======' - if (left || p[0] != '=' || p[1] != '=' || p[2] != '=' || p[3] != '=' || p[4] != '=' || p[5] != '=' || decode32_table[(unsigned char)p[6]] != -1) - *pfInvalid = true; - break; - - case 4: // 8n+4 base32 characters processed: require '====' - if (left || p[0] != '=' || p[1] != '=' || p[2] != '=' || p[3] != '=' || decode32_table[(unsigned char)p[4]] != -1) - *pfInvalid = true; - break; - - case 5: // 8n+5 base32 characters processed: require '===' - if (left || p[0] != '=' || p[1] != '=' || p[2] != '=' || decode32_table[(unsigned char)p[3]] != -1) - *pfInvalid = true; - break; - - case 7: // 8n+7 base32 characters processed: require '=' - if (left || p[0] != '=' || decode32_table[(unsigned char)p[1]] != -1) - *pfInvalid = true; - break; + const char* q = p; + while (valid && *p != 0) { + if (*p != '=') { + valid = false; + break; } + ++p; + } + valid = valid && (p - e) % 8 == 0 && p - q < 8; + if (pfInvalid) *pfInvalid = !valid; - return vchRet; + return ret; } string DecodeBase32(const string& str) From c8511dfc075ffee615c885c8591400ac5a4675d8 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 7 Jun 2018 15:30:44 +1200 Subject: [PATCH 5/9] Fix bech32::Encode() error handling Previously, an input with invalid characters would result in out-of-bounds reads, potentially exposing up to 224 bytes of memory following the location of the CHARSET constant. This commit fixes the function to return an empty string, which is what was originally documented as happening. --- src/bech32.cpp | 3 +++ src/test/bech32_tests.cpp | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/bech32.cpp b/src/bech32.cpp index 573eac58b..2889f8f99 100644 --- a/src/bech32.cpp +++ b/src/bech32.cpp @@ -150,6 +150,9 @@ std::string Encode(const std::string& hrp, const data& values) { std::string ret = hrp + '1'; ret.reserve(ret.size() + combined.size()); for (auto c : combined) { + if (c >= 32) { + return ""; + } ret += CHARSET[c]; } return ret; diff --git a/src/test/bech32_tests.cpp b/src/test/bech32_tests.cpp index ce4cddd64..f71ca1bf2 100644 --- a/src/test/bech32_tests.cpp +++ b/src/test/bech32_tests.cpp @@ -64,4 +64,37 @@ BOOST_AUTO_TEST_CASE(bip173_testvectors_invalid) } } +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); + if (i < 32) { + // Valid input + BOOST_CHECK(!encoded.empty()); + auto ret = bech32::Decode(encoded); + BOOST_CHECK(ret.first == "a"); + BOOST_CHECK(ret.second == input); + } else { + // Invalid input + BOOST_CHECK(encoded.empty()); + } + } + + for (size_t i = 0; i < 255; i++) { + std::vector input(43, i); + auto encoded = bech32::Encode("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); + } else { + // Invalid input + BOOST_CHECK(encoded.empty()); + } + } +} + BOOST_AUTO_TEST_SUITE_END() From bec3e62bc1ff23111f72cae5352c0774e1ad9ce1 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 7 Jun 2018 16:40:59 +1200 Subject: [PATCH 6/9] Implement encoding and decoding of Sapling keys and addresses --- src/key_io.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++ src/test/key_tests.cpp | 31 +++++++++++++++++++++++ src/zcash/Address.hpp | 8 +++--- 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/key_io.cpp b/src/key_io.cpp index a7f614f25..c73a3703f 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -85,6 +85,18 @@ public: return EncodeBase58Check(data); } + std::string operator()(const libzcash::SaplingPaymentAddress& zaddr) const + { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << zaddr; + // ConvertBits requires unsigned char, but CDataStream uses char + std::vector seraddr(ss.begin(), ss.end()); + std::vector data; + 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(m_params.Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS), data); + } + std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; } }; @@ -129,8 +141,26 @@ public: return ret; } + std::string operator()(const libzcash::SaplingSpendingKey& zkey) const + { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << zkey; + // ConvertBits requires unsigned char, but CDataStream uses char + std::vector serkey(ss.begin(), ss.end()); + std::vector data; + 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(m_params.Bech32HRP(CChainParams::SAPLING_SPENDING_KEY), data); + memory_cleanse(serkey.data(), serkey.size()); + memory_cleanse(data.data(), data.size()); + return ret; + } + std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; } }; + +const size_t ConvertedSaplingPaymentAddressSize = ((32 + 11) * 8 + 4) / 5; +const size_t ConvertedSaplingSpendingKeySize = (32 * 8 + 4) / 5; } // namespace CKey DecodeSecret(const std::string& str) @@ -248,6 +278,19 @@ libzcash::PaymentAddress DecodePaymentAddress(const std::string& str) return ret; } } + data.clear(); + auto bech = bech32::Decode(str); + if (bech.first == Params().Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS) && + bech.second.size() == ConvertedSaplingPaymentAddressSize) { + // 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())) { + CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION); + libzcash::SaplingPaymentAddress ret; + ss >> ret; + return ret; + } + } return libzcash::InvalidEncoding(); } @@ -301,6 +344,20 @@ libzcash::SpendingKey DecodeSpendingKey(const std::string& str) return ret; } } + data.clear(); + auto bech = bech32::Decode(str); + if (bech.first == Params().Bech32HRP(CChainParams::SAPLING_SPENDING_KEY) && + bech.second.size() == ConvertedSaplingSpendingKeySize) { + // 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())) { + CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION); + libzcash::SaplingSpendingKey ret; + ss >> ret; + memory_cleanse(data.data(), data.size()); + return ret; + } + } memory_cleanse(data.data(), data.size()); return libzcash::InvalidEncoding(); } diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index 9c7385419..25c30c223 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -220,4 +220,35 @@ BOOST_AUTO_TEST_CASE(zc_address_test) } } +BOOST_AUTO_TEST_CASE(zs_address_test) +{ + for (size_t i = 0; i < 1000; i++) { + auto sk = SaplingSpendingKey::random(); + { + std::string sk_string = EncodeSpendingKey(sk); + BOOST_CHECK(sk_string.compare(0, 24, "secret-spending-key-main") == 0); + + auto spendingkey2 = DecodeSpendingKey(sk_string); + BOOST_CHECK(IsValidSpendingKey(spendingkey2)); + + BOOST_ASSERT(boost::get(&spendingkey2) != nullptr); + auto sk2 = boost::get(spendingkey2); + BOOST_CHECK(sk == sk2); + } + { + auto addr = sk.default_address(); + + std::string addr_string = EncodePaymentAddress(*addr); + BOOST_CHECK(addr_string.compare(0, 2, "zs") == 0); + + auto paymentaddr2 = DecodePaymentAddress(addr_string); + BOOST_CHECK(IsValidPaymentAddress(paymentaddr2)); + + BOOST_ASSERT(boost::get(&paymentaddr2) != nullptr); + auto addr2 = boost::get(paymentaddr2); + BOOST_CHECK(addr == addr2); + } + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 6b8c310f4..614defdde 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -95,10 +95,6 @@ public: SproutPaymentAddress address() const; }; -typedef boost::variant PaymentAddress; -typedef boost::variant ViewingKey; -typedef boost::variant SpendingKey; - //! Sapling functions. class SaplingPaymentAddress { public: @@ -209,6 +205,10 @@ public: boost::optional default_address() const; }; +typedef boost::variant PaymentAddress; +typedef boost::variant ViewingKey; +typedef boost::variant SpendingKey; + } /** Check whether a PaymentAddress is not an InvalidEncoding. */ From dd7417c8d08796660c45d7e7c9c1d63235216f1d Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 12 Jun 2018 15:40:50 +1200 Subject: [PATCH 7/9] Add comment about size calculations for converted serialized keys --- src/key_io.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/key_io.cpp b/src/key_io.cpp index c73a3703f..b1754a494 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -92,6 +92,7 @@ public: // ConvertBits requires unsigned char, but CDataStream uses char std::vector seraddr(ss.begin(), ss.end()); std::vector data; + // 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(m_params.Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS), data); @@ -148,6 +149,7 @@ public: // ConvertBits requires unsigned char, but CDataStream uses char std::vector serkey(ss.begin(), ss.end()); std::vector data; + // 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(m_params.Bech32HRP(CChainParams::SAPLING_SPENDING_KEY), data); @@ -159,6 +161,10 @@ public: std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; } }; +// Sizes of SaplingPaymentAddress and SaplingSpendingKey after +// ConvertBits<8, 5, true>(). The calculations below take the +// regular serialized size in bytes, convert to bits, and then +// perform ceiling division to get the number of 5-bit clusters. const size_t ConvertedSaplingPaymentAddressSize = ((32 + 11) * 8 + 4) / 5; const size_t ConvertedSaplingSpendingKeySize = (32 * 8 + 4) / 5; } // namespace From f59093935c06c87a7d9d5d73730630f8d5b2b656 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 12 Jun 2018 16:51:59 +1200 Subject: [PATCH 8/9] Add examples of ConvertBits transformation --- src/utilstrencodings.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/utilstrencodings.h b/src/utilstrencodings.h index 32c1baef6..37a07ea06 100644 --- a/src/utilstrencodings.h +++ b/src/utilstrencodings.h @@ -133,7 +133,16 @@ bool TimingResistantEqual(const T& a, const T& b) */ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); -/** Convert from one power-of-2 number base to another. */ +/** + * Convert from one power-of-2 number base to another. + * + * Examples using ConvertBits<8, 5, true>(): + * 000000 -> 0000000000 + * 202020 -> 0400100200 + * 757575 -> 0e151a170a + * abcdef -> 150f061e1e + * ffffff -> 1f1f1f1f1e + */ template bool ConvertBits(const O& outfn, I it, I end) { size_t acc = 0; From 69aa0d8f28cd36e71de7b7101faf405492afae59 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 12 Jun 2018 18:38:36 +1200 Subject: [PATCH 9/9] Use CChainParams::Bech32HRP() in zs_address_test --- src/test/key_tests.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index 25c30c223..03661f7b2 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -4,6 +4,7 @@ #include "key.h" +#include "chainparams.h" #include "key_io.h" #include "script/script.h" #include "uint256.h" @@ -226,7 +227,7 @@ BOOST_AUTO_TEST_CASE(zs_address_test) auto sk = SaplingSpendingKey::random(); { std::string sk_string = EncodeSpendingKey(sk); - BOOST_CHECK(sk_string.compare(0, 24, "secret-spending-key-main") == 0); + BOOST_CHECK(sk_string.compare(0, 24, Params().Bech32HRP(CChainParams::SAPLING_SPENDING_KEY)) == 0); auto spendingkey2 = DecodeSpendingKey(sk_string); BOOST_CHECK(IsValidSpendingKey(spendingkey2)); @@ -239,7 +240,7 @@ BOOST_AUTO_TEST_CASE(zs_address_test) auto addr = sk.default_address(); std::string addr_string = EncodePaymentAddress(*addr); - BOOST_CHECK(addr_string.compare(0, 2, "zs") == 0); + BOOST_CHECK(addr_string.compare(0, 2, Params().Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS)) == 0); auto paymentaddr2 = DecodePaymentAddress(addr_string); BOOST_CHECK(IsValidPaymentAddress(paymentaddr2));