ZIP 212: validate Sapling and Orchard output of coinbase transactions (#3029)
* Part of ZIP 212: validate Sapling and Orchard output of coinbase transactions * Add Orchard test vector * Revert accidentally deleted link * Apply suggestions from code review Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com> * Use height from loop * Apply suggestions from code review Co-authored-by: Deirdre Connolly <deirdre@zfnd.org> * Fix formatting Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com> Co-authored-by: Deirdre Connolly <deirdre@zfnd.org>
This commit is contained in:
parent
a7299aa7f7
commit
6570ebeeb8
|
@ -4471,6 +4471,7 @@ dependencies = [
|
||||||
"itertools 0.10.1",
|
"itertools 0.10.1",
|
||||||
"jubjub 0.7.0",
|
"jubjub 0.7.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"orchard",
|
||||||
"proptest",
|
"proptest",
|
||||||
"proptest-derive",
|
"proptest-derive",
|
||||||
"rand 0.8.4",
|
"rand 0.8.4",
|
||||||
|
@ -4489,6 +4490,7 @@ dependencies = [
|
||||||
"uint",
|
"uint",
|
||||||
"x25519-dalek",
|
"x25519-dalek",
|
||||||
"zcash_history",
|
"zcash_history",
|
||||||
|
"zcash_note_encryption",
|
||||||
"zcash_primitives",
|
"zcash_primitives",
|
||||||
"zebra-test",
|
"zebra-test",
|
||||||
]
|
]
|
||||||
|
@ -4509,6 +4511,7 @@ dependencies = [
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"futures 0.3.17",
|
"futures 0.3.17",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"halo2",
|
||||||
"jubjub 0.7.0",
|
"jubjub 0.7.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"metrics",
|
"metrics",
|
||||||
|
|
|
@ -33,6 +33,7 @@ hex = "0.4"
|
||||||
incrementalmerkletree = "0.1.0"
|
incrementalmerkletree = "0.1.0"
|
||||||
jubjub = "0.7.0"
|
jubjub = "0.7.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
orchard = { git = "https://github.com/zcash/orchard.git", rev = "2c8241f25b943aa05203eacf9905db117c69bd29" }
|
||||||
rand_core = "0.6"
|
rand_core = "0.6"
|
||||||
ripemd160 = "0.9"
|
ripemd160 = "0.9"
|
||||||
secp256k1 = { version = "0.20.3", features = ["serde"] }
|
secp256k1 = { version = "0.20.3", features = ["serde"] }
|
||||||
|
@ -45,6 +46,7 @@ uint = "0.9.1"
|
||||||
x25519-dalek = { version = "1.1", features = ["serde"] }
|
x25519-dalek = { version = "1.1", features = ["serde"] }
|
||||||
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "53d0a51d33a421cb76d3e3124d1e4c1c9036068e" }
|
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "53d0a51d33a421cb76d3e3124d1e4c1c9036068e" }
|
||||||
zcash_primitives = { 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 = { version = "0.10", optional = true }
|
||||||
proptest-derive = { version = "0.3.0", optional = true }
|
proptest-derive = { version = "0.3.0", optional = true }
|
||||||
|
|
|
@ -20,5 +20,5 @@ pub use action::Action;
|
||||||
pub use address::Address;
|
pub use address::Address;
|
||||||
pub use commitment::{CommitmentRandomness, NoteCommitment, ValueCommitment};
|
pub use commitment::{CommitmentRandomness, NoteCommitment, ValueCommitment};
|
||||||
pub use keys::Diversifier;
|
pub use keys::Diversifier;
|
||||||
pub use note::{EncryptedNote, Note, Nullifier};
|
pub use note::{EncryptedNote, Note, Nullifier, WrappedNoteKey};
|
||||||
pub use shielded_data::{AuthorizedAction, Flags, ShieldedData};
|
pub use shielded_data::{AuthorizedAction, Flags, ShieldedData};
|
||||||
|
|
|
@ -15,4 +15,5 @@ pub use x25519_dalek as x25519;
|
||||||
pub use proofs::{Bctv14Proof, Groth16Proof, Halo2Proof, ZkSnarkProof};
|
pub use proofs::{Bctv14Proof, Groth16Proof, Halo2Proof, ZkSnarkProof};
|
||||||
|
|
||||||
pub mod zcash_history;
|
pub mod zcash_history;
|
||||||
|
pub mod zcash_note_encryption;
|
||||||
pub(crate) mod zcash_primitives;
|
pub(crate) mod zcash_primitives;
|
||||||
|
|
|
@ -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.
|
||||||
|
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)
|
||||||
|
.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() {
|
||||||
|
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
|
||||||
|
}
|
|
@ -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,
|
trans: &Transaction,
|
||||||
network_upgrade: NetworkUpgrade,
|
network_upgrade: NetworkUpgrade,
|
||||||
) -> Result<zcash_primitives::transaction::Transaction, io::Error> {
|
) -> Result<zcash_primitives::transaction::Transaction, io::Error> {
|
||||||
|
|
|
@ -42,6 +42,7 @@ proptest-derive = { version = "0.3.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
color-eyre = "0.5.11"
|
color-eyre = "0.5.11"
|
||||||
|
halo2 = "=0.1.0-beta.1"
|
||||||
proptest = "0.10"
|
proptest = "0.10"
|
||||||
proptest-derive = "0.3.0"
|
proptest-derive = "0.3.0"
|
||||||
rand07 = { package = "rand", version = "0.7" }
|
rand07 = { package = "rand", version = "0.7" }
|
||||||
|
|
|
@ -169,6 +169,12 @@ where
|
||||||
check::time_is_valid_at(&block.header, now, &height, &hash)
|
check::time_is_valid_at(&block.header, now, &height, &hash)
|
||||||
.map_err(VerifyBlockError::Time)?;
|
.map_err(VerifyBlockError::Time)?;
|
||||||
check::coinbase_is_first(&block)?;
|
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)?;
|
||||||
check::subsidy_is_valid(&block, network)?;
|
check::subsidy_is_valid(&block, network)?;
|
||||||
|
|
||||||
let mut async_checks = FuturesUnordered::new();
|
let mut async_checks = FuturesUnordered::new();
|
||||||
|
|
|
@ -50,6 +50,9 @@ pub enum TransactionError {
|
||||||
#[error("coinbase transaction MUST NOT have the EnableSpendsOrchard flag set")]
|
#[error("coinbase transaction MUST NOT have the EnableSpendsOrchard flag set")]
|
||||||
CoinbaseHasEnableSpendsOrchard,
|
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")]
|
#[error("coinbase inputs MUST NOT exist in mempool")]
|
||||||
CoinbaseInMempool,
|
CoinbaseInMempool,
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ use zebra_state as zs;
|
||||||
|
|
||||||
use crate::{error::TransactionError, primitives, script, BoxError};
|
use crate::{error::TransactionError, primitives, script, BoxError};
|
||||||
|
|
||||||
mod check;
|
pub mod check;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ use zebra_chain::{
|
||||||
block::Height,
|
block::Height,
|
||||||
orchard::Flags,
|
orchard::Flags,
|
||||||
parameters::{Network, NetworkUpgrade},
|
parameters::{Network, NetworkUpgrade},
|
||||||
|
primitives::zcash_note_encryption,
|
||||||
sapling::{Output, PerSpendAnchor, Spend},
|
sapling::{Output, PerSpendAnchor, Spend},
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
|
@ -208,3 +209,48 @@ where
|
||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use std::{collections::HashMap, convert::TryFrom, convert::TryInto, sync::Arc};
|
use std::{collections::HashMap, convert::TryFrom, convert::TryInto, sync::Arc};
|
||||||
|
|
||||||
|
use halo2::{arithmetic::FieldExt, pasta::pallas};
|
||||||
use tower::{service_fn, ServiceExt};
|
use tower::{service_fn, ServiceExt};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount::{Amount, NonNegative},
|
amount::{Amount, NonNegative},
|
||||||
block, orchard,
|
block::{self, Block, Height},
|
||||||
|
orchard::{self, AuthorizedAction, EncryptedNote, WrappedNoteKey},
|
||||||
parameters::{Network, NetworkUpgrade},
|
parameters::{Network, NetworkUpgrade},
|
||||||
primitives::{ed25519, x25519, Groth16Proof},
|
primitives::{ed25519, x25519, Groth16Proof},
|
||||||
sapling,
|
sapling,
|
||||||
serialization::ZcashDeserialize,
|
serialization::{ZcashDeserialize, ZcashDeserializeInto},
|
||||||
sprout,
|
sprout,
|
||||||
transaction::{
|
transaction::{
|
||||||
arbitrary::{
|
arbitrary::{
|
||||||
|
@ -1542,3 +1544,173 @@ fn add_to_sprout_pool_after_nu() {
|
||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn coinbase_outputs_are_decryptable_for_historical_blocks() -> Result<(), Report> {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
coinbase_outputs_are_decryptable_for_historical_blocks_for_network(Network::Mainnet)?;
|
||||||
|
coinbase_outputs_are_decryptable_for_historical_blocks_for_network(Network::Testnet)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn coinbase_outputs_are_decryptable_for_historical_blocks_for_network(
|
||||||
|
network: Network,
|
||||||
|
) -> Result<(), Report> {
|
||||||
|
let block_iter = match network {
|
||||||
|
Network::Mainnet => zebra_test::vectors::MAINNET_BLOCKS.iter(),
|
||||||
|
Network::Testnet => zebra_test::vectors::TESTNET_BLOCKS.iter(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut tested_coinbase_txs = 0;
|
||||||
|
let mut tested_non_coinbase_txs = 0;
|
||||||
|
|
||||||
|
for (height, block) in block_iter {
|
||||||
|
let block = block
|
||||||
|
.zcash_deserialize_into::<Block>()
|
||||||
|
.expect("block is structurally valid");
|
||||||
|
let height = Height(*height);
|
||||||
|
let heartwood_onward = height
|
||||||
|
>= NetworkUpgrade::Heartwood
|
||||||
|
.activation_height(network)
|
||||||
|
.unwrap();
|
||||||
|
let coinbase_tx = block
|
||||||
|
.transactions
|
||||||
|
.get(0)
|
||||||
|
.expect("must have coinbase transaction");
|
||||||
|
|
||||||
|
// Check if the coinbase outputs are decryptable with an all-zero key.
|
||||||
|
if heartwood_onward
|
||||||
|
&& (coinbase_tx.sapling_outputs().count() > 0
|
||||||
|
|| coinbase_tx.orchard_actions().count() > 0)
|
||||||
|
{
|
||||||
|
// We are only truly decrypting something if it's Heartwood-onward
|
||||||
|
// and there are relevant outputs.
|
||||||
|
tested_coinbase_txs += 1;
|
||||||
|
}
|
||||||
|
check::coinbase_outputs_are_decryptable(coinbase_tx, network, height)
|
||||||
|
.expect("coinbase outputs must be decryptable with an all-zero key");
|
||||||
|
|
||||||
|
// For remaining transactions, check if existing outputs are NOT decryptable
|
||||||
|
// with an all-zero key, if applicable.
|
||||||
|
for tx in block.transactions.iter().skip(1) {
|
||||||
|
let has_outputs = tx.sapling_outputs().count() > 0 || tx.orchard_actions().count() > 0;
|
||||||
|
if has_outputs && heartwood_onward {
|
||||||
|
tested_non_coinbase_txs += 1;
|
||||||
|
check::coinbase_outputs_are_decryptable(tx, network, height).expect_err(
|
||||||
|
"decrypting a non-coinbase output with an all-zero key should fail",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
check::coinbase_outputs_are_decryptable(tx, network, height)
|
||||||
|
.expect("a transaction without outputs, or pre-Heartwood, must be considered 'decryptable'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(tested_coinbase_txs > 0, "ensure it was actually tested");
|
||||||
|
assert!(tested_non_coinbase_txs > 0, "ensure it was actually tested");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given an Orchard action as a base, fill fields related to note encryption
|
||||||
|
/// from the given test vector and returned the modified action.
|
||||||
|
fn fill_action_with_note_encryption_test_vector(
|
||||||
|
action: &orchard::Action,
|
||||||
|
v: &zebra_test::vectors::TestVector,
|
||||||
|
) -> orchard::Action {
|
||||||
|
let mut action = action.clone();
|
||||||
|
action.cv = v.cv_net.try_into().expect("test vector must be valid");
|
||||||
|
action.cm_x = pallas::Base::from_bytes(&v.cmx).unwrap();
|
||||||
|
action.nullifier = v.rho.try_into().expect("test vector must be valid");
|
||||||
|
action.ephemeral_key = v
|
||||||
|
.ephemeral_key
|
||||||
|
.try_into()
|
||||||
|
.expect("test vector must be valid");
|
||||||
|
action.out_ciphertext = WrappedNoteKey(v.c_out);
|
||||||
|
action.enc_ciphertext = EncryptedNote(v.c_enc);
|
||||||
|
action
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test if shielded coinbase outputs are decryptable with an all-zero outgoing
|
||||||
|
/// viewing key.
|
||||||
|
#[test]
|
||||||
|
fn coinbase_outputs_are_decryptable_for_fake_v5_blocks() {
|
||||||
|
let network = Network::Testnet;
|
||||||
|
|
||||||
|
for v in zebra_test::vectors::ORCHARD_NOTE_ENCRYPTION_ZERO_VECTOR.iter() {
|
||||||
|
// Find a transaction with no inputs or outputs to use as base
|
||||||
|
let mut transaction =
|
||||||
|
fake_v5_transactions_for_network(network, zebra_test::vectors::TESTNET_BLOCKS.iter())
|
||||||
|
.rev()
|
||||||
|
.find(|transaction| {
|
||||||
|
transaction.inputs().is_empty()
|
||||||
|
&& transaction.outputs().is_empty()
|
||||||
|
&& transaction.sapling_spends_per_anchor().next().is_none()
|
||||||
|
&& transaction.sapling_outputs().next().is_none()
|
||||||
|
&& transaction.joinsplit_count() == 0
|
||||||
|
})
|
||||||
|
.expect("At least one fake V5 transaction with no inputs and no outputs");
|
||||||
|
|
||||||
|
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
|
||||||
|
shielded_data.flags = orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS;
|
||||||
|
|
||||||
|
let action =
|
||||||
|
fill_action_with_note_encryption_test_vector(&shielded_data.actions[0].action, v);
|
||||||
|
let sig = shielded_data.actions[0].spend_auth_sig;
|
||||||
|
shielded_data.actions = vec![AuthorizedAction::from_parts(action, sig)]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
check::coinbase_outputs_are_decryptable(
|
||||||
|
&transaction,
|
||||||
|
network,
|
||||||
|
NetworkUpgrade::Nu5.activation_height(network).unwrap(),
|
||||||
|
),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test if random shielded outputs are NOT decryptable with an all-zero outgoing
|
||||||
|
/// viewing key.
|
||||||
|
#[test]
|
||||||
|
fn shielded_outputs_are_not_decryptable_for_fake_v5_blocks() {
|
||||||
|
let network = Network::Testnet;
|
||||||
|
|
||||||
|
for v in zebra_test::vectors::ORCHARD_NOTE_ENCRYPTION_VECTOR.iter() {
|
||||||
|
// Find a transaction with no inputs or outputs to use as base
|
||||||
|
let mut transaction =
|
||||||
|
fake_v5_transactions_for_network(network, zebra_test::vectors::TESTNET_BLOCKS.iter())
|
||||||
|
.rev()
|
||||||
|
.find(|transaction| {
|
||||||
|
transaction.inputs().is_empty()
|
||||||
|
&& transaction.outputs().is_empty()
|
||||||
|
&& transaction.sapling_spends_per_anchor().next().is_none()
|
||||||
|
&& transaction.sapling_outputs().next().is_none()
|
||||||
|
&& transaction.joinsplit_count() == 0
|
||||||
|
})
|
||||||
|
.expect("At least one fake V5 transaction with no inputs and no outputs");
|
||||||
|
|
||||||
|
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
|
||||||
|
shielded_data.flags = orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS;
|
||||||
|
|
||||||
|
let action =
|
||||||
|
fill_action_with_note_encryption_test_vector(&shielded_data.actions[0].action, v);
|
||||||
|
let sig = shielded_data.actions[0].spend_auth_sig;
|
||||||
|
shielded_data.actions = vec![AuthorizedAction::from_parts(action, sig)]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
check::coinbase_outputs_are_decryptable(
|
||||||
|
&transaction,
|
||||||
|
network,
|
||||||
|
NetworkUpgrade::Nu5.activation_height(network).unwrap(),
|
||||||
|
),
|
||||||
|
Err(TransactionError::CoinbaseOutputsNotDecryptable)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,10 @@ use hex::FromHex;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
mod block;
|
mod block;
|
||||||
|
mod orchard_note_encryption;
|
||||||
|
|
||||||
pub use block::*;
|
pub use block::*;
|
||||||
|
pub use orchard_note_encryption::*;
|
||||||
|
|
||||||
/// A testnet transaction test vector
|
/// A testnet transaction test vector
|
||||||
///
|
///
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue