Skip to main content

zebra_chain/primitives/
zcash_note_encryption.rs

1//! Contains code that interfaces with the zcash_note_encryption crate from
2//! librustzcash.
3
4use crate::{
5    block::Height,
6    parameters::{Network, NetworkUpgrade},
7    transaction::Transaction,
8};
9
10/// Returns true if all Sapling, Orchard, or Ironwood outputs, if any, decrypt successfully
11/// with an all-zeroes outgoing viewing key.
12pub fn decrypts_successfully(tx: &Transaction, network: &Network, height: Height) -> bool {
13    let nu = NetworkUpgrade::current(network, height);
14
15    let Ok(tx) = tx.to_librustzcash(nu) else {
16        return false;
17    };
18
19    let null_sapling_ovk = sapling_crypto::keys::OutgoingViewingKey([0u8; 32]);
20
21    // Note that, since this function is used to validate coinbase transactions, we can ignore
22    // the "grace period" mentioned in ZIP-212.
23    let zip_212_enforcement = if nu >= NetworkUpgrade::Canopy {
24        sapling_crypto::note_encryption::Zip212Enforcement::On
25    } else {
26        sapling_crypto::note_encryption::Zip212Enforcement::Off
27    };
28
29    if let Some(bundle) = tx.sapling_bundle() {
30        for output in bundle.shielded_outputs().iter() {
31            let recovery = sapling_crypto::note_encryption::try_sapling_output_recovery(
32                &null_sapling_ovk,
33                output,
34                zip_212_enforcement,
35            );
36            if recovery.is_none() {
37                return false;
38            }
39        }
40    }
41
42    if let Some(bundle) = tx.orchard_bundle() {
43        for act in bundle.actions() {
44            if zcash_note_encryption::try_output_recovery_with_ovk(
45                &orchard::note_encryption::OrchardDomain::for_action(act),
46                &orchard::keys::OutgoingViewingKey::from([0u8; 32]),
47                act,
48                act.cv_net(),
49                &act.encrypted_note().out_ciphertext,
50            )
51            .is_none()
52            {
53                return false;
54            }
55        }
56    }
57
58    // From NU6.3, newly shielded coinbase value is routed to the Ironwood pool, so the coinbase
59    // output-decryptability rule must cover Ironwood actions too. The Ironwood bundle reuses the
60    // Orchard action shape but its notes use the `IronwoodDomain` (V3) note-plaintext version.
61    if let Some(bundle) = tx.ironwood_bundle() {
62        for act in bundle.actions() {
63            if zcash_note_encryption::try_output_recovery_with_ovk(
64                &orchard::note_encryption::IronwoodDomain::for_action(act),
65                &orchard::keys::OutgoingViewingKey::from([0u8; 32]),
66                act,
67                act.cv_net(),
68                &act.encrypted_note().out_ciphertext,
69            )
70            .is_none()
71            {
72                return false;
73            }
74        }
75    }
76
77    true
78}