zcash_client_backend/data_api/testing/
orchard.rs1use 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
38pub struct OrchardPoolTester;
40impl ShieldedPoolTester for OrchardPoolTester {
41 const SHIELDED_PROTOCOL: ShieldedPool = ShieldedPool::Orchard;
42 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 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 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 for index in 0.. {
240 match signer.sign_orchard(UnwrapErr(SysRng), index, &ask) {
241 Err(pczt::roles::signer::Error::InvalidIndex) => break,
243 Ok(())
245 | Err(pczt::roles::signer::Error::OrchardSign(
246 orchard::pczt::SignerError::WrongSpendAuthorizingKey,
247 )) => Ok(()),
248 Err(e) => Err(e),
250 }?;
251 }
252
253 Ok(())
254 }
255}