Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(consensus): Verify consensus branch ID in SIGHASH precomputation #9139

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
36b8ece
Add `has_foo` fns to `Transaction`
upbqdn Jan 17, 2025
d40c83a
Add V5 SIGHASH test based on consensus branch ID
upbqdn Jan 18, 2025
f35d8a2
Merge branch 'main' into fix-sighash
upbqdn Jan 18, 2025
a2f3a45
Guard `skip_checks` by test features
upbqdn Jan 18, 2025
9597792
Enable `proptest-impl` for `zebrad` in tests
upbqdn Jan 18, 2025
61f8735
Simplify conditional compilation
upbqdn Jan 18, 2025
e15d98a
Enable `proptest-impl` in scanner's dev deps
upbqdn Jan 18, 2025
cc80674
Fix conditional compilation in `zebra-chain` tests
upbqdn Jan 18, 2025
af6de10
Add error types for `zebra-chain`
upbqdn Jan 22, 2025
bc72acb
`impl TryFrom<u32> for NetworkUpgrade`
upbqdn Jan 22, 2025
1b43329
`impl TryFrom<ConsensusBranchId> for BranchId`
upbqdn Jan 22, 2025
3d831fb
Rm `fn from_branch_id() -> Option<NetworkUpgrade>`
upbqdn Jan 22, 2025
1cfda82
Check consensus branch ID in SIGHASH computation
upbqdn Jan 22, 2025
1a1c547
Simplify tx deserialization
upbqdn Jan 22, 2025
af43be0
Rm `impl TryFrom<&Trans> for zp_tx::Trans`
upbqdn Jan 22, 2025
54d4eed
Update tests
upbqdn Jan 22, 2025
b569974
Update tests
upbqdn Jan 22, 2025
034528e
Add docs for `to_librustzcash`
upbqdn Jan 22, 2025
1ea4fb0
Update docs for `PrecomputedTxData::new`
upbqdn Jan 22, 2025
f8cebad
Document the SIGHASH consensus rules we missed
upbqdn Jan 23, 2025
645d666
Update docs for script validation
upbqdn Jan 23, 2025
0f47ff4
Merge branch 'main' into fix-sighash
upbqdn Jan 23, 2025
05a9b61
Fix script verification tests
upbqdn Jan 24, 2025
1836375
Fix spelling
upbqdn Jan 24, 2025
054e2c6
Impl `NetworkUpgrade::iter()`
upbqdn Jan 27, 2025
9ec61fb
Refactor `Network_upgrade::next_upgrade`
upbqdn Jan 27, 2025
e518fee
Impl `NetworkUpgrade::previous_upgrade`
upbqdn Jan 27, 2025
a338b5f
Impl `Transaction::hash_shielded_data`
upbqdn Jan 27, 2025
9ba7045
Don't make `NETWORK_UPGRADES_IN_ORDER` `pub`
upbqdn Jan 27, 2025
efd2907
Test `Transaction::sighash` with cons branch ids
upbqdn Jan 27, 2025
eb8b4ac
Merge branch 'main' into fix-sighash
upbqdn Jan 27, 2025
62e7f0a
Extend the `consensus_branch_id` test
upbqdn Jan 27, 2025
eff2311
Derive `Debug` for `SigHasher`
upbqdn Jan 27, 2025
659afd2
Remove the beautiful test for tx verifier
upbqdn Jan 27, 2025
d5972d4
Remove the "skip check" functionality
upbqdn Jan 27, 2025
45a7d5a
Revert the compilation adjustments
upbqdn Jan 27, 2025
83fd675
Apply suggestions from code review
upbqdn Jan 28, 2025
2e32fd6
Fix docs
upbqdn Jan 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions zebra-chain/src/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ impl Network {
transaction,
Amount::try_from(1_000_000).expect("invalid value"),
0,
false,
)
.ok()
})
Expand Down
20 changes: 15 additions & 5 deletions zebra-chain/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,16 @@ impl Transaction {
!self.inputs().is_empty()
}

/// Does this transaction have transparent outputs?
pub fn has_transparent_outputs(&self) -> bool {
!self.outputs().is_empty()
}

/// Does this transaction have transparent inputs or outputs?
pub fn has_transparent_inputs_or_outputs(&self) -> bool {
self.has_transparent_inputs() || self.has_transparent_outputs()
}

/// Does this transaction have transparent or shielded inputs?
pub fn has_transparent_or_shielded_inputs(&self) -> bool {
self.has_transparent_inputs() || self.has_shielded_inputs()
Expand All @@ -276,11 +286,6 @@ impl Transaction {
.contains(orchard::Flags::ENABLE_SPENDS))
}

/// Does this transaction have transparent or shielded outputs?
pub fn has_transparent_or_shielded_outputs(&self) -> bool {
!self.outputs().is_empty() || self.has_shielded_outputs()
}

/// Does this transaction have shielded outputs?
///
/// See [`Self::has_transparent_or_shielded_outputs`] for details.
Expand All @@ -294,6 +299,11 @@ impl Transaction {
.contains(orchard::Flags::ENABLE_OUTPUTS))
}

/// Does this transaction have transparent or shielded outputs?
pub fn has_transparent_or_shielded_outputs(&self) -> bool {
self.has_transparent_outputs() || self.has_shielded_outputs()
}

/// Does this transaction has at least one flag when we have at least one orchard action?
pub fn has_enough_orchard_flags(&self) -> bool {
if self.version() < 5 || self.orchard_actions().count() == 0 {
Expand Down
18 changes: 14 additions & 4 deletions zebra-chain/src/transaction/unmined.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,21 +385,31 @@ impl VerifiedUnminedTx {
transaction: UnminedTx,
miner_fee: Amount<NonNegative>,
legacy_sigop_count: u64,
// Allows skipping ZIP 317 checks, only in tests.
#[cfg(feature = "proptest-impl")] skip_checks: bool,
) -> Result<Self, zip317::Error> {
let fee_weight_ratio = zip317::conventional_fee_weight_ratio(&transaction, miner_fee);
let conventional_actions = zip317::conventional_actions(&transaction.transaction);
let unpaid_actions = zip317::unpaid_actions(&transaction, miner_fee);
let tx_size = transaction.size;

zip317::mempool_checks(unpaid_actions, miner_fee, transaction.size)?;

Ok(Self {
let verified_unmined_tx = Self {
transaction,
miner_fee,
legacy_sigop_count,
fee_weight_ratio,
conventional_actions,
unpaid_actions,
})
};

#[cfg(feature = "proptest-impl")]
if skip_checks {
return Ok(verified_unmined_tx);
}

zip317::mempool_checks(unpaid_actions, miner_fee, tx_size)?;

Ok(verified_unmined_tx)
}

/// Returns `true` if the transaction pays at least the [ZIP-317] conventional fee.
Expand Down
2 changes: 2 additions & 0 deletions zebra-consensus/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ where
known_utxos: known_utxos.clone(),
height,
time: block.header.time,
#[cfg(any(test, feature = "proptest-impl"))]
skip_checks: None,
});
async_checks.push(rsp);
}
Expand Down
55 changes: 50 additions & 5 deletions zebra-consensus/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ pub enum Request {
height: block::Height,
/// The time that the block was mined.
time: DateTime<Utc>,
/// Verification checks to skip, only in tests.
#[cfg(any(test, feature = "proptest-impl"))]
skip_checks: Option<HashSet<SkipCheck>>,
},
/// Verify the supplied transaction as part of the mempool.
///
Expand All @@ -172,6 +175,9 @@ pub enum Request {
/// The next block is the first block that could possibly contain a
/// mempool transaction.
height: block::Height,
/// Verification checks to skip, only in tests.
#[cfg(any(test, feature = "proptest-impl"))]
skip_checks: Option<HashSet<SkipCheck>>,
},
}

Expand Down Expand Up @@ -318,6 +324,15 @@ impl Request {
pub fn is_mempool(&self) -> bool {
matches!(self, Request::Mempool { .. })
}

/// Returns the verification checks that should be skipped, only in tests.
#[cfg(any(test, feature = "proptest-impl"))]
pub fn skip_checks(&self) -> Option<HashSet<SkipCheck>> {
match self {
Request::Block { skip_checks, .. } => skip_checks.clone(),
Request::Mempool { skip_checks, .. } => skip_checks.clone(),
}
}
}

impl Response {
Expand Down Expand Up @@ -415,7 +430,12 @@ where
// Do quick checks first
check::has_inputs_and_outputs(&tx)?;
check::has_enough_orchard_flags(&tx)?;
#[cfg(not(test))]
check::consensus_branch_id(&tx, req.height(), &network)?;
#[cfg(test)]
if req.skip_checks().is_none_or(|c| !c.contains(&SkipCheck::ConsensusBranchId)) {
check::consensus_branch_id(&tx, req.height(), &network)?;
}

// Validate the coinbase input consensus rules
if req.is_mempool() && tx.is_coinbase() {
Expand All @@ -432,7 +452,12 @@ where
if tx.is_coinbase() {
check::coinbase_expiry_height(&req.height(), &tx, &network)?;
} else {
#[cfg(not(test))]
check::non_coinbase_expiry_height(&req.height(), &tx)?;
#[cfg(test)]
if req.skip_checks().is_none_or(|c| !c.contains(&SkipCheck::ExpiryHeight)) {
check::non_coinbase_expiry_height(&req.height(), &tx)?;
}
}

// Consensus rule:
Expand Down Expand Up @@ -575,12 +600,20 @@ where
miner_fee,
legacy_sigop_count,
},
Request::Mempool { transaction, .. } => {
Request::Mempool { transaction: ref tx, .. } => {
// #[cfg(test)]
#[cfg(any(test, feature = "proptest-impl"))]
let transaction = VerifiedUnminedTx::new(
transaction,
miner_fee.expect(
"unexpected mempool coinbase transaction: should have already rejected",
),
tx.clone(),
miner_fee.expect("fee should have been checked earlier"),
legacy_sigop_count,
req.skip_checks().is_some_and(|checks| checks.contains(&SkipCheck::Zip317))
)?;

#[cfg(not(any(test, feature = "proptest-impl")))]
let transaction = VerifiedUnminedTx::new(
tx.clone(),
miner_fee.expect("fee should have been checked earlier"),
legacy_sigop_count,
)?;

Expand Down Expand Up @@ -1388,3 +1421,15 @@ where
AsyncChecks(iterator.into_iter().map(FutureExt::boxed).collect())
}
}

/// Checks the tx verifier should skip, only in tests.
#[cfg(any(test, feature = "proptest-impl"))]
#[derive(Eq, Hash, PartialEq, Debug, Clone)]
pub enum SkipCheck {
/// Skip checks related to the transaction consensus branch ID.
ConsensusBranchId,
/// Skip mempool checks defined in ZIP 317.
Zip317,
/// Skip the expiry height check.
ExpiryHeight,
}
Loading
Loading