ironfish_primitives/sapling/
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 std::convert::TryInto;
8use std::io::{self, Read, Write};
9
10use crate::{
11    constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR},
12    keys::{prf_expand, OutgoingViewingKey},
13    zip32,
14};
15use ff::PrimeField;
16use group::{Group, GroupEncoding};
17use subtle::CtOption;
18
19use super::{PaymentAddress, ProofGenerationKey, SaplingIvk, ViewingKey};
20
21/// A Sapling expanded spending key
22#[derive(Clone)]
23pub struct ExpandedSpendingKey {
24    pub ask: ironfish_jubjub::Fr,
25    pub nsk: ironfish_jubjub::Fr,
26    pub ovk: OutgoingViewingKey,
27}
28
29/// A Sapling key that provides the capability to view incoming and outgoing transactions.
30#[derive(Debug)]
31pub struct FullViewingKey {
32    pub vk: ViewingKey,
33    pub ovk: OutgoingViewingKey,
34}
35
36impl ExpandedSpendingKey {
37    pub fn from_spending_key(sk: &[u8]) -> Self {
38        let ask = ironfish_jubjub::Fr::from_bytes_wide(prf_expand(sk, &[0x00]).as_array());
39        let nsk = ironfish_jubjub::Fr::from_bytes_wide(prf_expand(sk, &[0x01]).as_array());
40        let mut ovk = OutgoingViewingKey([0u8; 32]);
41        ovk.0
42            .copy_from_slice(&prf_expand(sk, &[0x02]).as_bytes()[..32]);
43        ExpandedSpendingKey { ask, nsk, ovk }
44    }
45
46    pub fn proof_generation_key(&self) -> ProofGenerationKey {
47        ProofGenerationKey {
48            ak: *SPENDING_KEY_GENERATOR * self.ask,
49            nsk: self.nsk,
50        }
51    }
52
53    pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
54        let mut ask_repr = [0u8; 32];
55        reader.read_exact(ask_repr.as_mut())?;
56        let ask = Option::from(ironfish_jubjub::Fr::from_repr(ask_repr))
57            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "ask not in field"))?;
58
59        let mut nsk_repr = [0u8; 32];
60        reader.read_exact(nsk_repr.as_mut())?;
61        let nsk = Option::from(ironfish_jubjub::Fr::from_repr(nsk_repr))
62            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "nsk not in field"))?;
63
64        let mut ovk = [0u8; 32];
65        reader.read_exact(&mut ovk)?;
66
67        Ok(ExpandedSpendingKey {
68            ask,
69            nsk,
70            ovk: OutgoingViewingKey(ovk),
71        })
72    }
73
74    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
75        writer.write_all(self.ask.to_repr().as_ref())?;
76        writer.write_all(self.nsk.to_repr().as_ref())?;
77        writer.write_all(&self.ovk.0)?;
78
79        Ok(())
80    }
81
82    pub fn to_bytes(&self) -> [u8; 96] {
83        let mut result = [0u8; 96];
84        self.write(&mut result[..])
85            .expect("should be able to serialize an ExpandedSpendingKey");
86        result
87    }
88}
89
90impl Clone for FullViewingKey {
91    fn clone(&self) -> Self {
92        FullViewingKey {
93            vk: ViewingKey {
94                ak: self.vk.ak,
95                nk: self.vk.nk,
96            },
97            ovk: self.ovk,
98        }
99    }
100}
101
102impl FullViewingKey {
103    pub fn from_expanded_spending_key(expsk: &ExpandedSpendingKey) -> Self {
104        FullViewingKey {
105            vk: ViewingKey {
106                ak: *SPENDING_KEY_GENERATOR * expsk.ask,
107                nk: *PROOF_GENERATION_KEY_GENERATOR * expsk.nsk,
108            },
109            ovk: expsk.ovk,
110        }
111    }
112
113    pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
114        let ak = {
115            let mut buf = [0u8; 32];
116            reader.read_exact(&mut buf)?;
117            ironfish_jubjub::SubgroupPoint::from_bytes(&buf).and_then(|p| CtOption::new(p, !p.is_identity()))
118        };
119        let nk = {
120            let mut buf = [0u8; 32];
121            reader.read_exact(&mut buf)?;
122            ironfish_jubjub::SubgroupPoint::from_bytes(&buf)
123        };
124        if ak.is_none().into() {
125            return Err(io::Error::new(
126                io::ErrorKind::InvalidInput,
127                "ak not of prime order",
128            ));
129        }
130        if nk.is_none().into() {
131            return Err(io::Error::new(
132                io::ErrorKind::InvalidInput,
133                "nk not in prime-order subgroup",
134            ));
135        }
136        let ak = ak.unwrap();
137        let nk = nk.unwrap();
138
139        let mut ovk = [0u8; 32];
140        reader.read_exact(&mut ovk)?;
141
142        Ok(FullViewingKey {
143            vk: ViewingKey { ak, nk },
144            ovk: OutgoingViewingKey(ovk),
145        })
146    }
147
148    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
149        writer.write_all(&self.vk.ak.to_bytes())?;
150        writer.write_all(&self.vk.nk.to_bytes())?;
151        writer.write_all(&self.ovk.0)?;
152
153        Ok(())
154    }
155
156    pub fn to_bytes(&self) -> [u8; 96] {
157        let mut result = [0u8; 96];
158        self.write(&mut result[..])
159            .expect("should be able to serialize a FullViewingKey");
160        result
161    }
162}
163
164/// The scope of a viewing key or address.
165///
166/// A "scope" narrows the visibility or usage to a level below "full".
167///
168/// Consistent usage of `Scope` enables the user to provide consistent views over a wallet
169/// to other people. For example, a user can give an external [`SaplingIvk`] to a merchant
170/// terminal, enabling it to only detect "real" transactions from customers and not
171/// internal transactions from the wallet.
172#[derive(Clone, Copy, Debug, PartialEq, Eq)]
173pub enum Scope {
174    /// A scope used for wallet-external operations, namely deriving addresses to give to
175    /// other users in order to receive funds.
176    External,
177    /// A scope used for wallet-internal operations, such as creating change notes,
178    /// auto-shielding, and note management.
179    Internal,
180}
181
182/// A Sapling key that provides the capability to view incoming and outgoing transactions.
183///
184/// This key is useful anywhere you need to maintain accurate balance, but do not want the
185/// ability to spend funds (such as a view-only wallet).
186///
187/// It comprises the subset of the ZIP 32 extended full viewing key that is used for the
188/// Sapling item in a [ZIP 316 Unified Full Viewing Key][zip-0316-ufvk].
189///
190/// [zip-0316-ufvk]: https://zips.z.cash/zip-0316#encoding-of-unified-full-incoming-viewing-keys
191#[derive(Clone, Debug)]
192pub struct DiversifiableFullViewingKey {
193    fvk: FullViewingKey,
194    dk: zip32::DiversifierKey,
195}
196
197impl From<zip32::ExtendedFullViewingKey> for DiversifiableFullViewingKey {
198    fn from(extfvk: zip32::ExtendedFullViewingKey) -> Self {
199        Self {
200            fvk: extfvk.fvk,
201            dk: extfvk.dk,
202        }
203    }
204}
205
206impl DiversifiableFullViewingKey {
207    /// Parses a `DiversifiableFullViewingKey` from its raw byte encoding.
208    ///
209    /// Returns `None` if the bytes do not contain a valid encoding of a diversifiable
210    /// Sapling full viewing key.
211    pub fn from_bytes(bytes: &[u8; 128]) -> Option<Self> {
212        FullViewingKey::read(&bytes[..96]).ok().map(|fvk| Self {
213            fvk,
214            dk: zip32::DiversifierKey(bytes[96..].try_into().unwrap()),
215        })
216    }
217
218    /// Returns the raw encoding of this `DiversifiableFullViewingKey`.
219    pub fn to_bytes(&self) -> [u8; 128] {
220        let mut bytes = [0; 128];
221        self.fvk
222            .write(&mut bytes[..96])
223            .expect("slice should be the correct length");
224        bytes[96..].copy_from_slice(&self.dk.0);
225        bytes
226    }
227
228    /// Derives the internal `DiversifiableFullViewingKey` corresponding to `self` (which
229    /// is assumed here to be an external DFVK).
230    fn derive_internal(&self) -> Self {
231        let (fvk, dk) = zip32::sapling_derive_internal_fvk(&self.fvk, &self.dk);
232        Self { fvk, dk }
233    }
234
235    /// Exposes the [`FullViewingKey`] component of this diversifiable full viewing key.
236    pub fn fvk(&self) -> &FullViewingKey {
237        &self.fvk
238    }
239
240    /// Derives an incoming viewing key corresponding to this full viewing key.
241    pub fn to_ivk(&self, scope: Scope) -> SaplingIvk {
242        match scope {
243            Scope::External => self.fvk.vk.ivk(),
244            Scope::Internal => self.derive_internal().fvk.vk.ivk(),
245        }
246    }
247
248    /// Derives an outgoing viewing key corresponding to this full viewing key.
249    pub fn to_ovk(&self, scope: Scope) -> OutgoingViewingKey {
250        match scope {
251            Scope::External => self.fvk.ovk,
252            Scope::Internal => self.derive_internal().fvk.ovk,
253        }
254    }
255
256    /// Attempts to produce a valid payment address for the given diversifier index.
257    ///
258    /// Returns `None` if the diversifier index does not produce a valid diversifier for
259    /// this `DiversifiableFullViewingKey`.
260    pub fn address(&self, j: zip32::DiversifierIndex) -> Option<PaymentAddress> {
261        zip32::sapling_address(&self.fvk, &self.dk, j)
262    }
263
264    /// Finds the next valid payment address starting from the given diversifier index.
265    ///
266    /// This searches the diversifier space starting at `j` and incrementing, to find an
267    /// index which will produce a valid diversifier (a 50% probability for each index).
268    ///
269    /// Returns the index at which the valid diversifier was found along with the payment
270    /// address constructed using that diversifier, or `None` if the maximum index was
271    /// reached and no valid diversifier was found.
272    pub fn find_address(
273        &self,
274        j: zip32::DiversifierIndex,
275    ) -> Option<(zip32::DiversifierIndex, PaymentAddress)> {
276        zip32::sapling_find_address(&self.fvk, &self.dk, j)
277    }
278
279    /// Returns the payment address corresponding to the smallest valid diversifier index,
280    /// along with that index.
281    // TODO: See if this is only used in tests.
282    pub fn default_address(&self) -> (zip32::DiversifierIndex, PaymentAddress) {
283        zip32::sapling_default_address(&self.fvk, &self.dk)
284    }
285
286    /// Attempts to decrypt the given address's diversifier with this full viewing key.
287    ///
288    /// This method extracts the diversifier from the given address and decrypts it as a
289    /// diversifier index, then verifies that this diversifier index produces the same
290    /// address. Decryption is attempted using both the internal and external parts of the
291    /// full viewing key.
292    ///
293    /// Returns the decrypted diversifier index and its scope, or `None` if the address
294    /// was not generated from this key.
295    pub fn decrypt_diversifier(
296        &self,
297        addr: &PaymentAddress,
298    ) -> Option<(zip32::DiversifierIndex, Scope)> {
299        let j_external = self.dk.diversifier_index(addr.diversifier());
300        if self.address(j_external).as_ref() == Some(addr) {
301            return Some((j_external, Scope::External));
302        }
303
304        let j_internal = self
305            .derive_internal()
306            .dk
307            .diversifier_index(addr.diversifier());
308        if self.address(j_internal).as_ref() == Some(addr) {
309            return Some((j_internal, Scope::Internal));
310        }
311
312        None
313    }
314}
315
316#[cfg(any(test, feature = "test-dependencies"))]
317pub mod testing {
318    use proptest::collection::vec;
319    use proptest::prelude::{any, prop_compose};
320
321    use crate::{
322        sapling::PaymentAddress,
323        zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
324    };
325
326    prop_compose! {
327        pub fn arb_extended_spending_key()(v in vec(any::<u8>(), 32..252)) -> ExtendedSpendingKey {
328            ExtendedSpendingKey::master(&v)
329        }
330    }
331
332    prop_compose! {
333        pub fn arb_shielded_addr()(extsk in arb_extended_spending_key()) -> PaymentAddress {
334            let extfvk = ExtendedFullViewingKey::from(&extsk);
335            extfvk.default_address().1
336        }
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use group::{Group, GroupEncoding};
343
344    use super::{DiversifiableFullViewingKey, FullViewingKey};
345    use crate::{constants::SPENDING_KEY_GENERATOR, zip32};
346
347    #[test]
348    fn ak_must_be_prime_order() {
349        let mut buf = [0; 96];
350        let identity = ironfish_jubjub::SubgroupPoint::identity();
351
352        // Set both ak and nk to the identity.
353        buf[0..32].copy_from_slice(&identity.to_bytes());
354        buf[32..64].copy_from_slice(&identity.to_bytes());
355
356        // ak is not allowed to be the identity.
357        assert_eq!(
358            FullViewingKey::read(&buf[..]).unwrap_err().to_string(),
359            "ak not of prime order"
360        );
361
362        // Set ak to a basepoint.
363        let basepoint = &*SPENDING_KEY_GENERATOR;
364        buf[0..32].copy_from_slice(&basepoint.to_bytes());
365
366        // nk is allowed to be the identity.
367        assert!(FullViewingKey::read(&buf[..]).is_ok());
368    }
369
370    #[test]
371    fn dfvk_round_trip() {
372        let dfvk = {
373            let extsk = zip32::ExtendedSpendingKey::master(&[]);
374            let extfvk = zip32::ExtendedFullViewingKey::from(&extsk);
375            DiversifiableFullViewingKey::from(extfvk)
376        };
377
378        // Check value -> bytes -> parsed round trip.
379        let dfvk_bytes = dfvk.to_bytes();
380        let dfvk_parsed = DiversifiableFullViewingKey::from_bytes(&dfvk_bytes).unwrap();
381        assert_eq!(dfvk_parsed.fvk.vk.ak, dfvk.fvk.vk.ak);
382        assert_eq!(dfvk_parsed.fvk.vk.nk, dfvk.fvk.vk.nk);
383        assert_eq!(dfvk_parsed.fvk.ovk, dfvk.fvk.ovk);
384        assert_eq!(dfvk_parsed.dk, dfvk.dk);
385
386        // Check bytes -> parsed -> bytes round trip.
387        assert_eq!(dfvk_parsed.to_bytes(), dfvk_bytes);
388    }
389}