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

ZIP 212: validate Sapling and Orchard output of coinbase transactions #3029

Merged
merged 11 commits into from
Nov 11, 2021
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions zebra-chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ hex = "0.4"
incrementalmerkletree = "0.1.0"
jubjub = "0.7.0"
lazy_static = "1.4.0"
orchard = { git = "https://github.com/zcash/orchard.git", rev = "2c8241f25b943aa05203eacf9905db117c69bd29" }
rand_core = "0.6"
ripemd160 = "0.9"
secp256k1 = { version = "0.20.3", features = ["serde"] }
Expand All @@ -45,6 +46,7 @@ uint = "0.9.1"
x25519-dalek = { version = "1.1", features = ["serde"] }
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "53d0a51d33a421cb76d3e3124d1e4c1c9036068e" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "53d0a51d33a421cb76d3e3124d1e4c1c9036068e" }
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "53d0a51d33a421cb76d3e3124d1e4c1c9036068e" }

proptest = { version = "0.10", optional = true }
proptest-derive = { version = "0.3.0", optional = true }
Expand Down
2 changes: 1 addition & 1 deletion zebra-chain/src/orchard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ pub use action::Action;
pub use address::Address;
pub use commitment::{CommitmentRandomness, NoteCommitment, ValueCommitment};
pub use keys::Diversifier;
pub use note::{EncryptedNote, Note, Nullifier};
pub use note::{EncryptedNote, Note, Nullifier, WrappedNoteKey};
pub use shielded_data::{AuthorizedAction, Flags, ShieldedData};
1 change: 1 addition & 0 deletions zebra-chain/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ pub use x25519_dalek as x25519;
pub use proofs::{Bctv14Proof, Groth16Proof, Halo2Proof, ZkSnarkProof};

pub mod zcash_history;
pub mod zcash_note_encryption;
pub(crate) mod zcash_primitives;
69 changes: 69 additions & 0 deletions zebra-chain/src/primitives/zcash_note_encryption.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! Contains code that interfaces with the zcash_note_encryption crate from
//! librustzcash.
//!
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.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
pub fn decrypts_successfully(transaction: &Transaction, network: Network, height: Height) -> bool {
dconnolly marked this conversation as resolved.
Show resolved Hide resolved
let network_upgrade = NetworkUpgrade::current(network, height);
let alt_tx = convert_tx_to_librustzcash(transaction, network_upgrade)
.expect("zcash_primitives and Zebra transaction formats must be compatible");

let alt_height = height.0.into();
let null_sapling_ovk = zcash_primitives::sapling::keys::OutgoingViewingKey([0u8; 32]);

if let Some(bundle) = alt_tx.sapling_bundle() {
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
for output in bundle.shielded_outputs.iter() {
let recovery = match network {
Network::Mainnet => {
zcash_primitives::sapling::note_encryption::try_sapling_output_recovery(
&zcash_primitives::consensus::MAIN_NETWORK,
alt_height,
&null_sapling_ovk,
output,
)
}
Network::Testnet => {
zcash_primitives::sapling::note_encryption::try_sapling_output_recovery(
&zcash_primitives::consensus::TEST_NETWORK,
alt_height,
&null_sapling_ovk,
output,
)
}
};
if recovery.is_none() {
return false;
}
}
}

if let Some(bundle) = alt_tx.orchard_bundle() {
for act in bundle.actions() {
if zcash_note_encryption::try_output_recovery_with_ovk(
&orchard::note_encryption::OrchardDomain::for_action(act),
&orchard::keys::OutgoingViewingKey::from([0u8; 32]),
act,
act.cv_net(),
&act.encrypted_note().out_ciphertext,
)
.is_none()
{
return false;
}
}
}

true
}
2 changes: 1 addition & 1 deletion zebra-chain/src/primitives/zcash_primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl TryFrom<&Transaction> for zcash_primitives::transaction::Transaction {
}
}

fn convert_tx_to_librustzcash(
pub(crate) fn convert_tx_to_librustzcash(
trans: &Transaction,
network_upgrade: NetworkUpgrade,
) -> Result<zcash_primitives::transaction::Transaction, io::Error> {
Expand Down
1 change: 1 addition & 0 deletions zebra-consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ proptest-derive = { version = "0.3.0", optional = true }

[dev-dependencies]
color-eyre = "0.5.11"
halo2 = "=0.1.0-beta.1"
proptest = "0.10"
proptest-derive = "0.3.0"
rand07 = { package = "rand", version = "0.7" }
Expand Down
6 changes: 6 additions & 0 deletions zebra-consensus/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ where
check::time_is_valid_at(&block.header, now, &height, &hash)
.map_err(VerifyBlockError::Time)?;
check::coinbase_is_first(&block)?;
let coinbase_tx = block
.transactions
.get(0)
.expect("must have coinbase transaction");
// Check compatibility with ZIP-212 shielded Sapling and Orchard coinbase output decryption
tx::check::coinbase_outputs_are_decryptable(coinbase_tx, network, height)?;
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
check::subsidy_is_valid(&block, network)?;

let mut async_checks = FuturesUnordered::new();
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 @@ -50,6 +50,9 @@ pub enum TransactionError {
#[error("coinbase transaction MUST NOT have the EnableSpendsOrchard flag set")]
CoinbaseHasEnableSpendsOrchard,

#[error("coinbase transaction Sapling or Orchard outputs MUST be decryptable with an all-zero outgoing viewing key")]
CoinbaseOutputsNotDecryptable,

#[error("coinbase inputs MUST NOT exist in mempool")]
CoinbaseInMempool,

Expand Down
2 changes: 1 addition & 1 deletion zebra-consensus/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use zebra_state as zs;

use crate::{error::TransactionError, primitives, script, BoxError};

mod check;
pub mod check;
#[cfg(test)]
mod tests;

Expand Down
46 changes: 46 additions & 0 deletions zebra-consensus/src/transaction/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use zebra_chain::{
block::Height,
orchard::Flags,
parameters::{Network, NetworkUpgrade},
primitives::zcash_note_encryption,
sapling::{Output, PerSpendAnchor, Spend},
transaction::Transaction,
};
Expand Down Expand Up @@ -208,3 +209,48 @@ where

Ok(())
}

/// Checks compatibility with [ZIP-212] shielded Sapling and Orchard coinbase output decryption
///
/// Pre-Heartwood: returns `Ok`.
/// Heartwood-onward: returns `Ok` if all Sapling or Orchard outputs, if any, decrypt successfully with
/// an all-zeroes outgoing viewing key. Returns `Err` otherwise.
///
/// This is used to validate coinbase transactions:
///
/// > [Heartwood onward] All Sapling and Orchard outputs in coinbase transactions MUST decrypt to a note
/// > plaintext, i.e. the procedure in § 4.19.3 ‘Decryption using a Full Viewing Key ( Sapling and Orchard )’ on p. 67
/// > does not return ⊥, using a sequence of 32 zero bytes as the outgoing viewing key. (This implies that before
/// > Canopy activation, Sapling outputs of a coinbase transaction MUST have note plaintext lead byte equal to
/// > 0x01.)
///
/// > [Canopy onward] Any Sapling or Orchard output of a coinbase transaction decrypted to a note plaintext
/// > according to the preceding rule MUST have note plaintext lead byte equal to 0x02. (This applies even during
/// > the "grace period" specified in [ZIP-212].)
///
/// [3.10]: https://zips.z.cash/protocol/protocol.pdf#coinbasetransactions
/// [ZIP-212]: https://zips.z.cash/zip-0212#consensus-rule-change-for-coinbase-transactions
///
/// TODO: Currently, a 0x01 lead byte is allowed in the "grace period" mentioned since we're
/// using `librustzcash` to implement this and it doesn't currently allow changing that behavior.
/// https://github.com/ZcashFoundation/zebra/issues/3027
pub fn coinbase_outputs_are_decryptable(
transaction: &Transaction,
network: Network,
height: Height,
) -> Result<(), TransactionError> {
// The consensus rule only applies to Heartwood onward.
if height
< NetworkUpgrade::Heartwood
.activation_height(network)
.expect("Heartwood height is known")
{
return Ok(());
}

if !zcash_note_encryption::decrypts_successfully(transaction, network, height) {
return Err(TransactionError::CoinbaseOutputsNotDecryptable);
}

Ok(())
}
Loading