From 2afc8eb0ecdb959310a0e2a6063ecdb7b4f0cf3e Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Wed, 11 Jul 2018 13:22:41 -0700 Subject: [PATCH 1/4] Add Sapling support to z_exportkey --- src/wallet/rpcdump.cpp | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 6ddecbad4..fe4b66ec5 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -777,14 +777,32 @@ UniValue z_exportkey(const UniValue& params, bool fHelp) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr"); } // TODO: Add Sapling support. For now, ensure we can freely convert. - assert(boost::get(&address) != nullptr); - auto addr = boost::get(address); - - libzcash::SproutSpendingKey k; - if (!pwalletMain->GetSpendingKey(addr, k)) - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private zkey for this zaddr"); - - return EncodeSpendingKey(k); + bool isValidAddr = boost::get(&address) != nullptr || boost::get(&address) != nullptr; + assert(isValidAddr); + + if (boost::get(&address) != nullptr) { + auto addr = boost::get(address); + + libzcash::SproutSpendingKey k; + if (!pwalletMain->GetSpendingKey(addr, k)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private zkey for this zaddr"); + } + + return EncodeSpendingKey(k); + } else { + auto addr = boost::get(address); + libzcash::SaplingIncomingViewingKey ivk; + libzcash::SaplingFullViewingKey fvk; + libzcash::SaplingSpendingKey sk; + + if (!pwalletMain->GetSaplingIncomingViewingKey(addr, ivk) || + !pwalletMain->GetSaplingFullViewingKey(ivk, fvk) || + !pwalletMain->GetSaplingSpendingKey(fvk, sk)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private zkey for this zaddr"); + } + + return EncodeSpendingKey(sk); + } } UniValue z_exportviewingkey(const UniValue& params, bool fHelp) From dd4c8a3c7057b2ae3a498b1a5a088194aa9cdb42 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Wed, 11 Jul 2018 13:46:41 -0700 Subject: [PATCH 2/4] Add Sapling support to z_importkey --- src/wallet/rpcdump.cpp | 74 ++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index fe4b66ec5..c10b0af95 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -620,34 +620,58 @@ UniValue z_importkey(const UniValue& params, bool fHelp) if (!IsValidSpendingKey(spendingkey)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key"); } - // TODO: Add Sapling support. For now, ensure we can freely convert. - assert(boost::get(&spendingkey) != nullptr); - auto key = boost::get(spendingkey); - auto addr = key.address(); - - { - // Don't throw error in case a key is already there - if (pwalletMain->HaveSpendingKey(addr)) { - if (fIgnoreExistingKey) { - return NullUniValue; + // Sapling support + bool isValidKey = boost::get(&spendingkey) != nullptr || boost::get(&spendingkey) != nullptr; + assert(isValidKey); + + if (boost::get(&spendingkey) != nullptr) { + auto key = boost::get(spendingkey); + auto addr = key.address(); + { + // Don't throw error in case a key is already there + if (pwalletMain->HaveSpendingKey(addr)) { + if (fIgnoreExistingKey) { + return NullUniValue; + } + } else { + pwalletMain->MarkDirty(); + + if (!pwalletMain-> AddZKey(key)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); + } + + pwalletMain->mapZKeyMetadata[addr].nCreateTime = 1; } - } else { - pwalletMain->MarkDirty(); - - if (!pwalletMain-> AddZKey(key)) - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); - - pwalletMain->mapZKeyMetadata[addr].nCreateTime = 1; } - - // whenever a key is imported, we need to scan the whole chain - pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value' - - // We want to scan for transactions and notes - if (fRescan) { - pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight], true); + } else { + auto sk = boost::get(spendingkey); + auto fvk = sk.full_viewing_key(); + auto addr = sk.default_address(); + { + // Don't throw error in case a key is already there + if (pwalletMain->HaveSaplingSpendingKey(fvk)) { + if (fIgnoreExistingKey) { + return NullUniValue; + } + } else { + pwalletMain->MarkDirty(); + + if (!pwalletMain-> AddSaplingZKey(sk)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); + } + + pwalletMain->mapSaplingZKeyMetadata[addr].nCreateTime = 1; + } } } + + // whenever a key is imported, we need to scan the whole chain + pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value' + + // We want to scan for transactions and notes + if (fRescan) { + pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight], true); + } return NullUniValue; } @@ -776,7 +800,7 @@ UniValue z_exportkey(const UniValue& params, bool fHelp) if (!IsValidPaymentAddress(address)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr"); } - // TODO: Add Sapling support. For now, ensure we can freely convert. + // Sapling support bool isValidAddr = boost::get(&address) != nullptr || boost::get(&address) != nullptr; assert(isValidAddr); From 8dd1dbcfe41c551113faeb44c05afdf0da20dca4 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Wed, 11 Jul 2018 14:01:07 -0700 Subject: [PATCH 3/4] Add Sapling to rpc_wallet_z_importexport test --- src/test/rpc_wallet_tests.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp index 49f436487..5064526ba 100644 --- a/src/test/rpc_wallet_tests.cpp +++ b/src/test/rpc_wallet_tests.cpp @@ -564,7 +564,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport) // verify import and export key for (int i = 0; i < n1; i++) { - // create a random key locally + // create a random Sprout key locally auto testSpendingKey = libzcash::SproutSpendingKey::random(); auto testPaymentAddress = testSpendingKey.address(); std::string testAddr = EncodePaymentAddress(testPaymentAddress); @@ -572,6 +572,15 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport) BOOST_CHECK_NO_THROW(CallRPC(string("z_importkey ") + testKey)); BOOST_CHECK_NO_THROW(retValue = CallRPC(string("z_exportkey ") + testAddr)); BOOST_CHECK_EQUAL(retValue.get_str(), testKey); + + // create a random Sapling key locally + auto testSaplingSpendingKey = libzcash::SaplingSpendingKey::random(); + auto testSaplingPaymentAddress = testSaplingSpendingKey.default_address(); + std::string testSaplingAddr = EncodePaymentAddress(testSaplingPaymentAddress); + std::string testSaplingKey = EncodeSpendingKey(testSaplingSpendingKey); + BOOST_CHECK_NO_THROW(CallRPC(string("z_importkey ") + testSaplingKey)); + BOOST_CHECK_NO_THROW(retValue = CallRPC(string("z_exportkey ") + testSaplingAddr)); + BOOST_CHECK_EQUAL(retValue.get_str(), testSaplingKey); } // Verify we can list the keys imported From 501de644ab9c0e13fcc11ea2450d8b126cbf7bb7 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Wed, 18 Jul 2018 19:52:55 -0700 Subject: [PATCH 4/4] Refactor into visitors and throw errors for invalid key or address. --- src/wallet/rpcdump.cpp | 162 ++++++++++++++++++++++++----------------- 1 file changed, 94 insertions(+), 68 deletions(-) diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index c10b0af95..8ef0f3abd 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -550,6 +550,58 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys) return exportfilepath.string(); } +class AddSpendingKeyToWallet : public boost::static_visitor +{ +private: + CWallet *m_wallet; +public: + AddSpendingKeyToWallet(CWallet *wallet) : m_wallet(wallet) {} + + bool operator()(const libzcash::SproutSpendingKey &sk) const { + auto addr = sk.address(); + // Don't throw error in case a key is already there + if (m_wallet->HaveSpendingKey(addr)) { + return true; + } else { + m_wallet->MarkDirty(); + + if (!m_wallet-> AddZKey(sk)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); + } + + m_wallet->mapZKeyMetadata[addr].nCreateTime = 1; + + return false; + } + } + + bool operator()(const libzcash::SaplingSpendingKey &sk) const { + auto fvk = sk.full_viewing_key(); + auto addr = sk.default_address(); + { + // Don't throw error in case a key is already there + if (m_wallet->HaveSaplingSpendingKey(fvk)) { + return true; + } else { + m_wallet->MarkDirty(); + + if (!m_wallet-> AddSaplingZKey(sk)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); + } + + m_wallet->mapSaplingZKeyMetadata[addr].nCreateTime = 1; + + return false; + } + } + } + + bool operator()(const libzcash::InvalidEncoding& no) const { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key"); + } + +}; + UniValue z_importkey(const UniValue& params, bool fHelp) { @@ -620,49 +672,11 @@ UniValue z_importkey(const UniValue& params, bool fHelp) if (!IsValidSpendingKey(spendingkey)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key"); } - // Sapling support - bool isValidKey = boost::get(&spendingkey) != nullptr || boost::get(&spendingkey) != nullptr; - assert(isValidKey); - if (boost::get(&spendingkey) != nullptr) { - auto key = boost::get(spendingkey); - auto addr = key.address(); - { - // Don't throw error in case a key is already there - if (pwalletMain->HaveSpendingKey(addr)) { - if (fIgnoreExistingKey) { - return NullUniValue; - } - } else { - pwalletMain->MarkDirty(); - - if (!pwalletMain-> AddZKey(key)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); - } - - pwalletMain->mapZKeyMetadata[addr].nCreateTime = 1; - } - } - } else { - auto sk = boost::get(spendingkey); - auto fvk = sk.full_viewing_key(); - auto addr = sk.default_address(); - { - // Don't throw error in case a key is already there - if (pwalletMain->HaveSaplingSpendingKey(fvk)) { - if (fIgnoreExistingKey) { - return NullUniValue; - } - } else { - pwalletMain->MarkDirty(); - - if (!pwalletMain-> AddSaplingZKey(sk)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); - } - - pwalletMain->mapSaplingZKeyMetadata[addr].nCreateTime = 1; - } - } + // Sapling support + auto keyAlreadyExists = boost::apply_visitor(AddSpendingKeyToWallet(pwalletMain), spendingkey); + if (keyAlreadyExists && fIgnoreExistingKey) { + return NullUniValue; } // whenever a key is imported, we need to scan the whole chain @@ -770,6 +784,41 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) return NullUniValue; } +class GetSpendingKeyForPaymentAddress : public boost::static_visitor +{ +private: + CWallet *m_wallet; +public: + GetSpendingKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {} + + libzcash::SpendingKey operator()(const libzcash::SproutPaymentAddress &zaddr) const + { + libzcash::SproutSpendingKey k; + if (!pwalletMain->GetSpendingKey(zaddr, k)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private zkey for this zaddr"); + } + return k; + } + + libzcash::SpendingKey operator()(const libzcash::SaplingPaymentAddress &zaddr) const + { + libzcash::SaplingIncomingViewingKey ivk; + libzcash::SaplingFullViewingKey fvk; + libzcash::SaplingSpendingKey sk; + + if (!pwalletMain->GetSaplingIncomingViewingKey(zaddr, ivk) || + !pwalletMain->GetSaplingFullViewingKey(ivk, fvk) || + !pwalletMain->GetSaplingSpendingKey(fvk, sk)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private zkey for this zaddr"); + } + return sk; + } + + libzcash::SpendingKey operator()(const libzcash::InvalidEncoding& no) const { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr"); + } +}; + UniValue z_exportkey(const UniValue& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp)) @@ -800,33 +849,10 @@ UniValue z_exportkey(const UniValue& params, bool fHelp) if (!IsValidPaymentAddress(address)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr"); } + // Sapling support - bool isValidAddr = boost::get(&address) != nullptr || boost::get(&address) != nullptr; - assert(isValidAddr); - - if (boost::get(&address) != nullptr) { - auto addr = boost::get(address); - - libzcash::SproutSpendingKey k; - if (!pwalletMain->GetSpendingKey(addr, k)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private zkey for this zaddr"); - } - - return EncodeSpendingKey(k); - } else { - auto addr = boost::get(address); - libzcash::SaplingIncomingViewingKey ivk; - libzcash::SaplingFullViewingKey fvk; - libzcash::SaplingSpendingKey sk; - - if (!pwalletMain->GetSaplingIncomingViewingKey(addr, ivk) || - !pwalletMain->GetSaplingFullViewingKey(ivk, fvk) || - !pwalletMain->GetSaplingSpendingKey(fvk, sk)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private zkey for this zaddr"); - } - - return EncodeSpendingKey(sk); - } + auto sk = boost::apply_visitor(GetSpendingKeyForPaymentAddress(pwalletMain), address); + return EncodeSpendingKey(sk); } UniValue z_exportviewingkey(const UniValue& params, bool fHelp)