pczt/roles/signer/mod.rs
1//! The Signer role (capability holders can contribute).
2//!
3//! - Needs the spend authorization randomizers to create signatures.
4//! - Needs sufficient information to verify that the proof is over the correct data,
5//! without needing to verify the proof itself.
6//! - A Signer should only need to implement:
7//! - Pedersen commitments using Jubjub / Pallas arithmetic (for note and value
8//! commitments)
9//! - BLAKE2b and BLAKE2s (and the various PRFs / CRHs they are used in)
10//! - Nullifier check (using Jubjub / Pallas arithmetic)
11//! - KDF plus note decryption (AEAD_CHACHA20_POLY1305)
12//! - SignatureHash algorithm
13//! - Signatures (RedJubjub / RedPallas)
14//! - A source of randomness.
15//!
16//! # Outputs a Signer must tolerate
17//!
18//! A zero-valued Orchard-protocol output whose `enc_ciphertext` does not decrypt is
19//! well-formed. A Signer MUST classify it as a tolerable dummy output, and MUST NOT
20//! reconstruct an expected ciphertext and reject the mismatch.
21//!
22//! After NU6.3 every Orchard-pool action is subject to the same-address restriction, so
23//! spending in the Orchard pool pairs each real spend with a fabricated zero-valued
24//! output addressed to the spent note's own receiver. That output's `enc_ciphertext` is
25//! random bytes rather than an encryption of the note plaintext ([ZIP 326]): a real
26//! encryption would trial-decrypt under the receiver's incoming viewing key in the same
27//! action that carries the spend's nullifier, letting any holder of that key link the
28//! nullifier to the address and so detect the spend.
29//!
30//! Such an output carries explicit `recipient`, `value`, and `rseed` fields but no
31//! `user_address`, so a Signer reads a value of zero and recovers nothing from the
32//! ciphertext. The note and its commitment are unaffected, so `cmx` is derived and
33//! checked as for any other output.
34//!
35//! # Signing before the anchor and witnesses are known
36//!
37//! For v6 and later transaction formats, a Signer MAY sign a spend whose `witness` is
38//! absent, in a bundle whose `anchor` is absent ([ZIP 374]).
39//!
40//! A v6 txid digest excludes shielded anchors, committing them in the authorizing-data
41//! digest instead, and a witness never enters the transaction. The sighash therefore
42//! binds every economically meaningful field — nullifiers, value commitments, outputs,
43//! and value balances — while the anchor, witnesses, and proofs govern only whether the
44//! transaction can be mined, not what it does once it is. They are installed by
45//! [`Updater`] before proving.
46//!
47//! This is what allows a chain of transactions to be signed in one session, before the
48//! parent whose outputs they spend has been mined or even broadcast. A Signer's user
49//! interface should be able to convey that a batch contains transactions spending the
50//! outputs of an in-batch, not-yet-mined parent.
51//!
52//! For v5 the sighash commits to the anchor, so it must be present before signing.
53//!
54//! [ZIP 326]: https://zips.z.cash/zip-0326#fabricatedsame-addressoutputsandrandomizednoteciphertexts
55//! [ZIP 374]: https://zips.z.cash/zip-0374#anchorsandpre-authorization
56//! [`Updater`]: crate::roles::updater::Updater
57
58use alloc::vec::Vec;
59
60use orchard::primitives::redpallas;
61use rand_core::{CryptoRng, Rng};
62
63use ::transparent::sighash::{SIGHASH_ANYONECANPAY, SIGHASH_NONE, SIGHASH_SINGLE, SighashPolicy};
64use zcash_primitives::transaction::sighash::{PrecomputedSighashData, SignableInput};
65
66use crate::{
67 ExtractError, ParsedPczt, Pczt,
68 common::{
69 FLAG_HAS_SIGHASH_SINGLE, FLAG_SHIELDED_MODIFIABLE, FLAG_TRANSPARENT_INPUTS_MODIFIABLE,
70 FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE, Global,
71 },
72 precompute_sighash_data, sighash,
73};
74
75pub use crate::EffectsOnly;
76
77pub mod batch;
78
79/// A spend authorization signature for an action in an Orchard-protocol value pool.
80///
81/// This type only represents signatures for the Orchard and Ironwood value pools; it
82/// does not represent Sapling spend authorization signatures. It can be used by external
83/// signers that return signatures separately from a PCZT. The value pool and action index
84/// identify where the signature belongs when it is later applied with
85/// [`Signer::apply_orchard_spend_auth_signature`].
86#[derive(Clone, Debug, Eq, PartialEq)]
87pub struct SpendAuthSignature {
88 value_pool: orchard::ValuePool,
89 action_index: usize,
90 signature: [u8; 64],
91}
92
93impl SpendAuthSignature {
94 /// Constructs a spend authorization signature for the action at `action_index`
95 /// in `value_pool`.
96 pub fn from_parts(
97 value_pool: orchard::ValuePool,
98 action_index: usize,
99 signature: [u8; 64],
100 ) -> Self {
101 Self {
102 value_pool,
103 action_index,
104 signature,
105 }
106 }
107
108 /// Returns the Orchard-protocol value pool containing the signed action.
109 pub fn value_pool(&self) -> orchard::ValuePool {
110 self.value_pool
111 }
112
113 /// Returns the index of the signed action within its value pool's bundle.
114 pub fn action_index(&self) -> usize {
115 self.action_index
116 }
117
118 /// Returns the raw RedPallas spend authorization signature bytes.
119 pub fn signature(&self) -> &[u8; 64] {
120 &self.signature
121 }
122}
123
124/// Extracts the Orchard-protocol spend authorization signatures present in `pczt`.
125///
126/// The returned signatures are tagged with their Orchard or Ironwood value pool and
127/// action index, so they can be transported independently and later applied to a
128/// corresponding PCZT with [`Signer::apply_orchard_spend_auth_signature`]. Actions
129/// without a spend authorization signature are omitted.
130pub fn extract_orchard_spend_auth_signatures(pczt: &Pczt) -> Vec<SpendAuthSignature> {
131 fn extract_from_bundle(
132 signatures: &mut Vec<SpendAuthSignature>,
133 value_pool: orchard::ValuePool,
134 bundle: &crate::orchard::Bundle,
135 ) {
136 for (action_index, action) in bundle.actions().iter().enumerate() {
137 if let Some(signature) = action.spend().spend_auth_sig() {
138 signatures.push(SpendAuthSignature::from_parts(
139 value_pool,
140 action_index,
141 *signature,
142 ));
143 }
144 }
145 }
146
147 let mut signatures = Vec::new();
148 extract_from_bundle(&mut signatures, orchard::ValuePool::Orchard, pczt.orchard());
149 extract_from_bundle(
150 &mut signatures,
151 orchard::ValuePool::Ironwood,
152 pczt.ironwood(),
153 );
154 signatures
155}
156
157pub struct Signer {
158 global: Global,
159 transparent: transparent::pczt::Bundle,
160 sapling: crate::sapling::Parsed,
161 orchard: crate::orchard::Parsed,
162 ironwood: crate::orchard::Parsed,
163 empty_ironwood: Option<crate::orchard::Bundle>,
164 /// Cached across multiple signatures.
165 sighash_data: PrecomputedSighashData<EffectsOnly>,
166 shielded_sighash: [u8; 32],
167 transparent_sighash_policy: SighashPolicy,
168}
169
170impl Signer {
171 /// Instantiates the Signer role with the given PCZT.
172 pub fn new(pczt: Pczt) -> Result<Self, Error> {
173 let anchor_requirement =
174 crate::common::AnchorRequirement::for_pre_authorization(pczt.global.tx_version);
175 let empty_ironwood = pczt
176 .ironwood
177 .actions
178 .is_empty()
179 .then(|| pczt.ironwood.clone());
180
181 let ParsedPczt {
182 global,
183 transparent,
184 sapling,
185 orchard,
186 ironwood,
187 tx_data,
188 } = pczt.extract_tx_data(
189 anchor_requirement,
190 |t| {
191 t.extract_effects()
192 .map_err(ExtractError::TransparentExtract)
193 },
194 |s| s.extract_effects().map_err(ExtractError::SaplingExtract),
195 |o| o.extract_effects().map_err(ExtractError::OrchardExtract),
196 |i| i.extract_effects().map_err(ExtractError::IronwoodExtract),
197 )?;
198 let sighash_data = precompute_sighash_data(&global, tx_data)?;
199 let shielded_sighash = sighash(&sighash_data, &SignableInput::Shielded);
200
201 Ok(Self {
202 global,
203 transparent,
204 sapling,
205 orchard,
206 ironwood,
207 empty_ironwood,
208 sighash_data,
209 shielded_sighash,
210 transparent_sighash_policy: SighashPolicy::ALL_ONLY,
211 })
212 }
213
214 /// Sets the sighash policy that this Signer applies to transparent inputs.
215 ///
216 /// The default is [`SighashPolicy::ALL_ONLY`]; see [`SighashPolicy`] for why the
217 /// sighash type a counterparty placed in the PCZT is only used if this Signer’s own
218 /// policy permits it.
219 pub fn with_transparent_sighash_policy(mut self, sighash_policy: SighashPolicy) -> Self {
220 self.transparent_sighash_policy = sighash_policy;
221 self
222 }
223
224 /// Calculates the signature digest that must be signed to authorize shielded spends.
225 ///
226 /// This can be used to produce a signature externally suitable for passing to e.g.
227 /// [`Self::apply_orchard_signature`].}
228 pub fn shielded_sighash(&self) -> [u8; 32] {
229 self.shielded_sighash
230 }
231
232 /// Calculates the signature digest that must be signed to authorize the transparent
233 /// spend at the given index.
234 ///
235 /// This can be used to produce a signature externally suitable for passing to e.g.
236 /// [`Self::append_transparent_signature`].}
237 ///
238 /// Whoever signs the returned sighash is authorizing whatever it commits to, so the
239 /// consistency of the input and the acceptability of its sighash type are checked
240 /// here, exactly as they are when this Signer produces the signature itself.
241 ///
242 /// Returns an error if `index` is invalid for this PCZT.
243 pub fn transparent_sighash(&self, index: usize) -> Result<[u8; 32], Error> {
244 let input = self
245 .transparent
246 .inputs()
247 .get(index)
248 .ok_or(Error::InvalidIndex)?;
249
250 input
251 .with_signable_input_with_sighash_policy(
252 index,
253 |signable_input| {
254 sighash(
255 &self.sighash_data,
256 &SignableInput::Transparent(signable_input),
257 )
258 },
259 self.transparent_sighash_policy,
260 )
261 .map_err(Error::TransparentSign)
262 }
263
264 /// Signs the transparent spend at the given index with the given spending key.
265 ///
266 /// It is the caller's responsibility to perform any semantic validity checks on the
267 /// PCZT (for example, comfirming that the change amounts are correct) before calling
268 /// this method.
269 pub fn sign_transparent(
270 &mut self,
271 index: usize,
272 sk: &secp256k1::SecretKey,
273 ) -> Result<(), Error> {
274 let sighash_policy = self.transparent_sighash_policy;
275 self.generate_or_append_transparent_signature(index, |input, sighash_data| {
276 input.sign_with_sighash_policy(
277 index,
278 |input| sighash(sighash_data, &SignableInput::Transparent(input)),
279 sk,
280 sighash_policy,
281 )
282 })
283 }
284
285 /// Appends the given signature to the transparent spend.
286 ///
287 /// It is the caller's responsibility to perform any semantic validity checks on the
288 /// PCZT (for example, comfirming that the change amounts are correct) before calling
289 /// this method.
290 pub fn append_transparent_signature(
291 &mut self,
292 index: usize,
293 signature: secp256k1::ecdsa::Signature,
294 ) -> Result<(), Error> {
295 let sighash_policy = self.transparent_sighash_policy;
296 self.generate_or_append_transparent_signature(index, |input, sighash_data| {
297 input.append_signature_with_sighash_policy(
298 index,
299 |input| sighash(sighash_data, &SignableInput::Transparent(input)),
300 signature,
301 sighash_policy,
302 )
303 })
304 }
305
306 fn generate_or_append_transparent_signature<F>(
307 &mut self,
308 index: usize,
309 f: F,
310 ) -> Result<(), Error>
311 where
312 F: FnOnce(
313 &mut transparent::pczt::Input,
314 &PrecomputedSighashData<EffectsOnly>,
315 ) -> Result<(), transparent::pczt::SignerError>,
316 {
317 let input = self
318 .transparent
319 .inputs_mut()
320 .get_mut(index)
321 .ok_or(Error::InvalidIndex)?;
322
323 // The consistency of the input being signed, and the acceptability of its sighash
324 // type, are checked by the `zcash_transparent` methods that `f` calls. They are
325 // enforced there rather than here because `crate::roles::low_level_signer` hands
326 // out the transparent bundle and calls those methods directly.
327
328 // Generate or apply the signature.
329 f(input, &self.sighash_data).map_err(Error::TransparentSign)?;
330
331 // Update transaction modifiability:
332 // - If the Signer added a signature that does not use `SIGHASH_ANYONECANPAY`, the
333 // Transparent Inputs Modifiable Flag must be set to False (because the
334 // signature commits to all inputs, not just the one at `index`).
335 if input.sighash_type().encode() & SIGHASH_ANYONECANPAY == 0 {
336 self.global.tx_modifiable &= !FLAG_TRANSPARENT_INPUTS_MODIFIABLE;
337 }
338 // - If the Signer added a signature that does not use `SIGHASH_NONE`, the
339 // Transparent Outputs Modifiable Flag must be set to False. Note that this
340 // applies to `SIGHASH_SINGLE` because we could otherwise remove the output at
341 // `index`, which would not remove the signature.
342 if (input.sighash_type().encode() & !SIGHASH_ANYONECANPAY) != SIGHASH_NONE {
343 self.global.tx_modifiable &= !FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE;
344 }
345 // - If the Signer added a signature that uses `SIGHASH_SINGLE`, the Has
346 // `SIGHASH_SINGLE` flag must be set to True.
347 if (input.sighash_type().encode() & !SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE {
348 self.global.tx_modifiable |= FLAG_HAS_SIGHASH_SINGLE;
349 }
350 // - Always set the Shielded Modifiable Flag to False.
351 self.global.tx_modifiable &= !FLAG_SHIELDED_MODIFIABLE;
352
353 Ok(())
354 }
355
356 /// Signs the Sapling spend at the given index with the given spend authorizing key.
357 ///
358 /// Requires the spend's `proof_generation_key` field to be set (because the API does
359 /// not take an FVK).
360 ///
361 /// It is the caller's responsibility to perform any semantic validity checks on the
362 /// PCZT (for example, comfirming that the change amounts are correct) before calling
363 /// this method.
364 ///
365 /// `rng` provides the randomness for the signature.
366 pub fn sign_sapling<R: Rng + CryptoRng>(
367 &mut self,
368 rng: R,
369 index: usize,
370 ask: &sapling::keys::SpendAuthorizingKey,
371 ) -> Result<(), Error> {
372 self.generate_or_apply_sapling_signature(index, |spend, shielded_sighash| {
373 spend.sign(shielded_sighash, ask, rng)
374 })
375 }
376
377 /// Applies the given signature to the Sapling spend.
378 ///
379 /// It is the caller's responsibility to perform any semantic validity checks on the
380 /// PCZT (for example, comfirming that the change amounts are correct) before calling
381 /// this method.
382 pub fn apply_sapling_signature(
383 &mut self,
384 index: usize,
385 signature: redjubjub::Signature<redjubjub::SpendAuth>,
386 ) -> Result<(), Error> {
387 self.generate_or_apply_sapling_signature(index, |spend, shielded_sighash| {
388 spend.apply_signature(shielded_sighash, signature)
389 })
390 }
391
392 fn generate_or_apply_sapling_signature<F>(&mut self, index: usize, f: F) -> Result<(), Error>
393 where
394 F: FnOnce(&mut sapling::pczt::Spend, [u8; 32]) -> Result<(), sapling::pczt::SignerError>,
395 {
396 let spend = self
397 .sapling
398 .bundle
399 .spends_mut()
400 .get_mut(index)
401 .ok_or(Error::InvalidIndex)?;
402
403 // Check consistency of the input being signed if we have its note components.
404 match spend.verify_nullifier(None) {
405 Err(
406 sapling::pczt::VerifyError::MissingRecipient
407 | sapling::pczt::VerifyError::MissingValue
408 | sapling::pczt::VerifyError::MissingRandomSeed,
409 ) => Ok(()),
410 r => r,
411 }
412 .map_err(Error::SaplingVerify)?;
413
414 // Generate or apply the signature.
415 f(spend, self.shielded_sighash).map_err(Error::SaplingSign)?;
416
417 // Update transaction modifiability: all transaction effects have been committed
418 // to by the signature.
419 self.global.tx_modifiable &= !(FLAG_TRANSPARENT_INPUTS_MODIFIABLE
420 | FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE
421 | FLAG_SHIELDED_MODIFIABLE);
422
423 Ok(())
424 }
425
426 /// Signs the Orchard spend at the given index with the given spend authorizing key.
427 ///
428 /// Requires the spend's `fvk` field to be set (because the API does not take an FVK).
429 ///
430 /// It is the caller's responsibility to perform any semantic validity checks on the
431 /// PCZT (for example, comfirming that the change amounts are correct) before calling
432 /// this method.
433 ///
434 /// `rng` provides the randomness for the signature.
435 pub fn sign_orchard<R: Rng + CryptoRng>(
436 &mut self,
437 rng: R,
438 index: usize,
439 ask: &orchard::keys::SpendAuthorizingKey,
440 ) -> Result<(), Error> {
441 self.generate_or_apply_orchard_signature(index, |spend, shielded_sighash| {
442 spend.sign(shielded_sighash, ask, rng)
443 })
444 }
445
446 /// Applies the given signature to the Orchard spend.
447 ///
448 /// It is the caller's responsibility to perform any semantic validity checks on the
449 /// PCZT (for example, comfirming that the change amounts are correct) before calling
450 /// this method.
451 pub fn apply_orchard_signature(
452 &mut self,
453 index: usize,
454 signature: redpallas::Signature<redpallas::SpendAuth>,
455 ) -> Result<(), Error> {
456 self.generate_or_apply_orchard_signature(index, |action, shielded_sighash| {
457 action.apply_signature(shielded_sighash, signature)
458 })
459 }
460
461 /// Applies an externally produced Orchard-protocol spend authorization signature.
462 ///
463 /// The signature's value pool selects the Orchard or Ironwood bundle, and its
464 /// action index selects the spend within that bundle. The signature is verified
465 /// against the action's randomized verification key before it is stored.
466 ///
467 /// Returns an error if the action index is invalid, the action data is
468 /// inconsistent, or the signature does not verify.
469 pub fn apply_orchard_spend_auth_signature(
470 &mut self,
471 signature: &SpendAuthSignature,
472 ) -> Result<(), Error> {
473 let spend_auth_sig =
474 redpallas::Signature::<redpallas::SpendAuth>::from(*signature.signature());
475 match signature.value_pool() {
476 orchard::ValuePool::Orchard => {
477 self.apply_orchard_signature(signature.action_index(), spend_auth_sig)
478 }
479 orchard::ValuePool::Ironwood => {
480 self.apply_ironwood_signature(signature.action_index(), spend_auth_sig)
481 }
482 }
483 }
484
485 fn generate_or_apply_orchard_signature<F>(&mut self, index: usize, f: F) -> Result<(), Error>
486 where
487 F: FnOnce(&mut orchard::pczt::Action, [u8; 32]) -> Result<(), orchard::pczt::SignerError>,
488 {
489 let action = self
490 .orchard
491 .bundle
492 .actions_mut()
493 .get_mut(index)
494 .ok_or(Error::InvalidIndex)?;
495
496 // Check consistency of the input being signed if we have its note components.
497 match action.spend().verify_nullifier(None) {
498 Err(
499 orchard::pczt::VerifyError::MissingRecipient
500 | orchard::pczt::VerifyError::MissingValue
501 | orchard::pczt::VerifyError::MissingRho
502 | orchard::pczt::VerifyError::MissingRandomSeed,
503 ) => Ok(()),
504 r => r,
505 }
506 .map_err(Error::OrchardVerify)?;
507
508 // Generate or apply the signature.
509 f(action, self.shielded_sighash).map_err(Error::OrchardSign)?;
510
511 // Update transaction modifiability: all transaction effects have been committed
512 // to by the signature.
513 self.global.tx_modifiable &= !(FLAG_TRANSPARENT_INPUTS_MODIFIABLE
514 | FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE
515 | FLAG_SHIELDED_MODIFIABLE);
516
517 Ok(())
518 }
519
520 /// Signs the Ironwood spend at the given index with the given spend authorizing key.
521 ///
522 /// Requires the spend's `fvk` field to be set (because the API does not take an FVK).
523 ///
524 /// It is the caller's responsibility to perform any semantic validity checks on the
525 /// PCZT (for example, comfirming that the change amounts are correct) before calling
526 /// this method.
527 ///
528 /// `rng` provides the randomness for the signature.
529 pub fn sign_ironwood<R: Rng + CryptoRng>(
530 &mut self,
531 rng: R,
532 index: usize,
533 ask: &orchard::keys::SpendAuthorizingKey,
534 ) -> Result<(), Error> {
535 self.generate_or_apply_ironwood_signature(index, |spend, shielded_sighash| {
536 spend.sign(shielded_sighash, ask, rng)
537 })
538 }
539
540 /// Applies the given signature to the Ironwood spend.
541 ///
542 /// It is the caller's responsibility to perform any semantic validity checks on the
543 /// PCZT (for example, comfirming that the change amounts are correct) before calling
544 /// this method.
545 pub fn apply_ironwood_signature(
546 &mut self,
547 index: usize,
548 signature: redpallas::Signature<redpallas::SpendAuth>,
549 ) -> Result<(), Error> {
550 self.generate_or_apply_ironwood_signature(index, |action, shielded_sighash| {
551 action.apply_signature(shielded_sighash, signature)
552 })
553 }
554
555 fn generate_or_apply_ironwood_signature<F>(&mut self, index: usize, f: F) -> Result<(), Error>
556 where
557 F: FnOnce(&mut orchard::pczt::Action, [u8; 32]) -> Result<(), orchard::pczt::SignerError>,
558 {
559 let action = self
560 .ironwood
561 .bundle
562 .actions_mut()
563 .get_mut(index)
564 .ok_or(Error::InvalidIndex)?;
565
566 // Check consistency of the input being signed if we have its note components.
567 match action.spend().verify_nullifier(None) {
568 Err(
569 orchard::pczt::VerifyError::MissingRecipient
570 | orchard::pczt::VerifyError::MissingValue
571 | orchard::pczt::VerifyError::MissingRho
572 | orchard::pczt::VerifyError::MissingRandomSeed,
573 ) => Ok(()),
574 r => r,
575 }
576 .map_err(Error::IronwoodVerify)?;
577
578 // Generate or apply the signature.
579 f(action, self.shielded_sighash).map_err(Error::IronwoodSign)?;
580
581 // Update transaction modifiability: all transaction effects have been committed
582 // to by the signature.
583 self.global.tx_modifiable &= !(FLAG_TRANSPARENT_INPUTS_MODIFIABLE
584 | FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE
585 | FLAG_SHIELDED_MODIFIABLE);
586
587 Ok(())
588 }
589
590 /// Finishes the Signer role, returning the updated PCZT.
591 pub fn finish(self) -> Pczt {
592 let Self {
593 global,
594 transparent,
595 sapling,
596 orchard,
597 ironwood,
598 empty_ironwood,
599 sighash_data: _,
600 shielded_sighash: _,
601 transparent_sighash_policy: _,
602 } = self;
603
604 Pczt {
605 global,
606 transparent: crate::transparent::Bundle::serialize_from(transparent),
607 sapling: sapling.reserialize(),
608 orchard: orchard.reserialize(),
609 ironwood: empty_ironwood.unwrap_or_else(|| ironwood.reserialize()),
610 }
611 }
612}
613
614/// Errors that can occur while creating signatures for a PCZT.
615#[derive(Debug)]
616pub enum Error {
617 Extract(crate::ExtractError),
618 InvalidIndex,
619 IronwoodSign(orchard::pczt::SignerError),
620 IronwoodVerify(orchard::pczt::VerifyError),
621 OrchardSign(orchard::pczt::SignerError),
622 OrchardVerify(orchard::pczt::VerifyError),
623 SaplingSign(sapling::pczt::SignerError),
624 SaplingVerify(sapling::pczt::VerifyError),
625 TransparentSign(transparent::pczt::SignerError),
626}
627
628impl From<crate::ExtractError> for Error {
629 fn from(e: crate::ExtractError) -> Self {
630 Error::Extract(e)
631 }
632}
633
634#[cfg(all(test, feature = "io-finalizer", feature = "orchard"))]
635mod tests {
636 use ff::{Field, PrimeField};
637 use pasta_curves::pallas;
638 use rand::{rand_core::UnwrapErr, rngs::SysRng};
639 use zcash_protocol::consensus::BranchId;
640
641 use super::Signer;
642 use crate::{
643 orchard::{Action, Spend},
644 roles::{creator::Creator, io_finalizer::IoFinalizer, updater::Updater},
645 };
646
647 /// Builds a fully-dummy (zero-valued, freshly-random-key) Ironwood action, whose
648 /// spend the IO Finalizer signs itself via `dummy_sk`. This lets the IO Finalizer
649 /// and Signer roles be exercised without going through the transaction builder
650 /// (which requires a concrete anchor to add any real spend).
651 fn dummy_action() -> Action {
652 let sk = orchard::keys::SpendingKey::from_bytes([7; 32]).unwrap();
653 let alpha = pallas::Scalar::ONE;
654 let base = crate::orchard::testing::dummy_action();
655
656 Action {
657 spend: Spend {
658 alpha: Some(alpha.to_repr()),
659 dummy_sk: Some(crate::common::SecretKeyBytes::new(*sk.to_bytes())),
660 ..base.spend
661 },
662 rcv: Some([3; 32]),
663 ..base
664 }
665 }
666
667 /// [ZIP 374] "Anchors and pre-authorization" requires that, for a v6 transaction,
668 /// signing (including the IO Finalizer's dummy-spend signing) works even while a
669 /// shielded bundle's anchor is absent. The Ironwood bundle here is built by
670 /// hand (rather than via the transaction builder, which requires a concrete
671 /// anchor up front to add any spend) specifically to exercise that state; the
672 /// full sign-then-prove-then-extract pipeline is covered by the end-to-end
673 /// Ironwood tests, whose transaction builder requires the anchor to be set at the
674 /// point the spend is added.
675 ///
676 /// [ZIP 374]: https://zips.z.cash/zip-0374#anchorsandpre-authorization
677 #[test]
678 fn io_finalizer_and_signer_succeed_with_absent_ironwood_anchor() {
679 let mut pczt = Creator::new(BranchId::Nu6_3.into(), 100, 133, None, None)
680 .unwrap()
681 .build()
682 .unwrap();
683 pczt.ironwood.actions.push(dummy_action());
684
685 assert!(pczt.ironwood.anchor.is_none());
686
687 let pczt = IoFinalizer::new(pczt)
688 .finalize_io(UnwrapErr(SysRng))
689 .unwrap();
690 assert!(pczt.ironwood.anchor.is_none());
691 assert!(pczt.ironwood.bsk.is_some());
692 // The IO Finalizer signs and clears the dummy spending key.
693 assert!(pczt.ironwood.actions[0].spend.dummy_sk.is_none());
694 assert!(pczt.ironwood.actions[0].spend.spend_auth_sig.is_some());
695
696 // `Signer::new` computes the shielded sighash and extracts transaction
697 // effects; this must succeed with the anchor still absent (a v6 sighash
698 // does not commit to it).
699 let signer = Signer::new(pczt).unwrap();
700 let pczt = signer.finish();
701 assert!(pczt.ironwood.anchor.is_none());
702
703 // The anchor may now be set by an Updater, at any point before the Prover
704 // runs; nothing about signing required it to be present.
705 let anchor = orchard::Anchor::empty_tree();
706 let pczt = Updater::new(pczt)
707 .set_ironwood_anchor(anchor)
708 .unwrap()
709 .finish();
710 assert_eq!(pczt.ironwood.anchor, Some(anchor.to_bytes()));
711 }
712
713 /// [ZIP 326] requires the zero-valued output fabricated for a real Orchard-pool
714 /// spend to carry a randomized `enc_ciphertext` instead of an encryption to the
715 /// spent note's receiver. Such an output is well-formed: the Signer role neither
716 /// decrypts it nor reconstructs an expected ciphertext to compare it against, so
717 /// signing succeeds and the ciphertext reaches the finished PCZT byte-identical.
718 ///
719 /// [ZIP 326]: https://zips.z.cash/zip-0326#fabricatedsame-addressoutputsandrandomizednoteciphertexts
720 #[test]
721 fn signer_tolerates_fabricated_output_with_randomized_ciphertext() {
722 use orchard::{
723 keys::{FullViewingKey, Scope},
724 note::{ExtractedNoteCommitment, NoteVersion, RandomSeed, Rho},
725 note_encryption::OrchardNoteEncryption,
726 value::NoteValue,
727 };
728
729 let mut action = dummy_action();
730
731 // The fabricated output is addressed to a real receiver and carries explicit
732 // `recipient`, `value` and `rseed` fields, so its note commitment is ordinary.
733 let rho = Option::from(Rho::from_bytes(&action.spend.nullifier)).unwrap();
734 let rseed = (0u8..)
735 .find_map(|i| {
736 let mut bytes = [0; 32];
737 bytes[0] = i;
738 Option::from(RandomSeed::from_bytes(bytes, &rho))
739 })
740 .unwrap();
741 let recipient =
742 FullViewingKey::from(&orchard::keys::SpendingKey::from_bytes([0; 32]).unwrap())
743 .address_at(0u32, Scope::External);
744 let value = NoteValue::from_raw(0);
745 let note = Option::from(orchard::Note::from_parts(
746 recipient,
747 value,
748 rho,
749 rseed,
750 NoteVersion::V3,
751 ))
752 .unwrap();
753
754 // Only the ephemeral key comes from the encryptor; the ciphertext is replaced
755 // with bytes that trial-decryption cannot recover a note from.
756 let encryptor = OrchardNoteEncryption::new(None, note, [0; 512]);
757 let mut randomized_ciphertext = encryptor.encrypt_note_plaintext().0.to_vec();
758 randomized_ciphertext.fill(0xab);
759
760 action.output.cmx = Some(ExtractedNoteCommitment::from(note.commitment()).to_bytes());
761 action.output.ephemeral_key =
762 <orchard::note_encryption::IronwoodDomain as zcash_note_encryption::Domain>::epk_bytes(
763 encryptor.epk(),
764 )
765 .0;
766 action.output.recipient = Some(recipient.to_raw_address_bytes());
767 action.output.value = Some(value.inner());
768 action.output.rseed = Some(*note.rseed().as_bytes());
769 action.output.user_address = None;
770 action.output.enc_ciphertext =
771 crate::orchard::EncCiphertext::Encrypted(randomized_ciphertext.clone());
772
773 let mut pczt = Creator::new(BranchId::Nu6_3.into(), 100, 133, None, None)
774 .unwrap()
775 .build()
776 .unwrap();
777 pczt.ironwood.actions.push(action);
778
779 let pczt = IoFinalizer::new(pczt)
780 .finalize_io(UnwrapErr(SysRng))
781 .unwrap();
782 let pczt = Signer::new(pczt).unwrap().finish();
783
784 assert_eq!(
785 pczt.ironwood.actions[0].output.enc_ciphertext,
786 crate::orchard::EncCiphertext::Encrypted(randomized_ciphertext)
787 );
788 assert_eq!(pczt.ironwood.actions[0].output.value, Some(0));
789 }
790}