From efb7662d4a49e5b13b11d62ef5ff64de6d3b8203 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Thu, 5 Jul 2018 14:37:33 -0700 Subject: [PATCH 1/5] Add Sapling Add/Have/Get to keystore --- src/keystore.cpp | 10 ++++ src/keystore.h | 38 +++++++++++++ src/wallet/gtest/test_wallet_zkeys.cpp | 33 +++++++++++ src/wallet/wallet.cpp | 76 ++++++++++++++++++++++---- src/wallet/wallet.h | 9 +++ 5 files changed, 156 insertions(+), 10 deletions(-) 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 From f4207d0c0ef563666e1b5823784500bd4a1da6f5 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Thu, 5 Jul 2018 14:52:46 -0700 Subject: [PATCH 2/5] Add SaplingIncomingViewingKeys map, SaplingFullViewingKey methods --- src/keystore.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++- src/keystore.h | 30 ++++++++++++++++++++++ src/wallet/wallet.cpp | 14 ++++------ 3 files changed, 94 insertions(+), 10 deletions(-) diff --git a/src/keystore.cpp b/src/keystore.cpp index 1a3742ac1..828cae677 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -99,7 +99,19 @@ bool CBasicKeyStore::AddSaplingSpendingKey(const libzcash::SaplingSpendingKey &s LOCK(cs_SpendingKeyStore); auto fvk = sk.full_viewing_key(); mapSaplingSpendingKeys[fvk] = sk; - //! TODO: Note decryptors for Sapling + + // if SaplingFullViewingKey is not in SaplingFullViewingKeyMap, add it + AddSaplingFullViewingKey(fvk); + + // Add addr -> SaplingIncomingViewing to SaplingIncomingViewingKeyMap + auto ivk = fvk.in_viewing_key(); + auto addrOpt = sk.default_address(); + if (addrOpt){ + auto addr = addrOpt.value(); + mapSaplingIncomingViewingKeys[addr] = ivk; + } else { + return false; + } return true; } @@ -112,6 +124,16 @@ bool CBasicKeyStore::AddViewingKey(const libzcash::SproutViewingKey &vk) return true; } +bool CBasicKeyStore::AddSaplingFullViewingKey(const libzcash::SaplingFullViewingKey &fvk) +{ + LOCK(cs_SpendingKeyStore); + auto ivk = fvk.in_viewing_key(); + mapSaplingFullViewingKeys[ivk] = fvk; + //! TODO: Note decryptors for Sapling + // mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(vk.sk_enc))); + return true; +} + bool CBasicKeyStore::RemoveViewingKey(const libzcash::SproutViewingKey &vk) { LOCK(cs_SpendingKeyStore); @@ -125,6 +147,18 @@ bool CBasicKeyStore::HaveViewingKey(const libzcash::SproutPaymentAddress &addres return mapViewingKeys.count(address) > 0; } +bool CBasicKeyStore::HaveSaplingFullViewingKey(const libzcash::SaplingIncomingViewingKey &ivk) const +{ + LOCK(cs_SpendingKeyStore); + return mapSaplingFullViewingKeys.count(ivk) > 0; +} + +bool CBasicKeyStore::HaveSaplingIncomingViewingKey(const libzcash::SaplingPaymentAddress &addr) const +{ + LOCK(cs_SpendingKeyStore); + return mapSaplingIncomingViewingKeys.count(addr) > 0; +} + bool CBasicKeyStore::GetViewingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutViewingKey &vkOut) const { @@ -136,3 +170,27 @@ bool CBasicKeyStore::GetViewingKey(const libzcash::SproutPaymentAddress &address } return false; } + +bool CBasicKeyStore::GetSaplingFullViewingKey(const libzcash::SaplingIncomingViewingKey &ivk, + libzcash::SaplingFullViewingKey &fvkOut) const +{ + LOCK(cs_SpendingKeyStore); + SaplingFullViewingKeyMap::const_iterator mi = mapSaplingFullViewingKeys.find(ivk); + if (mi != mapSaplingFullViewingKeys.end()) { + fvkOut = mi->second; + return true; + } + return false; +} + +bool CBasicKeyStore::GetSaplingIncomingViewingKey(const libzcash::SaplingPaymentAddress &addr, + libzcash::SaplingIncomingViewingKey &ivkOut) const +{ + LOCK(cs_SpendingKeyStore); + SaplingIncomingViewingKeyMap::const_iterator mi = mapSaplingIncomingViewingKeys.find(addr); + if (mi != mapSaplingIncomingViewingKeys.end()) { + ivkOut = mi->second; + return true; + } + return false; +} diff --git a/src/keystore.h b/src/keystore.h index d75e7bef3..3d230dc37 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -62,6 +62,19 @@ public: //! 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 Sapling full viewing keys + virtual bool AddSaplingFullViewingKey(const libzcash::SaplingFullViewingKey &fvk) =0; + virtual bool HaveSaplingFullViewingKey(const libzcash::SaplingIncomingViewingKey &ivk) const =0; + virtual bool GetSaplingFullViewingKey( + const libzcash::SaplingIncomingViewingKey &ivk, + libzcash::SaplingFullViewingKey& fvkOut) const =0; + + //! Sapling incoming viewing keys + virtual bool HaveSaplingIncomingViewingKey(const libzcash::SaplingPaymentAddress &addr) const =0; + virtual bool GetSaplingIncomingViewingKey( + const libzcash::SaplingPaymentAddress &addr, + libzcash::SaplingIncomingViewingKey& ivkOut) const =0; //! Support for viewing keys virtual bool AddViewingKey(const libzcash::SproutViewingKey &vk) =0; @@ -77,8 +90,12 @@ typedef std::map Sp typedef std::map ViewingKeyMap; typedef std::map NoteDecryptorMap; +// Full viewing key has equivalent functionality to a transparent address +// When encrypting wallet, encrypt SaplingSpendingKeyMap, while leaving SaplingFullViewingKeyMap unencrypted typedef std::map SaplingSpendingKeyMap; typedef std::map SaplingFullViewingKeyMap; +// Only maps from default addresses to ivk, may need to be reworked when adding diversified addresses. +typedef std::map SaplingIncomingViewingKeyMap; /** Basic key store, that keeps keys in an address->secret map */ class CBasicKeyStore : public CKeyStore @@ -92,6 +109,8 @@ protected: NoteDecryptorMap mapNoteDecryptors; SaplingSpendingKeyMap mapSaplingSpendingKeys; + SaplingFullViewingKeyMap mapSaplingFullViewingKeys; + SaplingIncomingViewingKeyMap mapSaplingIncomingViewingKeys; public: bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); @@ -220,6 +239,17 @@ public: } return false; } + + virtual bool AddSaplingFullViewingKey(const libzcash::SaplingFullViewingKey &fvk); + virtual bool HaveSaplingFullViewingKey(const libzcash::SaplingIncomingViewingKey &ivk) const; + virtual bool GetSaplingFullViewingKey( + const libzcash::SaplingIncomingViewingKey &ivk, + libzcash::SaplingFullViewingKey& fvkOut) const; + + virtual bool HaveSaplingIncomingViewingKey(const libzcash::SaplingPaymentAddress &addr) const; + virtual bool GetSaplingIncomingViewingKey( + const libzcash::SaplingPaymentAddress &addr, + libzcash::SaplingIncomingViewingKey& ivkOut) const; virtual bool AddViewingKey(const libzcash::SproutViewingKey &vk); virtual bool RemoveViewingKey(const libzcash::SproutViewingKey &vk); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index cee277fd8..b1bb54dac 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -138,16 +138,12 @@ bool CWallet::AddSaplingZKey(const libzcash::SaplingSpendingKey &sk) if (!CCryptoKeyStore::AddSaplingSpendingKey(sk)) { return false; } + + if (!fFileBacked) { + return true; + } - // // check if we need to remove from viewing keys - // if (HaveViewingKey(addr)) { - // RemoveViewingKey(key.viewing_key()); - // } - - // if (!fFileBacked) { - // return true; - // } - + // TODO: Persist to disk // if (!IsCrypted()) { // return CWalletDB(strWalletFile).WriteSaplingZKey(addr, // sk, From f82a864dc1c056ba7848936a2fcda3b0ae765145 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Fri, 22 Jun 2018 10:27:31 -0700 Subject: [PATCH 3/5] Add StoreAndRetrieveSaplingSpendingKey test --- src/gtest/test_keystore.cpp | 44 +++++++++++++++++++++- src/keystore.cpp | 13 +++---- src/wallet/gtest/test_wallet_zkeys.cpp | 33 ---------------- src/wallet/wallet.cpp | 52 ++++++++++++-------------- src/wallet/walletdb.h | 2 +- 5 files changed, 73 insertions(+), 71 deletions(-) diff --git a/src/gtest/test_keystore.cpp b/src/gtest/test_keystore.cpp index 7358472e6..d767967ba 100644 --- a/src/gtest/test_keystore.cpp +++ b/src/gtest/test_keystore.cpp @@ -55,8 +55,12 @@ TEST(keystore_tests, sapling_keys) { EXPECT_EQ(in_viewing_key, in_viewing_key_2); // Check that the default address from primitives and from sk method are the same - auto default_addr = sk.default_address(); - auto default_addr_2 = in_viewing_key.address(default_d); + auto addrOpt = sk.default_address(); + EXPECT_TRUE(addrOpt); + auto default_addr = addrOpt.value(); + auto addrOpt2 = in_viewing_key.address(default_d); + EXPECT_TRUE(addrOpt2); + auto default_addr_2 = addrOpt2.value(); EXPECT_EQ(default_addr, default_addr_2); auto default_addr_3 = libzcash::SaplingPaymentAddress(default_d, default_pk_d); @@ -161,6 +165,42 @@ TEST(keystore_tests, StoreAndRetrieveViewingKey) { EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut); } +// Sapling +TEST(keystore_tests, StoreAndRetrieveSaplingSpendingKey) { + CBasicKeyStore keyStore; + libzcash::SaplingSpendingKey skOut; + libzcash::SaplingFullViewingKey fvkOut; + libzcash::SaplingIncomingViewingKey ivkOut; + + auto sk = libzcash::SaplingSpendingKey::random(); + auto fvk = sk.full_viewing_key(); + auto ivk = fvk.in_viewing_key(); + auto addrOpt = sk.default_address(); + EXPECT_TRUE(addrOpt); + auto addr = addrOpt.value(); + + // Sanity-check: we can't get a key we haven't added + EXPECT_FALSE(keyStore.HaveSaplingSpendingKey(fvk)); + EXPECT_FALSE(keyStore.GetSaplingSpendingKey(fvk, skOut)); + // Sanity-check: we can't get a full viewing key we haven't added + EXPECT_FALSE(keyStore.HaveSaplingFullViewingKey(ivk)); + EXPECT_FALSE(keyStore.GetSaplingFullViewingKey(ivk, fvkOut)); + // Sanity-check: we can't get an incoming viewing key we haven't added + EXPECT_FALSE(keyStore.HaveSaplingIncomingViewingKey(addr)); + EXPECT_FALSE(keyStore.GetSaplingIncomingViewingKey(addr, ivkOut)); + + keyStore.AddSaplingSpendingKey(sk); + EXPECT_TRUE(keyStore.HaveSaplingSpendingKey(fvk)); + EXPECT_TRUE(keyStore.GetSaplingSpendingKey(fvk, skOut)); + EXPECT_TRUE(keyStore.HaveSaplingFullViewingKey(ivk)); + EXPECT_TRUE(keyStore.GetSaplingFullViewingKey(ivk, fvkOut)); + EXPECT_TRUE(keyStore.HaveSaplingIncomingViewingKey(addr)); + EXPECT_TRUE(keyStore.GetSaplingIncomingViewingKey(addr, ivkOut)); + EXPECT_EQ(sk, skOut); + EXPECT_EQ(fvk, fvkOut); + EXPECT_EQ(ivk, ivkOut); +} + #ifdef ENABLE_WALLET class TestCCryptoKeyStore : public CCryptoKeyStore { diff --git a/src/keystore.cpp b/src/keystore.cpp index 828cae677..d0e5b8ec6 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -106,12 +106,10 @@ bool CBasicKeyStore::AddSaplingSpendingKey(const libzcash::SaplingSpendingKey &s // Add addr -> SaplingIncomingViewing to SaplingIncomingViewingKeyMap auto ivk = fvk.in_viewing_key(); auto addrOpt = sk.default_address(); - if (addrOpt){ - auto addr = addrOpt.value(); - mapSaplingIncomingViewingKeys[addr] = ivk; - } else { - return false; - } + assert(addrOpt != boost::none); + auto addr = addrOpt.value(); + mapSaplingIncomingViewingKeys[addr] = ivk; + return true; } @@ -129,8 +127,9 @@ bool CBasicKeyStore::AddSaplingFullViewingKey(const libzcash::SaplingFullViewing LOCK(cs_SpendingKeyStore); auto ivk = fvk.in_viewing_key(); mapSaplingFullViewingKeys[ivk] = fvk; + //! TODO: Note decryptors for Sapling - // mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(vk.sk_enc))); + return true; } diff --git a/src/wallet/gtest/test_wallet_zkeys.cpp b/src/wallet/gtest/test_wallet_zkeys.cpp index 9145648d2..efc06b962 100644 --- a/src/wallet/gtest/test_wallet_zkeys.cpp +++ b/src/wallet/gtest/test_wallet_zkeys.cpp @@ -7,42 +7,9 @@ #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 b1bb54dac..410aec564 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -100,40 +100,40 @@ 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"); + + SaplingSpendingKey sk; + boost::optional addrOpt; + while (!addrOpt){ + sk = SaplingSpendingKey::random(); + addrOpt = sk.default_address(); } + + auto addr = addrOpt.value(); + auto fvk = sk.full_viewing_key(); + + // 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; } // 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; @@ -144,11 +144,7 @@ bool CWallet::AddSaplingZKey(const libzcash::SaplingSpendingKey &sk) } // TODO: Persist to disk - // if (!IsCrypted()) { - // return CWalletDB(strWalletFile).WriteSaplingZKey(addr, - // sk, - // mapSaplingZKeyMetadata[addr]); - // } + return true; } diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 318993a65..554f1672b 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -141,7 +141,7 @@ public: bool WriteViewingKey(const libzcash::SproutViewingKey &vk); bool EraseViewingKey(const libzcash::SproutViewingKey &vk); - + private: CWalletDB(const CWalletDB&); void operator=(const CWalletDB&); From 8e91ebf76caecbb66809979f21f979fb03acf03c Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Sat, 30 Jun 2018 12:57:04 -0700 Subject: [PATCH 4/5] Change default_address to return SaplingPaymentAddr and not boost::optional --- src/gtest/test_keystore.cpp | 8 ++------ src/gtest/test_sapling_note.cpp | 4 ++-- src/keystore.cpp | 11 ++++++----- src/keystore.h | 1 + src/wallet/wallet.cpp | 17 ++++++----------- src/wallet/walletdb.h | 2 +- src/zcash/Address.cpp | 7 +++++-- src/zcash/Address.hpp | 2 +- 8 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/gtest/test_keystore.cpp b/src/gtest/test_keystore.cpp index d767967ba..5660f369b 100644 --- a/src/gtest/test_keystore.cpp +++ b/src/gtest/test_keystore.cpp @@ -55,9 +55,7 @@ TEST(keystore_tests, sapling_keys) { EXPECT_EQ(in_viewing_key, in_viewing_key_2); // Check that the default address from primitives and from sk method are the same - auto addrOpt = sk.default_address(); - EXPECT_TRUE(addrOpt); - auto default_addr = addrOpt.value(); + auto default_addr = sk.default_address(); auto addrOpt2 = in_viewing_key.address(default_d); EXPECT_TRUE(addrOpt2); auto default_addr_2 = addrOpt2.value(); @@ -175,9 +173,7 @@ TEST(keystore_tests, StoreAndRetrieveSaplingSpendingKey) { auto sk = libzcash::SaplingSpendingKey::random(); auto fvk = sk.full_viewing_key(); auto ivk = fvk.in_viewing_key(); - auto addrOpt = sk.default_address(); - EXPECT_TRUE(addrOpt); - auto addr = addrOpt.value(); + auto addr = sk.default_address(); // Sanity-check: we can't get a key we haven't added EXPECT_FALSE(keyStore.HaveSaplingSpendingKey(fvk)); diff --git a/src/gtest/test_sapling_note.cpp b/src/gtest/test_sapling_note.cpp index de06b54c1..06f2d6188 100644 --- a/src/gtest/test_sapling_note.cpp +++ b/src/gtest/test_sapling_note.cpp @@ -56,7 +56,7 @@ TEST(SaplingNote, TestVectors) TEST(SaplingNote, Random) { // Test creating random notes using the same spending key - auto address = SaplingSpendingKey::random().default_address().get(); + auto address = SaplingSpendingKey::random().default_address(); SaplingNote note1(address, GetRand(MAX_MONEY)); SaplingNote note2(address, GetRand(MAX_MONEY)); @@ -66,7 +66,7 @@ TEST(SaplingNote, Random) ASSERT_NE(note1.r, note2.r); // Test diversifier and pk_d are not the same for different spending keys - SaplingNote note3(SaplingSpendingKey::random().default_address().get(), GetRand(MAX_MONEY)); + SaplingNote note3(SaplingSpendingKey::random().default_address(), GetRand(MAX_MONEY)); ASSERT_NE(note1.d, note3.d); ASSERT_NE(note1.pk_d, note3.pk_d); } diff --git a/src/keystore.cpp b/src/keystore.cpp index d0e5b8ec6..cac20a1ae 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -98,16 +98,17 @@ bool CBasicKeyStore::AddSaplingSpendingKey(const libzcash::SaplingSpendingKey &s { LOCK(cs_SpendingKeyStore); auto fvk = sk.full_viewing_key(); - mapSaplingSpendingKeys[fvk] = sk; // if SaplingFullViewingKey is not in SaplingFullViewingKeyMap, add it - AddSaplingFullViewingKey(fvk); + if (!AddSaplingFullViewingKey(fvk)){ + return false; + } + mapSaplingSpendingKeys[fvk] = sk; + // Add addr -> SaplingIncomingViewing to SaplingIncomingViewingKeyMap auto ivk = fvk.in_viewing_key(); - auto addrOpt = sk.default_address(); - assert(addrOpt != boost::none); - auto addr = addrOpt.value(); + auto addr = sk.default_address(); mapSaplingIncomingViewingKeys[addr] = ivk; return true; diff --git a/src/keystore.h b/src/keystore.h index 3d230dc37..541c0bca5 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -92,6 +92,7 @@ typedef std::map NoteDecryptor // Full viewing key has equivalent functionality to a transparent address // When encrypting wallet, encrypt SaplingSpendingKeyMap, while leaving SaplingFullViewingKeyMap unencrypted +// When implementing ZIP 32, add another map from SaplingFullViewingKey -> SaplingExpandedSpendingKey typedef std::map SaplingSpendingKeyMap; typedef std::map SaplingFullViewingKeyMap; // Only maps from default addresses to ivk, may need to be reworked when adding diversified addresses. diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 410aec564..420246f0b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -103,21 +103,16 @@ libzcash::PaymentAddress CWallet::GenerateNewZKey() // Generate a new Sapling spending key and return its public payment address SaplingPaymentAddress CWallet::GenerateNewSaplingZKey() { - AssertLockHeld(cs_wallet); // mapZKeyMetadata + AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata - SaplingSpendingKey sk; - boost::optional addrOpt; - while (!addrOpt){ - sk = SaplingSpendingKey::random(); - addrOpt = sk.default_address(); - } - - auto addr = addrOpt.value(); + auto sk = SaplingSpendingKey::random(); auto fvk = sk.full_viewing_key(); + auto addr = sk.default_address(); // Check for collision, even though it is unlikely to ever occur - if (CCryptoKeyStore::HaveSaplingSpendingKey(fvk)) - throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): Collision detected"); + if (CCryptoKeyStore::HaveSaplingSpendingKey(fvk)) { + throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): Collision detected"); + } // Create new metadata int64_t nCreationTime = GetTime(); diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 554f1672b..318993a65 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -141,7 +141,7 @@ public: bool WriteViewingKey(const libzcash::SproutViewingKey &vk); bool EraseViewingKey(const libzcash::SproutViewingKey &vk); - + private: CWalletDB(const CWalletDB&); void operator=(const CWalletDB&); diff --git a/src/zcash/Address.cpp b/src/zcash/Address.cpp index 43bd45ecd..e6dd0125a 100644 --- a/src/zcash/Address.cpp +++ b/src/zcash/Address.cpp @@ -75,8 +75,11 @@ boost::optional SaplingIncomingViewingKey::address(divers } } -boost::optional SaplingSpendingKey::default_address() const { - return full_viewing_key().in_viewing_key().address(default_diversifier(*this)); +SaplingPaymentAddress SaplingSpendingKey::default_address() const { + // Iterates within default_diversifier to ensure a valid address is returned + auto addrOpt = full_viewing_key().in_viewing_key().address(default_diversifier(*this)); + assert(addrOpt != boost::none); + return addrOpt.value(); } } diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 614defdde..2a0d7a481 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -202,7 +202,7 @@ public: SaplingFullViewingKey full_viewing_key() const; // Can derive Sapling addr from default diversifier - boost::optional default_address() const; + SaplingPaymentAddress default_address() const; }; typedef boost::variant PaymentAddress; From 2173767365e05acb1bb32b4f0cb664d786ea902d Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Tue, 3 Jul 2018 14:51:27 -0700 Subject: [PATCH 5/5] Add crypted keystore sapling add key --- src/keystore.h | 3 ++ src/test/key_tests.cpp | 2 +- src/wallet/crypter.cpp | 44 ++++++++++++++++++++++++++ src/wallet/crypter.h | 8 ++++- src/wallet/gtest/test_wallet_zkeys.cpp | 23 ++++++++++++++ src/zcash/Address.cpp | 6 ++++ src/zcash/Address.hpp | 3 ++ 7 files changed, 87 insertions(+), 2 deletions(-) diff --git a/src/keystore.h b/src/keystore.h index 541c0bca5..76772dbf1 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -262,4 +262,7 @@ typedef std::vector > CKeyingMate typedef std::map > > CryptedKeyMap; typedef std::map > CryptedSpendingKeyMap; +//! Sapling +typedef std::map > CryptedSaplingSpendingKeyMap; + #endif // BITCOIN_KEYSTORE_H diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index 03661f7b2..d864ca8be 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -239,7 +239,7 @@ BOOST_AUTO_TEST_CASE(zs_address_test) { auto addr = sk.default_address(); - std::string addr_string = EncodePaymentAddress(*addr); + std::string addr_string = EncodePaymentAddress(addr); BOOST_CHECK(addr_string.compare(0, 2, Params().Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS)) == 0); auto paymentaddr2 = DecodePaymentAddress(addr_string); diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 57ce0c428..a1e6b42f6 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -322,6 +322,35 @@ bool CCryptoKeyStore::AddSpendingKey(const libzcash::SproutSpendingKey &sk) return true; } +bool CCryptoKeyStore::AddSaplingSpendingKey(const libzcash::SaplingSpendingKey &sk) +{ + { + LOCK(cs_SpendingKeyStore); + if (!IsCrypted()) { + return CBasicKeyStore::AddSaplingSpendingKey(sk); + } + + if (IsLocked()) { + return false; + } + + std::vector vchCryptedSecret; + CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << sk; + CKeyingMaterial vchSecret(ss.begin(), ss.end()); + auto address = sk.default_address(); + auto fvk = sk.full_viewing_key(); + if (!EncryptSecret(vMasterKey, vchSecret, address.GetHash(), vchCryptedSecret)) { + return false; + } + + if (!AddCryptedSaplingSpendingKey(fvk, vchCryptedSecret)) { + return false; + } + } + return true; +} + bool CCryptoKeyStore::AddCryptedSpendingKey(const libzcash::SproutPaymentAddress &address, const libzcash::ReceivingKey &rk, const std::vector &vchCryptedSecret) @@ -337,6 +366,21 @@ bool CCryptoKeyStore::AddCryptedSpendingKey(const libzcash::SproutPaymentAddress return true; } +// TODO: Handle note decryptors +bool CCryptoKeyStore::AddCryptedSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, + const std::vector &vchCryptedSecret) +{ + { + LOCK(cs_SpendingKeyStore); + if (!SetCrypted()) { + return false; + } + + mapCryptedSaplingSpendingKeys[fvk] = vchCryptedSecret; + } + return true; +} + bool CCryptoKeyStore::GetSpendingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutSpendingKey &skOut) const { { diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index e42f5868a..dba4c4c12 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -129,7 +129,9 @@ class CCryptoKeyStore : public CBasicKeyStore private: CryptedKeyMap mapCryptedKeys; CryptedSpendingKeyMap mapCryptedSpendingKeys; - + + CryptedSaplingSpendingKeyMap mapCryptedSaplingSpendingKeys; + CKeyingMaterial vMasterKey; //! if fUseCrypto is true, mapKeys and mapSpendingKeys must be empty @@ -230,6 +232,10 @@ public: mi++; } } + //! Sapling + virtual bool AddCryptedSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, + const std::vector &vchCryptedSecret); + bool AddSaplingSpendingKey(const libzcash::SaplingSpendingKey &sk); /** * Wallet status (encrypted, locked) changed. diff --git a/src/wallet/gtest/test_wallet_zkeys.cpp b/src/wallet/gtest/test_wallet_zkeys.cpp index efc06b962..317e4ff54 100644 --- a/src/wallet/gtest/test_wallet_zkeys.cpp +++ b/src/wallet/gtest/test_wallet_zkeys.cpp @@ -7,6 +7,29 @@ #include +/** + * This test covers Sapling methods on CWallet + * GenerateNewSaplingZKey() + */ +TEST(wallet_zkeys_tests, store_and_load_sapling_zkeys) { + SelectParams(CBaseChainParams::MAIN); + + CWallet wallet; + + auto address = wallet.GenerateNewSaplingZKey(); + + // verify wallet has incoming viewing key for the address + ASSERT_TRUE(wallet.HaveSaplingIncomingViewingKey(address)); + + // manually add new spending key to wallet + auto sk = libzcash::SaplingSpendingKey::random(); + ASSERT_TRUE(wallet.AddSaplingZKey(sk)); + + // verify wallet did add it + auto fvk = sk.full_viewing_key(); + ASSERT_TRUE(wallet.HaveSaplingSpendingKey(fvk)); +} + /** * This test covers methods on CWallet * GenerateNewZKey() diff --git a/src/zcash/Address.cpp b/src/zcash/Address.cpp index e6dd0125a..a3db85a20 100644 --- a/src/zcash/Address.cpp +++ b/src/zcash/Address.cpp @@ -39,6 +39,12 @@ SproutPaymentAddress SproutSpendingKey::address() const { } //! Sapling +uint256 SaplingPaymentAddress::GetHash() const { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << *this; + return Hash(ss.begin(), ss.end()); +} + SaplingFullViewingKey SaplingExpandedSpendingKey::full_viewing_key() const { uint256 ak; uint256 nk; diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 2a0d7a481..01eb14f54 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -111,6 +111,9 @@ public: READWRITE(d); READWRITE(pk_d); } + + //! Get the 256-bit SHA256d hash of this payment address. + uint256 GetHash() const; friend inline bool operator==(const SaplingPaymentAddress& a, const SaplingPaymentAddress& b) { return a.d == b.d && a.pk_d == b.pk_d;