consensus: Check JoinSplit signatures against the previous network upgrade
We only check failing signatures against the previous epoch to minimise the extra computational load on nodes.
This commit is contained in:
parent
dc99cd74a0
commit
f21de9d0d6
|
@ -535,6 +535,54 @@ TEST(ChecktransactionTests, BadTxnsInvalidJoinsplitSignature) {
|
|||
ContextualCheckTransaction(tx, state, chainparams, 0, true, [](const CChainParams&) { return false; });
|
||||
}
|
||||
|
||||
TEST(ChecktransactionTests, JoinsplitSignatureDetectsOldBranchId) {
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, 1);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, 1);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_BLOSSOM, 10);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_HEARTWOOD, 20);
|
||||
auto chainparams = Params();
|
||||
|
||||
auto saplingBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId;
|
||||
auto blossomBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_BLOSSOM].nBranchId;
|
||||
|
||||
// Create a valid transaction for the Sapling epoch.
|
||||
CMutableTransaction mtx = GetValidTransaction(saplingBranchId);
|
||||
CTransaction tx(mtx);
|
||||
|
||||
MockCValidationState state;
|
||||
// Ensure that the transaction validates against Sapling.
|
||||
EXPECT_TRUE(ContextualCheckTransaction(
|
||||
tx, state, chainparams, 5, false,
|
||||
[](const CChainParams&) { return false; }));
|
||||
|
||||
// Attempt to validate the inputs against Blossom. We should be notified
|
||||
// that an old consensus branch ID was used for an input.
|
||||
EXPECT_CALL(state, DoS(
|
||||
10, false, REJECT_INVALID,
|
||||
strprintf("old-consensus-branch-id (Expected %s, found %s)",
|
||||
HexInt(blossomBranchId),
|
||||
HexInt(saplingBranchId)),
|
||||
false)).Times(1);
|
||||
EXPECT_FALSE(ContextualCheckTransaction(
|
||||
tx, state, chainparams, 15, false,
|
||||
[](const CChainParams&) { return false; }));
|
||||
|
||||
// Attempt to validate the inputs against Heartwood. All we should learn is
|
||||
// that the signature is invalid, because we don't check more than one
|
||||
// network upgrade back.
|
||||
EXPECT_CALL(state, DoS(
|
||||
10, false, REJECT_INVALID,
|
||||
"bad-txns-invalid-joinsplit-signature", false)).Times(1);
|
||||
EXPECT_FALSE(ContextualCheckTransaction(
|
||||
tx, state, chainparams, 25, false,
|
||||
[](const CChainParams&) { return false; }));
|
||||
|
||||
// Revert to default
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_HEARTWOOD, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
|
||||
RegtestDeactivateBlossom();
|
||||
}
|
||||
|
||||
TEST(ChecktransactionTests, NonCanonicalEd25519Signature) {
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
auto chainparams = Params();
|
||||
|
|
20
src/main.cpp
20
src/main.cpp
|
@ -936,17 +936,20 @@ bool ContextualCheckTransaction(
|
|||
}
|
||||
}
|
||||
|
||||
auto consensusBranchId = CurrentEpochBranchId(nHeight, chainparams.GetConsensus());
|
||||
auto prevConsensusBranchId = PrevEpochBranchId(consensusBranchId, chainparams.GetConsensus());
|
||||
uint256 dataToBeSigned;
|
||||
uint256 prevDataToBeSigned;
|
||||
|
||||
if (!tx.vJoinSplit.empty() ||
|
||||
!tx.vShieldedSpend.empty() ||
|
||||
!tx.vShieldedOutput.empty())
|
||||
{
|
||||
auto consensusBranchId = CurrentEpochBranchId(nHeight, chainparams.GetConsensus());
|
||||
// Empty output script.
|
||||
CScript scriptCode;
|
||||
try {
|
||||
dataToBeSigned = SignatureHash(scriptCode, tx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId);
|
||||
prevDataToBeSigned = SignatureHash(scriptCode, tx, NOT_AN_INPUT, SIGHASH_ALL, 0, prevConsensusBranchId);
|
||||
} catch (std::logic_error ex) {
|
||||
// A logic error should never occur because we pass NOT_AN_INPUT and
|
||||
// SIGHASH_ALL to SignatureHash().
|
||||
|
@ -965,6 +968,21 @@ bool ContextualCheckTransaction(
|
|||
dataToBeSigned.begin(), 32,
|
||||
tx.joinSplitPubKey.begin()
|
||||
) != 0) {
|
||||
// Check whether the failure was caused by an outdated consensus
|
||||
// branch ID; if so, inform the node that they need to upgrade. We
|
||||
// only check the previous epoch's branch ID, on the assumption that
|
||||
// users creating transactions will notice their transactions
|
||||
// failing before a second network upgrade occurs.
|
||||
if (crypto_sign_verify_detached(&tx.joinSplitSig[0],
|
||||
prevDataToBeSigned.begin(), 32,
|
||||
tx.joinSplitPubKey.begin()
|
||||
) == 0) {
|
||||
return state.DoS(
|
||||
dosLevelPotentiallyRelaxing, false, REJECT_INVALID, strprintf(
|
||||
"old-consensus-branch-id (Expected %s, found %s)",
|
||||
HexInt(consensusBranchId),
|
||||
HexInt(prevConsensusBranchId)));
|
||||
}
|
||||
return state.DoS(
|
||||
dosLevelPotentiallyRelaxing,
|
||||
error("CheckTransaction(): invalid joinsplit signature"),
|
||||
|
|
Loading…
Reference in New Issue