From 6679855147bbb83294a7b161a7f6d4a5192b173b Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 25 Apr 2018 17:10:34 -0600 Subject: [PATCH] Check that duplicate Sapling nullifiers don't exist within a transaction. --- src/main.cpp | 35 +++++++++++++++++++++++---------- src/test/transaction_tests.cpp | 36 ++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 784d5e09b..634dc177b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1040,11 +1040,11 @@ bool CheckTransactionWithoutProofVerification(const CTransaction& tx, CValidatio } // Transactions can contain empty `vin` and `vout` so long as - // `vjoinsplit` is non-empty. - if (tx.vin.empty() && tx.vjoinsplit.empty()) + // either `vjoinsplit` or `vShieldedSpend` are non-empty. + if (tx.vin.empty() && tx.vjoinsplit.empty() && tx.vShieldedSpend.empty()) return state.DoS(10, error("CheckTransaction(): vin empty"), REJECT_INVALID, "bad-txns-vin-empty"); - if (tx.vout.empty() && tx.vjoinsplit.empty()) + if (tx.vout.empty() && tx.vjoinsplit.empty() && tx.vShieldedSpend.empty()) return state.DoS(10, error("CheckTransaction(): vout empty"), REJECT_INVALID, "bad-txns-vout-empty"); @@ -1134,16 +1134,31 @@ bool CheckTransactionWithoutProofVerification(const CTransaction& tx, CValidatio } // Check for duplicate joinsplit nullifiers in this transaction - set vJoinSplitNullifiers; - BOOST_FOREACH(const JSDescription& joinsplit, tx.vjoinsplit) { - BOOST_FOREACH(const uint256& nf, joinsplit.nullifiers) + set vJoinSplitNullifiers; + BOOST_FOREACH(const JSDescription& joinsplit, tx.vjoinsplit) { - if (vJoinSplitNullifiers.count(nf)) - return state.DoS(100, error("CheckTransaction(): duplicate nullifiers"), - REJECT_INVALID, "bad-joinsplits-nullifiers-duplicate"); + BOOST_FOREACH(const uint256& nf, joinsplit.nullifiers) + { + if (vJoinSplitNullifiers.count(nf)) + return state.DoS(100, error("CheckTransaction(): duplicate nullifiers"), + REJECT_INVALID, "bad-joinsplits-nullifiers-duplicate"); - vJoinSplitNullifiers.insert(nf); + vJoinSplitNullifiers.insert(nf); + } + } + } + + // Check for duplicate sapling nullifiers in this transaction + { + set vSaplingNullifiers; + BOOST_FOREACH(const SpendDescription& spend_desc, tx.vShieldedSpend) + { + if (vSaplingNullifiers.count(spend_desc.nullifier)) + return state.DoS(100, error("CheckTransaction(): duplicate nullifiers"), + REJECT_INVALID, "bad-spend-description-nullifiers-duplicate"); + + vSaplingNullifiers.insert(spend_desc.nullifier); } } diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 1b0b26ee7..8524e1bc2 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -399,6 +399,34 @@ BOOST_AUTO_TEST_CASE(test_basic_joinsplit_verification) } } +void test_simple_sapling_invalidity(uint32_t consensusBranchId, CMutableTransaction tx) +{ + { + CMutableTransaction newTx(tx); + CValidationState state; + + BOOST_CHECK(!CheckTransactionWithoutProofVerification(newTx, state)); + BOOST_CHECK(state.GetRejectReason() == "bad-txns-vin-empty"); + } + { + // Ensure that nullifiers are never duplicated within a transaction. + CMutableTransaction newTx(tx); + CValidationState state; + + newTx.vShieldedSpend.push_back(SpendDescription()); + newTx.vShieldedSpend[0].nullifier = GetRandHash(); + newTx.vShieldedSpend.push_back(SpendDescription()); + newTx.vShieldedSpend[1].nullifier = newTx.vShieldedSpend[0].nullifier; + + BOOST_CHECK(!CheckTransactionWithoutProofVerification(newTx, state)); + BOOST_CHECK(state.GetRejectReason() == "bad-spend-description-nullifiers-duplicate"); + + newTx.vShieldedSpend[1].nullifier = GetRandHash(); + + BOOST_CHECK(CheckTransactionWithoutProofVerification(newTx, state)); + } +} + void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransaction tx) { auto verifier = libzcash::ProofVerifier::Strict(); @@ -548,6 +576,14 @@ BOOST_AUTO_TEST_CASE(test_simple_joinsplit_invalidity_driver) { test_simple_joinsplit_invalidity(NetworkUpgradeInfo[Consensus::UPGRADE_OVERWINTER].nBranchId, mtx); UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); + // Test Sapling things + mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID; + mtx.nVersion = SAPLING_TX_VERSION; + + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + test_simple_sapling_invalidity(NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId, mtx); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); + // Switch back to mainnet parameters as originally selected in test fixture SelectParams(CBaseChainParams::MAIN); }