zebra_chain/sapling/
keys.rs

1//! Sapling key types.
2//!
3//! Unused key types are not implemented, see PR #5476.
4//!
5//! "The spend authorizing key ask, proof authorizing key (ak, nsk),
6//! full viewing key (ak, nk, ovk), incoming viewing key ivk, and each
7//! diversified payment address addr_d = (d, pk_d ) are derived from sk,
8//! as described in [Sapling Key Components][ps]." - [§3.1][3.1]
9//!
10//! [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
11//! [3.1]: https://zips.z.cash/protocol/protocol.pdf#addressesandkeys
12
13use std::{fmt, io};
14
15use rand_core::{CryptoRng, RngCore};
16
17use crate::{
18    error::{AddressError, RandError},
19    primitives::redjubjub::SpendAuth,
20    serialization::{
21        serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
22    },
23};
24
25#[cfg(test)]
26mod test_vectors;
27
28/// The [Randomness Beacon][1] ("URS").
29///
30/// First 64 bytes of the BLAKE2s input during JubJub group hash.  URS
31/// is a 64-byte US-ASCII string, i.e. the first byte is 0x30, not
32/// 0x09.
33///
34/// From [zcash_primitives][0].
35///
36/// [0]: https://docs.rs/zcash_primitives/0.2.0/zcash_primitives/constants/constant.GH_FIRST_BLOCK.html
37/// [1]: https://zips.z.cash/protocol/protocol.pdf#beacon
38pub(super) const RANDOMNESS_BEACON_URS: &[u8; 64] =
39    b"096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0";
40
41/// GroupHash into Jubjub, aka _GroupHash_URS_
42///
43/// Produces a random point in the Jubjub curve. The point is
44/// guaranteed to be prime order and not the identity. From
45/// [zcash_primitives][0].
46///
47/// d is an 8-byte domain separator ("personalization"), m is the hash
48/// input.
49///
50/// [0]: https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/group_hash.rs#L15
51/// <https://zips.z.cash/protocol/protocol.pdf#concretegrouphashjubjub>
52fn jubjub_group_hash(d: [u8; 8], m: &[u8]) -> Option<jubjub::ExtendedPoint> {
53    let hash = blake2s_simd::Params::new()
54        .hash_length(32)
55        .personal(&d)
56        .to_state()
57        .update(RANDOMNESS_BEACON_URS)
58        .update(m)
59        .finalize();
60
61    let ct_option = jubjub::AffinePoint::from_bytes(*hash.as_array());
62
63    if ct_option.is_some().unwrap_u8() == 1 {
64        let extended_point = ct_option.unwrap().mul_by_cofactor();
65
66        if extended_point != jubjub::ExtendedPoint::identity() {
67            Some(extended_point)
68        } else {
69            None
70        }
71    } else {
72        None
73    }
74}
75
76/// Used to derive a diversified base point from a diversifier value.
77///
78/// <https://zips.z.cash/protocol/protocol.pdf#concretediversifyhash>
79fn diversify_hash(d: [u8; 11]) -> Option<jubjub::ExtendedPoint> {
80    jubjub_group_hash(*b"Zcash_gd", &d)
81}
82
83/// A _Diversifier_, as described in [protocol specification §4.2.2][ps].
84///
85/// Combined with an _IncomingViewingKey_, produces a _diversified
86/// payment address_.
87///
88/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
89#[derive(Copy, Clone, Eq, PartialEq)]
90#[cfg_attr(
91    any(test, feature = "proptest-impl"),
92    derive(proptest_derive::Arbitrary)
93)]
94pub struct Diversifier(pub(crate) [u8; 11]);
95
96impl fmt::Debug for Diversifier {
97    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98        f.debug_tuple("Diversifier")
99            .field(&hex::encode(self.0))
100            .finish()
101    }
102}
103
104impl From<[u8; 11]> for Diversifier {
105    fn from(bytes: [u8; 11]) -> Self {
106        Self(bytes)
107    }
108}
109
110impl From<Diversifier> for [u8; 11] {
111    fn from(d: Diversifier) -> [u8; 11] {
112        d.0
113    }
114}
115
116impl TryFrom<Diversifier> for jubjub::AffinePoint {
117    type Error = &'static str;
118
119    /// Get a diversified base point from a diversifier value in affine
120    /// representation.
121    fn try_from(d: Diversifier) -> Result<Self, Self::Error> {
122        if let Ok(extended_point) = jubjub::ExtendedPoint::try_from(d) {
123            Ok(extended_point.into())
124        } else {
125            Err("Invalid Diversifier -> jubjub::AffinePoint")
126        }
127    }
128}
129
130impl TryFrom<Diversifier> for jubjub::ExtendedPoint {
131    type Error = &'static str;
132
133    fn try_from(d: Diversifier) -> Result<Self, Self::Error> {
134        let possible_point = diversify_hash(d.0);
135
136        if let Some(point) = possible_point {
137            Ok(point)
138        } else {
139            Err("Invalid Diversifier -> jubjub::ExtendedPoint")
140        }
141    }
142}
143
144impl PartialEq<[u8; 11]> for Diversifier {
145    fn eq(&self, other: &[u8; 11]) -> bool {
146        self.0 == *other
147    }
148}
149
150impl Diversifier {
151    /// Generate a new _Diversifier_ that has already been confirmed
152    /// as a preimage to a valid diversified base point when used to
153    /// derive a diversified payment address.
154    ///
155    /// <https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents>
156    /// <https://zips.z.cash/protocol/protocol.pdf#concretediversifyhash>
157    pub fn new<T>(csprng: &mut T) -> Result<Self, AddressError>
158    where
159        T: RngCore + CryptoRng,
160    {
161        /// Number of times a `diversify_hash` will try to obtain a diversified base point.
162        const DIVERSIFY_HASH_TRIES: u8 = 2;
163
164        for _ in 0..DIVERSIFY_HASH_TRIES {
165            let mut bytes = [0u8; 11];
166            csprng
167                .try_fill_bytes(&mut bytes)
168                .map_err(|_| AddressError::from(RandError::FillBytes))?;
169
170            if diversify_hash(bytes).is_some() {
171                return Ok(Self(bytes));
172            }
173        }
174        Err(AddressError::DiversifierGenerationFailure)
175    }
176}
177
178/// A (diversified) _TransmissionKey_
179///
180/// In Sapling, secrets need to be transmitted to a recipient of funds
181/// in order for them to be later spent. To transmit these secrets
182/// securely to a recipient without requiring an out-of-band
183/// communication channel, the diversified transmission key is used to
184/// encrypt them.
185///
186/// Derived by multiplying a JubJub point [derived][ps] from a
187/// _Diversifier_ by the _IncomingViewingKey_ scalar.
188///
189/// The diversified TransmissionKey is denoted `pk_d` in the specification.
190/// Note that it can be the identity point, since its type is
191/// [`KA^{Sapling}.PublicPrimeSubgroup`][ka] which in turn is [`J^{(r)}`][jubjub].
192///
193/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
194/// [ka]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
195/// [jubjub]: https://zips.z.cash/protocol/protocol.pdf#jubjub
196#[derive(Copy, Clone, PartialEq)]
197pub struct TransmissionKey(pub(crate) jubjub::AffinePoint);
198
199impl fmt::Debug for TransmissionKey {
200    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
201        f.debug_struct("TransmissionKey")
202            .field("u", &hex::encode(self.0.get_u().to_bytes()))
203            .field("v", &hex::encode(self.0.get_v().to_bytes()))
204            .finish()
205    }
206}
207
208impl Eq for TransmissionKey {}
209
210impl TryFrom<[u8; 32]> for TransmissionKey {
211    type Error = &'static str;
212
213    /// Attempts to interpret a byte representation of an affine Jubjub point, failing if the
214    /// element is not on the curve, non-canonical, or not in the prime-order subgroup.
215    ///
216    /// <https://github.com/zkcrypto/jubjub/blob/master/src/lib.rs#L411>
217    /// <https://zips.z.cash/zip-0216>
218    fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
219        let affine_point = jubjub::AffinePoint::from_bytes(bytes).unwrap();
220        // Check if it's identity or has prime order (i.e. is in the prime-order subgroup).
221        if affine_point.is_torsion_free().into() {
222            Ok(Self(affine_point))
223        } else {
224            Err("Invalid jubjub::AffinePoint value for Sapling TransmissionKey")
225        }
226    }
227}
228
229impl From<TransmissionKey> for [u8; 32] {
230    fn from(pk_d: TransmissionKey) -> [u8; 32] {
231        pk_d.0.to_bytes()
232    }
233}
234
235impl PartialEq<[u8; 32]> for TransmissionKey {
236    fn eq(&self, other: &[u8; 32]) -> bool {
237        &self.0.to_bytes() == other
238    }
239}
240
241/// An [ephemeral public key][1] for Sapling key agreement.
242///
243/// Public keys containing points of small order are not allowed.
244///
245/// It is denoted by `epk` in the specification. (This type does _not_
246/// represent [KA^{Sapling}.Public][2], which allows any points, including
247/// of small order).
248///
249/// [1]: https://zips.z.cash/protocol/protocol.pdf#outputdesc
250/// [2]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
251#[derive(Copy, Clone, Deserialize, PartialEq, Serialize)]
252pub struct EphemeralPublicKey(
253    #[serde(with = "serde_helpers::AffinePoint")] pub(crate) jubjub::AffinePoint,
254);
255
256impl fmt::Debug for EphemeralPublicKey {
257    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
258        f.debug_struct("EphemeralPublicKey")
259            .field("u", &hex::encode(self.0.get_u().to_bytes()))
260            .field("v", &hex::encode(self.0.get_v().to_bytes()))
261            .finish()
262    }
263}
264
265impl Eq for EphemeralPublicKey {}
266
267impl From<EphemeralPublicKey> for [u8; 32] {
268    fn from(nk: EphemeralPublicKey) -> [u8; 32] {
269        nk.0.to_bytes()
270    }
271}
272
273impl From<&EphemeralPublicKey> for [u8; 32] {
274    fn from(nk: &EphemeralPublicKey) -> [u8; 32] {
275        nk.0.to_bytes()
276    }
277}
278
279impl PartialEq<[u8; 32]> for EphemeralPublicKey {
280    fn eq(&self, other: &[u8; 32]) -> bool {
281        &self.0.to_bytes() == other
282    }
283}
284
285impl TryFrom<[u8; 32]> for EphemeralPublicKey {
286    type Error = &'static str;
287
288    /// Read an EphemeralPublicKey from a byte array.
289    ///
290    /// Returns an error if the key is non-canonical, or [it is of small order][1].
291    ///
292    /// # Consensus
293    ///
294    /// > Check that a Output description's cv and epk are not of small order,
295    /// > i.e. \[h_J\]cv MUST NOT be 𝒪_J and \[h_J\]epk MUST NOT be 𝒪_J.
296    ///
297    /// [1]: https://zips.z.cash/protocol/protocol.pdf#outputdesc
298    fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
299        let possible_point = jubjub::AffinePoint::from_bytes(bytes);
300
301        if possible_point.is_none().into() {
302            return Err("Invalid jubjub::AffinePoint value for Sapling EphemeralPublicKey");
303        }
304        if possible_point.unwrap().is_small_order().into() {
305            Err("jubjub::AffinePoint value for Sapling EphemeralPublicKey point is of small order")
306        } else {
307            Ok(Self(possible_point.unwrap()))
308        }
309    }
310}
311
312impl ZcashSerialize for EphemeralPublicKey {
313    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
314        writer.write_all(&<[u8; 32]>::from(self)[..])?;
315        Ok(())
316    }
317}
318
319impl ZcashDeserialize for EphemeralPublicKey {
320    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
321        Self::try_from(reader.read_32_bytes()?).map_err(SerializationError::Parse)
322    }
323}
324
325/// A randomized [validating key][1] that should be used to validate `spendAuthSig`.
326///
327/// It is denoted by `rk` in the specification. (This type does _not_
328/// represent [SpendAuthSig^{Sapling}.Public][2], which allows any points, including
329/// of small order).
330///
331/// [1]: https://zips.z.cash/protocol/protocol.pdf#spenddesc
332/// [2]: https://zips.z.cash/protocol/protocol.pdf#concretereddsa
333#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
334pub struct ValidatingKey(redjubjub::VerificationKey<SpendAuth>);
335
336impl From<ValidatingKey> for redjubjub::VerificationKey<SpendAuth> {
337    fn from(rk: ValidatingKey) -> Self {
338        rk.0
339    }
340}
341
342impl TryFrom<redjubjub::VerificationKey<SpendAuth>> for ValidatingKey {
343    type Error = &'static str;
344
345    /// Convert an array into a ValidatingKey.
346    ///
347    /// Returns an error if the key is malformed or [is of small order][1].
348    ///
349    /// # Consensus
350    ///
351    /// > Check that a Spend description's cv and rk are not of small order,
352    /// > i.e. \[h_J\]cv MUST NOT be 𝒪_J and \[h_J\]rk MUST NOT be 𝒪_J.
353    ///
354    /// [1]: https://zips.z.cash/protocol/protocol.pdf#spenddesc
355    fn try_from(key: redjubjub::VerificationKey<SpendAuth>) -> Result<Self, Self::Error> {
356        if bool::from(
357            jubjub::AffinePoint::from_bytes(key.into())
358                .unwrap()
359                .is_small_order(),
360        ) {
361            Err("jubjub::AffinePoint value for Sapling ValidatingKey is of small order")
362        } else {
363            Ok(Self(key))
364        }
365    }
366}
367
368impl TryFrom<[u8; 32]> for ValidatingKey {
369    type Error = &'static str;
370
371    fn try_from(value: [u8; 32]) -> Result<Self, Self::Error> {
372        let vk = redjubjub::VerificationKey::<SpendAuth>::try_from(value)
373            .map_err(|_| "Invalid redjubjub::ValidatingKey for Sapling ValidatingKey")?;
374        vk.try_into()
375    }
376}
377
378impl From<ValidatingKey> for [u8; 32] {
379    fn from(key: ValidatingKey) -> Self {
380        key.0.into()
381    }
382}
383
384impl From<ValidatingKey> for redjubjub::VerificationKeyBytes<SpendAuth> {
385    fn from(key: ValidatingKey) -> Self {
386        key.0.into()
387    }
388}