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
1213use std::{fmt, io};
1415use rand_core::{CryptoRng, RngCore};
1617use crate::{
18 error::{AddressError, RandError},
19 primitives::redjubjub::SpendAuth,
20 serialization::{
21 serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
22 },
23};
2425#[cfg(test)]
26mod test_vectors;
2728/// 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] =
39b"096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0";
4041/// 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> {
53let 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();
6061let ct_option = jubjub::AffinePoint::from_bytes(*hash.as_array());
6263if ct_option.is_some().unwrap_u8() == 1 {
64let extended_point = ct_option.unwrap().mul_by_cofactor();
6566if extended_point != jubjub::ExtendedPoint::identity() {
67Some(extended_point)
68 } else {
69None
70}
71 } else {
72None
73}
74}
7576/// FindGroupHash for JubJub, from [zcash_primitives][0]
77///
78/// d is an 8-byte domain separator ("personalization"), m is the hash
79/// input.
80///
81/// [0]: https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/jubjub/mod.rs#L409
82/// <https://zips.z.cash/protocol/protocol.pdf#concretegrouphashjubjub>
83// TODO: move common functions like these out of the keys module into
84// a more appropriate location
85pub(super) fn find_group_hash(d: [u8; 8], m: &[u8]) -> jubjub::ExtendedPoint {
86let mut tag = m.to_vec();
87let i = tag.len();
88 tag.push(0u8);
8990loop {
91let gh = jubjub_group_hash(d, &tag[..]);
9293// We don't want to overflow and start reusing generators
94assert!(tag[i] != u8::MAX);
95 tag[i] += 1;
9697if let Some(gh) = gh {
98break gh;
99 }
100 }
101}
102103/// Used to derive a diversified base point from a diversifier value.
104///
105/// <https://zips.z.cash/protocol/protocol.pdf#concretediversifyhash>
106fn diversify_hash(d: [u8; 11]) -> Option<jubjub::ExtendedPoint> {
107 jubjub_group_hash(*b"Zcash_gd", &d)
108}
109110/// A _Diversifier_, as described in [protocol specification §4.2.2][ps].
111///
112/// Combined with an _IncomingViewingKey_, produces a _diversified
113/// payment address_.
114///
115/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
116#[derive(Copy, Clone, Eq, PartialEq)]
117#[cfg_attr(
118 any(test, feature = "proptest-impl"),
119 derive(proptest_derive::Arbitrary)
120)]
121pub struct Diversifier(pub(crate) [u8; 11]);
122123impl fmt::Debug for Diversifier {
124fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
125 f.debug_tuple("Diversifier")
126 .field(&hex::encode(self.0))
127 .finish()
128 }
129}
130131impl From<[u8; 11]> for Diversifier {
132fn from(bytes: [u8; 11]) -> Self {
133Self(bytes)
134 }
135}
136137impl From<Diversifier> for [u8; 11] {
138fn from(d: Diversifier) -> [u8; 11] {
139 d.0
140}
141}
142143impl TryFrom<Diversifier> for jubjub::AffinePoint {
144type Error = &'static str;
145146/// Get a diversified base point from a diversifier value in affine
147 /// representation.
148fn try_from(d: Diversifier) -> Result<Self, Self::Error> {
149if let Ok(extended_point) = jubjub::ExtendedPoint::try_from(d) {
150Ok(extended_point.into())
151 } else {
152Err("Invalid Diversifier -> jubjub::AffinePoint")
153 }
154 }
155}
156157impl TryFrom<Diversifier> for jubjub::ExtendedPoint {
158type Error = &'static str;
159160fn try_from(d: Diversifier) -> Result<Self, Self::Error> {
161let possible_point = diversify_hash(d.0);
162163if let Some(point) = possible_point {
164Ok(point)
165 } else {
166Err("Invalid Diversifier -> jubjub::ExtendedPoint")
167 }
168 }
169}
170171impl PartialEq<[u8; 11]> for Diversifier {
172fn eq(&self, other: &[u8; 11]) -> bool {
173self.0 == *other
174 }
175}
176177impl Diversifier {
178/// Generate a new _Diversifier_ that has already been confirmed
179 /// as a preimage to a valid diversified base point when used to
180 /// derive a diversified payment address.
181 ///
182 /// <https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents>
183 /// <https://zips.z.cash/protocol/protocol.pdf#concretediversifyhash>
184pub fn new<T>(csprng: &mut T) -> Result<Self, AddressError>
185where
186T: RngCore + CryptoRng,
187 {
188/// Number of times a `diversify_hash` will try to obtain a diversified base point.
189const DIVERSIFY_HASH_TRIES: u8 = 2;
190191for _ in 0..DIVERSIFY_HASH_TRIES {
192let mut bytes = [0u8; 11];
193 csprng
194 .try_fill_bytes(&mut bytes)
195 .map_err(|_| AddressError::from(RandError::FillBytes))?;
196197if diversify_hash(bytes).is_some() {
198return Ok(Self(bytes));
199 }
200 }
201Err(AddressError::DiversifierGenerationFailure)
202 }
203}
204205/// A (diversified) _TransmissionKey_
206///
207/// In Sapling, secrets need to be transmitted to a recipient of funds
208/// in order for them to be later spent. To transmit these secrets
209/// securely to a recipient without requiring an out-of-band
210/// communication channel, the diversified transmission key is used to
211/// encrypt them.
212///
213/// Derived by multiplying a JubJub point [derived][ps] from a
214/// _Diversifier_ by the _IncomingViewingKey_ scalar.
215///
216/// The diversified TransmissionKey is denoted `pk_d` in the specification.
217/// Note that it can be the identity point, since its type is
218/// [`KA^{Sapling}.PublicPrimeSubgroup`][ka] which in turn is [`J^{(r)}`][jubjub].
219///
220/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
221/// [ka]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
222/// [jubjub]: https://zips.z.cash/protocol/protocol.pdf#jubjub
223#[derive(Copy, Clone, PartialEq)]
224pub struct TransmissionKey(pub(crate) jubjub::AffinePoint);
225226impl fmt::Debug for TransmissionKey {
227fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
228 f.debug_struct("TransmissionKey")
229 .field("u", &hex::encode(self.0.get_u().to_bytes()))
230 .field("v", &hex::encode(self.0.get_v().to_bytes()))
231 .finish()
232 }
233}
234235impl Eq for TransmissionKey {}
236237impl TryFrom<[u8; 32]> for TransmissionKey {
238type Error = &'static str;
239240/// Attempts to interpret a byte representation of an affine Jubjub point, failing if the
241 /// element is not on the curve, non-canonical, or not in the prime-order subgroup.
242 ///
243 /// <https://github.com/zkcrypto/jubjub/blob/master/src/lib.rs#L411>
244 /// <https://zips.z.cash/zip-0216>
245fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
246let affine_point = jubjub::AffinePoint::from_bytes(bytes).unwrap();
247// Check if it's identity or has prime order (i.e. is in the prime-order subgroup).
248if affine_point.is_torsion_free().into() {
249Ok(Self(affine_point))
250 } else {
251Err("Invalid jubjub::AffinePoint value for Sapling TransmissionKey")
252 }
253 }
254}
255256impl From<TransmissionKey> for [u8; 32] {
257fn from(pk_d: TransmissionKey) -> [u8; 32] {
258 pk_d.0.to_bytes()
259 }
260}
261262impl PartialEq<[u8; 32]> for TransmissionKey {
263fn eq(&self, other: &[u8; 32]) -> bool {
264&self.0.to_bytes() == other
265 }
266}
267268/// An [ephemeral public key][1] for Sapling key agreement.
269///
270/// Public keys containing points of small order are not allowed.
271///
272/// It is denoted by `epk` in the specification. (This type does _not_
273/// represent [KA^{Sapling}.Public][2], which allows any points, including
274/// of small order).
275///
276/// [1]: https://zips.z.cash/protocol/protocol.pdf#outputdesc
277/// [2]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
278#[derive(Copy, Clone, Deserialize, PartialEq, Serialize)]
279pub struct EphemeralPublicKey(
280#[serde(with = "serde_helpers::AffinePoint")] pub(crate) jubjub::AffinePoint,
281);
282283impl fmt::Debug for EphemeralPublicKey {
284fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
285 f.debug_struct("EphemeralPublicKey")
286 .field("u", &hex::encode(self.0.get_u().to_bytes()))
287 .field("v", &hex::encode(self.0.get_v().to_bytes()))
288 .finish()
289 }
290}
291292impl Eq for EphemeralPublicKey {}
293294impl From<EphemeralPublicKey> for [u8; 32] {
295fn from(nk: EphemeralPublicKey) -> [u8; 32] {
296 nk.0.to_bytes()
297 }
298}
299300impl From<&EphemeralPublicKey> for [u8; 32] {
301fn from(nk: &EphemeralPublicKey) -> [u8; 32] {
302 nk.0.to_bytes()
303 }
304}
305306impl PartialEq<[u8; 32]> for EphemeralPublicKey {
307fn eq(&self, other: &[u8; 32]) -> bool {
308&self.0.to_bytes() == other
309 }
310}
311312impl TryFrom<[u8; 32]> for EphemeralPublicKey {
313type Error = &'static str;
314315/// Read an EphemeralPublicKey from a byte array.
316 ///
317 /// Returns an error if the key is non-canonical, or [it is of small order][1].
318 ///
319 /// # Consensus
320 ///
321 /// > Check that a Output description's cv and epk are not of small order,
322 /// > i.e. \[h_J\]cv MUST NOT be 𝒪_J and \[h_J\]epk MUST NOT be 𝒪_J.
323 ///
324 /// [1]: https://zips.z.cash/protocol/protocol.pdf#outputdesc
325fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
326let possible_point = jubjub::AffinePoint::from_bytes(bytes);
327328if possible_point.is_none().into() {
329return Err("Invalid jubjub::AffinePoint value for Sapling EphemeralPublicKey");
330 }
331if possible_point.unwrap().is_small_order().into() {
332Err("jubjub::AffinePoint value for Sapling EphemeralPublicKey point is of small order")
333 } else {
334Ok(Self(possible_point.unwrap()))
335 }
336 }
337}
338339impl ZcashSerialize for EphemeralPublicKey {
340fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
341 writer.write_all(&<[u8; 32]>::from(self)[..])?;
342Ok(())
343 }
344}
345346impl ZcashDeserialize for EphemeralPublicKey {
347fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
348Self::try_from(reader.read_32_bytes()?).map_err(SerializationError::Parse)
349 }
350}
351352/// A randomized [validating key][1] that should be used to validate `spendAuthSig`.
353///
354/// It is denoted by `rk` in the specification. (This type does _not_
355/// represent [SpendAuthSig^{Sapling}.Public][2], which allows any points, including
356/// of small order).
357///
358/// [1]: https://zips.z.cash/protocol/protocol.pdf#spenddesc
359/// [2]: https://zips.z.cash/protocol/protocol.pdf#concretereddsa
360#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
361pub struct ValidatingKey(redjubjub::VerificationKey<SpendAuth>);
362363impl TryFrom<redjubjub::VerificationKey<SpendAuth>> for ValidatingKey {
364type Error = &'static str;
365366/// Convert an array into a ValidatingKey.
367 ///
368 /// Returns an error if the key is malformed or [is of small order][1].
369 ///
370 /// # Consensus
371 ///
372 /// > Check that a Spend description's cv and rk are not of small order,
373 /// > i.e. \[h_J\]cv MUST NOT be 𝒪_J and \[h_J\]rk MUST NOT be 𝒪_J.
374 ///
375 /// [1]: https://zips.z.cash/protocol/protocol.pdf#spenddesc
376fn try_from(key: redjubjub::VerificationKey<SpendAuth>) -> Result<Self, Self::Error> {
377if bool::from(
378 jubjub::AffinePoint::from_bytes(key.into())
379 .unwrap()
380 .is_small_order(),
381 ) {
382Err("jubjub::AffinePoint value for Sapling ValidatingKey is of small order")
383 } else {
384Ok(Self(key))
385 }
386 }
387}
388389impl TryFrom<[u8; 32]> for ValidatingKey {
390type Error = &'static str;
391392fn try_from(value: [u8; 32]) -> Result<Self, Self::Error> {
393let vk = redjubjub::VerificationKey::<SpendAuth>::try_from(value)
394 .map_err(|_| "Invalid redjubjub::ValidatingKey for Sapling ValidatingKey")?;
395 vk.try_into()
396 }
397}
398399impl From<ValidatingKey> for [u8; 32] {
400fn from(key: ValidatingKey) -> Self {
401 key.0.into()
402 }
403}
404405impl From<ValidatingKey> for redjubjub::VerificationKeyBytes<SpendAuth> {
406fn from(key: ValidatingKey) -> Self {
407 key.0.into()
408 }
409}