zcashd/src/gtest/test_checktransaction.cpp

1666 lines
66 KiB
C++
Raw Normal View History

#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "main.h"
#include "primitives/transaction.h"
#include "consensus/validation.h"
2019-05-17 10:02:07 -07:00
#include "transaction_builder.h"
#include "gtest/utils.h"
#include "test/test_util.h"
#include "util/test.h"
#include "zcash/JoinSplit.hpp"
2019-05-17 10:02:07 -07:00
#include <librustzcash.h>
#include <rust/bridge.h>
#include <rust/ed25519.h>
#include <rust/orchard.h>
2019-05-17 10:02:07 -07:00
// Subclass of CTransaction which doesn't call UpdateHash when constructing
// from a CMutableTransaction. This enables us to create a CTransaction
// with bad values which normally trigger an exception during construction.
class UNSAFE_CTransaction : public CTransaction {
public:
UNSAFE_CTransaction(const CMutableTransaction &tx) : CTransaction(tx, true) {}
};
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, CheckVpubNotBothNonzero) {
CMutableTransaction tx;
tx.nVersion = 2;
{
// Ensure that values within the joinsplit are well-formed.
CMutableTransaction newTx(tx);
CValidationState state;
2019-06-16 04:39:05 -07:00
newTx.vJoinSplit.push_back(JSDescription());
2019-06-16 04:39:05 -07:00
JSDescription *jsdesc = &newTx.vJoinSplit[0];
jsdesc->vpub_old = 1;
jsdesc->vpub_new = 1;
EXPECT_FALSE(CheckTransactionWithoutProofVerification(newTx, state));
EXPECT_EQ(state.GetRejectReason(), "bad-txns-vpubs-both-nonzero");
}
}
class MockCValidationState : public CValidationState {
public:
MOCK_METHOD6(DoS, bool(int level, bool ret,
unsigned int chRejectCodeIn, const std::string &strRejectReasonIn,
bool corruptionIn,
const std::string &strDebugMessageIn));
MOCK_METHOD4(Invalid, bool(bool ret,
unsigned int _chRejectCode, const std::string _strRejectReason,
const std::string &_strDebugMessage));
MOCK_METHOD1(Error, bool(std::string strRejectReasonIn));
MOCK_CONST_METHOD0(IsValid, bool());
MOCK_CONST_METHOD0(IsInvalid, bool());
MOCK_CONST_METHOD0(IsError, bool());
MOCK_CONST_METHOD1(IsInvalid, bool(int &nDoSOut));
MOCK_CONST_METHOD0(CorruptionPossible, bool());
MOCK_CONST_METHOD0(GetRejectCode, unsigned int());
MOCK_CONST_METHOD0(GetRejectReason, std::string());
MOCK_CONST_METHOD0(GetDebugMessage, std::string());
};
void CreateJoinSplitSignature(CMutableTransaction& mtx, uint32_t consensusBranchId);
CMutableTransaction GetValidTransaction(uint32_t consensusBranchId=SPROUT_BRANCH_ID) {
CMutableTransaction mtx;
if (consensusBranchId == NetworkUpgradeInfo[Consensus::UPGRADE_OVERWINTER].nBranchId) {
mtx.fOverwintered = true;
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
mtx.nVersion = OVERWINTER_TX_VERSION;
} else if (consensusBranchId == NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId) {
mtx.fOverwintered = true;
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nVersion = SAPLING_TX_VERSION;
} else if (consensusBranchId != SPROUT_BRANCH_ID) {
// Unsupported consensus branch ID
assert(false);
}
mtx.vin.resize(2);
2016-06-23 15:59:00 -07:00
mtx.vin[0].prevout.hash = uint256S("0000000000000000000000000000000000000000000000000000000000000001");
mtx.vin[0].prevout.n = 0;
2016-06-23 15:59:00 -07:00
mtx.vin[1].prevout.hash = uint256S("0000000000000000000000000000000000000000000000000000000000000002");
mtx.vin[1].prevout.n = 0;
mtx.vout.resize(2);
// mtx.vout[0].scriptPubKey =
mtx.vout[0].nValue = 0;
mtx.vout[1].nValue = 0;
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit.resize(2);
mtx.vJoinSplit[0].nullifiers.at(0) = uint256S("0000000000000000000000000000000000000000000000000000000000000000");
mtx.vJoinSplit[0].nullifiers.at(1) = uint256S("0000000000000000000000000000000000000000000000000000000000000001");
mtx.vJoinSplit[1].nullifiers.at(0) = uint256S("0000000000000000000000000000000000000000000000000000000000000002");
mtx.vJoinSplit[1].nullifiers.at(1) = uint256S("0000000000000000000000000000000000000000000000000000000000000003");
if (mtx.nVersion >= SAPLING_TX_VERSION) {
libzcash::GrothProof emptyProof;
mtx.vJoinSplit[0].proof = emptyProof;
mtx.vJoinSplit[1].proof = emptyProof;
}
CreateJoinSplitSignature(mtx, consensusBranchId);
return mtx;
}
void CreateJoinSplitSignature(CMutableTransaction& mtx, uint32_t consensusBranchId) {
// Generate an ephemeral keypair.
2022-06-06 15:17:07 -07:00
ed25519::SigningKey joinSplitPrivKey;
ed25519::generate_keypair(joinSplitPrivKey, mtx.joinSplitPubKey);
// Compute the correct hSig.
// TODO: #966.
static const uint256 one(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
// Fake coins being spent.
std::vector<CTxOut> allPrevOutputs;
allPrevOutputs.resize(signTx.vin.size());
const PrecomputedTransactionData txdata(signTx, allPrevOutputs);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId, txdata);
if (dataToBeSigned == one) {
throw std::runtime_error("SignatureHash failed");
}
// Add the signature
2022-06-06 15:17:07 -07:00
ed25519::sign(
joinSplitPrivKey,
{dataToBeSigned.begin(), 32},
mtx.joinSplitSig);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, ValidTransaction) {
CMutableTransaction mtx = GetValidTransaction();
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadVersionTooLow) {
CMutableTransaction mtx = GetValidTransaction();
mtx.nVersion = 0;
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-version-too-low", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsVinEmpty) {
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit.resize(0);
mtx.vin.resize(0);
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-no-source-of-funds", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsVoutEmpty) {
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit.resize(0);
mtx.vout.resize(0);
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-no-sink-of-funds", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsOversize) {
SelectParams(CBaseChainParams::REGTEST);
CMutableTransaction mtx = GetValidTransaction();
mtx.vin[0].scriptSig = CScript();
std::vector<unsigned char> vchData(520);
for (unsigned int i = 0; i < 190; ++i)
mtx.vin[0].scriptSig << vchData << OP_DROP;
mtx.vin[0].scriptSig << OP_1;
{
// Transaction is just under the limit...
CTransaction tx(mtx);
CValidationState state;
ASSERT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
}
// Not anymore!
mtx.vin[1].scriptSig << vchData << OP_DROP;
mtx.vin[1].scriptSig << OP_1;
{
CTransaction tx(mtx);
ASSERT_EQ(::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION), 100202);
// Passes non-contextual checks...
MockCValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
// ... but fails contextual ones!
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-oversize", false, "")).Times(1);
EXPECT_FALSE(ContextualCheckTransaction(tx, state, Params(), 1, true));
}
{
// But should be fine again once Sapling activates!
2019-01-29 20:18:10 -08:00
RegtestActivateSapling();
mtx.fOverwintered = true;
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nVersion = SAPLING_TX_VERSION;
// Change the proof types (which requires re-signing the JoinSplit data)
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit[0].proof = libzcash::GrothProof();
mtx.vJoinSplit[1].proof = libzcash::GrothProof();
CreateJoinSplitSignature(mtx, NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId);
CTransaction tx(mtx);
EXPECT_EQ(::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION), 103713);
MockCValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 1, true));
// Revert to default
2019-01-29 20:18:10 -08:00
RegtestDeactivateSapling();
}
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, OversizeSaplingTxns) {
2019-01-29 20:18:10 -08:00
RegtestActivateSapling();
CMutableTransaction mtx = GetValidTransaction();
mtx.fOverwintered = true;
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nVersion = SAPLING_TX_VERSION;
// Change the proof types (which requires re-signing the JoinSplit data)
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit[0].proof = libzcash::GrothProof();
mtx.vJoinSplit[1].proof = libzcash::GrothProof();
CreateJoinSplitSignature(mtx, NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId);
// Transaction just under the limit
mtx.vin[0].scriptSig = CScript();
std::vector<unsigned char> vchData(520);
for (unsigned int i = 0; i < 3809; ++i)
mtx.vin[0].scriptSig << vchData << OP_DROP;
std::vector<unsigned char> vchDataRemainder(453);
mtx.vin[0].scriptSig << vchDataRemainder << OP_DROP;
mtx.vin[0].scriptSig << OP_1;
{
CTransaction tx(mtx);
EXPECT_EQ(::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION), MAX_TX_SIZE_AFTER_SAPLING - 1);
CValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
}
// Transaction equal to the limit
mtx.vin[1].scriptSig << OP_1;
{
CTransaction tx(mtx);
EXPECT_EQ(::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION), MAX_TX_SIZE_AFTER_SAPLING);
CValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
}
// Transaction just over the limit
mtx.vin[1].scriptSig << OP_1;
{
CTransaction tx(mtx);
EXPECT_EQ(::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION), MAX_TX_SIZE_AFTER_SAPLING + 1);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-oversize", false, "")).Times(1);
EXPECT_FALSE(CheckTransactionWithoutProofVerification(tx, state));
}
// Revert to default
2019-01-29 20:18:10 -08:00
RegtestDeactivateSapling();
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsVoutNegative) {
CMutableTransaction mtx = GetValidTransaction();
mtx.vout[0].nValue = -1;
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsVoutToolarge) {
CMutableTransaction mtx = GetValidTransaction();
mtx.vout[0].nValue = MAX_MONEY + 1;
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsTxouttotalToolargeOutputs) {
CMutableTransaction mtx = GetValidTransaction();
mtx.vout[0].nValue = MAX_MONEY;
mtx.vout[1].nValue = 1;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, ValueBalanceNonZero) {
CMutableTransaction mtx = GetValidTransaction();
mtx.valueBalanceSapling = 10;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-nonzero", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, PositiveValueBalanceTooLarge) {
CMutableTransaction mtx = GetValidTransaction();
mtx.vShieldedSpend.resize(1);
mtx.valueBalanceSapling = MAX_MONEY + 1;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, NegativeValueBalanceTooLarge) {
CMutableTransaction mtx = GetValidTransaction();
mtx.vShieldedSpend.resize(1);
mtx.valueBalanceSapling = -(MAX_MONEY + 1);
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, ValueBalanceOverflowsTotal) {
CMutableTransaction mtx = GetValidTransaction();
mtx.vShieldedSpend.resize(1);
mtx.vout[0].nValue = 1;
mtx.valueBalanceSapling = -MAX_MONEY;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsTxouttotalToolargeJoinsplit) {
CMutableTransaction mtx = GetValidTransaction();
mtx.vout[0].nValue = 1;
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit[0].vpub_old = MAX_MONEY;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsTxintotalToolargeJoinsplit) {
2016-09-05 11:18:43 -07:00
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit[0].vpub_new = MAX_MONEY - 1;
mtx.vJoinSplit[1].vpub_new = MAX_MONEY - 1;
2016-09-05 11:18:43 -07:00
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-txintotal-toolarge", false, "")).Times(1);
2016-09-05 11:18:43 -07:00
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsVpubOldNegative) {
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit[0].vpub_old = -1;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vpub_old-negative", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsVpubNewNegative) {
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit[0].vpub_new = -1;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vpub_new-negative", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsVpubOldToolarge) {
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit[0].vpub_old = MAX_MONEY + 1;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vpub_old-toolarge", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsVpubNewToolarge) {
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit[0].vpub_new = MAX_MONEY + 1;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vpub_new-toolarge", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsVpubsBothNonzero) {
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit[0].vpub_old = 1;
mtx.vJoinSplit[0].vpub_new = 1;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vpubs-both-nonzero", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2016-06-23 15:59:00 -07:00
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsInputsDuplicate) {
2016-06-23 15:59:00 -07:00
CMutableTransaction mtx = GetValidTransaction();
mtx.vin[1].prevout.hash = mtx.vin[0].prevout.hash;
mtx.vin[1].prevout.n = mtx.vin[0].prevout.n;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-inputs-duplicate", false, "")).Times(1);
2016-06-23 15:59:00 -07:00
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadJoinsplitsNullifiersDuplicateSameJoinsplit) {
2016-06-23 15:59:00 -07:00
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit[0].nullifiers.at(0) = uint256S("0000000000000000000000000000000000000000000000000000000000000000");
mtx.vJoinSplit[0].nullifiers.at(1) = uint256S("0000000000000000000000000000000000000000000000000000000000000000");
2016-06-23 15:59:00 -07:00
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-joinsplits-nullifiers-duplicate", false, "")).Times(1);
2016-06-23 15:59:00 -07:00
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadJoinsplitsNullifiersDuplicateDifferentJoinsplit) {
2016-06-23 15:59:00 -07:00
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit[0].nullifiers.at(0) = uint256S("0000000000000000000000000000000000000000000000000000000000000000");
mtx.vJoinSplit[1].nullifiers.at(0) = uint256S("0000000000000000000000000000000000000000000000000000000000000000");
2016-06-23 15:59:00 -07:00
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-joinsplits-nullifiers-duplicate", false, "")).Times(1);
2016-06-23 15:59:00 -07:00
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadCbHasJoinsplits) {
2016-06-23 15:59:00 -07:00
CMutableTransaction mtx = GetValidTransaction();
// Make it a coinbase.
mtx.vin.resize(1);
mtx.vin[0].prevout.SetNull();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit.resize(1);
2016-06-23 15:59:00 -07:00
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-has-joinsplits", false, "")).Times(1);
2016-06-23 15:59:00 -07:00
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadCbEmptyScriptsig) {
2016-06-23 15:59:00 -07:00
CMutableTransaction mtx = GetValidTransaction();
// Make it a coinbase.
mtx.vin.resize(1);
mtx.vin[0].prevout.SetNull();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit.resize(0);
2016-06-23 15:59:00 -07:00
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-length", false, "")).Times(1);
2016-06-23 15:59:00 -07:00
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxnsPrevoutNull) {
2016-06-23 15:59:00 -07:00
CMutableTransaction mtx = GetValidTransaction();
mtx.vin[1].prevout.SetNull();
CTransaction tx(mtx);
EXPECT_FALSE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-prevout-null", false, "")).Times(1);
2016-06-23 15:59:00 -07:00
CheckTransactionWithoutProofVerification(tx, state);
}
TEST(ContextualCheckShieldedInputsTest, BadTxnsInvalidJoinsplitSignature) {
SelectParams(CBaseChainParams::REGTEST);
auto consensus = Params().GetConsensus();
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = std::nullopt;
std::optional<rust::Box<orchard::BatchValidator>> orchardAuth = std::nullopt;
2016-06-23 15:59:00 -07:00
CMutableTransaction mtx = GetValidTransaction();
mtx.joinSplitSig.bytes[0] += 1;
2016-06-23 15:59:00 -07:00
CTransaction tx(mtx);
// Recreate the fake coins being spent.
std::vector<CTxOut> allPrevOutputs;
allPrevOutputs.resize(tx.vin.size());
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
2016-06-23 15:59:00 -07:00
MockCValidationState state;
Apply `HaveShieldedRequirements` to coinbase transactions Both transparent and shielded inputs have contextual checks that need to be enforced in the consensus rules. For shielded inputs, these are that the anchors in transactions correspond to real commitment tree states (to ensure that the spent notes existed), and that their nullifiers are not being double-spent. When Sprout was first added to the codebase, we added input checks in the same places that transparent inputs were checked; namely anywhere `CCoinsViewCache::HaveInputs` is called. These all happened to be gated on `!tx.IsCoinBase()`, which was fine because we did not allow Sprout JoinSplits in coinbase transactions (enforced with a non-contextual check). When we added Sapling we also allowed coinbase outputs to Sapling addresses (shielded coinbase). We updated `HaveShieldedRequirements` to check Sapling anchors and nullifiers, but didn't change the consensus code to call it on coinbase. This was fine because Sapling Spends and Outputs are separate, and we did not allow Sapling Spends in coinbase transactions (meaning that there were no anchors or nullifiers to enforce the input rules on). Orchard falls into an interesting middle-ground: - We allowed coinbase outputs to Orchard addresses, to enable Sapling shielded coinbase users to migrate to Orchard. - Orchard uses Actions, which are a hybrid of Sprout JoinSplits and Sapling Spends/Outputs. That is, an Orchard Action comprises a single spend and a single output. To maintain the "no shielded spends in coinbase" rule, we added an `enableSpends` flag to the Orchard circuit. We force it to be set to `false` for coinbase, ensuring that all Orchard spends in a coinbase use dummy (zero-valued) notes. However, this is insufficient: the coinbase transaction will still contain an Orchard anchor and nullifiers, and these need to be correctly constrained. In particular, not constraining the Orchard nullifiers in a coinbase transaction enables a Faerie Gold attack. We explicitly require that Orchard nullifiers are unique, so that there is a unique input to the nullifier derivation. Without the coinbase check, the following attack is possible: - An adversary creates an Orchard Action sending some amount of ZEC to a victim address, with a dummy spent note. The entire transaction can be fully-shielded by placing the real spent note in a separate Action. - The adversary uses the exact same dummy note in a coinbase transaction, creating the exact same output note (same victim address and amount). - The victim now has two notes with the same ZEC amount, but can only spend one of them because they have the same nullifier. This commit fixes the consensus bug by calling `HaveShieldedRequirements` outside of `!tx.IsCoinBase()` gates. To simplify its usage, there is now a `Consensus::CheckTxShieldedInputs` function that handles the logging and validation state updates. We also move shielded input checks from `ContextualCheckInputs` to `ContextualCheckShieldedInputs`; these now mirror each other in that they check contextual rules on transparent and shielded inputs respectively, followed by checking signatures.
2022-04-01 12:11:18 -07:00
AssumeShieldedInputsExistAndAreSpendable baseView;
CCoinsViewCache view(&baseView);
// during initial block download, for transactions being accepted into the
// mempool (and thus not mined), DoS ban score should be zero, else 10
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return true; });
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return false; });
// for transactions that have been mined in a block, DoS ban score should
// always be 100.
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return true; });
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return false; });
2016-06-23 15:59:00 -07:00
}
TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
SelectParams(CBaseChainParams::REGTEST);
auto consensus = Params().GetConsensus();
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = std::nullopt;
std::optional<rust::Box<orchard::BatchValidator>> orchardAuth = std::nullopt;
auto saplingBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId;
auto blossomBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_BLOSSOM].nBranchId;
auto heartwoodBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_HEARTWOOD].nBranchId;
// Create a valid transaction for the Sapling epoch.
CMutableTransaction mtx = GetValidTransaction(saplingBranchId);
CTransaction tx(mtx);
// Recreate the fake coins being spent.
std::vector<CTxOut> allPrevOutputs;
allPrevOutputs.resize(tx.vin.size());
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
MockCValidationState state;
Apply `HaveShieldedRequirements` to coinbase transactions Both transparent and shielded inputs have contextual checks that need to be enforced in the consensus rules. For shielded inputs, these are that the anchors in transactions correspond to real commitment tree states (to ensure that the spent notes existed), and that their nullifiers are not being double-spent. When Sprout was first added to the codebase, we added input checks in the same places that transparent inputs were checked; namely anywhere `CCoinsViewCache::HaveInputs` is called. These all happened to be gated on `!tx.IsCoinBase()`, which was fine because we did not allow Sprout JoinSplits in coinbase transactions (enforced with a non-contextual check). When we added Sapling we also allowed coinbase outputs to Sapling addresses (shielded coinbase). We updated `HaveShieldedRequirements` to check Sapling anchors and nullifiers, but didn't change the consensus code to call it on coinbase. This was fine because Sapling Spends and Outputs are separate, and we did not allow Sapling Spends in coinbase transactions (meaning that there were no anchors or nullifiers to enforce the input rules on). Orchard falls into an interesting middle-ground: - We allowed coinbase outputs to Orchard addresses, to enable Sapling shielded coinbase users to migrate to Orchard. - Orchard uses Actions, which are a hybrid of Sprout JoinSplits and Sapling Spends/Outputs. That is, an Orchard Action comprises a single spend and a single output. To maintain the "no shielded spends in coinbase" rule, we added an `enableSpends` flag to the Orchard circuit. We force it to be set to `false` for coinbase, ensuring that all Orchard spends in a coinbase use dummy (zero-valued) notes. However, this is insufficient: the coinbase transaction will still contain an Orchard anchor and nullifiers, and these need to be correctly constrained. In particular, not constraining the Orchard nullifiers in a coinbase transaction enables a Faerie Gold attack. We explicitly require that Orchard nullifiers are unique, so that there is a unique input to the nullifier derivation. Without the coinbase check, the following attack is possible: - An adversary creates an Orchard Action sending some amount of ZEC to a victim address, with a dummy spent note. The entire transaction can be fully-shielded by placing the real spent note in a separate Action. - The adversary uses the exact same dummy note in a coinbase transaction, creating the exact same output note (same victim address and amount). - The victim now has two notes with the same ZEC amount, but can only spend one of them because they have the same nullifier. This commit fixes the consensus bug by calling `HaveShieldedRequirements` outside of `!tx.IsCoinBase()` gates. To simplify its usage, there is now a `Consensus::CheckTxShieldedInputs` function that handles the logging and validation state updates. We also move shielded input checks from `ContextualCheckInputs` to `ContextualCheckShieldedInputs`; these now mirror each other in that they check contextual rules on transparent and shielded inputs respectively, followed by checking signatures.
2022-04-01 12:11:18 -07:00
AssumeShieldedInputsExistAndAreSpendable baseView;
CCoinsViewCache view(&baseView);
// Ensure that the transaction validates against Sapling.
EXPECT_TRUE(ContextualCheckShieldedInputs(
tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, false,
[](const Consensus::Params&) { return false; }));
// Attempt to validate the inputs against Blossom. We should be notified
// that an old consensus branch ID was used for an input.
EXPECT_CALL(state, DoS(
10, false, REJECT_INVALID,
strprintf("old-consensus-branch-id (Expected %s, found %s)",
HexInt(blossomBranchId),
HexInt(saplingBranchId)),
false, "")).Times(1);
EXPECT_FALSE(ContextualCheckShieldedInputs(
tx, txdata, state, view, saplingAuth, orchardAuth, consensus, blossomBranchId, false, false,
[](const Consensus::Params&) { return false; }));
// Attempt to validate the inputs against Heartwood. All we should learn is
// that the signature is invalid, because we don't check more than one
// network upgrade back.
EXPECT_CALL(state, DoS(
10, false, REJECT_INVALID,
"bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
EXPECT_FALSE(ContextualCheckShieldedInputs(
tx, txdata, state, view, saplingAuth, orchardAuth, consensus, heartwoodBranchId, false, false,
[](const Consensus::Params&) { return false; }));
}
TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) {
SelectParams(CBaseChainParams::REGTEST);
auto consensus = Params().GetConsensus();
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = std::nullopt;
std::optional<rust::Box<orchard::BatchValidator>> orchardAuth = std::nullopt;
Apply `HaveShieldedRequirements` to coinbase transactions Both transparent and shielded inputs have contextual checks that need to be enforced in the consensus rules. For shielded inputs, these are that the anchors in transactions correspond to real commitment tree states (to ensure that the spent notes existed), and that their nullifiers are not being double-spent. When Sprout was first added to the codebase, we added input checks in the same places that transparent inputs were checked; namely anywhere `CCoinsViewCache::HaveInputs` is called. These all happened to be gated on `!tx.IsCoinBase()`, which was fine because we did not allow Sprout JoinSplits in coinbase transactions (enforced with a non-contextual check). When we added Sapling we also allowed coinbase outputs to Sapling addresses (shielded coinbase). We updated `HaveShieldedRequirements` to check Sapling anchors and nullifiers, but didn't change the consensus code to call it on coinbase. This was fine because Sapling Spends and Outputs are separate, and we did not allow Sapling Spends in coinbase transactions (meaning that there were no anchors or nullifiers to enforce the input rules on). Orchard falls into an interesting middle-ground: - We allowed coinbase outputs to Orchard addresses, to enable Sapling shielded coinbase users to migrate to Orchard. - Orchard uses Actions, which are a hybrid of Sprout JoinSplits and Sapling Spends/Outputs. That is, an Orchard Action comprises a single spend and a single output. To maintain the "no shielded spends in coinbase" rule, we added an `enableSpends` flag to the Orchard circuit. We force it to be set to `false` for coinbase, ensuring that all Orchard spends in a coinbase use dummy (zero-valued) notes. However, this is insufficient: the coinbase transaction will still contain an Orchard anchor and nullifiers, and these need to be correctly constrained. In particular, not constraining the Orchard nullifiers in a coinbase transaction enables a Faerie Gold attack. We explicitly require that Orchard nullifiers are unique, so that there is a unique input to the nullifier derivation. Without the coinbase check, the following attack is possible: - An adversary creates an Orchard Action sending some amount of ZEC to a victim address, with a dummy spent note. The entire transaction can be fully-shielded by placing the real spent note in a separate Action. - The adversary uses the exact same dummy note in a coinbase transaction, creating the exact same output note (same victim address and amount). - The victim now has two notes with the same ZEC amount, but can only spend one of them because they have the same nullifier. This commit fixes the consensus bug by calling `HaveShieldedRequirements` outside of `!tx.IsCoinBase()` gates. To simplify its usage, there is now a `Consensus::CheckTxShieldedInputs` function that handles the logging and validation state updates. We also move shielded input checks from `ContextualCheckInputs` to `ContextualCheckShieldedInputs`; these now mirror each other in that they check contextual rules on transparent and shielded inputs respectively, followed by checking signatures.
2022-04-01 12:11:18 -07:00
AssumeShieldedInputsExistAndAreSpendable baseView;
CCoinsViewCache view(&baseView);
auto saplingBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId;
CMutableTransaction mtx = GetValidTransaction(saplingBranchId);
// Recreate the fake coins being spent.
std::vector<CTxOut> allPrevOutputs;
allPrevOutputs.resize(mtx.vin.size());
// Check that the signature is valid before we add L
{
CTransaction tx(mtx);
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
MockCValidationState state;
EXPECT_TRUE(ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true));
}
// Copied from libsodium/crypto_sign/ed25519/ref10/open.c
static const unsigned char L[32] =
{ 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 };
// Add L to S, which starts at mtx.joinSplitSig[32].
unsigned int s = 0;
for (size_t i = 0; i < 32; i++) {
s = mtx.joinSplitSig.bytes[32 + i] + L[i] + (s >> 8);
mtx.joinSplitSig.bytes[32 + i] = s & 0xff;
}
CTransaction tx(mtx);
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
MockCValidationState state;
// during initial block download, for transactions being accepted into the
// mempool (and thus not mined), DoS ban score should be zero, else 10
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return true; });
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return false; });
// for transactions that have been mined in a block, DoS ban score should
// always be 100.
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return true; });
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return false; });
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, OverwinterConstructors) {
CMutableTransaction mtx;
mtx.fOverwintered = true;
mtx.nVersion = OVERWINTER_TX_VERSION;
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
mtx.nExpiryHeight = 20;
// Check constructor with overwinter fields
CTransaction tx(mtx);
EXPECT_EQ(tx.nVersion, mtx.nVersion);
EXPECT_EQ(tx.fOverwintered, mtx.fOverwintered);
EXPECT_EQ(tx.nVersionGroupId, mtx.nVersionGroupId);
EXPECT_EQ(tx.nExpiryHeight, mtx.nExpiryHeight);
// Check constructor of mutable transaction struct
CMutableTransaction mtx2(tx);
EXPECT_EQ(mtx2.nVersion, mtx.nVersion);
EXPECT_EQ(mtx2.fOverwintered, mtx.fOverwintered);
EXPECT_EQ(mtx2.nVersionGroupId, mtx.nVersionGroupId);
EXPECT_EQ(mtx2.nExpiryHeight, mtx.nExpiryHeight);
EXPECT_TRUE(mtx2.GetHash() == mtx.GetHash());
// Check assignment of overwinter fields
CTransaction tx2 = tx;
EXPECT_EQ(tx2.nVersion, mtx.nVersion);
EXPECT_EQ(tx2.fOverwintered, mtx.fOverwintered);
EXPECT_EQ(tx2.nVersionGroupId, mtx.nVersionGroupId);
EXPECT_EQ(tx2.nExpiryHeight, mtx.nExpiryHeight);
EXPECT_TRUE(tx2 == tx);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, OverwinterSerialization) {
CMutableTransaction mtx;
mtx.fOverwintered = true;
mtx.nVersion = OVERWINTER_TX_VERSION;
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
mtx.nExpiryHeight = 99;
// Check round-trip serialization and deserialization from mtx to tx.
{
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
ss << mtx;
CTransaction tx;
ss >> tx;
EXPECT_EQ(mtx.nVersion, tx.nVersion);
EXPECT_EQ(mtx.fOverwintered, tx.fOverwintered);
EXPECT_EQ(mtx.nVersionGroupId, tx.nVersionGroupId);
EXPECT_EQ(mtx.nExpiryHeight, tx.nExpiryHeight);
EXPECT_EQ(mtx.GetHash(), CMutableTransaction(tx).GetHash());
EXPECT_EQ(tx.GetHash(), CTransaction(mtx).GetHash());
}
// Also check mtx to mtx
{
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
ss << mtx;
CMutableTransaction mtx2;
ss >> mtx2;
EXPECT_EQ(mtx.nVersion, mtx2.nVersion);
EXPECT_EQ(mtx.fOverwintered, mtx2.fOverwintered);
EXPECT_EQ(mtx.nVersionGroupId, mtx2.nVersionGroupId);
EXPECT_EQ(mtx.nExpiryHeight, mtx2.nExpiryHeight);
EXPECT_EQ(mtx.GetHash(), mtx2.GetHash());
}
// Also check tx to tx
{
CTransaction tx(mtx);
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
ss << tx;
CTransaction tx2;
ss >> tx2;
EXPECT_EQ(tx.nVersion, tx2.nVersion);
EXPECT_EQ(tx.fOverwintered, tx2.fOverwintered);
EXPECT_EQ(tx.nVersionGroupId, tx2.nVersionGroupId);
EXPECT_EQ(tx.nExpiryHeight, tx2.nExpiryHeight);
EXPECT_EQ(mtx.GetHash(), CMutableTransaction(tx).GetHash());
EXPECT_EQ(tx.GetHash(), tx2.GetHash());
}
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, OverwinterDefaultValues) {
// Check default values (this will fail when defaults change; test should then be updated)
CTransaction tx;
EXPECT_EQ(tx.nVersion, 1);
EXPECT_EQ(tx.fOverwintered, false);
EXPECT_EQ(tx.nVersionGroupId, 0);
EXPECT_EQ(tx.nExpiryHeight, 0);
}
// A valid v3 transaction with no joinsplits
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, OverwinterValidTx) {
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit.resize(0);
mtx.fOverwintered = true;
mtx.nVersion = OVERWINTER_TX_VERSION;
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
mtx.nExpiryHeight = 0;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, OverwinterExpiryHeight) {
const auto& params = RegtestActivateOverwinter();
CMutableTransaction mtx = GetValidTransaction(0x5ba81b19);
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit.resize(0);
mtx.nExpiryHeight = 0;
{
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
EXPECT_TRUE(ContextualCheckTransaction(tx, state, params, 1, true));
}
{
mtx.nExpiryHeight = TX_EXPIRY_HEIGHT_THRESHOLD - 1;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
EXPECT_TRUE(ContextualCheckTransaction(tx, state, params, 1, true));
}
{
mtx.nExpiryHeight = TX_EXPIRY_HEIGHT_THRESHOLD;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-expiry-height-too-high", false, "")).Times(1);
ContextualCheckTransaction(tx, state, params, 1, true);
}
{
mtx.nExpiryHeight = std::numeric_limits<uint32_t>::max();
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-expiry-height-too-high", false, "")).Times(1);
ContextualCheckTransaction(tx, state, params, 1, true);
}
RegtestDeactivateSapling();
}
TEST(checktransaction_tests, BlossomExpiryHeight) {
const Consensus::Params& params = RegtestActivateBlossom(false, 100).GetConsensus();
CMutableTransaction preBlossomMtx = CreateNewContextualCMutableTransaction(params, 99, false);
EXPECT_EQ(preBlossomMtx.nExpiryHeight, 100 - 1);
CMutableTransaction blossomMtx = CreateNewContextualCMutableTransaction(params, 100, false);
EXPECT_EQ(blossomMtx.nExpiryHeight, 100 + 40);
RegtestDeactivateBlossom();
}
// Test that a Sprout tx with a negative version number is detected
// given the new Overwinter logic
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, SproutTxVersionTooLow) {
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit.resize(0);
mtx.fOverwintered = false;
mtx.nVersion = -1;
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-version-too-low", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, SaplingSproutInputSumsTooLarge) {
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit.resize(0);
mtx.fOverwintered = true;
mtx.nVersion = SAPLING_TX_VERSION;
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nExpiryHeight = 0;
{
// create JSDescription
uint256 rt;
2022-06-06 15:17:07 -07:00
ed25519::VerificationKey joinSplitPubKey;
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs = {
libzcash::JSInput(),
libzcash::JSInput()
};
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs = {
libzcash::JSOutput(),
libzcash::JSOutput()
};
std::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
auto jsdesc = JSDescriptionInfo(
joinSplitPubKey, rt,
inputs, outputs,
0, 0
).BuildRandomized(
inputMap, outputMap,
false);
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit.push_back(jsdesc);
}
Use `RandomInvalidOutputDescription()` everywhere it makes sense As a pre-check inside `z_sendmany` we estimate the size of the transaction that would be created, to confirm it won't exceed any limits. We do this by creating a fake transaction with fake outputs and measuring its size. In the case of Sapling recipients, we'd push an empty `OutputDescription`. In zcash/zcash#6459 we pulled in changes that improved type safety in the Rust types. One of these changes was that the `cv` field in a Sapling Output Description is now enforced at parsing time to be not small order (where previously we enforced this at proof verification time). The two above paragraphs collide because when measuring the size of the fake transaction, we convert a `CMutableTransaction` into a `CTransaction`; this calls `UpdateHash` to pin its txid, and that causes the transaction to be serialized and then parsed across the FFI. This causes the null `OutputDescription` to reach the Rust parser which treats it as invalid. There are two solutions to this, which are used in various contexts: - Avoid pushing a null `OutputDescription` into a `CMutableTransaction`. This is the fix implemented in this PR for `z_sendmany`: we now call `RandomInvalidOutputDescription()` which gives us a consensus-invalid but parser-valid `OutputDescription`, suitable for estimating tx size. - Use `UNSAFE_CTransaction` to avoid having `UpdateHash` be called on construction. This type is used in tests where we explicitly want to construct an invalid type in C++, for consensus checking purposes. One of the `OutputDescription()` uses was in a test, but didn't trigger the issue because the test was checking a different part of the transaction being invalid. Technically no change is needed here; however we now also call `RandomInvalidOutputDescription()` here for uniformity. Part of zcash/zcash#6509.
2023-03-27 15:33:45 -07:00
mtx.vShieldedSpend.push_back(RandomInvalidSpendDescription());
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit[0].vpub_new = (MAX_MONEY / 2) + 10;
{
UNSAFE_CTransaction tx(mtx);
CValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
}
mtx.valueBalanceSapling = (MAX_MONEY / 2) + 10;
{
UNSAFE_CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-txintotal-toolarge", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
}
// Test bad Overwinter version number in CheckTransactionWithoutProofVerification
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, OverwinterVersionNumberLow) {
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit.resize(0);
mtx.fOverwintered = true;
mtx.nVersion = OVERWINTER_MIN_TX_VERSION - 1;
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
mtx.nExpiryHeight = 0;
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-overwinter-version-too-low", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
// Test bad Overwinter version number in ContextualCheckTransaction
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, OverwinterVersionNumberHigh) {
SelectParams(CBaseChainParams::REGTEST);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit.resize(0);
mtx.fOverwintered = true;
mtx.nVersion = OVERWINTER_MAX_TX_VERSION + 1;
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
mtx.nExpiryHeight = 0;
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-overwinter-version-too-high", false, "")).Times(1);
ContextualCheckTransaction(tx, state, Params(), 1, true);
// Revert to default
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
}
// Test bad Overwinter version group id
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, OverwinterBadVersionGroupId) {
CMutableTransaction mtx = GetValidTransaction();
2019-06-16 04:39:05 -07:00
mtx.vJoinSplit.resize(0);
mtx.fOverwintered = true;
mtx.nVersion = OVERWINTER_TX_VERSION;
mtx.nExpiryHeight = 0;
mtx.nVersionGroupId = 0x12345678;
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-version-group-id", false, "")).Times(1);
CheckTransactionWithoutProofVerification(tx, state);
}
// This tests an Overwinter transaction checked against Sprout
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, OverwinterNotActive) {
SelectParams(CBaseChainParams::TESTNET);
auto chainparams = Params();
CMutableTransaction mtx = GetValidTransaction();
mtx.fOverwintered = true;
mtx.nVersion = OVERWINTER_TX_VERSION;
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
mtx.nExpiryHeight = 0;
CTransaction tx(mtx);
MockCValidationState state;
// during initial block download, for transactions being accepted into the
// mempool (and thus not mined), DoS ban score should be zero, else 10
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "tx-overwinter-not-active", false, "")).Times(1);
ContextualCheckTransaction(tx, state, chainparams, 0, false, [](const Consensus::Params&) { return true; });
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "tx-overwinter-not-active", false, "")).Times(1);
ContextualCheckTransaction(tx, state, chainparams, 0, false, [](const Consensus::Params&) { return false; });
// for transactions that have been mined in a block, DoS ban score should
// always be 100.
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwinter-not-active", false, "")).Times(1);
ContextualCheckTransaction(tx, state, chainparams, 0, true, [](const Consensus::Params&) { return true; });
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwinter-not-active", false, "")).Times(1);
ContextualCheckTransaction(tx, state, chainparams, 0, true, [](const Consensus::Params&) { return false; });
}
// This tests a transaction without the fOverwintered flag set, against the Overwinter consensus rule set.
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, OverwinterFlagNotSet) {
SelectParams(CBaseChainParams::REGTEST);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
CMutableTransaction mtx = GetValidTransaction();
mtx.fOverwintered = false;
mtx.nVersion = OVERWINTER_TX_VERSION;
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
mtx.nExpiryHeight = 0;
CTransaction tx(mtx);
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwintered-flag-not-set", false, "")).Times(1);
ContextualCheckTransaction(tx, state, Params(), 1, true);
// Revert to default
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
}
// Overwinter (NU0) does not allow soft fork to version 4 Overwintered tx.
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, OverwinterInvalidSoftForkVersion) {
CMutableTransaction mtx = GetValidTransaction();
mtx.fOverwintered = true;
mtx.nVersion = 4; // This is not allowed
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
mtx.nExpiryHeight = 0;
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
try {
ss << mtx;
FAIL() << "Expected std::ios_base::failure 'Unknown transaction format'";
}
catch(std::ios_base::failure & err) {
EXPECT_THAT(err.what(), testing::HasSubstr(std::string("Unknown transaction format")));
}
catch(...) {
FAIL() << "Expected std::ios_base::failure 'Unknown transaction format', got some other exception";
}
}
2019-08-04 13:35:47 -07:00
static void ContextualCreateTxCheck(const Consensus::Params& params, int nHeight,
int expectedVersion, bool expectedOverwintered, int expectedVersionGroupId, int expectedExpiryHeight)
{
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(params, nHeight, false);
2019-08-04 13:35:47 -07:00
EXPECT_EQ(mtx.nVersion, expectedVersion);
EXPECT_EQ(mtx.fOverwintered, expectedOverwintered);
EXPECT_EQ(mtx.nVersionGroupId, expectedVersionGroupId);
EXPECT_EQ(mtx.nExpiryHeight, expectedExpiryHeight);
}
// Test CreateNewContextualCMutableTransaction sets default values based on height
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, OverwinteredContextualCreateTx) {
SelectParams(CBaseChainParams::REGTEST);
2019-08-04 13:35:47 -07:00
const Consensus::Params& params = Params().GetConsensus();
int overwinterActivationHeight = 5;
int saplingActivationHeight = 30;
2019-08-04 13:35:47 -07:00
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, overwinterActivationHeight);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, saplingActivationHeight);
2019-08-04 13:35:47 -07:00
ContextualCreateTxCheck(params, overwinterActivationHeight - 1,
1, false, 0, 0);
// Overwinter activates
2019-08-04 13:35:47 -07:00
ContextualCreateTxCheck(params, overwinterActivationHeight,
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, overwinterActivationHeight + DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA);
// Close to Sapling activation
2019-08-04 13:35:47 -07:00
ContextualCreateTxCheck(params, saplingActivationHeight - DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA - 2,
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 2);
2019-08-04 13:35:47 -07:00
ContextualCreateTxCheck(params, saplingActivationHeight - DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA - 1,
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
2019-08-04 13:35:47 -07:00
ContextualCreateTxCheck(params, saplingActivationHeight - DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA,
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
2019-08-04 13:35:47 -07:00
ContextualCreateTxCheck(params, saplingActivationHeight - DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA + 1,
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
2019-08-04 13:35:47 -07:00
ContextualCreateTxCheck(params, saplingActivationHeight - DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA + 2,
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
2019-08-04 13:35:47 -07:00
ContextualCreateTxCheck(params, saplingActivationHeight - DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA + 3,
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
// Just before Sapling activation
2019-08-04 13:35:47 -07:00
ContextualCreateTxCheck(params, saplingActivationHeight - 4,
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
2019-08-04 13:35:47 -07:00
ContextualCreateTxCheck(params, saplingActivationHeight - 3,
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
2019-08-04 13:35:47 -07:00
ContextualCreateTxCheck(params, saplingActivationHeight - 2,
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
2019-08-04 13:35:47 -07:00
ContextualCreateTxCheck(params, saplingActivationHeight - 1,
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
// Sapling activates
2019-08-04 13:35:47 -07:00
ContextualCreateTxCheck(params, saplingActivationHeight,
SAPLING_TX_VERSION, true, SAPLING_VERSION_GROUP_ID, saplingActivationHeight + DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA);
// Revert to default
2019-01-29 20:18:10 -08:00
RegtestDeactivateSapling();
}
// Test a v1 transaction which has a malformed header, perhaps modified in-flight
2018-10-09 20:18:52 -07:00
TEST(ChecktransactionTests, BadTxReceivedOverNetwork)
{
// First four bytes <01 00 00 00> have been modified to be <FC FF FF FF> (-4 as an int32)
std::string goodPrefix = "01000000";
std::string badPrefix = "fcffffff";
std::string hexTx = "0176c6541939b95f8d8b7779a77a0863b2a0267e281a050148326f0ea07c3608fb000000006a47304402207c68117a6263486281af0cc5d3bee6db565b6dce19ffacc4cb361906eece82f8022007f604382dee2c1fde41c4e6e7c1ae36cfa28b5b27350c4bfaa27f555529eace01210307ff9bef60f2ac4ceb1169a9f7d2c773d6c7f4ab6699e1e5ebc2e0c6d291c733feffffff02c0d45407000000001976a9145eaaf6718517ec8a291c6e64b16183292e7011f788ac5ef44534000000001976a91485e12fb9967c96759eae1c6b1e9c07ce977b638788acbe000000";
// Good v1 tx
{
std::vector<unsigned char> txData(ParseHex(goodPrefix + hexTx ));
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
CTransaction tx;
ssData >> tx;
EXPECT_EQ(tx.nVersion, 1);
EXPECT_EQ(tx.fOverwintered, false);
}
// Good v1 mutable tx
{
std::vector<unsigned char> txData(ParseHex(goodPrefix + hexTx ));
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
CMutableTransaction mtx;
ssData >> mtx;
EXPECT_EQ(mtx.nVersion, 1);
}
// Bad tx
{
std::vector<unsigned char> txData(ParseHex(badPrefix + hexTx ));
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
try {
CTransaction tx;
ssData >> tx;
FAIL() << "Expected std::ios_base::failure 'Unknown transaction format'";
}
catch(std::ios_base::failure & err) {
EXPECT_THAT(err.what(), testing::HasSubstr(std::string("Unknown transaction format")));
}
catch(...) {
FAIL() << "Expected std::ios_base::failure 'Unknown transaction format', got some other exception";
}
}
// Bad mutable tx
{
std::vector<unsigned char> txData(ParseHex(badPrefix + hexTx ));
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
try {
CMutableTransaction mtx;
ssData >> mtx;
FAIL() << "Expected std::ios_base::failure 'Unknown transaction format'";
}
catch(std::ios_base::failure & err) {
EXPECT_THAT(err.what(), testing::HasSubstr(std::string("Unknown transaction format")));
}
catch(...) {
FAIL() << "Expected std::ios_base::failure 'Unknown transaction format', got some other exception";
}
}
}
2019-05-17 10:02:07 -07:00
TEST(ChecktransactionTests, InvalidSaplingShieldedCoinbase) {
2019-05-17 10:02:07 -07:00
RegtestActivateSapling();
CMutableTransaction mtx = GetValidTransaction();
mtx.fOverwintered = true;
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nVersion = SAPLING_TX_VERSION;
// Make it an invalid shielded coinbase (no ciphertexts or commitments).
mtx.vin.resize(1);
mtx.vin[0].prevout.SetNull();
mtx.vShieldedOutput.push_back(RandomInvalidOutputDescription());
2019-05-17 10:02:07 -07:00
mtx.vJoinSplit.resize(0);
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
// Before Heartwood, output descriptions are rejected.
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-has-output-description", false, "")).Times(1);
2019-05-17 10:02:07 -07:00
ContextualCheckTransaction(tx, state, Params(), 10, 57);
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
// From Heartwood, the output description is allowed but invalid (undecryptable).
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-output-desc-invalid-outct", false, "")).Times(1);
2019-05-17 10:02:07 -07:00
ContextualCheckTransaction(tx, state, Params(), 10, 57);
RegtestDeactivateHeartwood();
}
TEST(ChecktransactionTests, HeartwoodAcceptsSaplingShieldedCoinbase) {
LoadProofParameters();
2019-05-17 10:02:07 -07:00
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
auto chainparams = Params();
uint256 ovk;
auto note = libzcash::SaplingNote(
libzcash::SaplingSpendingKey::random().default_address(), CAmount(123456), libzcash::Zip212Enabled::BeforeZip212);
2019-05-17 10:02:07 -07:00
auto output = OutputDescriptionInfo(ovk, note, {{0xF6}});
auto ctx = sapling::init_prover();
auto odesc = output.Build(ctx).value();
2019-05-17 10:02:07 -07:00
CMutableTransaction mtx = GetValidTransaction();
mtx.fOverwintered = true;
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nVersion = SAPLING_TX_VERSION;
mtx.vin.resize(1);
mtx.vin[0].prevout.SetNull();
mtx.vJoinSplit.resize(0);
mtx.vShieldedOutput.push_back(odesc);
// Transaction should fail with a bad public cmu.
{
auto cmOrig = mtx.vShieldedOutput[0].cmu;
mtx.vShieldedOutput[0].cmu = uint256S("1234");
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-output-desc-invalid-outct", false, "")).Times(1);
2019-05-17 10:02:07 -07:00
ContextualCheckTransaction(tx, state, chainparams, 10, 57);
mtx.vShieldedOutput[0].cmu = cmOrig;
}
// Transaction should fail with a bad outCiphertext.
{
auto outCtOrig = mtx.vShieldedOutput[0].outCiphertext;
mtx.vShieldedOutput[0].outCiphertext = {{}};
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-output-desc-invalid-outct", false, "")).Times(1);
2019-05-17 10:02:07 -07:00
ContextualCheckTransaction(tx, state, chainparams, 10, 57);
mtx.vShieldedOutput[0].outCiphertext = outCtOrig;
}
// Transaction should fail with a bad encCiphertext.
{
auto encCtOrig = mtx.vShieldedOutput[0].encCiphertext;
mtx.vShieldedOutput[0].encCiphertext = {{}};
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-output-desc-invalid-encct", false, "")).Times(1);
2019-05-17 10:02:07 -07:00
ContextualCheckTransaction(tx, state, chainparams, 10, 57);
mtx.vShieldedOutput[0].encCiphertext = encCtOrig;
}
// Test the success case.
2019-05-17 10:02:07 -07:00
{
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_TRUE(ContextualCheckTransaction(tx, state, chainparams, 10, 57));
2019-05-17 10:02:07 -07:00
}
RegtestDeactivateHeartwood();
}
// Check that the consensus rules relevant to valueBalanceSapling, vShieldedOutput, and
// bindingSig from https://zips.z.cash/protocol/protocol.pdf#txnencoding are
// applied to coinbase transactions.
TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
LoadProofParameters();
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
auto chainparams = Params();
uint256 ovk;
auto note = libzcash::SaplingNote(
libzcash::SaplingSpendingKey::random().default_address(), CAmount(123456), libzcash::Zip212Enabled::BeforeZip212);
auto output = OutputDescriptionInfo(ovk, note, {{0xF6}});
CMutableTransaction mtx = GetValidTransaction();
mtx.fOverwintered = true;
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nVersion = SAPLING_TX_VERSION;
mtx.vin.resize(1);
mtx.vin[0].prevout.SetNull();
mtx.vin[0].scriptSig << 123;
mtx.vJoinSplit.resize(0);
mtx.valueBalanceSapling = -1000;
// Coinbase transaction should fail non-contextual checks with no shielded
// outputs and non-zero valueBalanceSapling.
{
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-nonzero", false, "")).Times(1);
EXPECT_FALSE(CheckTransactionWithoutProofVerification(tx, state));
}
// Add a Sapling output.
auto ctx = sapling::init_prover();
auto odesc = output.Build(ctx).value();
mtx.vShieldedOutput.push_back(odesc);
// Coinbase transaction should fail non-contextual checks with valueBalanceSapling
// out of range.
{
mtx.valueBalanceSapling = MAX_MONEY + 1;
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false, "")).Times(1);
EXPECT_FALSE(CheckTransactionWithoutProofVerification(tx, state));
}
{
mtx.valueBalanceSapling = -MAX_MONEY - 1;
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false, "")).Times(1);
EXPECT_FALSE(CheckTransactionWithoutProofVerification(tx, state));
}
mtx.valueBalanceSapling = -1000;
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
// Coinbase transaction should now pass non-contextual checks.
MockCValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
// Coinbase transaction should pass contextual checks.
EXPECT_TRUE(ContextualCheckTransaction(tx, state, chainparams, 10, 57));
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = sapling::init_batch_validator(false);
std::optional<rust::Box<orchard::BatchValidator>> orchardAuth = std::nullopt;
auto heartwoodBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_HEARTWOOD].nBranchId;
// Coinbase transaction does not pass shielded input checks, as bindingSig
// consensus rule is enforced. ContextualCheckShieldedInputs passes because
// the rest of the input checks pass, but saplingAuth fails when it attempts
// to validate the batch of signatures that includes bindingSig.
// - Note that coinbase txs don't have a previous output corresponding to
// their transparent input; ZIP 244 handles this by making the coinbase
// sighash the txid.
PrecomputedTransactionData txdata(tx, {});
Apply `HaveShieldedRequirements` to coinbase transactions Both transparent and shielded inputs have contextual checks that need to be enforced in the consensus rules. For shielded inputs, these are that the anchors in transactions correspond to real commitment tree states (to ensure that the spent notes existed), and that their nullifiers are not being double-spent. When Sprout was first added to the codebase, we added input checks in the same places that transparent inputs were checked; namely anywhere `CCoinsViewCache::HaveInputs` is called. These all happened to be gated on `!tx.IsCoinBase()`, which was fine because we did not allow Sprout JoinSplits in coinbase transactions (enforced with a non-contextual check). When we added Sapling we also allowed coinbase outputs to Sapling addresses (shielded coinbase). We updated `HaveShieldedRequirements` to check Sapling anchors and nullifiers, but didn't change the consensus code to call it on coinbase. This was fine because Sapling Spends and Outputs are separate, and we did not allow Sapling Spends in coinbase transactions (meaning that there were no anchors or nullifiers to enforce the input rules on). Orchard falls into an interesting middle-ground: - We allowed coinbase outputs to Orchard addresses, to enable Sapling shielded coinbase users to migrate to Orchard. - Orchard uses Actions, which are a hybrid of Sprout JoinSplits and Sapling Spends/Outputs. That is, an Orchard Action comprises a single spend and a single output. To maintain the "no shielded spends in coinbase" rule, we added an `enableSpends` flag to the Orchard circuit. We force it to be set to `false` for coinbase, ensuring that all Orchard spends in a coinbase use dummy (zero-valued) notes. However, this is insufficient: the coinbase transaction will still contain an Orchard anchor and nullifiers, and these need to be correctly constrained. In particular, not constraining the Orchard nullifiers in a coinbase transaction enables a Faerie Gold attack. We explicitly require that Orchard nullifiers are unique, so that there is a unique input to the nullifier derivation. Without the coinbase check, the following attack is possible: - An adversary creates an Orchard Action sending some amount of ZEC to a victim address, with a dummy spent note. The entire transaction can be fully-shielded by placing the real spent note in a separate Action. - The adversary uses the exact same dummy note in a coinbase transaction, creating the exact same output note (same victim address and amount). - The victim now has two notes with the same ZEC amount, but can only spend one of them because they have the same nullifier. This commit fixes the consensus bug by calling `HaveShieldedRequirements` outside of `!tx.IsCoinBase()` gates. To simplify its usage, there is now a `Consensus::CheckTxShieldedInputs` function that handles the logging and validation state updates. We also move shielded input checks from `ContextualCheckInputs` to `ContextualCheckShieldedInputs`; these now mirror each other in that they check contextual rules on transparent and shielded inputs respectively, followed by checking signatures.
2022-04-01 12:11:18 -07:00
AssumeShieldedInputsExistAndAreSpendable baseView;
CCoinsViewCache view(&baseView);
EXPECT_TRUE(ContextualCheckShieldedInputs(
tx, txdata, state, view, saplingAuth, orchardAuth, chainparams.GetConsensus(), heartwoodBranchId, false, true));
EXPECT_FALSE(saplingAuth.value()->validate());
RegtestDeactivateHeartwood();
}
TEST(ChecktransactionTests, CanopyRejectsNonzeroVPubOld) {
RegtestActivateSapling();
CMutableTransaction mtx = GetValidTransaction(NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId);
// Make a JoinSplit with nonzero vpub_old
mtx.vJoinSplit.resize(1);
mtx.vJoinSplit[0].vpub_old = 1;
mtx.vJoinSplit[0].vpub_new = 0;
mtx.vJoinSplit[0].proof = libzcash::GrothProof();
CreateJoinSplitSignature(mtx, NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId);
CTransaction tx(mtx);
// Before Canopy, nonzero vpub_old is accepted in both non-contextual and contextual checks
MockCValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 1, true));
RegtestActivateCanopy(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
// After Canopy, nonzero vpub_old is accepted in non-contextual checks but rejected in contextual checks
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vpub_old-nonzero", false, "")).Times(1);
EXPECT_FALSE(ContextualCheckTransaction(tx, state, Params(), 10, true));
RegtestDeactivateCanopy();
}
TEST(ChecktransactionTests, CanopyAcceptsZeroVPubOld) {
CMutableTransaction mtx = GetValidTransaction(NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId);
// Make a JoinSplit with zero vpub_old
mtx.vJoinSplit.resize(1);
mtx.vJoinSplit[0].vpub_old = 0;
mtx.vJoinSplit[0].vpub_new = 1;
mtx.vJoinSplit[0].proof = libzcash::GrothProof();
CreateJoinSplitSignature(mtx, NetworkUpgradeInfo[Consensus::UPGRADE_CANOPY].nBranchId);
CTransaction tx(mtx);
// After Canopy, zero value vpub_old (i.e. unshielding) is accepted in both non-contextual and contextual checks
MockCValidationState state;
RegtestActivateCanopy(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 10, true));
RegtestDeactivateCanopy();
}
TEST(ChecktransactionTests, InvalidOrchardShieldedCoinbase) {
LoadProofParameters();
RegtestActivateCanopy();
CMutableTransaction mtx;
mtx.fOverwintered = true;
mtx.nVersionGroupId = ZIP225_VERSION_GROUP_ID;
mtx.nVersion = ZIP225_TX_VERSION;
mtx.nConsensusBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_NU5].nBranchId;
// Make it an invalid shielded coinbase, by creating an all-dummy Orchard bundle.
mtx.vin.resize(1);
mtx.vin[0].prevout.SetNull();
mtx.orchardBundle = orchard::Builder(false, true, uint256())
.Build().value()
.ProveAndSign({}, uint256()).value();
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
// Before NU5, v5 transactions are rejected.
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-sapling-tx-version-group-id", false, "")).Times(1);
ContextualCheckTransaction(tx, state, Params(), 10, 57);
RegtestActivateNU5();
// From NU5, the Orchard actions are allowed but invalid (undecryptable).
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-action-invalid-ciphertext", false, "")).Times(1);
ContextualCheckTransaction(tx, state, Params(), 10, 57);
RegtestDeactivateNU5();
}
TEST(ChecktransactionTests, NU5AcceptsOrchardShieldedCoinbase) {
LoadProofParameters();
RegtestActivateNU5();
auto chainparams = Params();
uint256 orchardAnchor;
auto builder = orchard::Builder(false, true, orchardAnchor);
// Shielded coinbase outputs must be recoverable with an all-zeroes ovk.
RawHDSeed rawSeed(32, 0);
GetRandBytes(rawSeed.data(), 32);
auto to = libzcash::OrchardSpendingKey::ForAccount(HDSeed(rawSeed), Params().BIP44CoinType(), 0)
.ToFullViewingKey()
.ToIncomingViewingKey()
.Address(0);
uint256 ovk;
builder.AddOutput(ovk, to, CAmount(123456), std::nullopt);
// orchard::Builder pads to two Actions, but does so using a "no OVK" policy for
// dummy outputs, which violates coinbase rules requiring all shielded outputs to
// be recoverable. We manually add a dummy output to sidestep this issue.
// TODO: If/when we have funding streams going to Orchard recipients, this dummy
// output can be removed.
builder.AddOutput(ovk, to, 0, std::nullopt);
auto bundle = builder
.Build().value()
.ProveAndSign({}, uint256()).value();
CMutableTransaction mtx;
mtx.fOverwintered = true;
mtx.nVersionGroupId = ZIP225_VERSION_GROUP_ID;
mtx.nVersion = ZIP225_TX_VERSION;
mtx.nConsensusBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_NU5].nBranchId;
mtx.vin.resize(1);
mtx.vin[0].prevout.SetNull();
mtx.orchardBundle = bundle;
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
// Write the transaction bytes out so we can modify them to test failure cases.
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
ss << tx;
// Define some constants to use when calculating offsets to modify below.
const size_t HEADER_SIZE = 4 + 4 + 4 + 4 + 4;
const size_t TRANSPARENT_BUNDLE_SIZE = 1 + 32 + 4 + 1 + 4 + 1;
const size_t SAPLING_BUNDLE_SIZE = 1 + 1;
const size_t ORCHARD_BUNDLE_START = (HEADER_SIZE + TRANSPARENT_BUNDLE_SIZE + SAPLING_BUNDLE_SIZE);
const size_t ORCHARD_BUNDLE_CMX_OFFSET = (ORCHARD_BUNDLE_START + ZC_ZIP225_ORCHARD_NUM_ACTIONS_SIZE + 32 + 32 + 32);
const size_t ORCHARD_CMX_SIZE = 32;
// Verify the transaction is the expected size.
size_t txsize = ORCHARD_BUNDLE_START + ZC_ZIP225_ORCHARD_BASE_SIZE + ZC_ZIP225_ORCHARD_MARGINAL_SIZE * 2;
EXPECT_EQ(ss.size(), txsize);
// Transaction should fail with a bad public cmx.
{
auto cmxBad = uint256S("1234");
std::vector<char> txBytes(ss.begin(), ss.end());
std::copy(cmxBad.begin(), cmxBad.end(), txBytes.data() + ORCHARD_BUNDLE_CMX_OFFSET);
CDataStream ssBad(txBytes, SER_DISK, PROTOCOL_VERSION);
CTransaction tx;
ssBad >> tx;
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-action-invalid-ciphertext", false, "")).Times(1);
ContextualCheckTransaction(tx, state, chainparams, 10, 57);
}
// Transaction should fail with a bad encCiphertext.
{
std::vector<char> txBytes(ss.begin(), ss.end());
for (int i = 0; i < ZC_SAPLING_ENCCIPHERTEXT_SIZE; i++) {
txBytes[ORCHARD_BUNDLE_CMX_OFFSET + ORCHARD_CMX_SIZE + i] = 0;
}
CDataStream ssBad(txBytes, SER_DISK, PROTOCOL_VERSION);
CTransaction tx;
ssBad >> tx;
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-action-invalid-ciphertext", false, "")).Times(1);
ContextualCheckTransaction(tx, state, chainparams, 10, 57);
}
// Transaction should fail with a bad outCiphertext.
{
std::vector<char> txBytes(ss.begin(), ss.end());
for (int i = 0; i < ZC_SAPLING_OUTCIPHERTEXT_SIZE; i++) {
txBytes[ORCHARD_BUNDLE_CMX_OFFSET + ORCHARD_CMX_SIZE + ZC_SAPLING_ENCCIPHERTEXT_SIZE + i] = 0;
}
CDataStream ssBad(txBytes, SER_DISK, PROTOCOL_VERSION);
CTransaction tx;
ssBad >> tx;
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-action-invalid-ciphertext", false, "")).Times(1);
ContextualCheckTransaction(tx, state, chainparams, 10, 57);
}
// Test the success case.
{
MockCValidationState state;
EXPECT_TRUE(ContextualCheckTransaction(tx, state, chainparams, 10, 57));
}
RegtestDeactivateNU5();
}
// Check that the consensus rules relevant to valueBalanceOrchard, and
// vOrchardActions from https://zips.z.cash/protocol/protocol.pdf#txnencoding
// are applied to coinbase transactions.
TEST(ChecktransactionTests, NU5EnforcesOrchardRulesOnShieldedCoinbase) {
LoadProofParameters();
RegtestActivateNU5();
auto chainparams = Params();
uint256 orchardAnchor;
auto builder = orchard::Builder(false, true, orchardAnchor);
// Shielded coinbase outputs must be recoverable with an all-zeroes ovk.
RawHDSeed rawSeed(32, 0);
GetRandBytes(rawSeed.data(), 32);
auto to = libzcash::OrchardSpendingKey::ForAccount(HDSeed(rawSeed), Params().BIP44CoinType(), 0)
.ToFullViewingKey()
.ToIncomingViewingKey()
.Address(0);
uint256 ovk;
builder.AddOutput(ovk, to, CAmount(1000), std::nullopt);
// orchard::Builder pads to two Actions, but does so using a "no OVK" policy for
// dummy outputs, which violates coinbase rules requiring all shielded outputs to
// be recoverable. We manually add a dummy output to sidestep this issue.
// TODO: If/when we have funding streams going to Orchard recipients, this dummy
// output can be removed.
builder.AddOutput(ovk, to, 0, std::nullopt);
auto bundle = builder
.Build().value()
.ProveAndSign({}, uint256()).value();
CMutableTransaction mtx;
mtx.fOverwintered = true;
mtx.nVersionGroupId = ZIP225_VERSION_GROUP_ID;
mtx.nVersion = ZIP225_TX_VERSION;
mtx.nConsensusBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_NU5].nBranchId;
mtx.vin.resize(1);
mtx.vin[0].prevout.SetNull();
mtx.vin[0].scriptSig << 123;
mtx.orchardBundle = bundle;
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
// Write the transaction bytes out so we can modify them to test failure cases.
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
ss << tx;
// Define some constants to use when calculating offsets to modify below.
const size_t HEADER_SIZE = 4 + 4 + 4 + 4 + 4;
const size_t TRANSPARENT_BUNDLE_SIZE = 1 + 32 + 4 + 1 + 2 + 4 + 1;
const size_t SAPLING_BUNDLE_SIZE = 1 + 1;
const size_t ORCHARD_BUNDLE_START = (HEADER_SIZE + TRANSPARENT_BUNDLE_SIZE + SAPLING_BUNDLE_SIZE);
const size_t ORCHARD_BUNDLE_VALUEBALANCE_OFFSET = (
ORCHARD_BUNDLE_START +
ZC_ZIP225_ORCHARD_NUM_ACTIONS_SIZE +
ZC_ZIP225_ORCHARD_ACTION_SIZE * 2 +
ZC_ZIP225_ORCHARD_FLAGS_SIZE);
// Verify the transaction is the expected size.
size_t txsize = ORCHARD_BUNDLE_START + ZC_ZIP225_ORCHARD_BASE_SIZE + ZC_ZIP225_ORCHARD_MARGINAL_SIZE * 2;
EXPECT_EQ(ss.size(), txsize);
// Coinbase transaction should fail non-contextual checks with valueBalanceSapling
// out of range.
{
std::vector<char> txBytes(ss.begin(), ss.end());
uint64_t valueBalanceBad = htole64(MAX_MONEY + 1);
std::copy((char*)&valueBalanceBad, (char*)&valueBalanceBad + 8, txBytes.data() + ORCHARD_BUNDLE_VALUEBALANCE_OFFSET);
CDataStream ssBad(txBytes, SER_DISK, PROTOCOL_VERSION);
CTransaction tx;
EXPECT_THROW((ssBad >> tx), std::ios_base::failure);
// We can't actually reach the CheckTransactionWithoutProofVerification
// consensus rule, because Rust is doing this validation at parse time.
// MockCValidationState state;
// EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false, "")).Times(1);
// EXPECT_FALSE(CheckTransactionWithoutProofVerification(tx, state));
}
{
std::vector<char> txBytes(ss.begin(), ss.end());
uint64_t valueBalanceBad = htole64(-MAX_MONEY - 1);
std::copy((char*)&valueBalanceBad, (char*)&valueBalanceBad + 8, txBytes.data() + ORCHARD_BUNDLE_VALUEBALANCE_OFFSET);
CDataStream ssBad(txBytes, SER_DISK, PROTOCOL_VERSION);
CTransaction tx;
EXPECT_THROW((ssBad >> tx), std::ios_base::failure);
// We can't actually reach the CheckTransactionWithoutProofVerification
// consensus rule, because Rust is doing this validation at parse time.
// MockCValidationState state;
// EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false, "")).Times(1);
// EXPECT_FALSE(CheckTransactionWithoutProofVerification(tx, state));
}
// Test the success case.
{
// The unmodified coinbase transaction should pass non-contextual checks.
MockCValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
// Coinbase transaction should pass contextual checks, as bindingSigOrchard
// consensus rule is not enforced here.
EXPECT_TRUE(ContextualCheckTransaction(tx, state, chainparams, 10, 57));
}
RegtestDeactivateNU5();
}