Skip to content

Commit

Permalink
ZIP-211: Validate Disabling Addition of New Value to the Sprout Value…
Browse files Browse the repository at this point in the history
… Pool (#2399)

* add disabled sprout pool check

* change method name

* change error name

* fix typo

* make the success test case in other tx than the coinbase

* use new `height` method instead of deriving `PartialOrd` in `NetworkUpgrade`

* move check of network upgrade into function, rename, docs

* increase test coverage

* fix comment
  • Loading branch information
oxarbitrage authored Jul 1, 2021
1 parent 515dc4b commit e4ab01d
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 2 deletions.
50 changes: 49 additions & 1 deletion zebra-chain/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub use sapling::FieldNotPresent;
pub use sighash::HashType;

use crate::{
block, orchard,
amount, block, orchard,
parameters::NetworkUpgrade,
primitives::{Bctv14Proof, Groth16Proof},
sapling, sprout, transparent,
Expand Down Expand Up @@ -294,6 +294,54 @@ impl Transaction {
}
}

/// Returns the `vpub_old` fields from `JoinSplit`s in this transaction, regardless of version.
///
/// This value is removed from the transparent value pool of this transaction, and added to the
/// sprout value pool.
pub fn sprout_pool_added_values(
&self,
) -> Box<dyn Iterator<Item = &amount::Amount<amount::NonNegative>> + '_> {
match self {
// JoinSplits with Bctv14 Proofs
Transaction::V2 {
joinsplit_data: Some(joinsplit_data),
..
}
| Transaction::V3 {
joinsplit_data: Some(joinsplit_data),
..
} => Box::new(
joinsplit_data
.joinsplits()
.map(|joinsplit| &joinsplit.vpub_old),
),
// JoinSplits with Groth Proofs
Transaction::V4 {
joinsplit_data: Some(joinsplit_data),
..
} => Box::new(
joinsplit_data
.joinsplits()
.map(|joinsplit| &joinsplit.vpub_old),
),
// No JoinSplits
Transaction::V1 { .. }
| Transaction::V2 {
joinsplit_data: None,
..
}
| Transaction::V3 {
joinsplit_data: None,
..
}
| Transaction::V4 {
joinsplit_data: None,
..
}
| Transaction::V5 { .. } => Box::new(std::iter::empty()),
}
}

/// Access the sprout::Nullifiers in this transaction, regardless of version.
pub fn sprout_nullifiers(&self) -> Box<dyn Iterator<Item = &sprout::Nullifier> + '_> {
// This function returns a boxed iterator because the different
Expand Down
3 changes: 3 additions & 0 deletions zebra-consensus/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ pub enum TransactionError {
// temporary error type until #1186 is fixed
#[error("Downcast from BoxError to redjubjub::Error failed")]
InternalDowncastError(String),

#[error("adding to the sprout pool is disabled after Canopy")]
DisabledAddToSproutPool,
}

impl From<BoxError> for TransactionError {
Expand Down
4 changes: 4 additions & 0 deletions zebra-consensus/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ where
check::coinbase_tx_no_prevout_joinsplit_spend(&tx)?;
}

// [Canopy onward]: `vpub_old` MUST be zero.
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
check::disabled_add_to_sprout_pool(&tx, req.height(), network)?;

// "The consensus rules applied to valueBalance, vShieldedOutput, and bindingSig
// in non-coinbase transactions MUST also be applied to coinbase transactions."
//
Expand Down
35 changes: 35 additions & 0 deletions zebra-consensus/src/transaction/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
//! Code in this file can freely assume that no pre-V4 transactions are present.
use zebra_chain::{
amount::{Amount, NonNegative},
block::Height,
orchard::Flags,
parameters::{Network, NetworkUpgrade},
sapling::{Output, PerSpendAnchor, Spend},
transaction::Transaction,
};

use crate::error::TransactionError;

use std::convert::TryFrom;

/// Checks that the transaction has inputs and outputs.
///
/// For `Transaction::V4`:
Expand Down Expand Up @@ -119,3 +124,33 @@ pub fn output_cv_epk_not_small_order(output: &Output) -> Result<(), TransactionE
Ok(())
}
}

/// Check if a transaction is adding to the sprout pool after Canopy
/// network upgrade given a block height and a network.
///
/// https://zips.z.cash/zip-0211
/// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
pub fn disabled_add_to_sprout_pool(
tx: &Transaction,
height: Height,
network: Network,
) -> Result<(), TransactionError> {
let canopy_activation_height = NetworkUpgrade::Canopy
.activation_height(network)
.expect("Canopy activation height must be present for both networks");

// [Canopy onward]: `vpub_old` MUST be zero.
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
if height >= canopy_activation_height {
let zero = Amount::<NonNegative>::try_from(0).expect("an amount of 0 is always valid");

let tx_sprout_pool = tx.sprout_pool_added_values();
for vpub_old in tx_sprout_pool {
if *vpub_old != zero {
return Err(TransactionError::DisabledAddToSproutPool);
}
}
}

Ok(())
}
60 changes: 59 additions & 1 deletion zebra-consensus/src/transaction/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{collections::HashMap, convert::TryFrom, convert::TryInto, sync::Arc};
use tower::{service_fn, ServiceExt};

use zebra_chain::{
amount::Amount,
amount::{Amount, NonNegative},
block, orchard,
parameters::{Network, NetworkUpgrade},
primitives::{ed25519, x25519, Groth16Proof},
Expand Down Expand Up @@ -929,3 +929,61 @@ fn mock_sprout_join_split_data() -> (JoinSplitData<Groth16Proof>, ed25519::Signi

(joinsplit_data, signing_key)
}

#[test]
fn add_to_sprout_pool_after_nu() {
zebra_test::init();

// get a block that we know it haves a transaction with `vpub_old` field greater than 0.
let block: Arc<_> = zebra_chain::block::Block::zcash_deserialize(
&zebra_test::vectors::BLOCK_MAINNET_419199_BYTES[..],
)
.unwrap()
.into();

// create a block height at canopy activation.
let network = Network::Mainnet;
let block_height = NetworkUpgrade::Canopy.activation_height(network).unwrap();

// create a zero amount.
let zero = Amount::<NonNegative>::try_from(0).expect("an amount of 0 is always valid");

// the coinbase transaction should pass the check.
assert_eq!(
check::disabled_add_to_sprout_pool(&block.transactions[0], block_height, network),
Ok(())
);

// the 2nd transaction has no joinsplits, should pass the check.
assert_eq!(block.transactions[1].joinsplit_count(), 0);
assert_eq!(
check::disabled_add_to_sprout_pool(&block.transactions[1], block_height, network),
Ok(())
);

// the 5th transaction has joinsplits and the `vpub_old` cumulative is greater than 0,
// should fail the check.
assert!(block.transactions[4].joinsplit_count() > 0);
let vpub_old: Amount<NonNegative> = block.transactions[4]
.sprout_pool_added_values()
.fold(zero, |acc, &x| (acc + x).unwrap());
assert!(vpub_old > zero);

assert_eq!(
check::disabled_add_to_sprout_pool(&block.transactions[3], block_height, network),
Err(TransactionError::DisabledAddToSproutPool)
);

// the 8th transaction has joinsplits and the `vpub_old` cumulative is 0,
// should pass the check.
assert!(block.transactions[7].joinsplit_count() > 0);
let vpub_old: Amount<NonNegative> = block.transactions[7]
.sprout_pool_added_values()
.fold(zero, |acc, &x| (acc + x).unwrap());
assert_eq!(vpub_old, zero);

assert_eq!(
check::disabled_add_to_sprout_pool(&block.transactions[7], block_height, network),
Ok(())
);
}

0 comments on commit e4ab01d

Please sign in to comment.