zebra_chain/orchard/
commitment.rs

1//! Note and value commitments.
2
3use std::{fmt, io};
4
5use group::{
6    ff::{FromUniformBytes, PrimeField},
7    prime::PrimeCurveAffine,
8    GroupEncoding,
9};
10use halo2::{
11    arithmetic::{Coordinates, CurveAffine},
12    pasta::pallas,
13};
14use lazy_static::lazy_static;
15use rand_core::{CryptoRng, RngCore};
16
17use crate::{
18    amount::Amount,
19    error::RandError,
20    serialization::{
21        serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
22    },
23};
24
25use super::sinsemilla::*;
26
27/// Generates a random scalar from the scalar field 𝔽_{q_P}.
28///
29/// <https://zips.z.cash/protocol/nu5.pdf#pallasandvesta>
30pub fn generate_trapdoor<T>(csprng: &mut T) -> Result<pallas::Scalar, RandError>
31where
32    T: RngCore + CryptoRng,
33{
34    let mut bytes = [0u8; 64];
35    csprng
36        .try_fill_bytes(&mut bytes)
37        .map_err(|_| RandError::FillBytes)?;
38    // pallas::Scalar::from_uniform_bytes() reduces the input modulo q_P under the hood.
39    Ok(pallas::Scalar::from_uniform_bytes(&bytes))
40}
41
42/// The randomness used in the Simsemilla hash for note commitment.
43#[derive(Copy, Clone, Debug, PartialEq, Eq)]
44pub struct CommitmentRandomness(pallas::Scalar);
45
46/// Note commitments for the output notes.
47#[derive(Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
48pub struct NoteCommitment(#[serde(with = "serde_helpers::Affine")] pub pallas::Affine);
49
50impl fmt::Debug for NoteCommitment {
51    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
52        let mut d = f.debug_struct("NoteCommitment");
53
54        let option: Option<Coordinates<pallas::Affine>> = self.0.coordinates().into();
55
56        match option {
57            Some(coordinates) => d
58                .field("x", &hex::encode(coordinates.x().to_repr()))
59                .field("y", &hex::encode(coordinates.y().to_repr()))
60                .finish(),
61            None => d
62                .field("x", &hex::encode(pallas::Base::zero().to_repr()))
63                .field("y", &hex::encode(pallas::Base::zero().to_repr()))
64                .finish(),
65        }
66    }
67}
68
69impl From<pallas::Point> for NoteCommitment {
70    fn from(projective_point: pallas::Point) -> Self {
71        Self(pallas::Affine::from(projective_point))
72    }
73}
74
75impl From<NoteCommitment> for [u8; 32] {
76    fn from(cm: NoteCommitment) -> [u8; 32] {
77        cm.0.to_bytes()
78    }
79}
80
81impl TryFrom<[u8; 32]> for NoteCommitment {
82    type Error = &'static str;
83
84    fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
85        let possible_point = pallas::Affine::from_bytes(&bytes);
86
87        if possible_point.is_some().into() {
88            Ok(Self(possible_point.unwrap()))
89        } else {
90            Err("Invalid pallas::Affine value")
91        }
92    }
93}
94
95impl NoteCommitment {
96    /// Extract the x coordinate of the note commitment.
97    pub fn extract_x(&self) -> pallas::Base {
98        extract_p(self.0.into())
99    }
100}
101
102/// A homomorphic Pedersen commitment to the net value of a _note_, used in
103/// Action descriptions.
104///
105/// <https://zips.z.cash/protocol/nu5.pdf#concretehomomorphiccommit>
106#[derive(Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
107pub struct ValueCommitment(#[serde(with = "serde_helpers::Affine")] pub pallas::Affine);
108
109impl<'a> std::ops::Add<&'a ValueCommitment> for ValueCommitment {
110    type Output = Self;
111
112    fn add(self, rhs: &'a ValueCommitment) -> Self::Output {
113        self + *rhs
114    }
115}
116
117impl std::ops::Add<ValueCommitment> for ValueCommitment {
118    type Output = Self;
119
120    fn add(self, rhs: ValueCommitment) -> Self::Output {
121        ValueCommitment((self.0 + rhs.0).into())
122    }
123}
124
125impl std::ops::AddAssign<ValueCommitment> for ValueCommitment {
126    fn add_assign(&mut self, rhs: ValueCommitment) {
127        *self = *self + rhs
128    }
129}
130
131impl fmt::Debug for ValueCommitment {
132    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133        let mut d = f.debug_struct("ValueCommitment");
134
135        let option: Option<Coordinates<pallas::Affine>> = self.0.coordinates().into();
136
137        match option {
138            Some(coordinates) => d
139                .field("x", &hex::encode(coordinates.x().to_repr()))
140                .field("y", &hex::encode(coordinates.y().to_repr()))
141                .finish(),
142            None => d
143                .field("x", &hex::encode(pallas::Base::zero().to_repr()))
144                .field("y", &hex::encode(pallas::Base::zero().to_repr()))
145                .finish(),
146        }
147    }
148}
149
150impl From<pallas::Point> for ValueCommitment {
151    fn from(projective_point: pallas::Point) -> Self {
152        Self(pallas::Affine::from(projective_point))
153    }
154}
155
156/// LEBS2OSP256(repr_P(cv))
157///
158/// <https://zips.z.cash/protocol/nu5.pdf#pallasandvesta>
159impl From<ValueCommitment> for [u8; 32] {
160    fn from(cm: ValueCommitment) -> [u8; 32] {
161        cm.0.to_bytes()
162    }
163}
164
165impl<'a> std::ops::Sub<&'a ValueCommitment> for ValueCommitment {
166    type Output = Self;
167
168    fn sub(self, rhs: &'a ValueCommitment) -> Self::Output {
169        self - *rhs
170    }
171}
172
173impl std::ops::Sub<ValueCommitment> for ValueCommitment {
174    type Output = Self;
175
176    fn sub(self, rhs: ValueCommitment) -> Self::Output {
177        ValueCommitment((self.0 - rhs.0).into())
178    }
179}
180
181impl std::ops::SubAssign<ValueCommitment> for ValueCommitment {
182    fn sub_assign(&mut self, rhs: ValueCommitment) {
183        *self = *self - rhs;
184    }
185}
186
187impl std::iter::Sum for ValueCommitment {
188    fn sum<I>(iter: I) -> Self
189    where
190        I: Iterator<Item = Self>,
191    {
192        iter.fold(
193            ValueCommitment(pallas::Affine::identity()),
194            std::ops::Add::add,
195        )
196    }
197}
198
199/// LEBS2OSP256(repr_P(cv))
200///
201/// <https://zips.z.cash/protocol/nu5.pdf#pallasandvesta>
202impl TryFrom<[u8; 32]> for ValueCommitment {
203    type Error = &'static str;
204
205    fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
206        let possible_point = pallas::Affine::from_bytes(&bytes);
207
208        if possible_point.is_some().into() {
209            Ok(Self(possible_point.unwrap()))
210        } else {
211            Err("Invalid pallas::Affine value")
212        }
213    }
214}
215
216impl ZcashSerialize for ValueCommitment {
217    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
218        writer.write_all(&<[u8; 32]>::from(*self)[..])?;
219        Ok(())
220    }
221}
222
223impl ZcashDeserialize for ValueCommitment {
224    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
225        Self::try_from(reader.read_32_bytes()?).map_err(SerializationError::Parse)
226    }
227}
228
229impl ValueCommitment {
230    /// Generate a new _ValueCommitment_.
231    ///
232    /// <https://zips.z.cash/protocol/nu5.pdf#concretehomomorphiccommit>
233    pub fn randomized<T>(csprng: &mut T, value: Amount) -> Result<Self, RandError>
234    where
235        T: RngCore + CryptoRng,
236    {
237        let rcv = generate_trapdoor(csprng)?;
238
239        Ok(Self::new(rcv, value))
240    }
241
242    /// Generate a new `ValueCommitment` from an existing `rcv on a `value`.
243    ///
244    /// ValueCommit^Orchard(v) :=
245    ///
246    /// <https://zips.z.cash/protocol/nu5.pdf#concretehomomorphiccommit>
247    #[allow(non_snake_case)]
248    pub fn new(rcv: pallas::Scalar, value: Amount) -> Self {
249        let v = pallas::Scalar::from(value);
250        Self::from(*V * v + *R * rcv)
251    }
252}
253
254lazy_static! {
255    static ref V: pallas::Point = pallas_group_hash(b"z.cash:Orchard-cv", b"v");
256    static ref R: pallas::Point = pallas_group_hash(b"z.cash:Orchard-cv", b"r");
257}
258
259#[cfg(test)]
260mod tests {
261
262    use std::ops::Neg;
263
264    use group::Group;
265
266    use super::*;
267
268    #[test]
269    fn add() {
270        let _init_guard = zebra_test::init();
271
272        let identity = ValueCommitment(pallas::Affine::identity());
273
274        let g = ValueCommitment(pallas::Affine::generator());
275
276        assert_eq!(identity + g, g);
277    }
278
279    #[test]
280    fn add_assign() {
281        let _init_guard = zebra_test::init();
282
283        let mut identity = ValueCommitment(pallas::Affine::identity());
284
285        let g = ValueCommitment(pallas::Affine::generator());
286
287        identity += g;
288        let new_g = identity;
289
290        assert_eq!(new_g, g);
291    }
292
293    #[test]
294    fn sub() {
295        let _init_guard = zebra_test::init();
296
297        let g_point = pallas::Affine::generator();
298
299        let identity = ValueCommitment(pallas::Affine::identity());
300
301        let g = ValueCommitment(g_point);
302
303        assert_eq!(identity - g, ValueCommitment(g_point.neg()));
304    }
305
306    #[test]
307    fn sub_assign() {
308        let _init_guard = zebra_test::init();
309
310        let g_point = pallas::Affine::generator();
311
312        let mut identity = ValueCommitment(pallas::Affine::identity());
313
314        let g = ValueCommitment(g_point);
315
316        identity -= g;
317        let new_g = identity;
318
319        assert_eq!(new_g, ValueCommitment(g_point.neg()));
320    }
321
322    #[test]
323    fn sum() {
324        let _init_guard = zebra_test::init();
325
326        let g_point = pallas::Affine::generator();
327
328        let g = ValueCommitment(g_point);
329        let other_g = ValueCommitment(g_point);
330
331        let sum: ValueCommitment = vec![g, other_g].into_iter().sum();
332
333        let doubled_g = ValueCommitment(g_point.to_curve().double().into());
334
335        assert_eq!(sum, doubled_g);
336    }
337}