Skip to main content

zcash_client_backend/data_api/testing/
orchard.rs

1use std::hash::Hash;
2
3use ::orchard::{
4    keys::{FullViewingKey, SpendingKey},
5    note_encryption::OrchardDomain,
6    tree::MerkleHashOrchard,
7};
8use incrementalmerkletree::{Address as TreeAddress, Hashable, Level, Position};
9use shardtree::error::ShardTreeError;
10
11use zcash_keys::{
12    address::{Address, UnifiedAddress},
13    keys::UnifiedSpendingKey,
14};
15use zcash_note_encryption::try_output_recovery_with_ovk;
16use zcash_primitives::transaction::Transaction;
17use zcash_protocol::{
18    ShieldedPool,
19    consensus::{self, BlockHeight},
20    memo::MemoBytes,
21    value::Zatoshis,
22};
23
24use crate::{
25    data_api::{
26        DecryptedTransaction, InputSource, TargetValue, WalletCommitmentTrees, WalletSummary,
27        WalletTest,
28        chain::{CommitmentTreeRoot, ScanSummary},
29        testing::{TestState, pool::ShieldedPoolTester},
30        wallet::{
31            ConfirmationsPolicy, TargetHeight,
32            input_selection::{LockFilter, LockedInputPolicy},
33        },
34    },
35    wallet::{Note, ReceivedNote},
36};
37
38/// Type for running pool-agnostic tests on the Orchard pool.
39pub struct OrchardPoolTester;
40impl ShieldedPoolTester for OrchardPoolTester {
41    const SHIELDED_PROTOCOL: ShieldedPool = ShieldedPool::Orchard;
42    // const MERKLE_TREE_DEPTH: u8 = {orchard::NOTE_COMMITMENT_TREE_DEPTH as u8};
43
44    type Sk = SpendingKey;
45    type Fvk = FullViewingKey;
46    type MerkleTreeHash = MerkleHashOrchard;
47    type Note = orchard::note::Note;
48
49    fn test_account_fvk<Cache, DbT: WalletTest, P: consensus::Parameters>(
50        st: &TestState<Cache, DbT, P>,
51    ) -> Self::Fvk {
52        st.test_account_orchard().unwrap().clone()
53    }
54
55    fn usk_to_sk(usk: &UnifiedSpendingKey) -> &Self::Sk {
56        usk.orchard()
57    }
58
59    fn sk(seed: &[u8]) -> Self::Sk {
60        let mut account = zip32::AccountId::ZERO;
61        loop {
62            if let Ok(sk) = SpendingKey::from_zip32_seed(seed, 1, account) {
63                break sk;
64            }
65            account = account.next().unwrap();
66        }
67    }
68
69    fn sk_to_fvk(sk: &Self::Sk) -> Self::Fvk {
70        sk.into()
71    }
72
73    fn sk_default_address(sk: &Self::Sk) -> Address {
74        Self::fvk_default_address(&Self::sk_to_fvk(sk))
75    }
76
77    fn fvk_default_address(fvk: &Self::Fvk) -> Address {
78        UnifiedAddress::from_receivers(
79            Some(fvk.address_at(0u32, zip32::Scope::External)),
80            None,
81            None,
82            None,
83            None,
84        )
85        .unwrap()
86        .into()
87    }
88
89    fn fvks_equal(a: &Self::Fvk, b: &Self::Fvk) -> bool {
90        a == b
91    }
92
93    fn empty_tree_leaf() -> Self::MerkleTreeHash {
94        MerkleHashOrchard::empty_leaf()
95    }
96
97    fn empty_tree_root(level: Level) -> Self::MerkleTreeHash {
98        MerkleHashOrchard::empty_root(level)
99    }
100
101    fn put_subtree_roots<Cache, DbT: WalletTest + WalletCommitmentTrees, P>(
102        st: &mut TestState<Cache, DbT, P>,
103        start_index: u64,
104        roots: &[CommitmentTreeRoot<Self::MerkleTreeHash>],
105    ) -> Result<(), ShardTreeError<<DbT as WalletCommitmentTrees>::Error>> {
106        st.wallet_mut()
107            .put_orchard_subtree_roots(start_index, roots)
108    }
109
110    fn shard_root<Cache, DbT: WalletTest + WalletCommitmentTrees, P>(
111        st: &mut TestState<Cache, DbT, P>,
112        shard_index: u64,
113    ) -> Result<Self::MerkleTreeHash, ShardTreeError<<DbT as WalletCommitmentTrees>::Error>> {
114        let shard_height = crate::data_api::ORCHARD_SHARD_HEIGHT;
115        let addr = TreeAddress::from_parts(Level::from(shard_height), shard_index);
116        let end_position = Position::from((shard_index + 1) << shard_height);
117        st.wallet_mut()
118            .with_orchard_tree_mut(|tree| tree.root(addr, end_position))
119    }
120
121    fn next_subtree_index<A: Hash + Eq>(s: &WalletSummary<A>) -> u64 {
122        s.next_orchard_subtree_index()
123    }
124
125    fn note_value(note: &Self::Note) -> Zatoshis {
126        Zatoshis::const_from_u64(note.value().inner())
127    }
128
129    fn select_spendable_notes<Cache, DbT: InputSource + WalletTest, P>(
130        st: &TestState<Cache, DbT, P>,
131        account: <DbT as InputSource>::AccountId,
132        target_value: TargetValue,
133        target_height: TargetHeight,
134        confirmations_policy: ConfirmationsPolicy,
135        exclude: &[DbT::NoteRef],
136    ) -> Result<Vec<ReceivedNote<DbT::NoteRef, Self::Note>>, <DbT as InputSource>::Error> {
137        st.wallet()
138            .select_spendable_notes(
139                account,
140                target_value,
141                &[ShieldedPool::Orchard],
142                target_height,
143                confirmations_policy,
144                exclude,
145                LockFilter::Policy(&LockedInputPolicy::Exclude),
146            )
147            .map(|n| n.take_orchard())
148    }
149
150    fn select_unspent_notes<Cache, DbT: InputSource + WalletTest, P>(
151        st: &TestState<Cache, DbT, P>,
152        account: <DbT as InputSource>::AccountId,
153        target_height: TargetHeight,
154        exclude: &[DbT::NoteRef],
155    ) -> Result<Vec<ReceivedNote<DbT::NoteRef, Self::Note>>, <DbT as InputSource>::Error> {
156        st.wallet()
157            .select_unspent_notes(
158                account,
159                &[ShieldedPool::Orchard],
160                target_height,
161                exclude,
162                LockFilter::Policy(&LockedInputPolicy::Exclude),
163            )
164            .map(|n| n.take_orchard())
165    }
166
167    fn decrypted_pool_outputs_count<A>(d_tx: &DecryptedTransaction<Transaction, A>) -> usize {
168        d_tx.orchard_outputs().len()
169    }
170
171    fn with_decrypted_pool_memos<A>(
172        d_tx: &DecryptedTransaction<Transaction, A>,
173        mut f: impl FnMut(&MemoBytes),
174    ) {
175        for output in d_tx.orchard_outputs() {
176            f(output.memo());
177        }
178    }
179
180    fn try_output_recovery<P: consensus::Parameters>(
181        _params: &P,
182        _: BlockHeight,
183        tx: &Transaction,
184        fvk: &Self::Fvk,
185    ) -> Option<(Note, Address, MemoBytes)> {
186        for action in tx.orchard_bundle().unwrap().actions() {
187            // Find the output that decrypts with the external OVK
188            let result = try_output_recovery_with_ovk(
189                &OrchardDomain::for_action(action),
190                &fvk.to_ovk(zip32::Scope::External),
191                action,
192                action.cv_net(),
193                &action.encrypted_note().out_ciphertext,
194            );
195
196            if result.is_some() {
197                return result.map(|(note, addr, memo)| {
198                    (
199                        Note::Orchard {
200                            note,
201                            pool: orchard::ValuePool::Orchard,
202                        },
203                        UnifiedAddress::from_receivers(Some(addr), None, None, None, None)
204                            .unwrap()
205                            .into(),
206                        MemoBytes::from_bytes(&memo).expect("correct length"),
207                    )
208                });
209            }
210        }
211
212        None
213    }
214
215    fn received_note_count(summary: &ScanSummary) -> usize {
216        summary.received_orchard_note_count()
217    }
218
219    #[cfg(feature = "pczt")]
220    fn add_proof_generation_keys(
221        pczt: pczt::Pczt,
222        _: &UnifiedSpendingKey,
223    ) -> Result<pczt::Pczt, pczt::roles::updater::SaplingError> {
224        // No-op; Orchard doesn't have proof generation keys.
225        Ok(pczt)
226    }
227
228    #[cfg(feature = "pczt")]
229    fn apply_signatures_to_pczt(
230        signer: &mut pczt::roles::signer::Signer,
231        usk: &UnifiedSpendingKey,
232    ) -> Result<(), pczt::roles::signer::Error> {
233        let sk = Self::usk_to_sk(usk);
234        use rand::{rand_core::UnwrapErr, rngs::SysRng};
235
236        let ask = orchard::keys::SpendAuthorizingKey::from(sk);
237
238        // Figuring out which one is for us is hard. Let's just try signing all of them!
239        for index in 0.. {
240            match signer.sign_orchard(UnwrapErr(SysRng), index, &ask) {
241                // Loop termination.
242                Err(pczt::roles::signer::Error::InvalidIndex) => break,
243                // Ignore any errors due to using the wrong key.
244                Ok(())
245                | Err(pczt::roles::signer::Error::OrchardSign(
246                    orchard::pczt::SignerError::WrongSpendAuthorizingKey,
247                )) => Ok(()),
248                // Raise any unexpected errors.
249                Err(e) => Err(e),
250            }?;
251        }
252
253        Ok(())
254    }
255}