Skip to main content

sapling_crypto/
keys.rs

1//! Sapling key components.
2//!
3//! Implements [section 4.2.2] of the Zcash Protocol Specification.
4//!
5//! [section 4.2.2]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
6
7use alloc::vec::Vec;
8use core::fmt;
9use corez::io::{self, Read, Write};
10
11use super::{
12    address::PaymentAddress,
13    constants::{self, PROOF_GENERATION_KEY_GENERATOR},
14    note_encryption::KDF_SAPLING_PERSONALIZATION,
15    spec::{
16        PreparedBase, PreparedBaseSubgroup, PreparedScalar, crh_ivk, diversify_hash,
17        ka_sapling_agree, ka_sapling_agree_prepared, ka_sapling_derive_public,
18        ka_sapling_derive_public_subgroup_prepared,
19    },
20};
21
22use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
23use ff::{Field, PrimeField};
24use group::{Curve, Group, GroupEncoding};
25use redjubjub::SpendAuth;
26use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
27use zcash_note_encryption::EphemeralKeyBytes;
28use zcash_spec::PrfExpand;
29
30#[cfg(all(feature = "circuit", test))]
31use rand_core::Rng;
32
33/// Domain separation for the existing Sapling ZIP 32 master expansion.
34pub(crate) const SAPLING_MASTER_PERSONALIZATION: &[u8; 16] = b"ZcashIP32Sapling";
35
36/// Expands the seed using the existing Sapling master-key derivation.
37pub(crate) fn master_key_expansion(seed: &[u8]) -> Blake2bHash {
38    Blake2bParams::new()
39        .hash_length(64)
40        .personal(SAPLING_MASTER_PERSONALIZATION)
41        .hash(seed)
42}
43
44/// Errors that can occur in the decoding of Sapling spending keys.
45#[derive(Debug)]
46#[non_exhaustive]
47pub enum DecodingError {
48    /// The length of the byte slice provided for decoding was incorrect.
49    LengthInvalid { expected: usize, actual: usize },
50    /// Could not decode the `ask` bytes to a jubjub field element.
51    InvalidAsk,
52    /// Could not decode the `nsk` bytes to a jubjub field element.
53    InvalidNsk,
54    /// An extended spending key had an unsupported child index: either a non-hardened
55    /// index, or a non-zero index at depth 0.
56    UnsupportedChildIndex,
57}
58
59impl fmt::Display for DecodingError {
60    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61        match self {
62            DecodingError::LengthInvalid { expected, actual } => {
63                write!(f, "invalid slice length (expected {expected}, got {actual}")
64            }
65            DecodingError::InvalidAsk => write!(f, "invalid `ask`"),
66            DecodingError::InvalidNsk => write!(f, "invalid `nsk`"),
67            DecodingError::UnsupportedChildIndex => write!(
68                f,
69                "unsupported child index (either non-hardened, or non-zero at depth 0)"
70            ),
71        }
72    }
73}
74
75#[cfg(feature = "std")]
76impl std::error::Error for DecodingError {}
77
78/// A spend authorizing key, used to create spend authorization signatures.
79///
80/// $\mathsf{ask}$ as defined in [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents].
81///
82/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
83#[derive(Clone, Debug)]
84pub struct SpendAuthorizingKey(redjubjub::SigningKey<SpendAuth>);
85
86impl PartialEq for SpendAuthorizingKey {
87    fn eq(&self, other: &Self) -> bool {
88        <[u8; 32]>::from(self.0)
89            .ct_eq(&<[u8; 32]>::from(other.0))
90            .into()
91    }
92}
93
94impl Eq for SpendAuthorizingKey {}
95
96impl From<&SpendValidatingKey> for jubjub::ExtendedPoint {
97    fn from(spend_validating_key: &SpendValidatingKey) -> jubjub::ExtendedPoint {
98        jubjub::ExtendedPoint::from_bytes(&spend_validating_key.to_bytes()).unwrap()
99    }
100}
101
102impl SpendAuthorizingKey {
103    /// Derives ask from sk. Internal use only, does not enforce all constraints.
104    fn derive_inner(sk: &[u8]) -> jubjub::Scalar {
105        jubjub::Scalar::from_bytes_wide(&PrfExpand::SAPLING_ASK.with(sk))
106    }
107
108    /// Constructs a `SpendAuthorizingKey` from a raw scalar.
109    pub(crate) fn from_scalar(ask: jubjub::Scalar) -> Option<Self> {
110        if ask.is_zero().into() {
111            None
112        } else {
113            Some(SpendAuthorizingKey(ask.to_bytes().try_into().unwrap()))
114        }
115    }
116
117    /// Derives a `SpendAuthorizingKey` from a spending key.
118    fn from_spending_key(sk: &[u8]) -> Option<Self> {
119        Self::from_scalar(Self::derive_inner(sk))
120    }
121
122    /// Parses a `SpendAuthorizingKey` from its encoded form.
123    pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
124        <[u8; 32]>::try_from(bytes)
125            .ok()
126            .and_then(|b| {
127                // RedJubjub.Private permits the full set of Jubjub scalars including
128                // zero. However, a SpendAuthorizingKey is further restricted within the
129                // Sapling key tree to be a non-zero scalar.
130                jubjub::Scalar::from_repr(b)
131                    .and_then(|s| {
132                        CtOption::new(
133                            redjubjub::SigningKey::try_from(b)
134                                .expect("RedJubjub permits the set of valid SpendAuthorizingKeys"),
135                            !s.is_zero(),
136                        )
137                    })
138                    .into()
139            })
140            .map(SpendAuthorizingKey)
141    }
142
143    /// Converts this spend authorizing key to its serialized form.
144    pub fn to_bytes(&self) -> [u8; 32] {
145        <[u8; 32]>::from(self.0)
146    }
147
148    /// Converts this spend authorizing key to a raw scalar.
149    ///
150    /// Only used for ZIP 32 child derivation.
151    #[cfg(feature = "wallet")]
152    pub(crate) fn to_scalar(&self) -> jubjub::Scalar {
153        jubjub::Scalar::from_repr(self.0.into()).unwrap()
154    }
155
156    /// Randomizes this spend authorizing key with the given `randomizer`.
157    ///
158    /// The resulting key can be used to actually sign a spend.
159    pub fn randomize(&self, randomizer: &jubjub::Scalar) -> redjubjub::SigningKey<SpendAuth> {
160        self.0.randomize(randomizer)
161    }
162}
163
164/// A key used to validate spend authorization signatures.
165///
166/// Defined in [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents].
167///
168/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
169#[derive(Clone, Debug)]
170pub struct SpendValidatingKey(redjubjub::VerificationKey<SpendAuth>);
171
172impl From<&SpendAuthorizingKey> for SpendValidatingKey {
173    fn from(ask: &SpendAuthorizingKey) -> Self {
174        SpendValidatingKey((&ask.0).into())
175    }
176}
177
178impl PartialEq for SpendValidatingKey {
179    fn eq(&self, other: &Self) -> bool {
180        <[u8; 32]>::from(self.0)
181            .ct_eq(&<[u8; 32]>::from(other.0))
182            .into()
183    }
184}
185
186impl Eq for SpendValidatingKey {}
187
188impl SpendValidatingKey {
189    /// For circuit tests only.
190    #[cfg(all(feature = "circuit", test))]
191    pub(crate) fn fake_random<R: Rng>(mut rng: R) -> Self {
192        loop {
193            if let Some(k) = Self::from_bytes(&jubjub::SubgroupPoint::random(&mut rng).to_bytes()) {
194                break k;
195            }
196        }
197    }
198
199    /// Only exposed for `zcashd` unit tests.
200    #[cfg(feature = "temporary-zcashd")]
201    pub fn temporary_zcash_from_bytes(bytes: &[u8]) -> Option<Self> {
202        Self::from_bytes(bytes)
203    }
204
205    /// Parses a `SpendValidatingKey` from its encoded form.
206    pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
207        <[u8; 32]>::try_from(bytes)
208            .ok()
209            .and_then(|b| {
210                // RedJubjub.Public permits the full set of Jubjub points including the
211                // identity and cofactors; this is the type used for `rk` in Spend
212                // descriptions. However, a SpendValidatingKey is further restricted
213                // within the Sapling key tree to be a non-identity element of the
214                // prime-order subgroup.
215                jubjub::SubgroupPoint::from_bytes(&b)
216                    .and_then(|p| {
217                        CtOption::new(
218                            redjubjub::VerificationKey::try_from(b)
219                                .expect("RedJubjub permits the set of valid SpendValidatingKeys"),
220                            !p.is_identity(),
221                        )
222                    })
223                    .into()
224            })
225            .map(SpendValidatingKey)
226    }
227
228    /// Converts this spend validating key to its serialized form,
229    /// `LEBS2OSP_256(repr_J(ak))`.
230    pub fn to_bytes(&self) -> [u8; 32] {
231        <[u8; 32]>::from(self.0)
232    }
233
234    /// Randomizes this spend validating key with the given `randomizer`.
235    pub fn randomize(&self, randomizer: &jubjub::Scalar) -> redjubjub::VerificationKey<SpendAuth> {
236        self.0.randomize(randomizer)
237    }
238}
239
240/// An outgoing viewing key
241#[derive(Clone, Copy, Debug, PartialEq, Eq)]
242pub struct OutgoingViewingKey(pub [u8; 32]);
243
244/// A Sapling expanded spending key
245#[derive(Clone)]
246pub struct ExpandedSpendingKey {
247    pub ask: SpendAuthorizingKey,
248    pub nsk: jubjub::Fr,
249    pub ovk: OutgoingViewingKey,
250}
251
252impl fmt::Debug for ExpandedSpendingKey {
253    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254        f.debug_struct("ExpandedSpendingKey")
255            .finish_non_exhaustive()
256    }
257}
258
259impl ExpandedSpendingKey {
260    /// Expands a spending key into its components.
261    ///
262    /// # Panics
263    ///
264    /// Panics if this spending key expands to `ask = 0`. This has a negligible
265    /// probability of occurring.
266    pub fn from_spending_key(sk: &[u8]) -> Self {
267        let ask =
268            SpendAuthorizingKey::from_spending_key(sk).expect("negligible chance of ask == 0");
269        let nsk = jubjub::Fr::from_bytes_wide(&PrfExpand::SAPLING_NSK.with(sk));
270        let mut ovk = OutgoingViewingKey([0u8; 32]);
271        ovk.0
272            .copy_from_slice(&PrfExpand::SAPLING_OVK.with(sk)[..32]);
273        ExpandedSpendingKey { ask, nsk, ovk }
274    }
275
276    pub fn proof_generation_key(&self) -> ProofGenerationKey {
277        ProofGenerationKey {
278            ak: (&self.ask).into(),
279            nsk: self.nsk,
280        }
281    }
282
283    /// Decodes the expanded spending key from its serialized representation
284    /// as part of the encoding of the extended spending key as defined in
285    /// [ZIP 32](https://zips.z.cash/zip-0032)
286    pub fn from_bytes(b: &[u8]) -> Result<Self, DecodingError> {
287        if b.len() != 96 {
288            return Err(DecodingError::LengthInvalid {
289                expected: 96,
290                actual: b.len(),
291            });
292        }
293
294        let ask = SpendAuthorizingKey::from_bytes(&b[0..32]).ok_or(DecodingError::InvalidAsk)?;
295        let nsk = Option::from(jubjub::Fr::from_repr(b[32..64].try_into().unwrap()))
296            .ok_or(DecodingError::InvalidNsk)?;
297        let ovk = OutgoingViewingKey(b[64..96].try_into().unwrap());
298
299        Ok(ExpandedSpendingKey { ask, nsk, ovk })
300    }
301
302    pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
303        let mut repr = [0u8; 96];
304        reader.read_exact(repr.as_mut())?;
305        Self::from_bytes(&repr).map_err(|e| match e {
306            DecodingError::InvalidAsk => {
307                io::Error::new(io::ErrorKind::InvalidData, "ask not in field")
308            }
309            DecodingError::InvalidNsk => {
310                io::Error::new(io::ErrorKind::InvalidData, "nsk not in field")
311            }
312            DecodingError::LengthInvalid { .. } | DecodingError::UnsupportedChildIndex => {
313                unreachable!()
314            }
315        })
316    }
317
318    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
319        writer.write_all(&self.to_bytes())
320    }
321
322    /// Encodes the expanded spending key to its serialized representation
323    /// as part of the encoding of the extended spending key as defined in
324    /// [ZIP 32](https://zips.z.cash/zip-0032)
325    pub fn to_bytes(&self) -> [u8; 96] {
326        let mut result = [0u8; 96];
327        result[0..32].copy_from_slice(&self.ask.to_bytes());
328        result[32..64].copy_from_slice(&self.nsk.to_repr());
329        result[64..96].copy_from_slice(&self.ovk.0);
330        result
331    }
332}
333
334#[derive(Clone)]
335pub struct ProofGenerationKey {
336    pub ak: SpendValidatingKey,
337    pub nsk: jubjub::Fr,
338}
339
340impl fmt::Debug for ProofGenerationKey {
341    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342        f.debug_struct("ProofGenerationKey")
343            .field("ak", &self.ak)
344            .finish_non_exhaustive()
345    }
346}
347
348impl ProofGenerationKey {
349    pub fn to_viewing_key(&self) -> ViewingKey {
350        ViewingKey {
351            ak: self.ak.clone(),
352            nk: NullifierDerivingKey(constants::PROOF_GENERATION_KEY_GENERATOR * self.nsk),
353        }
354    }
355}
356
357/// A key used to derive the nullifier for a Sapling note.
358#[derive(Debug, Copy, Clone, PartialEq, Eq)]
359pub struct NullifierDerivingKey(pub jubjub::SubgroupPoint);
360
361#[derive(Debug, Clone, PartialEq, Eq)]
362pub struct ViewingKey {
363    pub ak: SpendValidatingKey,
364    pub nk: NullifierDerivingKey,
365}
366
367impl ViewingKey {
368    pub fn rk(&self, ar: jubjub::Fr) -> redjubjub::VerificationKey<SpendAuth> {
369        self.ak.randomize(&ar)
370    }
371
372    pub fn ivk(&self) -> SaplingIvk {
373        SaplingIvk(crh_ivk(self.ak.to_bytes(), self.nk.0.to_bytes()))
374    }
375
376    pub fn to_payment_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
377        self.ivk().to_payment_address(diversifier)
378    }
379}
380
381/// A Sapling key that provides the capability to view incoming and outgoing transactions.
382#[derive(Debug, PartialEq, Eq)]
383pub struct FullViewingKey {
384    pub vk: ViewingKey,
385    pub ovk: OutgoingViewingKey,
386}
387
388impl Clone for FullViewingKey {
389    fn clone(&self) -> Self {
390        FullViewingKey {
391            vk: ViewingKey {
392                ak: self.vk.ak.clone(),
393                nk: self.vk.nk,
394            },
395            ovk: self.ovk,
396        }
397    }
398}
399
400impl FullViewingKey {
401    pub fn from_expanded_spending_key(expsk: &ExpandedSpendingKey) -> Self {
402        FullViewingKey {
403            vk: ViewingKey {
404                ak: (&expsk.ask).into(),
405                nk: NullifierDerivingKey(PROOF_GENERATION_KEY_GENERATOR * expsk.nsk),
406            },
407            ovk: expsk.ovk,
408        }
409    }
410
411    pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
412        let ak = {
413            let mut buf = [0u8; 32];
414            reader.read_exact(&mut buf)?;
415            SpendValidatingKey::from_bytes(&buf)
416        };
417        let nk = {
418            let mut buf = [0u8; 32];
419            reader.read_exact(&mut buf)?;
420            jubjub::SubgroupPoint::from_bytes(&buf)
421        };
422        if ak.is_none() {
423            return Err(io::Error::new(
424                io::ErrorKind::InvalidInput,
425                "ak not of prime order",
426            ));
427        }
428        if nk.is_none().into() {
429            return Err(io::Error::new(
430                io::ErrorKind::InvalidInput,
431                "nk not in prime-order subgroup",
432            ));
433        }
434        let ak = ak.unwrap();
435        let nk = NullifierDerivingKey(nk.unwrap());
436
437        let mut ovk = [0u8; 32];
438        reader.read_exact(&mut ovk)?;
439
440        Ok(FullViewingKey {
441            vk: ViewingKey { ak, nk },
442            ovk: OutgoingViewingKey(ovk),
443        })
444    }
445
446    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
447        writer.write_all(&self.vk.ak.to_bytes())?;
448        writer.write_all(&self.vk.nk.0.to_bytes())?;
449        writer.write_all(&self.ovk.0)?;
450
451        Ok(())
452    }
453
454    pub fn to_bytes(&self) -> [u8; 96] {
455        let mut result = [0u8; 96];
456        self.write(&mut result[..])
457            .expect("should be able to serialize a FullViewingKey");
458        result
459    }
460}
461
462#[derive(Debug, Clone, PartialEq, Eq)]
463pub struct SaplingIvk(pub jubjub::Fr);
464
465impl SaplingIvk {
466    pub fn to_payment_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
467        let prepared_ivk = PreparedIncomingViewingKey::new(self);
468        DiversifiedTransmissionKey::derive(&prepared_ivk, &diversifier)
469            .and_then(|pk_d| PaymentAddress::from_parts(diversifier, pk_d))
470    }
471
472    pub fn to_repr(&self) -> [u8; 32] {
473        self.0.to_repr()
474    }
475}
476
477/// A Sapling incoming viewing key that has been precomputed for trial decryption.
478#[derive(Clone, Debug)]
479pub struct PreparedIncomingViewingKey(PreparedScalar);
480
481impl PreparedIncomingViewingKey {
482    /// Performs the necessary precomputations to use a `SaplingIvk` for note decryption.
483    pub fn new(ivk: &SaplingIvk) -> Self {
484        Self(PreparedScalar::new(&ivk.0))
485    }
486}
487
488#[derive(Copy, Clone, Debug, PartialEq, Eq)]
489pub struct Diversifier(pub [u8; 11]);
490
491impl Diversifier {
492    pub fn g_d(&self) -> Option<jubjub::SubgroupPoint> {
493        diversify_hash(&self.0)
494    }
495}
496
497/// The diversified transmission key for a given payment address.
498///
499/// Defined in [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents].
500///
501/// Note that this type is allowed to be the identity in the protocol, but we reject this
502/// in [`PaymentAddress::from_parts`].
503///
504/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
505#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
506pub struct DiversifiedTransmissionKey(jubjub::SubgroupPoint);
507
508impl DiversifiedTransmissionKey {
509    /// Defined in [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents].
510    ///
511    /// Returns `None` if `d` is an invalid diversifier.
512    ///
513    /// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
514    pub(crate) fn derive(ivk: &PreparedIncomingViewingKey, d: &Diversifier) -> Option<Self> {
515        d.g_d()
516            .map(PreparedBaseSubgroup::new)
517            .map(|g_d| ka_sapling_derive_public_subgroup_prepared(&ivk.0, &g_d))
518            .map(DiversifiedTransmissionKey)
519    }
520
521    /// $abst_J(bytes)$
522    pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
523        jubjub::SubgroupPoint::from_bytes(bytes).map(DiversifiedTransmissionKey)
524    }
525
526    /// $repr_J(self)$
527    pub(crate) fn to_bytes(self) -> [u8; 32] {
528        self.0.to_bytes()
529    }
530
531    /// Returns true if this is the identity.
532    pub(crate) fn is_identity(&self) -> bool {
533        self.0.is_identity().into()
534    }
535
536    /// Exposes the inner Jubjub point.
537    ///
538    /// This API is exposed for `zcash_proof` usage, and will be removed when this type is
539    /// refactored into the `sapling-crypto` crate.
540    pub fn inner(&self) -> jubjub::SubgroupPoint {
541        self.0
542    }
543}
544
545impl ConditionallySelectable for DiversifiedTransmissionKey {
546    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
547        DiversifiedTransmissionKey(jubjub::SubgroupPoint::conditional_select(
548            &a.0, &b.0, choice,
549        ))
550    }
551}
552
553/// An ephemeral secret key used to encrypt an output note on-chain.
554///
555/// `esk` is "ephemeral" in the sense that each secret key is only used once. In
556/// practice, `esk` is derived deterministically from the note that it is encrypting.
557///
558/// $\mathsf{KA}^\mathsf{Sapling}.\mathsf{Private} := \mathbb{F}_{r_J}$
559///
560/// Defined in [section 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement].
561///
562/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
563#[derive(Debug)]
564pub struct EphemeralSecretKey(pub(crate) jubjub::Scalar);
565
566impl ConstantTimeEq for EphemeralSecretKey {
567    fn ct_eq(&self, other: &Self) -> subtle::Choice {
568        self.0.ct_eq(&other.0)
569    }
570}
571
572impl EphemeralSecretKey {
573    pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
574        jubjub::Scalar::from_bytes(bytes).map(EphemeralSecretKey)
575    }
576
577    pub(crate) fn derive_public(&self, g_d: jubjub::ExtendedPoint) -> EphemeralPublicKey {
578        EphemeralPublicKey(ka_sapling_derive_public(&self.0, &g_d))
579    }
580
581    pub(crate) fn agree(&self, pk_d: &DiversifiedTransmissionKey) -> SharedSecret {
582        SharedSecret(ka_sapling_agree(&self.0, &pk_d.0.into()))
583    }
584}
585
586/// An ephemeral public key used to encrypt an output note on-chain.
587///
588/// `epk` is "ephemeral" in the sense that each public key is only used once. In practice,
589/// `epk` is derived deterministically from the note that it is encrypting.
590///
591/// $\mathsf{KA}^\mathsf{Sapling}.\mathsf{Public} := \mathbb{J}$
592///
593/// Defined in [section 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement].
594///
595/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
596#[derive(Debug)]
597pub struct EphemeralPublicKey(jubjub::ExtendedPoint);
598
599impl EphemeralPublicKey {
600    pub(crate) fn from_affine(epk: jubjub::AffinePoint) -> Self {
601        EphemeralPublicKey(epk.into())
602    }
603
604    pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
605        jubjub::ExtendedPoint::from_bytes(bytes).map(EphemeralPublicKey)
606    }
607
608    pub(crate) fn to_bytes(&self) -> EphemeralKeyBytes {
609        EphemeralKeyBytes(self.0.to_bytes())
610    }
611}
612
613/// A Sapling ephemeral public key that has been precomputed for trial decryption.
614#[derive(Clone, Debug)]
615pub struct PreparedEphemeralPublicKey(PreparedBase);
616
617impl PreparedEphemeralPublicKey {
618    pub(crate) fn new(epk: EphemeralPublicKey) -> Self {
619        PreparedEphemeralPublicKey(PreparedBase::new(epk.0))
620    }
621
622    pub(crate) fn agree(&self, ivk: &PreparedIncomingViewingKey) -> SharedSecret {
623        SharedSecret(ka_sapling_agree_prepared(&ivk.0, &self.0))
624    }
625}
626
627/// $\mathsf{KA}^\mathsf{Sapling}.\mathsf{SharedSecret} := \mathbb{J}^{(r)}$
628///
629/// Defined in [section 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement].
630///
631/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
632#[derive(Debug)]
633pub struct SharedSecret(jubjub::SubgroupPoint);
634
635impl SharedSecret {
636    /// For checking test vectors only.
637    #[cfg(test)]
638    pub(crate) fn to_bytes(&self) -> [u8; 32] {
639        self.0.to_bytes()
640    }
641
642    /// Only for use in batched note encryption.
643    pub(crate) fn batch_to_affine(
644        shared_secrets: Vec<Option<Self>>,
645    ) -> impl Iterator<Item = Option<jubjub::AffinePoint>> {
646        // Filter out the positions for which ephemeral_key was not a valid encoding.
647        let secrets: Vec<_> = shared_secrets
648            .iter()
649            .filter_map(|s| s.as_ref().map(|s| jubjub::ExtendedPoint::from(s.0)))
650            .collect();
651
652        // Batch-normalize the shared secrets.
653        let mut secrets_affine = vec![jubjub::AffinePoint::identity(); secrets.len()];
654        group::Curve::batch_normalize(&secrets, &mut secrets_affine);
655
656        // Re-insert the invalid ephemeral_key positions.
657        let mut secrets_affine = secrets_affine.into_iter();
658        shared_secrets
659            .into_iter()
660            .map(move |s| s.and_then(|_| secrets_affine.next()))
661    }
662
663    /// Defined in [Zcash Protocol Spec § 5.4.5.4: Sapling Key Agreement][concretesaplingkdf].
664    ///
665    /// [concretesaplingkdf]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkdf
666    pub(crate) fn kdf_sapling(self, ephemeral_key: &EphemeralKeyBytes) -> Blake2bHash {
667        Self::kdf_sapling_inner(
668            jubjub::ExtendedPoint::from(self.0).to_affine(),
669            ephemeral_key,
670        )
671    }
672
673    /// Only for direct use in batched note encryption.
674    pub(crate) fn kdf_sapling_inner(
675        secret: jubjub::AffinePoint,
676        ephemeral_key: &EphemeralKeyBytes,
677    ) -> Blake2bHash {
678        Blake2bParams::new()
679            .hash_length(32)
680            .personal(KDF_SAPLING_PERSONALIZATION)
681            .to_state()
682            .update(&secret.to_bytes())
683            .update(ephemeral_key.as_ref())
684            .finalize()
685    }
686}
687
688#[cfg(any(test, feature = "test-dependencies"))]
689#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
690pub mod testing {
691    use proptest::collection::vec;
692    use proptest::prelude::*;
693
694    use super::{ExpandedSpendingKey, FullViewingKey, SaplingIvk};
695
696    prop_compose! {
697        pub fn arb_expanded_spending_key()(v in vec(any::<u8>(), 32..252)) -> ExpandedSpendingKey {
698            ExpandedSpendingKey::from_spending_key(&v)
699        }
700    }
701
702    prop_compose! {
703        pub fn arb_full_viewing_key()(sk in arb_expanded_spending_key()) -> FullViewingKey {
704            FullViewingKey::from_expanded_spending_key(&sk)
705        }
706    }
707
708    prop_compose! {
709        pub fn arb_incoming_viewing_key()(fvk in arb_full_viewing_key()) -> SaplingIvk {
710            fvk.vk.ivk()
711        }
712    }
713}
714
715#[cfg(test)]
716mod tests {
717    use alloc::string::ToString;
718    use group::{Group, GroupEncoding};
719
720    use super::{FullViewingKey, SpendAuthorizingKey, SpendValidatingKey};
721    use crate::{constants::SPENDING_KEY_GENERATOR, test_vectors};
722
723    #[test]
724    fn ak_must_be_prime_order() {
725        let mut buf = [0; 96];
726        let identity = jubjub::SubgroupPoint::identity();
727
728        // Set both ak and nk to the identity.
729        buf[0..32].copy_from_slice(&identity.to_bytes());
730        buf[32..64].copy_from_slice(&identity.to_bytes());
731
732        // ak is not allowed to be the identity.
733        assert_eq!(
734            FullViewingKey::read(&buf[..]).unwrap_err().to_string(),
735            "ak not of prime order"
736        );
737
738        // Set ak to a basepoint.
739        let basepoint = SPENDING_KEY_GENERATOR;
740        buf[0..32].copy_from_slice(&basepoint.to_bytes());
741
742        // nk is allowed to be the identity.
743        assert!(FullViewingKey::read(&buf[..]).is_ok());
744    }
745
746    #[test]
747    fn spend_auth_sig_test_vectors() {
748        for tv in test_vectors::signatures::make_test_vectors() {
749            let sk = SpendAuthorizingKey::from_bytes(&tv.sk).unwrap();
750            let vk = SpendValidatingKey::from_bytes(&tv.vk).unwrap();
751            let rvk = redjubjub::VerificationKey::try_from(tv.rvk).unwrap();
752            let sig = redjubjub::Signature::from(tv.sig);
753            let rsig = redjubjub::Signature::from(tv.rsig);
754
755            let alpha = jubjub::Scalar::from_bytes(&tv.alpha).unwrap();
756
757            assert_eq!(<[u8; 32]>::from(sk.randomize(&alpha)), tv.rsk);
758            assert_eq!(vk.randomize(&alpha), rvk);
759
760            // assert_eq!(vk.0.verify(&tv.m, &sig), Ok(()));
761            // assert_eq!(rvk.verify(&tv.m, &rsig), Ok(()));
762            assert_eq!(
763                vk.0.verify(&tv.m, &rsig),
764                Err(redjubjub::Error::InvalidSignature),
765            );
766            assert_eq!(
767                rvk.verify(&tv.m, &sig),
768                Err(redjubjub::Error::InvalidSignature),
769            );
770        }
771    }
772}