ledger_zcash_builder/txbuilder/
builder_data.rs1use std::io::{self, Write};
5
6use chacha20poly1305::{
7 aead::{Aead, NewAead},
8 ChaCha20Poly1305, Key, Nonce,
9};
10use group::{cofactor::CofactorGroup, GroupEncoding};
11use jubjub::SubgroupPoint;
12use rand::{CryptoRng, RngCore};
13use sha2::{Digest, Sha256};
14use zcash_note_encryption::NoteEncryption;
15use zcash_primitives::{
16 consensus,
17 keys::OutgoingViewingKey,
18 legacy::{Script, TransparentAddress},
19 memo::MemoBytes as Memo,
20 merkle_tree::MerklePath,
21 sapling::{
22 note_encryption::sapling_note_encryption, Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed,
23 },
24 transaction::{
25 self,
26 components::{sapling, transparent, Amount, OutPoint, TxIn, TxOut, GROTH_PROOF_SIZE},
27 sighash::{signature_hash, SignableInput, SIGHASH_ALL},
28 TransactionData,
29 },
30};
31
32use crate::{data::HashSeed, errors::Error, txbuilder::hsmauth, txprover::HsmTxProver};
33
34const OUT_PLAINTEXT_SIZE: usize = 32 + 32; const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + 16;
37
38#[derive(educe::Educe, Clone)]
39#[educe(Debug)]
40pub struct SpendDescriptionInfo {
41 pub diversifier: Diversifier,
43 pub note: Note,
44 pub alpha: jubjub::Fr,
45 pub merkle_path: MerklePath<Node>,
47 #[educe(Debug(ignore))]
48 pub proofkey: ProofGenerationKey,
49 pub rcv: jubjub::Fr,
51}
52
53#[derive(Clone)]
54pub struct SaplingOutput {
55 pub ovk: Option<OutgoingViewingKey>,
57 pub to: PaymentAddress,
59 pub note: Note,
60 pub memo: Memo,
61 pub rcv: jubjub::Fr, pub hashseed: Option<HashSeed>,
63}
64
65impl SaplingOutput {
66 pub fn new<R: RngCore + CryptoRng, P: consensus::Parameters>(
67 ovk: Option<OutgoingViewingKey>,
68 to: PaymentAddress,
69 value: Amount,
70 memo: Option<Memo>,
71 rcv: jubjub::Fr,
72 rseed: Rseed,
73 hashseed: Option<HashSeed>,
74 ) -> Result<Self, Error> {
75 let g_d = match to.g_d() {
76 Some(g_d) => g_d,
77 None => return Err(Error::InvalidAddress),
78 };
79 if value.is_negative() {
80 return Err(Error::InvalidAmount);
81 }
82
83 let note = Note { g_d, pk_d: *to.pk_d(), value: value.into(), rseed };
86
87 Ok(SaplingOutput { ovk, to, note, memo: memo.unwrap_or_else(Memo::empty), rcv, hashseed })
88 }
89
90 pub fn build<P: consensus::Parameters, PR: HsmTxProver, R: RngCore + CryptoRng>(
91 self,
92 prover: &PR,
93 ctx: &mut PR::SaplingProvingContext,
94 rng: &mut R,
95 params: &P,
96 ) -> transaction::components::OutputDescription<<hsmauth::sapling::Unauthorized as sapling::Authorization>::Proof>
97 {
98 let mut encryptor =
99 sapling_note_encryption::<R, P>(self.ovk, self.note.clone(), self.to.clone(), self.memo, rng);
100
101 let (zkproof, cv) =
102 prover.output_proof(ctx, *encryptor.esk(), self.to, self.note.rcm(), self.note.value, self.rcv);
103
104 let cmu = self.note.cmu();
105
106 let enc_ciphertext = encryptor.encrypt_note_plaintext();
107 let out_ciphertext = if self.ovk.is_some() {
108 encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng)
109 } else {
110 let seed = self.hashseed.unwrap().0;
111 let mut randbytes = [0u8; 32 + OUT_PLAINTEXT_SIZE];
112 for i in 0 .. 3 {
113 let mut sha256 = Sha256::new();
114 sha256.update([i as u8]);
115 sha256.update(seed);
116 let h = sha256.finalize();
117 randbytes[i * 32 .. (i + 1) * 32].copy_from_slice(&h);
118 }
119
120 let ock = Key::from_slice(&randbytes[0 .. 32]);
121 let out_ciphertext = ChaCha20Poly1305::new(ock)
122 .encrypt(Nonce::from_slice(&[0u8; 12]), &randbytes[32 ..])
123 .unwrap();
124
125 assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE);
126
127 let mut array = [0u8; OUT_CIPHERTEXT_SIZE];
128 array.copy_from_slice(&out_ciphertext);
129 array
130 };
131
132 let ephemeral_key = encryptor.epk().to_bytes().into();
133
134 transaction::components::OutputDescription { cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof }
135 }
136}
137
138#[derive(Debug, Clone)]
139pub struct TransparentInputInfo {
140 pub pubkey: secp256k1::PublicKey,
141 pub coin: TxOut,
142}
143
144#[derive(Debug, PartialEq, Clone, Default)]
146pub struct SaplingMetadata {
147 pub(crate) spend_indices: Vec<usize>,
148 pub(crate) output_indices: Vec<usize>,
149}
150
151impl SaplingMetadata {
152 pub fn new() -> Self {
153 Default::default()
154 }
155
156 pub fn spend_index(
166 &self,
167 n: usize,
168 ) -> Option<usize> {
169 self.spend_indices.get(n).copied()
170 }
171
172 pub fn output_index(
182 &self,
183 n: usize,
184 ) -> Option<usize> {
185 self.output_indices.get(n).copied()
186 }
187}
188
189impl From<sapling::builder::SaplingMetadata> for SaplingMetadata {
190 fn from(tx_meta: sapling::builder::SaplingMetadata) -> Self {
191 let mut spends = vec![];
192 let mut outputs = vec![];
193
194 let mut i = 0;
195 while let Some(ix) = tx_meta.spend_index(i) {
196 spends.push(ix);
197 i += 1;
198 }
199
200 i = 0;
201 while let Some(ix) = tx_meta.output_index(i) {
202 outputs.push(ix);
203 i += 1;
204 }
205
206 Self { spend_indices: spends, output_indices: outputs }
207 }
208}
209
210#[derive(Clone)]
211pub struct NullifierInput {
212 pub rcm_old: [u8; 32],
213 pub note_position: [u8; 8],
214}
215
216impl NullifierInput {
217 pub fn write<W: Write>(
218 &self,
219 mut writer: W,
220 ) -> io::Result<()> {
221 writer.write_all(&self.rcm_old)?;
222 writer.write_all(&self.note_position)
223 }
224}
225
226#[derive(Clone)]
227pub struct TransparentScriptData {
228 pub prevout: [u8; 36],
229 pub script_pubkey: [u8; 26],
230 pub value: [u8; 8],
231 pub sequence: [u8; 4],
232}
233
234impl TransparentScriptData {
235 pub fn write<W: Write>(
236 &self,
237 mut writer: W,
238 ) -> io::Result<()> {
239 writer.write_all(&self.prevout)?;
240 writer.write_all(&self.script_pubkey)?;
241 writer.write_all(&self.value)?;
242 writer.write_all(&self.sequence)
243 }
244}
245
246#[derive(Clone)]
247pub struct SpendDescription {
248 pub cv: [u8; 32],
249 pub anchor: [u8; 32],
250 pub nullifier: [u8; 32],
251 pub rk: [u8; 32],
252 pub zkproof: [u8; GROTH_PROOF_SIZE],
253}
254
255impl SpendDescription {
256 pub fn from(info: &sapling::SpendDescription<hsmauth::sapling::Unauthorized>) -> SpendDescription {
257 SpendDescription {
258 cv: info.cv.to_bytes(),
259 anchor: info.anchor.to_bytes(),
260 nullifier: info.nullifier.0,
261 rk: info.rk.0.to_bytes(),
262 zkproof: info.zkproof,
263 }
264 }
265
266 pub fn write<W: Write>(
267 &self,
268 mut writer: W,
269 ) -> io::Result<()> {
270 writer.write_all(&self.cv)?;
271 writer.write_all(&self.anchor)?;
272 writer.write_all(&self.nullifier)?;
273 writer.write_all(&self.rk)?;
274 writer.write_all(&self.zkproof)
275 }
276}
277
278#[derive(Clone)]
279pub struct OutputDescription {
280 pub cv: [u8; 32],
281 pub cmu: [u8; 32],
282 pub ephemeral_key: [u8; 32],
283 pub enc_ciphertext: [u8; 580],
284 pub out_ciphertext: [u8; 80],
285 pub zkproof: [u8; GROTH_PROOF_SIZE],
286}
287
288impl
289 From<&transaction::components::OutputDescription<<hsmauth::sapling::Unauthorized as sapling::Authorization>::Proof>>
290 for OutputDescription
291{
292 fn from(
293 from: &transaction::components::OutputDescription<
294 <hsmauth::sapling::Unauthorized as sapling::Authorization>::Proof,
295 >
296 ) -> Self {
297 Self {
298 cv: from.cv.to_bytes(),
299 cmu: from.cmu.to_bytes(),
300 ephemeral_key: from.ephemeral_key.0,
301 enc_ciphertext: from.enc_ciphertext,
302 out_ciphertext: from.out_ciphertext,
303 zkproof: from.zkproof,
304 }
305 }
306}
307
308impl OutputDescription {
309 pub fn write<W: Write>(
310 &self,
311 mut writer: W,
312 ) -> io::Result<()> {
313 writer.write_all(&self.cv)?;
314 writer.write_all(&self.cmu)?;
315 writer.write_all(&self.ephemeral_key)?;
316 writer.write_all(&self.enc_ciphertext)?;
317 writer.write_all(&self.out_ciphertext)?;
318 writer.write_all(&self.zkproof)
319 }
320}
321
322pub fn spend_data_hms_fromtx(
324 input: &[sapling::SpendDescription<hsmauth::sapling::Unauthorized>]
325) -> Vec<SpendDescription> {
326 let mut data = Vec::new();
327 for info in input.iter() {
328 let description = SpendDescription::from(info);
329 data.push(description);
330 }
331 data
332}
333
334pub fn output_data_hsm_fromtx(
336 input: &[sapling::OutputDescription<sapling::GrothProofBytes>]
337) -> Vec<OutputDescription> {
338 let mut data = Vec::new();
339 for info in input.iter() {
340 let description = OutputDescription::from(info);
341 data.push(description);
342 }
343 data
344}
345
346pub fn spend_old_data_fromtx(data: &[SpendDescriptionInfo]) -> Vec<NullifierInput> {
348 let mut v = Vec::new();
349 for info in data.iter() {
350 let n = NullifierInput {
351 rcm_old: info.note.rcm().to_bytes(),
352 note_position: info.merkle_path.position.to_le_bytes(),
353 };
354 v.push(n);
355 }
356 v
357}
358
359pub fn transparent_script_data_fromtx<A: transparent::Authorization>(
362 vins: &[TxIn<A>],
363 inputs: &[TransparentInputInfo],
364) -> Result<Vec<TransparentScriptData>, Error> {
365 let mut data = Vec::new();
366 for (i, (info, vin)) in inputs.iter().zip(vins).enumerate() {
367 let mut prevout = [0u8; 36];
368 prevout[0 .. 32].copy_from_slice(vin.prevout.hash().as_ref());
369 prevout[32 .. 36].copy_from_slice(&vin.prevout.n().to_le_bytes());
370
371 let mut script_pubkey = [0u8; 26];
372 info.coin
373 .script_pubkey
374 .write(&mut script_pubkey[..])?;
375
376 let mut value = [0u8; 8];
377 value.copy_from_slice(&info.coin.value.to_i64_le_bytes());
378
379 let mut sequence = [0u8; 4];
380 sequence.copy_from_slice(&vin.sequence.to_le_bytes());
381
382 let ts = TransparentScriptData { prevout, script_pubkey, value, sequence };
383 data.push(ts);
384 }
385 Ok(data)
386}