Skip to main content

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}