diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index 72c0369b..ff4251cd 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -53,6 +53,8 @@ pub enum VerifyTransactionError { WrongVersion, /// A transaction MUST move money around. NoTransfer, + /// The balance of money moving around doesn't compute. + BadBalance, /// Could not verify a transparent script Script(#[from] zebra_script::Error), /// Could not verify a Groth16 proof of a JoinSplit/Spend/Output description @@ -180,6 +182,7 @@ where } if let Some(shielded_data) = shielded_data { + check::shielded_balances_match(&shielded_data, *value_balance)?; for spend in shielded_data.spends() { // TODO: check that spend.cv and spend.rk are NOT of small // order. diff --git a/zebra-consensus/src/transaction/check.rs b/zebra-consensus/src/transaction/check.rs index 4756dc95..7f7e1b0a 100644 --- a/zebra-consensus/src/transaction/check.rs +++ b/zebra-consensus/src/transaction/check.rs @@ -1,7 +1,7 @@ -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use zebra_chain::{ - amount::Amount, + amount::{self, Amount, NegativeAllowed}, primitives::{ ed25519, redjubjub::{self, Binding}, @@ -76,3 +76,19 @@ pub fn any_coinbase_inputs_no_transparent_outputs( _ => return Err(VerifyTransactionError::WrongVersion), } } + +/// Check that if there are no Spends or Outputs, that valueBalance is also 0. +/// +/// https://zips.z.cash/protocol/canopy.pdf#consensusfrombitcoin +pub fn shielded_balances_match( + shielded_data: &ShieldedData, + value_balance: Amount, +) -> Result<(), VerifyTransactionError> { + if shielded_data.spends().count() + shielded_data.outputs().count() != 0 { + return Ok(()); + } else if i64::from(value_balance) == 0 { + return Ok(()); + } else { + return Err(VerifyTransactionError::BadBalance); + } +}