nam_reddsa/
orchard.rs

1//! Signature types for the Orchard protocol.
2
3#[cfg(feature = "alloc")]
4use alloc::vec::Vec;
5#[cfg(feature = "alloc")]
6use core::borrow::Borrow;
7
8use group::GroupEncoding;
9#[cfg(feature = "alloc")]
10use group::{ff::PrimeField, Group};
11use pasta_curves::pallas;
12
13use crate::{private, SigType};
14
15#[cfg(feature = "alloc")]
16use crate::scalar_mul::{LookupTable5, NonAdjacentForm, VartimeMultiscalarMul};
17
18#[cfg(test)]
19mod tests;
20
21/// The byte-encoding of the basepoint for the Orchard `SpendAuthSig` on the [Pallas curve][pallasandvesta].
22///
23/// [pallasandvesta]: https://zips.z.cash/protocol/nu5.pdf#pallasandvesta
24// Reproducible by pallas::Point::hash_to_curve("z.cash:Orchard")(b"G").to_bytes()
25const ORCHARD_SPENDAUTHSIG_BASEPOINT_BYTES: [u8; 32] = [
26    99, 201, 117, 184, 132, 114, 26, 141, 12, 161, 112, 123, 227, 12, 127, 12, 95, 68, 95, 62, 124,
27    24, 141, 59, 6, 214, 241, 40, 179, 35, 85, 183,
28];
29
30/// The byte-encoding of the basepoint for the Orchard `BindingSig` on the Pallas curve.
31// Reproducible by pallas::Point::hash_to_curve("z.cash:Orchard-cv")(b"r").to_bytes()
32const ORCHARD_BINDINGSIG_BASEPOINT_BYTES: [u8; 32] = [
33    145, 90, 60, 136, 104, 198, 195, 14, 47, 128, 144, 238, 69, 215, 110, 64, 72, 32, 141, 234, 91,
34    35, 102, 79, 187, 9, 164, 15, 85, 68, 244, 7,
35];
36
37/// A type variable corresponding to Zcash's `OrchardSpendAuthSig`.
38#[derive(Copy, Clone, PartialEq, Eq, Debug)]
39pub enum SpendAuth {}
40// This should not exist, but is necessary to use zeroize::DefaultIsZeroes.
41impl Default for SpendAuth {
42    fn default() -> Self {
43        unimplemented!()
44    }
45}
46impl SigType for SpendAuth {}
47impl super::SpendAuth for SpendAuth {}
48
49/// A type variable corresponding to Zcash's `OrchardBindingSig`.
50#[derive(Copy, Clone, PartialEq, Eq, Debug)]
51pub enum Binding {}
52// This should not exist, but is necessary to use zeroize::DefaultIsZeroes.
53impl Default for Binding {
54    fn default() -> Self {
55        unimplemented!()
56    }
57}
58impl SigType for Binding {}
59impl super::Binding for Binding {}
60
61impl private::SealedScalar for pallas::Scalar {
62    fn from_bytes_wide(bytes: &[u8; 64]) -> Self {
63        <pallas::Scalar as group::ff::FromUniformBytes<64>>::from_uniform_bytes(bytes)
64    }
65    fn from_raw(val: [u64; 4]) -> Self {
66        pallas::Scalar::from_raw(val)
67    }
68}
69impl private::Sealed<SpendAuth> for SpendAuth {
70    const H_STAR_PERSONALIZATION: &'static [u8; 16] = b"Zcash_RedPallasH";
71    type Point = pallas::Point;
72    type Scalar = pallas::Scalar;
73
74    fn basepoint() -> pallas::Point {
75        pallas::Point::from_bytes(&ORCHARD_SPENDAUTHSIG_BASEPOINT_BYTES).unwrap()
76    }
77}
78impl private::Sealed<Binding> for Binding {
79    const H_STAR_PERSONALIZATION: &'static [u8; 16] = b"Zcash_RedPallasH";
80    type Point = pallas::Point;
81    type Scalar = pallas::Scalar;
82
83    fn basepoint() -> pallas::Point {
84        pallas::Point::from_bytes(&ORCHARD_BINDINGSIG_BASEPOINT_BYTES).unwrap()
85    }
86}
87
88#[cfg(feature = "alloc")]
89impl NonAdjacentForm for pallas::Scalar {
90    fn inner_to_bytes(&self) -> [u8; 32] {
91        self.to_repr()
92    }
93
94    /// The NAF length for Pallas is 255 since Pallas' order is about 2<sup>254</sup> +
95    /// 2<sup>125.1</sup>.
96    fn naf_length() -> usize {
97        255
98    }
99}
100
101#[cfg(feature = "alloc")]
102impl<'a> From<&'a pallas::Point> for LookupTable5<pallas::Point> {
103    #[allow(non_snake_case)]
104    fn from(A: &'a pallas::Point) -> Self {
105        let mut Ai = [*A; 8];
106        let A2 = A.double();
107        for i in 0..7 {
108            Ai[i + 1] = A2 + Ai[i];
109        }
110        // Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A]
111        LookupTable5(Ai)
112    }
113}
114
115#[cfg(feature = "alloc")]
116impl VartimeMultiscalarMul for pallas::Point {
117    type Scalar = pallas::Scalar;
118    type Point = pallas::Point;
119
120    #[allow(non_snake_case)]
121    fn optional_multiscalar_mul<I, J>(scalars: I, points: J) -> Option<pallas::Point>
122    where
123        I: IntoIterator,
124        I::Item: Borrow<Self::Scalar>,
125        J: IntoIterator<Item = Option<pallas::Point>>,
126    {
127        let nafs: Vec<_> = scalars
128            .into_iter()
129            .map(|c| c.borrow().non_adjacent_form(5))
130            .collect();
131
132        let lookup_tables = points
133            .into_iter()
134            .map(|P_opt| P_opt.map(|P| LookupTable5::<pallas::Point>::from(&P)))
135            .collect::<Option<Vec<_>>>()?;
136
137        let mut r = pallas::Point::identity();
138        let naf_size = Self::Scalar::naf_length();
139
140        for i in (0..naf_size).rev() {
141            let mut t = r.double();
142
143            for (naf, lookup_table) in nafs.iter().zip(lookup_tables.iter()) {
144                #[allow(clippy::comparison_chain)]
145                if naf[i] > 0 {
146                    t += lookup_table.select(naf[i] as usize);
147                } else if naf[i] < 0 {
148                    t -= lookup_table.select(-naf[i] as usize);
149                }
150            }
151
152            r = t;
153        }
154
155        Some(r)
156    }
157}