1use std::sync::Arc;
6
7use chrono::{DateTime, Utc};
8
9use color_eyre::{Report, Result};
10use ff::{Field, PrimeField};
11use group::GroupEncoding;
12use rand::{rngs::OsRng, thread_rng, RngCore};
13
14use zcash_client_backend::{
15 encoding::encode_extended_full_viewing_key,
16 proto::compact_formats::{
17 ChainMetadata, CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx,
18 },
19};
20use zcash_note_encryption::Domain;
21use zcash_primitives::{block::BlockHash, consensus::BlockHeight, memo::MemoBytes};
22
23use ::sapling_crypto::{
24 constants::SPENDING_KEY_GENERATOR,
25 note_encryption::{sapling_note_encryption, SaplingDomain},
26 util::generate_random_rseed,
27 value::NoteValue,
28 zip32, Note, Nullifier,
29};
30
31use zebra_chain::{
32 amount::{Amount, NegativeAllowed},
33 block::{self, merkle, Block, Header, Height},
34 fmt::HexDebug,
35 parameters::Network,
36 primitives::{redjubjub, Groth16Proof},
37 sapling::{self, PerSpendAnchor, Spend, TransferData},
38 serialization::AtLeastOne,
39 transaction::{LockTime, Transaction},
40 transparent::{CoinbaseData, Input},
41 work::{difficulty::CompactDifficulty, equihash::Solution},
42};
43use zebra_state::SaplingScanningKey;
44
45#[cfg(test)]
46mod vectors;
47
48pub const ZECPAGES_SAPLING_VIEWING_KEY: &str = "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz";
50
51pub const FAKE_SAPLING_VIEWING_KEY: &str = "zxviewsfake";
53
54pub fn mock_sapling_scanning_keys(num_keys: u8, network: &Network) -> Vec<SaplingScanningKey> {
59 let mut keys: Vec<SaplingScanningKey> = vec![];
60
61 for seed in 0..num_keys {
62 keys.push(encode_extended_full_viewing_key(
63 network.sapling_efvk_hrp(),
64 &mock_sapling_efvk(&[seed]),
65 ));
66 }
67
68 keys
69}
70
71#[allow(deprecated)]
73pub fn mock_sapling_efvk(seed: &[u8]) -> zip32::ExtendedFullViewingKey {
74 zip32::ExtendedSpendingKey::master(seed).to_extended_full_viewing_key()
77}
78
79pub fn fake_block(
87 height: BlockHeight,
88 nf: Nullifier,
89 dfvk: &zip32::DiversifiableFullViewingKey,
90 value: u64,
91 tx_after: bool,
92 initial_sapling_tree_size: Option<u32>,
93) -> (Block, u32) {
94 let header = Header {
95 version: 4,
96 previous_block_hash: block::Hash::default(),
97 merkle_root: merkle::Root::default(),
98 commitment_bytes: HexDebug::default(),
99 time: DateTime::<Utc>::default(),
100 difficulty_threshold: CompactDifficulty::default(),
101 nonce: HexDebug::default(),
102 solution: Solution::default(),
103 };
104
105 let block = fake_compact_block(
106 height,
107 BlockHash([0; 32]),
108 nf,
109 dfvk,
110 value,
111 tx_after,
112 initial_sapling_tree_size,
113 );
114
115 let mut transactions: Vec<Arc<Transaction>> = block
116 .vtx
117 .iter()
118 .map(|tx| compact_to_v4(tx).expect("A fake compact tx should be convertible to V4."))
119 .map(Arc::new)
120 .collect();
121
122 let coinbase_input = Input::Coinbase {
123 height: Height(1),
124 data: CoinbaseData::new(vec![]),
125 sequence: u32::MAX,
126 };
127
128 let coinbase = Transaction::V4 {
129 inputs: vec![coinbase_input],
130 outputs: vec![],
131 lock_time: LockTime::Height(Height(1)),
132 expiry_height: Height(1),
133 joinsplit_data: None,
134 sapling_shielded_data: None,
135 };
136
137 transactions.insert(0, Arc::new(coinbase));
138
139 let sapling_tree_size = block
140 .chain_metadata
141 .as_ref()
142 .unwrap()
143 .sapling_commitment_tree_size;
144
145 (
146 Block {
147 header: Arc::new(header),
148 transactions,
149 },
150 sapling_tree_size,
151 )
152}
153
154pub fn fake_compact_block(
160 height: BlockHeight,
161 prev_hash: BlockHash,
162 nf: Nullifier,
163 dfvk: &zip32::DiversifiableFullViewingKey,
164 value: u64,
165 tx_after: bool,
166 initial_sapling_tree_size: Option<u32>,
167) -> CompactBlock {
168 let to = dfvk.default_address().1;
169
170 let mut rng = OsRng;
172 let rseed = generate_random_rseed(
173 ::sapling_crypto::note_encryption::Zip212Enforcement::Off,
174 &mut rng,
175 );
176
177 let note = Note::from_parts(to, NoteValue::from_raw(value), rseed);
178 let encryptor = sapling_note_encryption::<_>(
179 Some(dfvk.fvk().ovk),
180 note.clone(),
181 *MemoBytes::empty().as_array(),
182 &mut rng,
183 );
184 let cmu = note.cmu().to_bytes().to_vec();
185 let ephemeral_key = SaplingDomain::epk_bytes(encryptor.epk()).0.to_vec();
186 let enc_ciphertext = encryptor.encrypt_note_plaintext();
187
188 let mut cb = CompactBlock {
190 hash: {
191 let mut hash = vec![0; 32];
192 rng.fill_bytes(&mut hash);
193 hash
194 },
195 prev_hash: prev_hash.0.to_vec(),
196 height: height.into(),
197 ..Default::default()
198 };
199
200 {
202 let mut tx = random_compact_tx(&mut rng);
203 tx.index = cb.vtx.len() as u64;
204 cb.vtx.push(tx);
205 }
206
207 let cspend = CompactSaplingSpend { nf: nf.0.to_vec() };
208 let cout = CompactSaplingOutput {
209 cmu,
210 ephemeral_key,
211 ciphertext: enc_ciphertext[..52].to_vec(),
212 };
213 let mut ctx = CompactTx::default();
214 let mut txid = vec![0; 32];
215 rng.fill_bytes(&mut txid);
216 ctx.hash = txid;
217 ctx.spends.push(cspend);
218 ctx.outputs.push(cout);
219 ctx.index = cb.vtx.len() as u64;
220 cb.vtx.push(ctx);
221
222 if tx_after {
224 let mut tx = random_compact_tx(&mut rng);
225 tx.index = cb.vtx.len() as u64;
226 cb.vtx.push(tx);
227 }
228
229 cb.chain_metadata = initial_sapling_tree_size.map(|s| ChainMetadata {
230 sapling_commitment_tree_size: s + cb
231 .vtx
232 .iter()
233 .map(|tx| tx.outputs.len() as u32)
234 .sum::<u32>(),
235 ..Default::default()
236 });
237
238 cb
239}
240
241pub fn random_compact_tx(mut rng: impl RngCore) -> CompactTx {
246 let fake_nf = {
247 let mut nf = vec![0; 32];
248 rng.fill_bytes(&mut nf);
249 nf
250 };
251 let fake_cmu = {
252 let fake_cmu = bls12_381::Scalar::random(&mut rng);
253 fake_cmu.to_repr().to_vec()
254 };
255 let fake_epk = {
256 let mut buffer = [0; 64];
257 rng.fill_bytes(&mut buffer);
258 let fake_esk = jubjub::Fr::from_bytes_wide(&buffer);
259 let fake_epk = SPENDING_KEY_GENERATOR * fake_esk;
260 fake_epk.to_bytes().to_vec()
261 };
262 let cspend = CompactSaplingSpend { nf: fake_nf };
263 let cout = CompactSaplingOutput {
264 cmu: fake_cmu,
265 ephemeral_key: fake_epk,
266 ciphertext: vec![0; 52],
267 };
268 let mut ctx = CompactTx::default();
269 let mut txid = vec![0; 32];
270 rng.fill_bytes(&mut txid);
271 ctx.hash = txid;
272 ctx.spends.push(cspend);
273 ctx.outputs.push(cout);
274 ctx
275}
276
277pub fn compact_to_v4(tx: &CompactTx) -> Result<Transaction> {
279 let sk = redjubjub::SigningKey::<redjubjub::SpendAuth>::new(thread_rng());
280 let vk = redjubjub::VerificationKey::from(&sk);
281 let dummy_rk = sapling::keys::ValidatingKey::try_from(vk)
282 .expect("Internally generated verification key should be convertible to a validating key.");
283
284 let spends = tx
285 .spends
286 .iter()
287 .map(|spend| {
288 Ok(Spend {
289 cv: sapling::NotSmallOrderValueCommitment::default(),
290 per_spend_anchor: sapling::tree::Root::default(),
291 nullifier: sapling::Nullifier::from(
292 spend.nf().map_err(|_| Report::msg("Invalid nullifier."))?.0,
293 ),
294 rk: dummy_rk.clone(),
295 zkproof: Groth16Proof([0; 192]),
296 spend_auth_sig: redjubjub::Signature::<redjubjub::SpendAuth>::from([0; 64]),
297 })
298 })
299 .collect::<Result<Vec<Spend<PerSpendAnchor>>>>()?;
300
301 let spends = AtLeastOne::<Spend<PerSpendAnchor>>::try_from(spends)?;
302
303 let maybe_outputs = tx
304 .outputs
305 .iter()
306 .map(|output| {
307 let mut ciphertext = output.ciphertext.clone();
308 ciphertext.resize(580, 0);
309 let ciphertext: [u8; 580] = ciphertext
310 .try_into()
311 .map_err(|_| Report::msg("Could not convert ciphertext to `[u8; 580]`"))?;
312 let enc_ciphertext = sapling::EncryptedNote::from(ciphertext);
313
314 Ok(sapling::Output {
315 cv: sapling::NotSmallOrderValueCommitment::default(),
316 cm_u: Option::from(jubjub::Fq::from_bytes(
317 &output
318 .cmu()
319 .map_err(|_| Report::msg("Invalid commitment."))?
320 .to_bytes(),
321 ))
322 .ok_or(Report::msg("Invalid commitment."))?,
323 ephemeral_key: sapling::keys::EphemeralPublicKey::try_from(
324 output
325 .ephemeral_key()
326 .map_err(|_| Report::msg("Invalid ephemeral key."))?
327 .0,
328 )
329 .map_err(Report::msg)?,
330 enc_ciphertext,
331 out_ciphertext: sapling::WrappedNoteKey::from([0; 80]),
332 zkproof: Groth16Proof([0; 192]),
333 })
334 })
335 .collect::<Result<Vec<sapling::Output>>>()?;
336
337 let transfers = TransferData::SpendsAndMaybeOutputs {
338 shared_anchor: sapling::FieldNotPresent,
339 spends,
340 maybe_outputs,
341 };
342
343 let shielded_data = sapling::ShieldedData {
344 value_balance: Amount::<NegativeAllowed>::default(),
345 transfers,
346 binding_sig: redjubjub::Signature::<redjubjub::Binding>::from([0; 64]),
347 };
348
349 Ok(Transaction::V4 {
350 inputs: vec![],
351 outputs: vec![],
352 lock_time: LockTime::Height(Height(0)),
353 expiry_height: Height(0),
354 joinsplit_data: None,
355 sapling_shielded_data: (Some(shielded_data)),
356 })
357}