diff --git a/src/Makefile.test.include b/src/Makefile.test.include index a082ffbc1..95a52e3d4 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -62,6 +62,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/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/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; diff --git a/src/key_io.cpp b/src/key_io.cpp index a7f614f25..b1754a494 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -85,6 +85,19 @@ 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; + // 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); + } + std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; } }; @@ -129,8 +142,31 @@ 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; + // 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); + memory_cleanse(serkey.data(), serkey.size()); + memory_cleanse(data.data(), data.size()); + return ret; + } + 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 CKey DecodeSecret(const std::string& str) @@ -248,6 +284,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 +350,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/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 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() diff --git a/src/test/convertbits_tests.cpp b/src/test/convertbits_tests.cpp new file mode 100644 index 000000000..a8908ebfc --- /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>([&](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); + } + + for (size_t i = 0; i < 256; i++) { + std::vector input(43, i); + std::vector data; + std::vector output; + 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); + } +} + +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>([&](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)); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index 9c7385419..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" @@ -220,4 +221,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, Params().Bech32HRP(CChainParams::SAPLING_SPENDING_KEY)) == 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, Params().Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS)) == 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/utilstrencodings.cpp b/src/utilstrencodings.cpp index af09c5564..ece2ec73e 100644 --- a/src/utilstrencodings.cpp +++ b/src/utilstrencodings.cpp @@ -127,46 +127,11 @@ string EncodeBase64(const unsigned char* pch, size_t len) { static const char *pbase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - string strRet=""; - strRet.reserve((len+2)/3*4); - - int mode=0, left=0; - const unsigned char *pchEnd = pch+len; - - while (pch> 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) diff --git a/src/utilstrencodings.h b/src/utilstrencodings.h index 2375d0c4f..37a07ea06 100644 --- a/src/utilstrencodings.h +++ b/src/utilstrencodings.h @@ -133,4 +133,37 @@ 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. + * + * 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; + 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; + outfn((acc >> bits) & maxv); + } + ++it; + } + if (pad) { + if (bits) outfn((acc << (tobits - bits)) & maxv); + } else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { + return false; + } + return true; +} + #endif // BITCOIN_UTILSTRENCODINGS_H 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. */