diff --git a/src/coins.cpp b/src/coins.cpp index f183c99a0..38fac8252 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -628,6 +628,33 @@ bool CCoinsViewCache::HaveJoinSplitRequirements(const CTransaction& tx) const { boost::unordered_map intermediates; + BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) + { + BOOST_FOREACH(const uint256& nullifier, joinsplit.nullifiers) + { + if (GetNullifier(nullifier, SPROUT)) { + // If the nullifier is set, this transaction + // double-spends! + return false; + } + } + + SproutMerkleTree tree; + auto it = intermediates.find(joinsplit.anchor); + if (it != intermediates.end()) { + tree = it->second; + } else if (!GetSproutAnchorAt(joinsplit.anchor, tree)) { + return false; + } + + BOOST_FOREACH(const uint256& commitment, joinsplit.commitments) + { + tree.append(commitment); + } + + intermediates.insert(std::make_pair(tree.root(), tree)); + } + for (const SpendDescription &spendDescription : tx.vShieldedSpend) { if (GetNullifier(spendDescription.nullifier, SAPLING)) { // Prevent double spends LogPrintf("%s: sapling nullifier %s exists, preventing double spend\n", __FUNCTION__, spendDescription.nullifier.GetHex().c_str()); diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 01c95adc3..2cde04f2c 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -26,6 +26,83 @@ #include "librustzcash.h" +JSDescription::JSDescription( + ZCJoinSplit& params, + const uint256& joinSplitPubKey, + const uint256& anchor, + const std::array& inputs, + const std::array& outputs, + CAmount vpub_old, + CAmount vpub_new, + bool computeProof, + uint256 *esk // payment disclosure +) : vpub_old(vpub_old), vpub_new(vpub_new), anchor(anchor) +{ + std::array notes; + + proof = params.prove( + inputs, + outputs, + notes, + ciphertexts, + ephemeralKey, + joinSplitPubKey, + randomSeed, + macs, + nullifiers, + commitments, + vpub_old, + vpub_new, + anchor, + computeProof, + esk // payment disclosure + ); +} + +JSDescription JSDescription::Randomized( + ZCJoinSplit& params, + const uint256& joinSplitPubKey, + const uint256& anchor, + std::array& inputs, + std::array& outputs, + std::array& inputMap, + std::array& outputMap, + CAmount vpub_old, + CAmount vpub_new, + bool computeProof, + uint256 *esk, // payment disclosure + std::function gen +) +{ + // Randomize the order of the inputs and outputs + inputMap = {0, 1}; + outputMap = {0, 1}; + + assert(gen); + + MappedShuffle(inputs.begin(), inputMap.begin(), ZC_NUM_JS_INPUTS, gen); + MappedShuffle(outputs.begin(), outputMap.begin(), ZC_NUM_JS_OUTPUTS, gen); + + return JSDescription( + params, joinSplitPubKey, anchor, inputs, outputs, + vpub_old, vpub_new, computeProof, + esk // payment disclosure + ); +} + +bool JSDescription::Verify( + ZCJoinSplit& params, + libzcash::ProofVerifier& verifier, + const uint256& joinSplitPubKey +) const { + return false; +} + +uint256 JSDescription::h_sig(ZCJoinSplit& params, const uint256& joinSplitPubKey) const +{ + return params.h_sig(randomSeed, nullifiers, joinSplitPubKey); +} + std::string COutPoint::ToString() const { return strprintf("COutPoint(%s, %u)", hash.ToString().substr(0,10), n); diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index b3afecd87..2ece7b255 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -159,6 +159,183 @@ public: } }; +template +class SproutProofSerializer : public boost::static_visitor<> +{ + Stream& s; + bool useGroth; + +public: + SproutProofSerializer(Stream& s, bool useGroth) : s(s), useGroth(useGroth) {} + + void operator()(const libzcash::PHGRProof& proof) const + { + if (useGroth) { + throw std::ios_base::failure("Invalid Sprout proof for transaction format (expected GrothProof, found PHGRProof)"); + } + ::Serialize(s, proof); + } + + void operator()(const libzcash::GrothProof& proof) const + { + if (!useGroth) { + throw std::ios_base::failure("Invalid Sprout proof for transaction format (expected PHGRProof, found GrothProof)"); + } + ::Serialize(s, proof); + } +}; + +template +inline void SerReadWriteSproutProof(Stream& s, const T& proof, bool useGroth, CSerActionSerialize ser_action) +{ + auto ps = SproutProofSerializer(s, useGroth); + boost::apply_visitor(ps, proof); +} + +template +inline void SerReadWriteSproutProof(Stream& s, T& proof, bool useGroth, CSerActionUnserialize ser_action) +{ + if (useGroth) { + libzcash::GrothProof grothProof; + ::Unserialize(s, grothProof); + proof = grothProof; + } else { + libzcash::PHGRProof pghrProof; + ::Unserialize(s, pghrProof); + proof = pghrProof; + } +} + +class JSDescription +{ +public: + // These values 'enter from' and 'exit to' the value + // pool, respectively. + CAmount vpub_old; + CAmount vpub_new; + + // JoinSplits are always anchored to a root in the note + // commitment tree at some point in the blockchain + // history or in the history of the current + // transaction. + uint256 anchor; + + // Nullifiers are used to prevent double-spends. They + // are derived from the secrets placed in the note + // and the secret spend-authority key known by the + // spender. + std::array nullifiers; + + // Note commitments are introduced into the commitment + // tree, blinding the public about the values and + // destinations involved in the JoinSplit. The presence of + // a commitment in the note commitment tree is required + // to spend it. + std::array commitments; + + // Ephemeral key + uint256 ephemeralKey; + + // Ciphertexts + // These contain trapdoors, values and other information + // that the recipient needs, including a memo field. It + // is encrypted using the scheme implemented in crypto/NoteEncryption.cpp + std::array ciphertexts = {{ {{0}} }}; + + // Random seed + uint256 randomSeed; + + // MACs + // The verification of the JoinSplit requires these MACs + // to be provided as an input. + std::array macs; + + // JoinSplit proof + // This is a zk-SNARK which ensures that this JoinSplit is valid. + libzcash::SproutProof proof; + + JSDescription(): vpub_old(0), vpub_new(0) { } + + JSDescription( + ZCJoinSplit& params, + const uint256& joinSplitPubKey, + const uint256& rt, + const std::array& inputs, + const std::array& outputs, + CAmount vpub_old, + CAmount vpub_new, + bool computeProof = true, // Set to false in some tests + uint256 *esk = nullptr // payment disclosure + ); + + static JSDescription Randomized( + ZCJoinSplit& params, + const uint256& joinSplitPubKey, + const uint256& rt, + std::array& inputs, + std::array& outputs, + std::array& inputMap, + std::array& outputMap, + CAmount vpub_old, + CAmount vpub_new, + bool computeProof = true, // Set to false in some tests + uint256 *esk = nullptr, // payment disclosure + std::function gen = GetRandInt + ); + + // Verifies that the JoinSplit proof is correct. + bool Verify( + ZCJoinSplit& params, + libzcash::ProofVerifier& verifier, + const uint256& joinSplitPubKey + ) const; + + // Returns the calculated h_sig + uint256 h_sig(ZCJoinSplit& params, const uint256& joinSplitPubKey) const; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + // nVersion is set by CTransaction and CMutableTransaction to + // (tx.fOverwintered << 31) | tx.nVersion + bool fOverwintered = s.GetVersion() >> 31; + int32_t txVersion = s.GetVersion() & 0x7FFFFFFF; + bool useGroth = fOverwintered && txVersion >= SAPLING_TX_VERSION; + + READWRITE(vpub_old); + READWRITE(vpub_new); + READWRITE(anchor); + READWRITE(nullifiers); + READWRITE(commitments); + READWRITE(ephemeralKey); + READWRITE(randomSeed); + READWRITE(macs); + ::SerReadWriteSproutProof(s, proof, useGroth, ser_action); + READWRITE(ciphertexts); + } + + friend bool operator==(const JSDescription& a, const JSDescription& b) + { + return ( + a.vpub_old == b.vpub_old && + a.vpub_new == b.vpub_new && + a.anchor == b.anchor && + a.nullifiers == b.nullifiers && + a.commitments == b.commitments && + a.ephemeralKey == b.ephemeralKey && + a.ciphertexts == b.ciphertexts && + a.randomSeed == b.randomSeed && + a.macs == b.macs && + a.proof == b.proof + ); + } + + friend bool operator!=(const JSDescription& a, const JSDescription& b) + { + return !(a == b); + } +}; class BaseOutPoint { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 604e21799..37f67042b 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4268,7 +4268,7 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp, const CPubKey& my "\nResult:\n" "{\n" " \"transparent\": xxxxx, (numeric) the total balance of transparent funds\n" - " \"private\": xxxxx, (numeric) the total balance of private funds (in Sapling addresses)\n" + " \"private\": xxxxx, (numeric) the total balance of private funds (in both Sprout and Sapling addresses)\n" " \"total\": xxxxx, (numeric) the total balance of both transparent and private funds\n" "}\n" "\nExamples:\n" diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f3526c583..d82e100ed 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1408,6 +1408,7 @@ private: public: PaymentAddressBelongsToWallet(CWallet *wallet) : m_wallet(wallet) {} + bool operator()(const libzcash::SproutPaymentAddress &zaddr) const; bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const; bool operator()(const libzcash::InvalidEncoding& no) const; }; @@ -1420,6 +1421,7 @@ private: public: IncomingViewingKeyBelongsToWallet(CWallet *wallet) : m_wallet(wallet) {} + bool operator()(const libzcash::SproutPaymentAddress &zaddr) const; bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const; bool operator()(const libzcash::InvalidEncoding& no) const; }; @@ -1431,6 +1433,7 @@ private: public: HaveSpendingKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {} + bool operator()(const libzcash::SproutPaymentAddress &zaddr) const; bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const; bool operator()(const libzcash::InvalidEncoding& no) const; }; @@ -1442,6 +1445,7 @@ private: public: GetSpendingKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {} + boost::optional operator()(const libzcash::SproutPaymentAddress &zaddr) const; boost::optional operator()(const libzcash::SaplingPaymentAddress &zaddr) const; boost::optional operator()(const libzcash::InvalidEncoding& no) const; }; @@ -1513,6 +1517,7 @@ public: ) : m_wallet(wallet), params(params), nTime(_nTime), hdKeypath(_hdKeypath), seedFpStr(_seedFp), log(_log) {} + SpendingKeyAddResult operator()(const libzcash::SproutSpendingKey &sk) const; SpendingKeyAddResult operator()(const libzcash::SaplingExtendedSpendingKey &sk) const; SpendingKeyAddResult operator()(const libzcash::InvalidEncoding& no) const; }; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 4f6942bb7..1177bbedd 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -653,7 +653,6 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, } else if (strType == "czkey") { - /* libzcash::SproutPaymentAddress addr; ssKey >> addr; // Deserialization of a pair is just one item after another @@ -663,7 +662,6 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, vector vchCryptedSecret; ssValue >> vchCryptedSecret; wss.nCKeys++; - */ //if (!pwallet->LoadCryptedZKey(addr, rk, vchCryptedSecret)) //{ @@ -706,13 +704,11 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, } else if (strType == "zkeymeta") { - /* libzcash::SproutPaymentAddress addr; ssKey >> addr; CKeyMetadata keyMeta; ssValue >> keyMeta; wss.nZKeyMeta++; - */ // pwallet->LoadZKeyMetadata(addr, keyMeta); diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 8c34588ec..9069e3a08 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -1,6 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2013 The Bitcoin Core developers -// Copyright (c) 2019-2020 The Hush developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -200,14 +199,23 @@ public: bool WriteHDChain(const CHDChain& chain); /// Write spending key to wallet database, where key is payment address and value is spending key. + bool WriteZKey(const libzcash::SproutPaymentAddress& addr, const libzcash::SproutSpendingKey& key, const CKeyMetadata &keyMeta); bool WriteSaplingZKey(const libzcash::SaplingIncomingViewingKey &ivk, const libzcash::SaplingExtendedSpendingKey &key, const CKeyMetadata &keyMeta); bool WriteSaplingPaymentAddress(const libzcash::SaplingPaymentAddress &addr, const libzcash::SaplingIncomingViewingKey &ivk); + bool WriteCryptedZKey(const libzcash::SproutPaymentAddress & addr, + const libzcash::ReceivingKey & rk, + const std::vector& vchCryptedSecret, + const CKeyMetadata &keyMeta); bool WriteCryptedSaplingZKey(const libzcash::SaplingExtendedFullViewingKey &extfvk, const std::vector& vchCryptedSecret, const CKeyMetadata &keyMeta); + + bool WriteSproutViewingKey(const libzcash::SproutViewingKey &vk); + bool EraseSproutViewingKey(const libzcash::SproutViewingKey &vk); + private: CWalletDB(const CWalletDB&); void operator=(const CWalletDB&);