diff --git a/src/keystore.cpp b/src/keystore.cpp index edab1e50a..1a3742ac1 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -93,6 +93,16 @@ bool CBasicKeyStore::AddSpendingKey(const libzcash::SproutSpendingKey &sk) return true; } +//! Sapling +bool CBasicKeyStore::AddSaplingSpendingKey(const libzcash::SaplingSpendingKey &sk) +{ + LOCK(cs_SpendingKeyStore); + auto fvk = sk.full_viewing_key(); + mapSaplingSpendingKeys[fvk] = sk; + //! TODO: Note decryptors for Sapling + return true; +} + bool CBasicKeyStore::AddViewingKey(const libzcash::SproutViewingKey &vk) { LOCK(cs_SpendingKeyStore); diff --git a/src/keystore.h b/src/keystore.h index 3195c40b3..d75e7bef3 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -55,6 +55,13 @@ public: virtual bool HaveSpendingKey(const libzcash::SproutPaymentAddress &address) const =0; virtual bool GetSpendingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutSpendingKey& skOut) const =0; virtual void GetPaymentAddresses(std::set &setAddress) const =0; + + //! Add a Sapling spending key to the store. + virtual bool AddSaplingSpendingKey(const libzcash::SaplingSpendingKey &sk) =0; + + //! Check whether a Sapling spending key corresponding to a given Sapling viewing key is present in the store. + virtual bool HaveSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk) const =0; + virtual bool GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingSpendingKey& skOut) const =0; //! Support for viewing keys virtual bool AddViewingKey(const libzcash::SproutViewingKey &vk) =0; @@ -70,6 +77,9 @@ typedef std::map Sp typedef std::map ViewingKeyMap; typedef std::map NoteDecryptorMap; +typedef std::map SaplingSpendingKeyMap; +typedef std::map SaplingFullViewingKeyMap; + /** Basic key store, that keeps keys in an address->secret map */ class CBasicKeyStore : public CKeyStore { @@ -80,6 +90,8 @@ protected: SpendingKeyMap mapSpendingKeys; ViewingKeyMap mapViewingKeys; NoteDecryptorMap mapNoteDecryptors; + + SaplingSpendingKeyMap mapSaplingSpendingKeys; public: bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); @@ -182,6 +194,32 @@ public: } } } + + //! Sapling + bool AddSaplingSpendingKey(const libzcash::SaplingSpendingKey &sk); + bool HaveSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk) const + { + bool result; + { + LOCK(cs_SpendingKeyStore); + result = (mapSaplingSpendingKeys.count(fvk) > 0); + } + return result; + } + bool GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingSpendingKey &skOut) const + { + { + LOCK(cs_SpendingKeyStore); + + SaplingSpendingKeyMap::const_iterator mi = mapSaplingSpendingKeys.find(fvk); + if (mi != mapSaplingSpendingKeys.end()) + { + skOut = mi->second; + return true; + } + } + return false; + } virtual bool AddViewingKey(const libzcash::SproutViewingKey &vk); virtual bool RemoveViewingKey(const libzcash::SproutViewingKey &vk); diff --git a/src/wallet/gtest/test_wallet_zkeys.cpp b/src/wallet/gtest/test_wallet_zkeys.cpp index efc06b962..9145648d2 100644 --- a/src/wallet/gtest/test_wallet_zkeys.cpp +++ b/src/wallet/gtest/test_wallet_zkeys.cpp @@ -7,9 +7,42 @@ #include +/** + * This test covers Sapling methods on CWallet + * GenerateNewSaplingZKey() + */ +TEST(wallet_zkeys_tests, store_and_load_sapling_zkeys) { + CWallet wallet; + + // wallet should be empty + // std::set addrs; + // wallet.GetSaplingPaymentAddresses(addrs); + // ASSERT_EQ(0, addrs.size()); + + // wallet should have one key + auto saplingAddr = wallet.GenerateNewSaplingZKey(); + // ASSERT_NE(boost::get(&address), nullptr); + // auto sapling_addr = boost::get(saplingAddr); + // wallet.GetSaplingPaymentAddresses(addrs); + // ASSERT_EQ(1, addrs.size()); + + auto sk = libzcash::SaplingSpendingKey::random(); + auto full_viewing_key = sk.full_viewing_key(); + ASSERT_TRUE(wallet.AddSaplingSpendingKey(sk)); + + // verify wallet has spending key for the address + ASSERT_TRUE(wallet.HaveSaplingSpendingKey(full_viewing_key)); + + // check key is the same + libzcash::SaplingSpendingKey keyOut; + wallet.GetSaplingSpendingKey(full_viewing_key, keyOut); + ASSERT_EQ(sk, keyOut); +} + /** * This test covers methods on CWallet * GenerateNewZKey() + * GenerateNewSaplingZKey() * AddZKey() * LoadZKey() * LoadZKeyMetadata() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 27f96af58..cee277fd8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -100,8 +100,64 @@ libzcash::PaymentAddress CWallet::GenerateNewZKey() return addr; } +//! TODO: Should be Sapling address format, SaplingPaymentAddress +// Generate a new Sapling spending key and return its public payment address +SaplingPaymentAddress CWallet::GenerateNewSaplingZKey() +{ + AssertLockHeld(cs_wallet); // mapZKeyMetadata + auto sk = SaplingSpendingKey::random(); + auto fvk = sk.full_viewing_key(); + auto addrOpt = sk.default_address(); + if (addrOpt){ + auto addr = addrOpt.value(); + // Check for collision, even though it is unlikely to ever occur + if (CCryptoKeyStore::HaveSaplingSpendingKey(fvk)) + throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): Collision detected"); + + // Create new metadata + int64_t nCreationTime = GetTime(); + mapSaplingZKeyMetadata[addr] = CKeyMetadata(nCreationTime); + + if (!AddSaplingZKey(sk)) { + throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): AddSaplingZKey failed"); + } + // return default sapling payment address. + return addr; + } else { + throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): default_address() did not return address"); + } +} + +// Add spending key to keystore +// TODO: persist to disk +bool CWallet::AddSaplingZKey(const libzcash::SaplingSpendingKey &sk) +{ + AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata + auto addr = sk.default_address(); + + if (!CCryptoKeyStore::AddSaplingSpendingKey(sk)) { + return false; + } + + // // check if we need to remove from viewing keys + // if (HaveViewingKey(addr)) { + // RemoveViewingKey(key.viewing_key()); + // } + + // if (!fFileBacked) { + // return true; + // } + + // if (!IsCrypted()) { + // return CWalletDB(strWalletFile).WriteSaplingZKey(addr, + // sk, + // mapSaplingZKeyMetadata[addr]); + // } + return true; +} + + // Add spending key to keystore and persist to disk -// TODO: Add Sapling support bool CWallet::AddZKey(const libzcash::SproutSpendingKey &key) { AssertLockHeld(cs_wallet); // mapZKeyMetadata @@ -176,7 +232,7 @@ bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const vector &vchCryptedSecret) { - + if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) return false; if (!fFileBacked) @@ -516,7 +572,7 @@ bool CWallet::Verify(const string& walletFile, string& warningString, string& er } catch (const boost::filesystem::filesystem_error&) { // failure is ok (well, not really, but it's not worse than what we started with) } - + // try again if (!bitdb.Open(GetDataDir())) { // if it still fails, it probably means we can't even create the database env @@ -525,14 +581,14 @@ bool CWallet::Verify(const string& walletFile, string& warningString, string& er return true; } } - + if (GetBoolArg("-salvagewallet", false)) { // Recover readable keypairs: if (!CWalletDB::Recover(bitdb, walletFile, true)) return false; } - + if (boost::filesystem::exists(GetDataDir() / walletFile)) { CDBEnv::VerifyResult r = bitdb.Verify(walletFile, CWalletDB::Recover); @@ -546,7 +602,7 @@ bool CWallet::Verify(const string& walletFile, string& warningString, string& er if (r == CDBEnv::RECOVER_FAIL) errorString += _("wallet.dat corrupt, salvage failed"); } - + return true; } @@ -2523,7 +2579,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC CReserveKey reservekey(this); CWalletTx wtx; - + if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosRet, strFailReason, &coinControl, false)) return false; @@ -2590,7 +2646,7 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING; } - + // Discourage fee sniping. // // However because of a off-by-one-error in previous versions we need to @@ -3062,7 +3118,7 @@ bool CWallet::SetDefaultKey(const CPubKey &vchPubKey) /** * Mark old keypool keys as used, - * and generate all new keys + * and generate all new keys */ bool CWallet::NewKeyPool() { @@ -3767,7 +3823,7 @@ void CWallet::GetFilteredNotes( if (ignoreUnspendable && !HaveSpendingKey(pa)) { continue; } - + // skip locked notes if (IsLockedNote(jsop)) { continue; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index df99a001f..3fe8e95e5 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -786,6 +786,7 @@ public: std::set setKeyPool; std::map mapKeyMetadata; std::map mapZKeyMetadata; + std::map mapSaplingZKeyMetadata; typedef std::map MasterKeyMap; MasterKeyMap mapMasterKeys; @@ -977,6 +978,14 @@ public: bool RemoveViewingKey(const libzcash::SproutViewingKey &vk); //! Adds a viewing key to the store, without saving it to disk (used by LoadWallet) bool LoadViewingKey(const libzcash::SproutViewingKey &dest); + + /** + * Sapling ZKeys + */ + //! Generates new Sapling key + libzcash::SaplingPaymentAddress GenerateNewSaplingZKey(); + //! Adds Sapling spending key to the store, and saves it to disk + bool AddSaplingZKey(const libzcash::SaplingSpendingKey &key); /** * Increment the next transaction order id