From bec3e62bc1ff23111f72cae5352c0774e1ad9ce1 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 7 Jun 2018 16:40:59 +1200 Subject: [PATCH] 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. */