2016-04-13 15:52:24 -07:00
|
|
|
#include <gtest/gtest.h>
|
2016-06-15 16:01:55 -07:00
|
|
|
#include <gmock/gmock.h>
|
2016-04-13 15:52:24 -07:00
|
|
|
|
|
|
|
#include "main.h"
|
|
|
|
#include "primitives/transaction.h"
|
|
|
|
#include "consensus/validation.h"
|
2019-05-17 10:02:07 -07:00
|
|
|
#include "transaction_builder.h"
|
2022-03-21 17:53:16 -07:00
|
|
|
#include "gtest/utils.h"
|
2022-07-06 09:25:28 -07:00
|
|
|
#include "util/test.h"
|
2020-02-21 09:06:55 -08:00
|
|
|
#include "zcash/JoinSplit.hpp"
|
2016-04-13 15:52:24 -07:00
|
|
|
|
2019-05-17 10:02:07 -07:00
|
|
|
#include <librustzcash.h>
|
2020-07-31 07:15:04 -07:00
|
|
|
#include <rust/ed25519.h>
|
2022-07-04 10:33:07 -07:00
|
|
|
#include <rust/sapling.h>
|
2022-01-22 12:54:37 -08:00
|
|
|
#include <rust/orchard.h>
|
2019-05-17 10:02:07 -07:00
|
|
|
|
2021-06-12 16:28:25 -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) {
|
2016-04-13 15:52:24 -07:00
|
|
|
CMutableTransaction tx;
|
|
|
|
tx.nVersion = 2;
|
|
|
|
|
|
|
|
{
|
2016-07-14 15:51:36 -07:00
|
|
|
// Ensure that values within the joinsplit are well-formed.
|
2016-04-13 15:52:24 -07:00
|
|
|
CMutableTransaction newTx(tx);
|
|
|
|
CValidationState state;
|
|
|
|
|
2019-06-16 04:39:05 -07:00
|
|
|
newTx.vJoinSplit.push_back(JSDescription());
|
2016-04-13 15:52:24 -07:00
|
|
|
|
2019-06-16 04:39:05 -07:00
|
|
|
JSDescription *jsdesc = &newTx.vJoinSplit[0];
|
2016-07-14 15:51:36 -07:00
|
|
|
jsdesc->vpub_old = 1;
|
|
|
|
jsdesc->vpub_new = 1;
|
2016-04-13 15:52:24 -07:00
|
|
|
|
2016-06-23 15:35:31 -07:00
|
|
|
EXPECT_FALSE(CheckTransactionWithoutProofVerification(newTx, state));
|
2016-04-13 15:52:24 -07:00
|
|
|
EXPECT_EQ(state.GetRejectReason(), "bad-txns-vpubs-both-nonzero");
|
|
|
|
}
|
|
|
|
}
|
2016-06-15 16:01:55 -07:00
|
|
|
|
|
|
|
class MockCValidationState : public CValidationState {
|
|
|
|
public:
|
2015-08-06 00:47:01 -07:00
|
|
|
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));
|
2016-06-15 16:01:55 -07:00
|
|
|
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());
|
2015-03-16 18:36:43 -07:00
|
|
|
MOCK_CONST_METHOD0(GetRejectCode, unsigned int());
|
2016-06-15 16:01:55 -07:00
|
|
|
MOCK_CONST_METHOD0(GetRejectReason, std::string());
|
2015-08-06 00:47:01 -07:00
|
|
|
MOCK_CONST_METHOD0(GetDebugMessage, std::string());
|
2016-06-15 16:01:55 -07:00
|
|
|
};
|
|
|
|
|
2018-04-26 06:18:36 -07:00
|
|
|
void CreateJoinSplitSignature(CMutableTransaction& mtx, uint32_t consensusBranchId);
|
2016-06-22 16:25:35 -07:00
|
|
|
|
2020-02-21 09:06:55 -08:00
|
|
|
CMutableTransaction GetValidTransaction(uint32_t consensusBranchId=SPROUT_BRANCH_ID) {
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx;
|
2020-02-21 09:06:55 -08:00
|
|
|
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;
|
2020-04-08 20:43:18 -07:00
|
|
|
} else if (consensusBranchId != SPROUT_BRANCH_ID) {
|
|
|
|
// Unsupported consensus branch ID
|
|
|
|
assert(false);
|
2020-02-21 09:06:55 -08:00
|
|
|
}
|
|
|
|
|
2016-06-22 16:25:35 -07:00
|
|
|
mtx.vin.resize(2);
|
2016-06-23 15:59:00 -07:00
|
|
|
mtx.vin[0].prevout.hash = uint256S("0000000000000000000000000000000000000000000000000000000000000001");
|
2016-06-22 16:25:35 -07:00
|
|
|
mtx.vin[0].prevout.n = 0;
|
2016-06-23 15:59:00 -07:00
|
|
|
mtx.vin[1].prevout.hash = uint256S("0000000000000000000000000000000000000000000000000000000000000002");
|
2016-06-22 16:25:35 -07:00
|
|
|
mtx.vin[1].prevout.n = 0;
|
|
|
|
mtx.vout.resize(2);
|
2020-08-25 19:07:06 -07:00
|
|
|
// mtx.vout[0].scriptPubKey =
|
2016-06-22 16:25:35 -07:00
|
|
|
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");
|
2016-06-23 15:35:31 -07:00
|
|
|
|
2020-02-21 09:06:55 -08:00
|
|
|
if (mtx.nVersion >= SAPLING_TX_VERSION) {
|
|
|
|
libzcash::GrothProof emptyProof;
|
|
|
|
mtx.vJoinSplit[0].proof = emptyProof;
|
|
|
|
mtx.vJoinSplit[1].proof = emptyProof;
|
|
|
|
}
|
|
|
|
|
2018-04-26 06:18:36 -07:00
|
|
|
CreateJoinSplitSignature(mtx, consensusBranchId);
|
|
|
|
return mtx;
|
|
|
|
}
|
2016-06-23 15:35:31 -07:00
|
|
|
|
2018-04-26 06:18:36 -07:00
|
|
|
void CreateJoinSplitSignature(CMutableTransaction& mtx, uint32_t consensusBranchId) {
|
2016-06-23 15:35:31 -07:00
|
|
|
// Generate an ephemeral keypair.
|
2020-07-31 07:15:04 -07:00
|
|
|
Ed25519SigningKey joinSplitPrivKey;
|
|
|
|
ed25519_generate_keypair(&joinSplitPrivKey, &mtx.joinSplitPubKey);
|
2016-06-23 15:35:31 -07:00
|
|
|
|
|
|
|
// Compute the correct hSig.
|
|
|
|
// TODO: #966.
|
|
|
|
static const uint256 one(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
|
|
|
|
// Empty output script.
|
|
|
|
CScript scriptCode;
|
|
|
|
CTransaction signTx(mtx);
|
2022-01-22 18:37:10 -08:00
|
|
|
// Fake coins being spent.
|
|
|
|
std::vector<CTxOut> allPrevOutputs;
|
|
|
|
allPrevOutputs.resize(signTx.vin.size());
|
|
|
|
const PrecomputedTransactionData txdata(signTx, allPrevOutputs);
|
2022-01-22 14:59:28 -08:00
|
|
|
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId, txdata);
|
2016-06-23 15:35:31 -07:00
|
|
|
if (dataToBeSigned == one) {
|
|
|
|
throw std::runtime_error("SignatureHash failed");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the signature
|
2020-07-31 07:15:04 -07:00
|
|
|
assert(ed25519_sign(
|
|
|
|
&joinSplitPrivKey,
|
|
|
|
dataToBeSigned.begin(), 32,
|
|
|
|
&mtx.joinSplitSig));
|
2016-06-22 16:25:35 -07:00
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, ValidTransaction) {
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
MockCValidationState state;
|
2016-06-23 15:35:31 -07:00
|
|
|
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
|
2016-06-22 16:25:35 -07:00
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, BadVersionTooLow) {
|
2016-10-21 21:07:50 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
|
|
|
mtx.nVersion = 0;
|
|
|
|
|
2021-06-12 16:28:25 -07:00
|
|
|
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
|
|
|
UNSAFE_CTransaction tx(mtx);
|
2016-10-21 21:07:50 -07:00
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-version-too-low", false, "")).Times(1);
|
2016-10-21 21:07:50 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, BadTxnsVinEmpty) {
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit.resize(0);
|
2016-06-22 16:25:35 -07:00
|
|
|
mtx.vin.resize(0);
|
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
2016-06-15 16:01:55 -07:00
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-no-source-of-funds", false, "")).Times(1);
|
2016-06-23 15:35:31 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
2016-06-15 16:01:55 -07:00
|
|
|
}
|
2016-06-22 16:25:35 -07:00
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, BadTxnsVoutEmpty) {
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit.resize(0);
|
2016-06-22 16:25:35 -07:00
|
|
|
mtx.vout.resize(0);
|
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-no-sink-of-funds", false, "")).Times(1);
|
2016-06-23 15:35:31 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
2016-06-22 16:25:35 -07:00
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, BadTxnsOversize) {
|
2018-04-26 06:18:36 -07:00
|
|
|
SelectParams(CBaseChainParams::REGTEST);
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
|
|
|
|
|
|
|
mtx.vin[0].scriptSig = CScript();
|
|
|
|
std::vector<unsigned char> vchData(520);
|
2016-10-07 23:00:23 -07:00
|
|
|
for (unsigned int i = 0; i < 190; ++i)
|
2016-06-22 16:25:35 -07:00
|
|
|
mtx.vin[0].scriptSig << vchData << OP_DROP;
|
|
|
|
mtx.vin[0].scriptSig << OP_1;
|
|
|
|
|
2016-10-07 23:00:23 -07:00
|
|
|
{
|
|
|
|
// Transaction is just under the limit...
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
CValidationState state;
|
|
|
|
ASSERT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
|
|
|
|
}
|
2016-06-22 16:25:35 -07:00
|
|
|
|
2016-10-07 23:00:23 -07:00
|
|
|
// 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);
|
|
|
|
|
2018-04-26 06:18:36 -07:00
|
|
|
// Passes non-contextual checks...
|
2016-10-07 23:00:23 -07:00
|
|
|
MockCValidationState state;
|
2018-04-26 06:18:36 -07:00
|
|
|
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
|
|
|
|
|
|
|
|
// ... but fails contextual ones!
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-oversize", false, "")).Times(1);
|
2020-02-06 06:30:37 -08:00
|
|
|
EXPECT_FALSE(ContextualCheckTransaction(tx, state, Params(), 1, true));
|
2018-04-26 06:18:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
// But should be fine again once Sapling activates!
|
2019-01-29 20:18:10 -08:00
|
|
|
RegtestActivateSapling();
|
2018-04-26 06:18:36 -07:00
|
|
|
|
|
|
|
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();
|
2018-04-26 06:18:36 -07:00
|
|
|
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));
|
2020-02-06 06:30:37 -08:00
|
|
|
EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 1, true));
|
2018-04-26 06:18:36 -07:00
|
|
|
|
|
|
|
// Revert to default
|
2019-01-29 20:18:10 -08:00
|
|
|
RegtestDeactivateSapling();
|
2016-10-07 23:00:23 -07:00
|
|
|
}
|
2016-06-22 16:25:35 -07:00
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, OversizeSaplingTxns) {
|
2019-01-29 20:18:10 -08:00
|
|
|
RegtestActivateSapling();
|
2018-05-01 12:15:04 -07:00
|
|
|
|
|
|
|
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();
|
2018-05-01 12:15:04 -07:00
|
|
|
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;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-oversize", false, "")).Times(1);
|
2018-05-01 12:15:04 -07:00
|
|
|
EXPECT_FALSE(CheckTransactionWithoutProofVerification(tx, state));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Revert to default
|
2019-01-29 20:18:10 -08:00
|
|
|
RegtestDeactivateSapling();
|
2018-05-01 12:15:04 -07:00
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, BadTxnsVoutNegative) {
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
|
|
|
mtx.vout[0].nValue = -1;
|
|
|
|
|
2021-06-12 16:28:25 -07:00
|
|
|
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
|
|
|
UNSAFE_CTransaction tx(mtx);
|
2016-06-22 16:25:35 -07:00
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative", false, "")).Times(1);
|
2016-06-23 15:35:31 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
2016-06-22 16:25:35 -07:00
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, BadTxnsVoutToolarge) {
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
|
|
|
mtx.vout[0].nValue = MAX_MONEY + 1;
|
|
|
|
|
2021-06-12 16:28:25 -07:00
|
|
|
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
|
|
|
UNSAFE_CTransaction tx(mtx);
|
2016-06-22 16:25:35 -07:00
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge", false, "")).Times(1);
|
2016-06-23 15:35:31 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
2016-06-22 16:25:35 -07:00
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, BadTxnsTxouttotalToolargeOutputs) {
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
|
|
|
mtx.vout[0].nValue = MAX_MONEY;
|
|
|
|
mtx.vout[1].nValue = 1;
|
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge", false, "")).Times(1);
|
2016-06-23 15:35:31 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
2016-06-22 16:25:35 -07:00
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, ValueBalanceNonZero) {
|
2018-05-08 05:51:54 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2021-06-29 13:40:26 -07:00
|
|
|
mtx.valueBalanceSapling = 10;
|
2018-05-08 05:51:54 -07:00
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-nonzero", false, "")).Times(1);
|
2018-05-08 05:51:54 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, PositiveValueBalanceTooLarge) {
|
2018-05-08 05:51:54 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
|
|
|
mtx.vShieldedSpend.resize(1);
|
2021-06-29 13:40:26 -07:00
|
|
|
mtx.valueBalanceSapling = MAX_MONEY + 1;
|
2018-05-08 05:51:54 -07:00
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false, "")).Times(1);
|
2018-05-08 05:51:54 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, NegativeValueBalanceTooLarge) {
|
2018-05-08 05:51:54 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
|
|
|
mtx.vShieldedSpend.resize(1);
|
2021-06-29 13:40:26 -07:00
|
|
|
mtx.valueBalanceSapling = -(MAX_MONEY + 1);
|
2018-05-08 05:51:54 -07:00
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false, "")).Times(1);
|
2018-05-08 05:51:54 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, ValueBalanceOverflowsTotal) {
|
2018-05-08 05:51:54 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
|
|
|
mtx.vShieldedSpend.resize(1);
|
|
|
|
mtx.vout[0].nValue = 1;
|
2021-06-29 13:40:26 -07:00
|
|
|
mtx.valueBalanceSapling = -MAX_MONEY;
|
2018-05-08 05:51:54 -07:00
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge", false, "")).Times(1);
|
2018-05-08 05:51:54 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, BadTxnsTxouttotalToolargeJoinsplit) {
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
|
|
|
mtx.vout[0].nValue = 1;
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit[0].vpub_old = MAX_MONEY;
|
2016-06-22 16:25:35 -07:00
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge", false, "")).Times(1);
|
2016-06-23 15:35:31 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
2016-06-22 16:25:35 -07:00
|
|
|
}
|
|
|
|
|
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;
|
2015-08-06 00:47:01 -07:00
|
|
|
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) {
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit[0].vpub_old = -1;
|
2016-06-22 16:25:35 -07:00
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vpub_old-negative", false, "")).Times(1);
|
2016-06-23 15:35:31 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
2016-06-22 16:25:35 -07:00
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, BadTxnsVpubNewNegative) {
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit[0].vpub_new = -1;
|
2016-06-22 16:25:35 -07:00
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vpub_new-negative", false, "")).Times(1);
|
2016-06-23 15:35:31 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
2016-06-22 16:25:35 -07:00
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, BadTxnsVpubOldToolarge) {
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit[0].vpub_old = MAX_MONEY + 1;
|
2016-06-22 16:25:35 -07:00
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vpub_old-toolarge", false, "")).Times(1);
|
2016-06-23 15:35:31 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
2016-06-22 16:25:35 -07:00
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, BadTxnsVpubNewToolarge) {
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit[0].vpub_new = MAX_MONEY + 1;
|
2016-06-22 16:25:35 -07:00
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vpub_new-toolarge", false, "")).Times(1);
|
2016-06-23 15:35:31 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
2016-06-22 16:25:35 -07:00
|
|
|
}
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, BadTxnsVpubsBothNonzero) {
|
2016-06-22 16:25:35 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit[0].vpub_old = 1;
|
|
|
|
mtx.vJoinSplit[0].vpub_new = 1;
|
2016-06-22 16:25:35 -07:00
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vpubs-both-nonzero", false, "")).Times(1);
|
2016-06-23 15:35:31 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
2016-06-22 16:25:35 -07:00
|
|
|
}
|
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;
|
2015-08-06 00:47:01 -07:00
|
|
|
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;
|
2015-08-06 00:47:01 -07:00
|
|
|
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;
|
2015-08-06 00:47:01 -07:00
|
|
|
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;
|
2015-08-06 00:47:01 -07:00
|
|
|
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;
|
2015-08-06 00:47:01 -07:00
|
|
|
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;
|
2015-08-06 00:47:01 -07:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-01-22 12:54:37 -08:00
|
|
|
TEST(ContextualCheckShieldedInputsTest, BadTxnsInvalidJoinsplitSignature) {
|
2018-02-01 17:49:42 -08:00
|
|
|
SelectParams(CBaseChainParams::REGTEST);
|
2022-01-22 12:54:37 -08:00
|
|
|
auto consensus = Params().GetConsensus();
|
2022-07-04 10:33:07 -07:00
|
|
|
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = std::nullopt;
|
2022-01-22 12:54:37 -08:00
|
|
|
auto orchardAuth = orchard::AuthValidator::Disabled();
|
2018-02-01 17:49:42 -08:00
|
|
|
|
2016-06-23 15:59:00 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2020-07-31 07:15:04 -07:00
|
|
|
mtx.joinSplitSig.bytes[0] += 1;
|
2016-06-23 15:59:00 -07:00
|
|
|
CTransaction tx(mtx);
|
2022-01-22 18:37:10 -08:00
|
|
|
|
|
|
|
// 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);
|
2020-02-06 06:30:37 -08:00
|
|
|
// during initial block download, for transactions being accepted into the
|
|
|
|
// mempool (and thus not mined), DoS ban score should be zero, else 10
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
2022-07-04 10:33:07 -07:00
|
|
|
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return true; });
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
2022-07-04 10:33:07 -07:00
|
|
|
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return false; });
|
2020-02-06 06:30:37 -08:00
|
|
|
// for transactions that have been mined in a block, DoS ban score should
|
|
|
|
// always be 100.
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
2022-07-04 10:33:07 -07:00
|
|
|
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return true; });
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
2022-07-04 10:33:07 -07:00
|
|
|
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return false; });
|
2016-06-23 15:59:00 -07:00
|
|
|
}
|
2016-06-23 17:22:20 -07:00
|
|
|
|
2022-01-22 12:54:37 -08:00
|
|
|
TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
|
2020-02-21 09:07:57 -08:00
|
|
|
SelectParams(CBaseChainParams::REGTEST);
|
2022-01-22 12:54:37 -08:00
|
|
|
auto consensus = Params().GetConsensus();
|
2022-07-04 10:33:07 -07:00
|
|
|
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = std::nullopt;
|
2022-01-22 12:54:37 -08:00
|
|
|
auto orchardAuth = orchard::AuthValidator::Disabled();
|
2020-02-21 09:07:57 -08:00
|
|
|
|
|
|
|
auto saplingBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId;
|
|
|
|
auto blossomBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_BLOSSOM].nBranchId;
|
2022-01-22 12:54:37 -08:00
|
|
|
auto heartwoodBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_HEARTWOOD].nBranchId;
|
2020-02-21 09:07:57 -08:00
|
|
|
|
|
|
|
// Create a valid transaction for the Sapling epoch.
|
|
|
|
CMutableTransaction mtx = GetValidTransaction(saplingBranchId);
|
|
|
|
CTransaction tx(mtx);
|
2022-01-22 18:37:10 -08:00
|
|
|
|
|
|
|
// Recreate the fake coins being spent.
|
|
|
|
std::vector<CTxOut> allPrevOutputs;
|
|
|
|
allPrevOutputs.resize(tx.vin.size());
|
|
|
|
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
|
2020-02-21 09:07:57 -08: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);
|
2020-02-21 09:07:57 -08:00
|
|
|
// Ensure that the transaction validates against Sapling.
|
2022-01-22 12:54:37 -08:00
|
|
|
EXPECT_TRUE(ContextualCheckShieldedInputs(
|
2022-07-04 10:33:07 -07:00
|
|
|
tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, false,
|
2020-07-10 10:34:53 -07:00
|
|
|
[](const Consensus::Params&) { return false; }));
|
2020-02-21 09:07:57 -08:00
|
|
|
|
|
|
|
// 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)),
|
2015-08-06 00:47:01 -07:00
|
|
|
false, "")).Times(1);
|
2022-01-22 12:54:37 -08:00
|
|
|
EXPECT_FALSE(ContextualCheckShieldedInputs(
|
2022-07-04 10:33:07 -07:00
|
|
|
tx, txdata, state, view, saplingAuth, orchardAuth, consensus, blossomBranchId, false, false,
|
2020-07-10 10:34:53 -07:00
|
|
|
[](const Consensus::Params&) { return false; }));
|
2020-02-21 09:07:57 -08:00
|
|
|
|
|
|
|
// 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,
|
2015-08-06 00:47:01 -07:00
|
|
|
"bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
2022-01-22 12:54:37 -08:00
|
|
|
EXPECT_FALSE(ContextualCheckShieldedInputs(
|
2022-07-04 10:33:07 -07:00
|
|
|
tx, txdata, state, view, saplingAuth, orchardAuth, consensus, heartwoodBranchId, false, false,
|
2020-07-10 10:34:53 -07:00
|
|
|
[](const Consensus::Params&) { return false; }));
|
2020-02-21 09:07:57 -08:00
|
|
|
}
|
|
|
|
|
2022-01-22 12:54:37 -08:00
|
|
|
TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) {
|
2018-02-01 17:49:42 -08:00
|
|
|
SelectParams(CBaseChainParams::REGTEST);
|
2022-01-22 12:54:37 -08:00
|
|
|
auto consensus = Params().GetConsensus();
|
2022-07-04 10:33:07 -07:00
|
|
|
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = std::nullopt;
|
2022-01-22 12:54:37 -08:00
|
|
|
auto orchardAuth = orchard::AuthValidator::Disabled();
|
2018-02-01 17:49:42 -08:00
|
|
|
|
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);
|
|
|
|
|
2022-01-22 12:54:37 -08:00
|
|
|
auto saplingBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId;
|
|
|
|
CMutableTransaction mtx = GetValidTransaction(saplingBranchId);
|
2016-06-23 17:22:20 -07:00
|
|
|
|
2022-01-22 18:37:10 -08:00
|
|
|
// Recreate the fake coins being spent.
|
|
|
|
std::vector<CTxOut> allPrevOutputs;
|
|
|
|
allPrevOutputs.resize(mtx.vin.size());
|
|
|
|
|
2016-08-18 15:38:20 -07:00
|
|
|
// Check that the signature is valid before we add L
|
|
|
|
{
|
|
|
|
CTransaction tx(mtx);
|
2022-01-22 18:37:10 -08:00
|
|
|
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
|
2016-08-18 15:38:20 -07:00
|
|
|
MockCValidationState state;
|
2022-07-04 10:33:07 -07:00
|
|
|
EXPECT_TRUE(ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true));
|
2016-08-18 15:38:20 -07:00
|
|
|
}
|
|
|
|
|
2016-06-23 17:22:20 -07:00
|
|
|
// 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++) {
|
2020-07-31 07:15:04 -07:00
|
|
|
s = mtx.joinSplitSig.bytes[32 + i] + L[i] + (s >> 8);
|
|
|
|
mtx.joinSplitSig.bytes[32 + i] = s & 0xff;
|
2016-06-23 17:22:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
2022-01-22 18:37:10 -08:00
|
|
|
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
|
2016-06-23 17:22:20 -07:00
|
|
|
|
|
|
|
MockCValidationState state;
|
2020-02-06 06:30:37 -08:00
|
|
|
// during initial block download, for transactions being accepted into the
|
|
|
|
// mempool (and thus not mined), DoS ban score should be zero, else 10
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
2022-07-04 10:33:07 -07:00
|
|
|
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return true; });
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
2022-07-04 10:33:07 -07:00
|
|
|
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return false; });
|
2020-02-06 06:30:37 -08:00
|
|
|
// for transactions that have been mined in a block, DoS ban score should
|
|
|
|
// always be 100.
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
2022-07-04 10:33:07 -07:00
|
|
|
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return true; });
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
2022-07-04 10:33:07 -07:00
|
|
|
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return false; });
|
2016-06-23 17:22:20 -07:00
|
|
|
}
|
2018-02-15 22:19:36 -08:00
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, OverwinterConstructors) {
|
2018-02-15 22:19:36 -08:00
|
|
|
CMutableTransaction mtx;
|
|
|
|
mtx.fOverwintered = true;
|
2018-04-23 08:54:18 -07:00
|
|
|
mtx.nVersion = OVERWINTER_TX_VERSION;
|
2018-02-15 22:19:36 -08:00
|
|
|
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) {
|
2018-02-15 22:19:36 -08:00
|
|
|
CMutableTransaction mtx;
|
|
|
|
mtx.fOverwintered = true;
|
2018-04-23 08:54:18 -07:00
|
|
|
mtx.nVersion = OVERWINTER_TX_VERSION;
|
2018-02-15 22:19:36 -08:00
|
|
|
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) {
|
2018-02-15 22:19:36 -08:00
|
|
|
// 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) {
|
2018-02-15 22:19:36 -08:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit.resize(0);
|
2018-02-15 22:19:36 -08:00
|
|
|
mtx.fOverwintered = true;
|
2018-04-23 08:54:18 -07:00
|
|
|
mtx.nVersion = OVERWINTER_TX_VERSION;
|
2018-02-15 22:19:36 -08:00
|
|
|
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) {
|
2021-07-01 05:16:31 -07:00
|
|
|
const auto& params = RegtestActivateOverwinter();
|
|
|
|
CMutableTransaction mtx = GetValidTransaction(0x5ba81b19);
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit.resize(0);
|
2018-02-15 22:19:36 -08:00
|
|
|
mtx.nExpiryHeight = 0;
|
|
|
|
|
|
|
|
{
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
MockCValidationState state;
|
|
|
|
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
|
2021-07-01 05:16:31 -07:00
|
|
|
EXPECT_TRUE(ContextualCheckTransaction(tx, state, params, 1, true));
|
2018-02-15 22:19:36 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
mtx.nExpiryHeight = TX_EXPIRY_HEIGHT_THRESHOLD - 1;
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
MockCValidationState state;
|
|
|
|
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
|
2021-07-01 05:16:31 -07:00
|
|
|
EXPECT_TRUE(ContextualCheckTransaction(tx, state, params, 1, true));
|
2018-02-15 22:19:36 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
mtx.nExpiryHeight = TX_EXPIRY_HEIGHT_THRESHOLD;
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
MockCValidationState state;
|
2021-07-01 05:16:31 -07:00
|
|
|
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-expiry-height-too-high", false, "")).Times(1);
|
2021-07-01 05:16:31 -07:00
|
|
|
ContextualCheckTransaction(tx, state, params, 1, true);
|
2018-02-15 22:19:36 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
mtx.nExpiryHeight = std::numeric_limits<uint32_t>::max();
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
MockCValidationState state;
|
2021-07-01 05:16:31 -07:00
|
|
|
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-expiry-height-too-high", false, "")).Times(1);
|
2021-07-01 05:16:31 -07:00
|
|
|
ContextualCheckTransaction(tx, state, params, 1, true);
|
2018-02-15 22:19:36 -08:00
|
|
|
}
|
2021-07-01 05:16:31 -07:00
|
|
|
|
|
|
|
RegtestDeactivateSapling();
|
2018-02-15 22:19:36 -08:00
|
|
|
}
|
|
|
|
|
2019-07-31 13:34:48 -07:00
|
|
|
TEST(checktransaction_tests, BlossomExpiryHeight) {
|
2020-07-10 10:34:53 -07:00
|
|
|
const Consensus::Params& params = RegtestActivateBlossom(false, 100).GetConsensus();
|
2022-05-19 08:30:17 -07:00
|
|
|
CMutableTransaction preBlossomMtx = CreateNewContextualCMutableTransaction(params, 99, false);
|
2019-08-05 10:50:05 -07:00
|
|
|
EXPECT_EQ(preBlossomMtx.nExpiryHeight, 100 - 1);
|
2022-05-19 08:30:17 -07:00
|
|
|
CMutableTransaction blossomMtx = CreateNewContextualCMutableTransaction(params, 100, false);
|
2019-07-31 13:34:48 -07:00
|
|
|
EXPECT_EQ(blossomMtx.nExpiryHeight, 100 + 40);
|
|
|
|
RegtestDeactivateBlossom();
|
|
|
|
}
|
2018-02-15 22:19:36 -08:00
|
|
|
|
|
|
|
// 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) {
|
2018-02-15 22:19:36 -08:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit.resize(0);
|
2018-02-15 22:19:36 -08:00
|
|
|
mtx.fOverwintered = false;
|
|
|
|
mtx.nVersion = -1;
|
|
|
|
|
2021-06-12 16:28:25 -07:00
|
|
|
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
|
|
|
UNSAFE_CTransaction tx(mtx);
|
2018-02-15 22:19:36 -08:00
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-version-too-low", false, "")).Times(1);
|
2018-02-15 22:19:36 -08:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, SaplingSproutInputSumsTooLarge) {
|
2018-07-30 21:35:31 -07:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit.resize(0);
|
2018-07-30 21:35:31 -07:00
|
|
|
mtx.fOverwintered = true;
|
|
|
|
mtx.nVersion = SAPLING_TX_VERSION;
|
|
|
|
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
|
|
|
|
mtx.nExpiryHeight = 0;
|
|
|
|
|
|
|
|
{
|
|
|
|
// create JSDescription
|
|
|
|
uint256 rt;
|
2020-07-31 07:15:04 -07:00
|
|
|
Ed25519VerificationKey joinSplitPubKey;
|
2018-07-30 21:35:31 -07:00
|
|
|
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;
|
|
|
|
|
2020-07-09 00:40:06 -07:00
|
|
|
auto jsdesc = JSDescriptionInfo(
|
2020-07-07 17:44:03 -07:00
|
|
|
joinSplitPubKey, rt,
|
2018-07-30 21:35:31 -07:00
|
|
|
inputs, outputs,
|
2020-07-09 00:40:06 -07:00
|
|
|
0, 0
|
|
|
|
).BuildRandomized(
|
2018-07-30 21:35:31 -07:00
|
|
|
inputMap, outputMap,
|
2020-07-09 00:40:06 -07:00
|
|
|
false);
|
2018-07-30 21:35:31 -07:00
|
|
|
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit.push_back(jsdesc);
|
2018-07-30 21:35:31 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
mtx.vShieldedSpend.push_back(SpendDescription());
|
|
|
|
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit[0].vpub_new = (MAX_MONEY / 2) + 10;
|
2018-07-30 21:35:31 -07:00
|
|
|
|
|
|
|
{
|
|
|
|
UNSAFE_CTransaction tx(mtx);
|
|
|
|
CValidationState state;
|
|
|
|
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
|
|
|
|
}
|
|
|
|
|
2021-06-29 13:40:26 -07:00
|
|
|
mtx.valueBalanceSapling = (MAX_MONEY / 2) + 10;
|
2018-07-30 21:35:31 -07:00
|
|
|
|
|
|
|
{
|
|
|
|
UNSAFE_CTransaction tx(mtx);
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-txintotal-toolarge", false, "")).Times(1);
|
2018-07-30 21:35:31 -07:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
|
|
|
}
|
|
|
|
}
|
2018-02-15 22:19:36 -08:00
|
|
|
|
|
|
|
// Test bad Overwinter version number in CheckTransactionWithoutProofVerification
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, OverwinterVersionNumberLow) {
|
2018-02-15 22:19:36 -08:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit.resize(0);
|
2018-02-15 22:19:36 -08:00
|
|
|
mtx.fOverwintered = true;
|
|
|
|
mtx.nVersion = OVERWINTER_MIN_TX_VERSION - 1;
|
|
|
|
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
|
|
|
|
mtx.nExpiryHeight = 0;
|
|
|
|
|
2021-06-12 16:28:25 -07:00
|
|
|
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
2018-02-15 22:19:36 -08:00
|
|
|
UNSAFE_CTransaction tx(mtx);
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-overwinter-version-too-low", false, "")).Times(1);
|
2018-02-15 22:19:36 -08:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test bad Overwinter version number in ContextualCheckTransaction
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, OverwinterVersionNumberHigh) {
|
2018-02-15 22:19:36 -08:00
|
|
|
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);
|
2018-02-15 22:19:36 -08:00
|
|
|
mtx.fOverwintered = true;
|
|
|
|
mtx.nVersion = OVERWINTER_MAX_TX_VERSION + 1;
|
|
|
|
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
|
|
|
|
mtx.nExpiryHeight = 0;
|
|
|
|
|
2021-06-12 16:28:25 -07:00
|
|
|
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
2018-02-15 22:19:36 -08:00
|
|
|
UNSAFE_CTransaction tx(mtx);
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-overwinter-version-too-high", false, "")).Times(1);
|
2020-02-06 06:30:37 -08:00
|
|
|
ContextualCheckTransaction(tx, state, Params(), 1, true);
|
2018-02-15 22:19:36 -08:00
|
|
|
|
|
|
|
// 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) {
|
2018-02-15 22:19:36 -08:00
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit.resize(0);
|
2018-02-15 22:19:36 -08:00
|
|
|
mtx.fOverwintered = true;
|
2018-04-23 08:54:18 -07:00
|
|
|
mtx.nVersion = OVERWINTER_TX_VERSION;
|
2018-02-15 22:19:36 -08:00
|
|
|
mtx.nExpiryHeight = 0;
|
|
|
|
mtx.nVersionGroupId = 0x12345678;
|
|
|
|
|
2021-06-12 16:28:25 -07:00
|
|
|
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
2018-02-15 22:19:36 -08:00
|
|
|
UNSAFE_CTransaction tx(mtx);
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-version-group-id", false, "")).Times(1);
|
2018-02-15 22:19:36 -08:00
|
|
|
CheckTransactionWithoutProofVerification(tx, state);
|
|
|
|
}
|
|
|
|
|
|
|
|
// This tests an Overwinter transaction checked against Sprout
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, OverwinterNotActive) {
|
2018-02-15 22:19:36 -08:00
|
|
|
SelectParams(CBaseChainParams::TESTNET);
|
2019-03-14 04:11:10 -07:00
|
|
|
auto chainparams = Params();
|
2018-02-15 22:19:36 -08:00
|
|
|
|
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
|
|
|
mtx.fOverwintered = true;
|
2018-04-23 08:54:18 -07:00
|
|
|
mtx.nVersion = OVERWINTER_TX_VERSION;
|
2018-02-15 22:19:36 -08:00
|
|
|
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
|
|
|
|
mtx.nExpiryHeight = 0;
|
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
MockCValidationState state;
|
2020-02-06 06:30:37 -08:00
|
|
|
// during initial block download, for transactions being accepted into the
|
|
|
|
// mempool (and thus not mined), DoS ban score should be zero, else 10
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "tx-overwinter-not-active", false, "")).Times(1);
|
2020-07-10 10:34:53 -07:00
|
|
|
ContextualCheckTransaction(tx, state, chainparams, 0, false, [](const Consensus::Params&) { return true; });
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "tx-overwinter-not-active", false, "")).Times(1);
|
2020-07-10 10:34:53 -07:00
|
|
|
ContextualCheckTransaction(tx, state, chainparams, 0, false, [](const Consensus::Params&) { return false; });
|
2020-02-06 06:30:37 -08:00
|
|
|
// for transactions that have been mined in a block, DoS ban score should
|
|
|
|
// always be 100.
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwinter-not-active", false, "")).Times(1);
|
2020-07-10 10:34:53 -07:00
|
|
|
ContextualCheckTransaction(tx, state, chainparams, 0, true, [](const Consensus::Params&) { return true; });
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwinter-not-active", false, "")).Times(1);
|
2020-07-10 10:34:53 -07:00
|
|
|
ContextualCheckTransaction(tx, state, chainparams, 0, true, [](const Consensus::Params&) { return false; });
|
2018-02-15 22:19:36 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2018-02-15 22:19:36 -08:00
|
|
|
SelectParams(CBaseChainParams::REGTEST);
|
|
|
|
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
|
|
|
|
|
|
|
CMutableTransaction mtx = GetValidTransaction();
|
|
|
|
mtx.fOverwintered = false;
|
2018-04-23 08:54:18 -07:00
|
|
|
mtx.nVersion = OVERWINTER_TX_VERSION;
|
2018-02-15 22:19:36 -08:00
|
|
|
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
|
|
|
|
mtx.nExpiryHeight = 0;
|
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwintered-flag-not-set", false, "")).Times(1);
|
2020-02-06 06:30:37 -08:00
|
|
|
ContextualCheckTransaction(tx, state, Params(), 1, true);
|
2018-02-15 22:19:36 -08:00
|
|
|
|
|
|
|
// 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) {
|
2018-02-15 22:19:36 -08:00
|
|
|
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)
|
|
|
|
{
|
2022-05-19 08:30:17 -07:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2018-02-15 22:19:36 -08:00
|
|
|
|
|
|
|
// Test CreateNewContextualCMutableTransaction sets default values based on height
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, OverwinteredContextualCreateTx) {
|
2018-02-15 22:19:36 -08:00
|
|
|
SelectParams(CBaseChainParams::REGTEST);
|
2019-08-04 13:35:47 -07:00
|
|
|
const Consensus::Params& params = Params().GetConsensus();
|
|
|
|
int overwinterActivationHeight = 5;
|
2018-05-03 05:48:20 -07:00
|
|
|
int saplingActivationHeight = 30;
|
2019-08-04 13:35:47 -07:00
|
|
|
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, overwinterActivationHeight);
|
2018-05-03 05:48:20 -07:00
|
|
|
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, saplingActivationHeight);
|
2018-02-15 22:19:36 -08:00
|
|
|
|
2019-08-04 13:35:47 -07:00
|
|
|
ContextualCreateTxCheck(params, overwinterActivationHeight - 1,
|
|
|
|
1, false, 0, 0);
|
2018-02-15 22:19:36 -08:00
|
|
|
// Overwinter activates
|
2019-08-04 13:35:47 -07:00
|
|
|
ContextualCreateTxCheck(params, overwinterActivationHeight,
|
2019-08-05 10:50:05 -07:00
|
|
|
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, overwinterActivationHeight + DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA);
|
2018-05-03 05:48:20 -07:00
|
|
|
// Close to Sapling activation
|
2019-08-04 13:35:47 -07:00
|
|
|
ContextualCreateTxCheck(params, saplingActivationHeight - DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA - 2,
|
2019-08-05 10:50:05 -07:00
|
|
|
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,
|
2019-08-05 10:50:05 -07:00
|
|
|
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,
|
2019-08-05 10:50:05 -07:00
|
|
|
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,
|
2019-08-05 10:50:05 -07:00
|
|
|
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,
|
2019-08-05 10:50:05 -07:00
|
|
|
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,
|
2019-08-05 10:50:05 -07:00
|
|
|
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
|
2018-05-03 05:48:20 -07:00
|
|
|
// Just before Sapling activation
|
2019-08-04 13:35:47 -07:00
|
|
|
ContextualCreateTxCheck(params, saplingActivationHeight - 4,
|
2019-08-05 10:50:05 -07:00
|
|
|
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
|
2019-08-04 13:35:47 -07:00
|
|
|
ContextualCreateTxCheck(params, saplingActivationHeight - 3,
|
2019-08-05 10:50:05 -07:00
|
|
|
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
|
2019-08-04 13:35:47 -07:00
|
|
|
ContextualCreateTxCheck(params, saplingActivationHeight - 2,
|
2019-08-05 10:50:05 -07:00
|
|
|
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
|
2019-08-04 13:35:47 -07:00
|
|
|
ContextualCreateTxCheck(params, saplingActivationHeight - 1,
|
2019-08-05 10:50:05 -07:00
|
|
|
OVERWINTER_TX_VERSION, true, OVERWINTER_VERSION_GROUP_ID, saplingActivationHeight - 1);
|
2018-05-03 05:48:20 -07:00
|
|
|
// 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);
|
2018-02-15 22:19:36 -08:00
|
|
|
|
|
|
|
// Revert to default
|
2019-01-29 20:18:10 -08:00
|
|
|
RegtestDeactivateSapling();
|
2018-02-15 22:19:36 -08:00
|
|
|
}
|
2018-02-16 15:42:04 -08:00
|
|
|
|
|
|
|
// Test a v1 transaction which has a malformed header, perhaps modified in-flight
|
2018-10-09 20:18:52 -07:00
|
|
|
TEST(ChecktransactionTests, BadTxReceivedOverNetwork)
|
2018-02-16 15:42:04 -08:00
|
|
|
{
|
|
|
|
// 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";
|
|
|
|
}
|
|
|
|
}
|
2018-07-26 11:29:04 -07:00
|
|
|
}
|
2019-05-17 10:02:07 -07:00
|
|
|
|
2022-03-24 19:12:20 -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.resize(1);
|
|
|
|
mtx.vJoinSplit.resize(0);
|
|
|
|
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
EXPECT_TRUE(tx.IsCoinBase());
|
|
|
|
|
|
|
|
// Before Heartwood, output descriptions are rejected.
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
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).
|
2015-08-06 00:47:01 -07:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2022-03-24 19:12:20 -07:00
|
|
|
TEST(ChecktransactionTests, HeartwoodAcceptsSaplingShieldedCoinbase) {
|
2022-03-03 22:23:18 -08:00
|
|
|
LoadProofParameters();
|
|
|
|
|
2019-05-17 10:02:07 -07:00
|
|
|
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
|
|
|
auto chainparams = Params();
|
|
|
|
|
|
|
|
uint256 ovk;
|
|
|
|
auto note = libzcash::SaplingNote(
|
2020-07-02 02:45:02 -07:00
|
|
|
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 = librustzcash_sapling_proving_ctx_init();
|
2020-10-20 16:39:23 -07:00
|
|
|
auto odesc = output.Build(ctx).value();
|
2019-05-17 10:02:07 -07:00
|
|
|
librustzcash_sapling_proving_ctx_free(ctx);
|
|
|
|
|
|
|
|
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;
|
2015-08-06 00:47:01 -07:00
|
|
|
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;
|
2015-08-06 00:47:01 -07:00
|
|
|
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;
|
2015-08-06 00:47:01 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-01-22 12:54:37 -08:00
|
|
|
// Test the success case.
|
2019-05-17 10:02:07 -07:00
|
|
|
{
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
EXPECT_TRUE(tx.IsCoinBase());
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2022-01-22 12:54:37 -08:00
|
|
|
EXPECT_TRUE(ContextualCheckTransaction(tx, state, chainparams, 10, 57));
|
2019-05-17 10:02:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
RegtestDeactivateHeartwood();
|
|
|
|
}
|
2020-03-05 18:24:38 -08:00
|
|
|
|
2021-06-29 13:40:26 -07:00
|
|
|
// Check that the consensus rules relevant to valueBalanceSapling, vShieldedOutput, and
|
2020-03-05 18:24:38 -08:00
|
|
|
// bindingSig from https://zips.z.cash/protocol/protocol.pdf#txnencoding are
|
|
|
|
// applied to coinbase transactions.
|
2020-05-11 19:45:51 -07:00
|
|
|
TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
|
2022-03-03 22:23:18 -08:00
|
|
|
LoadProofParameters();
|
|
|
|
|
2020-03-05 18:24:38 -08:00
|
|
|
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
|
|
|
auto chainparams = Params();
|
|
|
|
|
|
|
|
uint256 ovk;
|
|
|
|
auto note = libzcash::SaplingNote(
|
2020-07-02 02:45:02 -07:00
|
|
|
libzcash::SaplingSpendingKey::random().default_address(), CAmount(123456), libzcash::Zip212Enabled::BeforeZip212);
|
2020-03-05 18:24:38 -08:00
|
|
|
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);
|
2021-06-29 13:40:26 -07:00
|
|
|
mtx.valueBalanceSapling = -1000;
|
2020-03-05 18:24:38 -08:00
|
|
|
|
|
|
|
// Coinbase transaction should fail non-contextual checks with no shielded
|
2021-06-29 13:40:26 -07:00
|
|
|
// outputs and non-zero valueBalanceSapling.
|
2020-03-05 18:24:38 -08:00
|
|
|
{
|
|
|
|
CTransaction tx(mtx);
|
|
|
|
EXPECT_TRUE(tx.IsCoinBase());
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-nonzero", false, "")).Times(1);
|
2020-03-05 18:24:38 -08:00
|
|
|
EXPECT_FALSE(CheckTransactionWithoutProofVerification(tx, state));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a Sapling output.
|
|
|
|
auto ctx = librustzcash_sapling_proving_ctx_init();
|
2020-10-20 16:39:23 -07:00
|
|
|
auto odesc = output.Build(ctx).value();
|
2020-03-05 18:24:38 -08:00
|
|
|
librustzcash_sapling_proving_ctx_free(ctx);
|
|
|
|
mtx.vShieldedOutput.push_back(odesc);
|
|
|
|
|
2021-06-29 13:40:26 -07:00
|
|
|
// Coinbase transaction should fail non-contextual checks with valueBalanceSapling
|
2020-03-05 18:24:38 -08:00
|
|
|
// out of range.
|
|
|
|
{
|
2021-06-29 13:40:26 -07:00
|
|
|
mtx.valueBalanceSapling = MAX_MONEY + 1;
|
2021-06-12 16:28:25 -07:00
|
|
|
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
|
|
|
UNSAFE_CTransaction tx(mtx);
|
2020-03-05 18:24:38 -08:00
|
|
|
EXPECT_TRUE(tx.IsCoinBase());
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false, "")).Times(1);
|
2020-03-05 18:24:38 -08:00
|
|
|
EXPECT_FALSE(CheckTransactionWithoutProofVerification(tx, state));
|
|
|
|
}
|
|
|
|
{
|
2021-06-29 13:40:26 -07:00
|
|
|
mtx.valueBalanceSapling = -MAX_MONEY - 1;
|
2021-06-12 16:28:25 -07:00
|
|
|
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
|
|
|
UNSAFE_CTransaction tx(mtx);
|
2020-03-05 18:24:38 -08:00
|
|
|
EXPECT_TRUE(tx.IsCoinBase());
|
|
|
|
|
|
|
|
MockCValidationState state;
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false, "")).Times(1);
|
2020-03-05 18:24:38 -08:00
|
|
|
EXPECT_FALSE(CheckTransactionWithoutProofVerification(tx, state));
|
|
|
|
}
|
|
|
|
|
2021-06-29 13:40:26 -07:00
|
|
|
mtx.valueBalanceSapling = -1000;
|
2020-03-05 18:24:38 -08:00
|
|
|
CTransaction tx(mtx);
|
|
|
|
EXPECT_TRUE(tx.IsCoinBase());
|
|
|
|
|
|
|
|
// Coinbase transaction should now pass non-contextual checks.
|
|
|
|
MockCValidationState state;
|
|
|
|
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
|
|
|
|
|
2022-01-22 12:54:37 -08:00
|
|
|
// Coinbase transaction should pass contextual checks.
|
|
|
|
EXPECT_TRUE(ContextualCheckTransaction(tx, state, chainparams, 10, 57));
|
|
|
|
|
2022-07-04 10:33:07 -07:00
|
|
|
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = sapling::init_batch_validator();
|
2022-01-22 12:54:37 -08:00
|
|
|
auto orchardAuth = orchard::AuthValidator::Disabled();
|
|
|
|
auto heartwoodBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_HEARTWOOD].nBranchId;
|
|
|
|
|
|
|
|
// Coinbase transaction does not pass shielded input checks, as bindingSig
|
2022-07-04 10:33:07 -07:00
|
|
|
// 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.
|
2022-01-22 18:37:10 -08:00
|
|
|
// - 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);
|
2022-07-04 10:33:07 -07:00
|
|
|
EXPECT_TRUE(ContextualCheckShieldedInputs(
|
|
|
|
tx, txdata, state, view, saplingAuth, orchardAuth, chainparams.GetConsensus(), heartwoodBranchId, false, true));
|
|
|
|
EXPECT_FALSE(saplingAuth.value()->validate());
|
2020-03-05 18:24:38 -08:00
|
|
|
|
|
|
|
RegtestDeactivateHeartwood();
|
|
|
|
}
|
2020-05-11 19:45:51 -07:00
|
|
|
|
|
|
|
|
|
|
|
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));
|
2015-08-06 00:47:01 -07:00
|
|
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vpub_old-nonzero", false, "")).Times(1);
|
2020-05-11 19:45:51 -07:00
|
|
|
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();
|
|
|
|
|
|
|
|
}
|
2022-03-24 19:12:20 -07:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|