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 diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 6ddecbad4..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,33 +672,19 @@ 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; - } - } 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); - } + + // 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 + 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; @@ -746,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)) @@ -776,15 +849,10 @@ 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. - 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); + // Sapling support + auto sk = boost::apply_visitor(GetSpendingKeyForPaymentAddress(pwalletMain), address); + return EncodeSpendingKey(sk); } UniValue z_exportviewingkey(const UniValue& params, bool fHelp)