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

Merged
merged 40 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 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
3b25023
Clarify panic conditions in docs
upbqdn Jan 30, 2025
ed9d2d3
remove duplicated verification
oxarbitrage Feb 1, 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
2 changes: 1 addition & 1 deletion book/src/dev/rfcs/0003-inventory-tracking.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ specific inventory request is ready, because until we get the request, we
can't determine which peers might be required to process it.

We could attempt to ensure that the peer set would be ready to process a
specific inventory request would be to pre-emptively "reserve" a peer as soon
specific inventory request would be to preemptively "reserve" a peer as soon
as it advertises an inventory item. But this doesn't actually work to ensure
readiness, because a peer could advertise two inventory items, and only be
able to service one request at a time. It also potentially locks the peer
Expand Down
4 changes: 2 additions & 2 deletions book/src/dev/rfcs/0006-contextual-difficulty.md
Original file line number Diff line number Diff line change
Expand Up @@ -753,10 +753,10 @@ would be a security issue.
# Future possibilities
[future-possibilities]: #future-possibilities

## Re-using the relevant chain API in other contextual checks
## Reusing the relevant chain API in other contextual checks
[relevant-chain-api-reuse]: #relevant-chain-api-reuse

The relevant chain iterator can be re-used to implement other contextual
The relevant chain iterator can be reused to implement other contextual
validation checks.

For example, responding to peer requests for block locators, which means
Expand Down
2 changes: 1 addition & 1 deletion book/src/dev/rfcs/drafts/0005-treestate.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ finished validating everything that can be validated without the context of
their anchor's finalization state.

So for each transaction, for both `Spend` descriptions and `JoinSplit`s, we can
pre-emptively try to do our consensus check by looking up the anchors in our
preemptively try to do our consensus check by looking up the anchors in our
finalized set first. For `Spend`s, we then trigger the remaining validation and
when that finishes we are full done with those. For `JoinSplit`s, the anchor
state check may pass early if it's a previous block Sprout `NoteCommitment` tree
Expand Down
20 changes: 20 additions & 0 deletions zebra-chain/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
//! Errors that can occur inside any `zebra-chain` submodule.

use std::io;
use thiserror::Error;

// TODO: Move all these enums into a common enum at the bottom.

/// Errors related to random bytes generation.
#[derive(Error, Copy, Clone, Debug, PartialEq, Eq)]
pub enum RandError {
Expand Down Expand Up @@ -51,3 +55,19 @@ pub enum AddressError {
#[error("Randomness did not hash into the Jubjub group for producing a new diversifier")]
DiversifierGenerationFailure,
}

/// `zebra-chain`'s errors
#[derive(Error, Debug)]
pub enum Error {
/// Invalid consensus branch ID.
#[error("invalid consensus branch id")]
InvalidConsensusBranchId,

/// Zebra's type could not be converted to its librustzcash equivalent.
#[error("Zebra's type could not be converted to its librustzcash equivalent: ")]
Conversion(#[from] io::Error),

/// The transaction is missing a network upgrade.
#[error("the transaction is missing a network upgrade")]
MissingNetworkUpgrade,
}
2 changes: 2 additions & 0 deletions zebra-chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ pub mod transparent;
pub mod value_balance;
pub mod work;

pub use error::Error;

#[cfg(any(test, feature = "proptest-impl"))]
pub use block::LedgerState;

Expand Down
6 changes: 3 additions & 3 deletions zebra-chain/src/parameters/network/testnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
constants::{magics, SLOW_START_INTERVAL, SLOW_START_SHIFT},
network_upgrade::TESTNET_ACTIVATION_HEIGHTS,
subsidy::{funding_stream_address_period, FUNDING_STREAM_RECEIVER_DENOMINATOR},
Network, NetworkKind, NetworkUpgrade, NETWORK_UPGRADES_IN_ORDER,
Network, NetworkKind, NetworkUpgrade,
},
work::difficulty::{ExpandedDifficulty, U256},
};
Expand Down Expand Up @@ -369,7 +369,7 @@ impl ParametersBuilder {

// Check that the provided network upgrade activation heights are in the same order by height as the default testnet activation heights
let mut activation_heights_iter = activation_heights.iter();
for expected_network_upgrade in NETWORK_UPGRADES_IN_ORDER {
for expected_network_upgrade in NetworkUpgrade::iter() {
if !network_upgrades.contains(&expected_network_upgrade) {
continue;
} else if let Some((&height, &network_upgrade)) = activation_heights_iter.next() {
Expand All @@ -381,7 +381,7 @@ impl ParametersBuilder {

assert!(
network_upgrade == expected_network_upgrade,
"network upgrades must be activated in order, the correct order is {NETWORK_UPGRADES_IN_ORDER:?}"
"network upgrades must be activated in order specified by the protocol"
);
}
}
Expand Down
11 changes: 5 additions & 6 deletions zebra-chain/src/parameters/network/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ use crate::{
self, ConfiguredActivationHeights, ConfiguredFundingStreamRecipient,
ConfiguredFundingStreams, MAX_NETWORK_NAME_LENGTH, RESERVED_NETWORK_NAMES,
},
Network, NetworkUpgrade, MAINNET_ACTIVATION_HEIGHTS, NETWORK_UPGRADES_IN_ORDER,
TESTNET_ACTIVATION_HEIGHTS,
Network, NetworkUpgrade, MAINNET_ACTIVATION_HEIGHTS, TESTNET_ACTIVATION_HEIGHTS,
},
};

Expand Down Expand Up @@ -124,7 +123,7 @@ fn activates_network_upgrades_correctly() {
"activation height for all networks after Genesis and BeforeOverwinter should match NU5 activation height"
);

for nu in NETWORK_UPGRADES_IN_ORDER.into_iter().skip(1) {
for nu in NetworkUpgrade::iter().skip(1) {
let activation_height = nu
.activation_height(&network)
.expect("must return an activation height");
Expand Down Expand Up @@ -286,8 +285,8 @@ fn check_full_activation_list() {
})
.to_network();

// We expect the first 8 network upgrades to be included, up to NU5
let expected_network_upgrades = &NETWORK_UPGRADES_IN_ORDER[..8];
// We expect the first 8 network upgrades to be included, up to and including NU5
let expected_network_upgrades = NetworkUpgrade::iter().take(8);
let full_activation_list_network_upgrades: Vec<_> = network
.full_activation_list()
.into_iter()
Expand All @@ -296,7 +295,7 @@ fn check_full_activation_list() {

for expected_network_upgrade in expected_network_upgrades {
assert!(
full_activation_list_network_upgrades.contains(expected_network_upgrade),
full_activation_list_network_upgrades.contains(&expected_network_upgrade),
"full activation list should contain expected network upgrade"
);
}
Expand Down
51 changes: 32 additions & 19 deletions zebra-chain/src/parameters/network_upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use hex::{FromHex, ToHex};
use proptest_derive::Arbitrary;

/// A list of network upgrades in the order that they must be activated.
pub const NETWORK_UPGRADES_IN_ORDER: [NetworkUpgrade; 9] = [
const NETWORK_UPGRADES_IN_ORDER: [NetworkUpgrade; 9] = [
Genesis,
BeforeOverwinter,
Overwinter,
Expand Down Expand Up @@ -63,6 +63,18 @@ pub enum NetworkUpgrade {
Nu6,
}

impl TryFrom<u32> for NetworkUpgrade {
type Error = crate::Error;

fn try_from(branch_id: u32) -> Result<Self, Self::Error> {
CONSENSUS_BRANCH_IDS
.iter()
.find(|id| id.1 == ConsensusBranchId(branch_id))
.map(|nu| nu.0)
.ok_or(Self::Error::InvalidConsensusBranchId)
}
}

impl fmt::Display for NetworkUpgrade {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Same as the debug representation for now
Expand Down Expand Up @@ -204,6 +216,15 @@ impl fmt::Display for ConsensusBranchId {
}
}

impl TryFrom<ConsensusBranchId> for zcash_primitives::consensus::BranchId {
type Error = crate::Error;

fn try_from(id: ConsensusBranchId) -> Result<Self, Self::Error> {
zcash_primitives::consensus::BranchId::try_from(u32::from(id))
.map_err(|_| Self::Error::InvalidConsensusBranchId)
}
}

/// Network Upgrade Consensus Branch Ids.
///
/// Branch ids are the same for mainnet and testnet. If there is a testnet
Expand Down Expand Up @@ -327,19 +348,14 @@ impl NetworkUpgrade {
.expect("every height has a current network upgrade")
}

/// Returns the next expected network upgrade after this network upgrade
/// Returns the next expected network upgrade after this network upgrade.
pub fn next_upgrade(self) -> Option<Self> {
match self {
Genesis => Some(BeforeOverwinter),
BeforeOverwinter => Some(Overwinter),
Overwinter => Some(Sapling),
Sapling => Some(Blossom),
Blossom => Some(Heartwood),
Heartwood => Some(Canopy),
Canopy => Some(Nu5),
Nu5 => Some(Nu6),
Nu6 => None,
}
Self::iter().skip_while(|&nu| self != nu).nth(1)
}

/// Returns the previous network upgrade before this network upgrade.
pub fn previous_upgrade(self) -> Option<Self> {
Self::iter().rev().skip_while(|&nu| self != nu).nth(1)
}

/// Returns the next network upgrade for `network` and `height`.
Expand Down Expand Up @@ -518,12 +534,9 @@ impl NetworkUpgrade {
NetworkUpgrade::current(network, height).averaging_window_timespan()
}

/// Returns the NetworkUpgrade given an u32 as ConsensusBranchId
pub fn from_branch_id(branch_id: u32) -> Option<NetworkUpgrade> {
CONSENSUS_BRANCH_IDS
.iter()
.find(|id| id.1 == ConsensusBranchId(branch_id))
.map(|nu| nu.0)
/// Returns an iterator over [`NetworkUpgrade`] variants.
pub fn iter() -> impl DoubleEndedIterator<Item = NetworkUpgrade> {
NETWORK_UPGRADES_IN_ORDER.into_iter()
}
}

Expand Down
27 changes: 9 additions & 18 deletions zebra-chain/src/primitives/zcash_note_encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,29 @@
use crate::{
block::Height,
parameters::{Network, NetworkUpgrade},
primitives::zcash_primitives::convert_tx_to_librustzcash,
transaction::Transaction,
};

/// Returns true if all Sapling or Orchard outputs, if any, decrypt successfully with
/// an all-zeroes outgoing viewing key.
///
/// # Panics
///
/// If passed a network/height without matching consensus branch ID (pre-Overwinter),
/// since `librustzcash` won't be able to parse it.
pub fn decrypts_successfully(transaction: &Transaction, network: &Network, height: Height) -> bool {
let network_upgrade = NetworkUpgrade::current(network, height);
let alt_tx = convert_tx_to_librustzcash(
transaction,
network_upgrade
.branch_id()
.expect("should have a branch ID"),
)
.expect("zcash_primitives and Zebra transaction formats must be compatible");
pub fn decrypts_successfully(tx: &Transaction, network: &Network, height: Height) -> bool {
let nu = NetworkUpgrade::current(network, height);

let Ok(tx) = tx.to_librustzcash(nu) else {
return false;
};

let null_sapling_ovk = sapling_crypto::keys::OutgoingViewingKey([0u8; 32]);

// Note that, since this function is used to validate coinbase transactions, we can ignore
// the "grace period" mentioned in ZIP-212.
let zip_212_enforcement = if network_upgrade >= NetworkUpgrade::Canopy {
let zip_212_enforcement = if nu >= NetworkUpgrade::Canopy {
sapling_crypto::note_encryption::Zip212Enforcement::On
} else {
sapling_crypto::note_encryption::Zip212Enforcement::Off
};

if let Some(bundle) = alt_tx.sapling_bundle() {
if let Some(bundle) = tx.sapling_bundle() {
for output in bundle.shielded_outputs().iter() {
let recovery = sapling_crypto::note_encryption::try_sapling_output_recovery(
&null_sapling_ovk,
Expand All @@ -48,7 +39,7 @@ pub fn decrypts_successfully(transaction: &Transaction, network: &Network, heigh
}
}

if let Some(bundle) = alt_tx.orchard_bundle() {
if let Some(bundle) = tx.orchard_bundle() {
for act in bundle.actions() {
if zcash_note_encryption::try_output_recovery_with_ovk(
&orchard::note_encryption::OrchardDomain::for_action(act),
Expand Down
Loading
Loading