diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 7efb7d74c..78ae7d930 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -97,7 +97,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "zcrawjoinsplit", 4 }, { "zcbenchmark", 1 }, { "zcbenchmark", 2 }, - { "getblocksubsidy", 0} + { "getblocksubsidy", 0}, + { "z_importkey", 1 } }; class CRPCConvertTable diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 1a8562b87..b0d4154d0 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -381,7 +381,10 @@ static const CRPCCommand vRPCCommands[] = { "wallet", "zcrawkeygen", &zc_raw_keygen, true }, { "wallet", "zcrawjoinsplit", &zc_raw_joinsplit, true }, { "wallet", "zcrawreceive", &zc_raw_receive, true }, - { "wallet", "zcsamplejoinsplit", &zc_sample_joinsplit, true } + { "wallet", "zcsamplejoinsplit", &zc_sample_joinsplit, true }, + { "wallet", "z_getnewaddress", &z_getnewaddress, true }, + { "wallet", "z_exportkey", &z_exportkey, true }, + { "wallet", "z_importkey", &z_importkey, true } #endif // ENABLE_WALLET }; diff --git a/src/rpcserver.h b/src/rpcserver.h index d90564891..f34914a45 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -243,6 +243,10 @@ extern json_spirit::Value reconsiderblock(const json_spirit::Array& params, bool extern json_spirit::Value getblocksubsidy(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value z_exportkey(const json_spirit::Array& params, bool fHelp); // in rpcdump.cpp +extern json_spirit::Value z_importkey(const json_spirit::Array& params, bool fHelp); // in rpcdump.cpp +extern json_spirit::Value z_getnewaddress(const json_spirit::Array& params, bool fHelp); // in rpcwallet.cpp + // in rest.cpp extern bool HTTPReq_REST(AcceptedConnection *conn, const std::string& strURI, diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index ab951d1d7..db5f36ed0 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -265,6 +265,32 @@ Value importwallet(const Array& params, bool fHelp) boost::split(vstr, line, boost::is_any_of(" ")); if (vstr.size() < 2) continue; + + // Let's see if the address is a valid Zcash spending key + try { + CZCSpendingKey spendingkey(vstr[0]); + libzcash::SpendingKey key = spendingkey.Get(); + libzcash::PaymentAddress addr = key.address(); + if (pwalletMain->HaveSpendingKey(addr)) { + LogPrintf("Skipping import of zaddr %s (key already present)\n", CZCPaymentAddress(addr).ToString()); + continue; + } + int64_t nTime = DecodeDumpTime(vstr[1]); + LogPrintf("Importing zaddr %s...\n", CZCPaymentAddress(addr).ToString()); + if (!pwalletMain->AddZKey(key)) { + // Something went wrong + fGood = false; + continue; + } + // Successfully imported zaddr. Now import the metadata. + pwalletMain->mapZKeyMetadata[addr].nCreateTime = nTime; + continue; + } + catch (...) { + // Not a valid spending key, so carry on and see if it's a Bitcoin style address. + } + + CBitcoinSecret vchSecret; if (!vchSecret.SetString(vstr[0])) continue; @@ -419,7 +445,124 @@ Value dumpwallet(const Array& params, bool fHelp) } } file << "\n"; + + // dump the zkeys + std::set addresses; + pwalletMain->GetPaymentAddresses(addresses); + file << "\n"; + file << "# Zkeys\n"; + file << "\n"; + for (auto addr : addresses ) { + libzcash::SpendingKey key; + if (pwalletMain->GetSpendingKey(addr, key)) { + std::string strTime = EncodeDumpTime(pwalletMain->mapZKeyMetadata[addr].nCreateTime); + file << strprintf("%s %s # zaddr=%s\n", CZCSpendingKey(key).ToString(), strTime, CZCPaymentAddress(addr).ToString()); + } + } + file << "\n"; + file << "# End of dump\n"; file.close(); return Value::null; } + + +Value z_importkey(const Array& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return Value::null; + + if (fHelp || params.size() < 1 || params.size() > 3) + throw runtime_error( + "z_importkey \"zkey\" ( \"label\" rescan )\n" + "\nAdds a zkey (as returned by z_exportkey) to your wallet.\n" + "\nArguments:\n" + "1. \"zkey\" (string, required) The zkey (see z_exportkey)\n" + "2. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" + "\nNote: This call can take minutes to complete if rescan is true.\n" + "\nExamples:\n" + "\nExport a zkey\n" + + HelpExampleCli("z_exportkey", "\"myaddress\"") + + "\nImport the zkey with rescan\n" + + HelpExampleCli("z_importkey", "\"mykey\"") + + "\nImport using a label and without rescan\n" + + HelpExampleCli("z_importkey", "\"mykey\" \"testing\" false") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("z_importkey", "\"mykey\", \"testing\", false") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + EnsureWalletIsUnlocked(); + + // Whether to perform rescan after import + bool fRescan = true; + if (params.size() > 1) + fRescan = params[1].get_bool(); + + string strSecret = params[0].get_str(); + CZCSpendingKey spendingkey(strSecret); + auto key = spendingkey.Get(); + auto addr = key.address(); + + { + pwalletMain->MarkDirty(); + + // Don't throw error in case a key is already there + if (pwalletMain->HaveSpendingKey(addr)) + return Value::null; + + if (!pwalletMain-> AddZKey(key)) + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); + + pwalletMain->mapZKeyMetadata[addr].nCreateTime = 1; + + // We want to scan for transactions and notes + if (fRescan) { + pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); + } + } + + return Value::null; +} + + +Value z_exportkey(const Array& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return Value::null; + + if (fHelp || params.size() != 1) + throw runtime_error( + "z_exportkey \"zaddr\"\n" + "\nReveals the zkey corresponding to 'zaddr'.\n" + "Then the z_importkey can be used with this output\n" + "\nArguments:\n" + "1. \"zaddr\" (string, required) The zaddr for the private key\n" + "\nResult:\n" + "\"key\" (string) The private key\n" + "\nExamples:\n" + + HelpExampleCli("z_exportkey", "\"myaddress\"") + + HelpExampleCli("z_importkey", "\"mykey\"") + + HelpExampleRpc("z_exportkey", "\"myaddress\"") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + EnsureWalletIsUnlocked(); + + string strAddress = params[0].get_str(); + + CZCPaymentAddress address(strAddress); + auto addr = address.Get(); + if (!pwalletMain->HaveSpendingKey(addr)) + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private zkey for this zaddr"); + + libzcash::SpendingKey k; + if (!pwalletMain->GetSpendingKey(addr, k)) + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private zkey for this zaddr"); + + CZCSpendingKey spendingkey(k); + return spendingkey.ToString(); +} + diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index b9d588b9a..7315d7014 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2769,3 +2769,29 @@ Value zc_raw_keygen(const json_spirit::Array& params, bool fHelp) result.push_back(Pair("zcviewingkey", viewing_hex)); return result; } + + +Value z_getnewaddress(const Array& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return Value::null; + + if (fHelp || params.size() > 1) + throw runtime_error( + "z_getnewaddress\n" + "\nReturns a new zaddr for receiving payments.\n" + "\nArguments:\n" + "\nResult:\n" + "\"zcashaddress\" (string) The new zaddr\n" + "\nExamples:\n" + + HelpExampleCli("z_getnewaddress", "") + + HelpExampleRpc("z_getnewaddress", "") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + CZCPaymentAddress pubaddr = pwalletMain->GenerateNewZKey(); + std::string result = pubaddr.ToString(); + return result; +} + diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f8215c939..d2878a40e 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -25,6 +25,7 @@ #include using namespace std; +using namespace libzcash; /** * Settings @@ -70,6 +71,47 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const return &(it->second); } +// Generate a new spending key and return its public payment address +CZCPaymentAddress CWallet::GenerateNewZKey() +{ + AssertLockHeld(cs_wallet); // mapZKeyMetadata + auto k = SpendingKey::random(); + auto addr = k.address(); + + // Check for collision, even though it is unlikely to ever occur + if (CCryptoKeyStore::HaveSpendingKey(addr)) + throw std::runtime_error("CWallet::GenerateNewSpendingKey(): Collision detected"); + + // Create new metadata + int64_t nCreationTime = GetTime(); + mapZKeyMetadata[addr] = CKeyMetadata(nCreationTime); + + CZCPaymentAddress pubaddr(addr); + if (!AddZKey(k)) + throw std::runtime_error("CWallet::GenerateNewSpendingKey(): AddZKey failed"); + return pubaddr; +} + +// Add spending key to keystore and persist to disk +bool CWallet::AddZKey(const libzcash::SpendingKey &key) +{ + AssertLockHeld(cs_wallet); // mapZKeyMetadata + auto addr = key.address(); + + if (!CCryptoKeyStore::AddSpendingKey(key)) + return false; + + if (!fFileBacked) + return true; + + if (!IsCrypted()) { + return CWalletDB(strWalletFile).WriteZKey(addr, + key, + mapZKeyMetadata[addr]); + } + return true; +} + CPubKey CWallet::GenerateNewKey() { AssertLockHeld(cs_wallet); // mapKeyMetadata @@ -149,11 +191,23 @@ bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta) return true; } +bool CWallet::LoadZKeyMetadata(const PaymentAddress &addr, const CKeyMetadata &meta) +{ + AssertLockHeld(cs_wallet); // mapZKeyMetadata + mapZKeyMetadata[addr] = meta; + return true; +} + bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) { return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); } +bool CWallet::LoadZKey(const libzcash::SpendingKey &key) +{ + return CCryptoKeyStore::AddSpendingKey(key); +} + bool CWallet::AddCScript(const CScript& redeemScript) { if (!CCryptoKeyStore::AddCScript(redeemScript)) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f84f857f5..0651c6404 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -18,6 +18,8 @@ #include "wallet/crypter.h" #include "wallet/wallet_ismine.h" #include "wallet/walletdb.h" +#include "zcash/Address.hpp" +#include "base58.h" #include #include @@ -486,6 +488,7 @@ public: std::set setKeyPool; std::map mapKeyMetadata; + std::map mapZKeyMetadata; typedef std::map MasterKeyMap; MasterKeyMap mapMasterKeys; @@ -595,6 +598,19 @@ public: void GetKeyBirthTimes(std::map &mapKeyBirth) const; + /** + * ZKeys + */ + //! Generates a new zaddr + CZCPaymentAddress GenerateNewZKey(); + //! Adds spending key to the store, and saves it to disk + bool AddZKey(const libzcash::SpendingKey &key); + //! Adds spending key to the store, without saving it to disk (used by LoadWallet) + bool LoadZKey(const libzcash::SpendingKey &key); + //! Load spending key metadata (used by LoadWallet) + bool LoadZKeyMetadata(const libzcash::PaymentAddress &addr, const CKeyMetadata &meta); + + /** * Increment the next transaction order id * @return next transaction order id diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 6f08ad681..2917445a5 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -110,6 +110,17 @@ bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey) return Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true); } +bool CWalletDB::WriteZKey(const libzcash::PaymentAddress& addr, const libzcash::SpendingKey& key, const CKeyMetadata &keyMeta) +{ + nWalletDBUpdated++; + + if (!Write(std::make_pair(std::string("zkeymeta"), addr), keyMeta)) + return false; + + // pair is: tuple_key("spendingkey", paymentaddress) --> secretkey + return Write(std::make_pair(std::string("zkey"), addr), key, false); +} + bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript) { nWalletDBUpdated++; @@ -330,13 +341,15 @@ public: unsigned int nKeys; unsigned int nCKeys; unsigned int nKeyMeta; + unsigned int nZKeys; + unsigned int nZKeyMeta; bool fIsEncrypted; bool fAnyUnordered; int nFileVersion; vector vWalletUpgrade; CWalletScanState() { - nKeys = nCKeys = nKeyMeta = 0; + nKeys = nCKeys = nKeyMeta = nZKeys = nZKeyMeta = 0; fIsEncrypted = false; fAnyUnordered = false; nFileVersion = 0; @@ -429,6 +442,23 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, // so set the wallet birthday to the beginning of time. pwallet->nTimeFirstKey = 1; } + else if (strType == "zkey") + { + libzcash::PaymentAddress addr; + ssKey >> addr; + libzcash::SpendingKey key; + ssValue >> key; + + if (!pwallet->LoadZKey(key)) + { + strErr = "Error reading wallet database: LoadZKey failed"; + return false; + } + + wss.nZKeys++; + + CZCPaymentAddress pubaddr(addr); + } else if (strType == "key" || strType == "wkey") { CPubKey vchPubKey; @@ -538,6 +568,18 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, (keyMeta.nCreateTime < pwallet->nTimeFirstKey)) pwallet->nTimeFirstKey = keyMeta.nCreateTime; } + else if (strType == "zkeymeta") + { + libzcash::PaymentAddress addr; + ssKey >> addr; + CKeyMetadata keyMeta; + ssValue >> keyMeta; + wss.nZKeyMeta++; + + pwallet->LoadZKeyMetadata(addr, keyMeta); + + // ignore earliest key creation time as taddr will exist before any zaddr + } else if (strType == "defaultkey") { ssValue >> pwallet->vchDefaultKey; @@ -685,6 +727,10 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) LogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total\n", wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys); + // TODO: Keep track of encrypted ZKeys + LogPrintf("ZKeys: %u plaintext, -- encrypted, %u w/metadata, %u total\n", + wss.nZKeys, wss.nZKeyMeta, wss.nZKeys + 0); + // nTimeFirstKey is only reliable if all keys have metadata if ((wss.nKeys + wss.nCKeys) != wss.nKeyMeta) pwallet->nTimeFirstKey = 1; // 0 would be considered 'no value' diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index bc1a104b5..d261c9644 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -10,6 +10,7 @@ #include "wallet/db.h" #include "key.h" #include "keystore.h" +#include "zcash/Address.hpp" #include #include @@ -130,6 +131,9 @@ public: static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys); static bool Recover(CDBEnv& dbenv, const std::string& filename); + /// Write spending key to wallet database, where key is payment address and value is spending key. + bool WriteZKey(const libzcash::PaymentAddress& addr, const libzcash::SpendingKey& key, const CKeyMetadata &keyMeta); + private: CWalletDB(const CWalletDB&); void operator=(const CWalletDB&);