sapling_crypto/
value.rs

1//! Monetary values within the Sapling shielded pool.
2//!
3//! Values are represented in three places within the Sapling protocol:
4//! - [`NoteValue`], the value of an individual note. It is an unsigned 64-bit integer
5//!   (with maximum value [`MAX_NOTE_VALUE`]), and is serialized in a note plaintext.
6//! - [`ValueSum`], the sum of note values within a Sapling [`Bundle`]. It is represented
7//!   as an `i128` and places an upper bound on the maximum number of notes within a
8//!   single [`Bundle`].
9//! - `valueBalanceSapling`, which is a signed 63-bit integer. This is represented
10//!   by a user-defined type parameter on [`Bundle`], returned by
11//!   [`Bundle::value_balance`] and [`Builder::value_balance`].
12//!
13//! If your specific instantiation of the Sapling protocol requires a smaller bound on
14//! valid note values (for example, Zcash's `MAX_MONEY` fits into a 51-bit integer), you
15//! should enforce this in two ways:
16//!
17//! - Define your `valueBalanceSapling` type to enforce your valid value range. This can
18//!   be checked in its `TryFrom<i64>` implementation.
19//! - Define your own "amount" type for note values, and convert it to `NoteValue` prior
20//!   to calling [`Builder::add_output`].
21//!
22//! Inside the circuit, note values are constrained to be unsigned 64-bit integers.
23//!
24//! # Caution!
25//!
26//! An `i64` is _not_ a signed 64-bit integer! The [Rust documentation] calls `i64` the
27//! 64-bit signed integer type, which is true in the sense that its encoding in memory
28//! takes up 64 bits. Numerically, however, `i64` is a signed 63-bit integer.
29//!
30//! Fortunately, users of this crate should never need to construct [`ValueSum`] directly;
31//! you should only need to interact with [`NoteValue`] (which can be safely constructed
32//! from a `u64`) and `valueBalanceSapling` (which can be represented as an `i64`).
33//!
34//! [`Bundle`]: crate::Bundle
35//! [`Bundle::value_balance`]: crate::Bundle::value_balance
36//! [`Builder::value_balance`]: crate::builder::Builder::value_balance
37//! [`Builder::add_output`]: crate::builder::Builder::add_output
38//! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html
39
40use bitvec::{array::BitArray, order::Lsb0};
41use ff::{Field, PrimeField};
42use group::GroupEncoding;
43use rand::RngCore;
44use subtle::CtOption;
45
46use super::constants::{VALUE_COMMITMENT_RANDOMNESS_GENERATOR, VALUE_COMMITMENT_VALUE_GENERATOR};
47
48mod sums;
49pub use sums::{CommitmentSum, OverflowError, TrapdoorSum, ValueSum};
50
51/// Maximum note value.
52pub const MAX_NOTE_VALUE: u64 = u64::MAX;
53
54/// The non-negative value of an individual Sapling note.
55#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
56pub struct NoteValue(u64);
57
58impl NoteValue {
59    /// The zero note value.
60    pub const ZERO: NoteValue = NoteValue(0);
61
62    /// Returns the raw underlying value.
63    pub fn inner(&self) -> u64 {
64        self.0
65    }
66
67    /// Creates a note value from its raw numeric value.
68    ///
69    /// This only enforces that the value is an unsigned 64-bit integer. Callers should
70    /// enforce any additional constraints on the value's valid range themselves.
71    pub fn from_raw(value: u64) -> Self {
72        NoteValue(value)
73    }
74
75    pub(crate) fn from_bytes(bytes: [u8; 8]) -> Self {
76        NoteValue(u64::from_le_bytes(bytes))
77    }
78
79    pub(crate) fn to_le_bits(self) -> BitArray<[u8; 8], Lsb0> {
80        BitArray::<_, Lsb0>::new(self.0.to_le_bytes())
81    }
82}
83
84/// The blinding factor for a [`ValueCommitment`].
85#[derive(Clone, Debug)]
86pub struct ValueCommitTrapdoor(jubjub::Scalar);
87
88impl ValueCommitTrapdoor {
89    /// Generates a new value commitment trapdoor.
90    ///
91    /// This is public for access by `zcash_proofs`.
92    pub fn random(rng: impl RngCore) -> Self {
93        ValueCommitTrapdoor(jubjub::Scalar::random(rng))
94    }
95
96    /// Constructs `ValueCommitTrapdoor` from the byte representation of a scalar.
97    ///
98    /// Returns a `None` [`CtOption`] if `bytes` is not a canonical representation of a
99    /// Jubjub scalar.
100    ///
101    /// This is a low-level API, requiring a detailed understanding of the
102    /// [use of value commitment trapdoors][saplingbalance] in the Zcash protocol
103    /// to use correctly and securely. It is intended to be used in combination
104    /// with [`ValueCommitment::derive`].
105    ///
106    /// [saplingbalance]: https://zips.z.cash/protocol/protocol.pdf#saplingbalance
107    pub fn from_bytes(bytes: [u8; 32]) -> CtOption<Self> {
108        jubjub::Scalar::from_repr(bytes).map(ValueCommitTrapdoor)
109    }
110
111    /// Returns the inner Jubjub scalar representing this trapdoor.
112    ///
113    /// This is public for access by `zcash_proofs`.
114    pub fn inner(&self) -> jubjub::Scalar {
115        self.0
116    }
117}
118
119/// A commitment to a [`ValueSum`].
120///
121/// # Consensus rules
122///
123/// The Zcash Protocol Spec requires Sapling Spend Descriptions and Output Descriptions to
124/// not contain a small order `ValueCommitment`. However, the `ValueCommitment` type as
125/// specified (and implemented here) may contain a small order point. In practice, it will
126/// not occur:
127/// - [`ValueCommitment::derive`] will only produce a small order point if both the given
128///   [`NoteValue`] and [`ValueCommitTrapdoor`] are zero. However, the only constructor
129///   available for `ValueCommitTrapdoor` is [`ValueCommitTrapdoor::random`], which will
130///   produce zero with negligible probability (assuming a non-broken PRNG).
131/// - [`ValueCommitment::from_bytes_not_small_order`] enforces this by definition, and is
132///   the only constructor that can be used with data received over the network.
133#[derive(Clone, Debug)]
134pub struct ValueCommitment(jubjub::ExtendedPoint);
135
136impl ValueCommitment {
137    /// Derives a `ValueCommitment` by $\mathsf{ValueCommit^{Sapling}}$.
138    ///
139    /// Defined in [Zcash Protocol Spec § 5.4.8.3: Homomorphic Pedersen commitments (Sapling and Orchard)][concretehomomorphiccommit].
140    ///
141    /// [concretehomomorphiccommit]: https://zips.z.cash/protocol/protocol.pdf#concretehomomorphiccommit
142    pub fn derive(value: NoteValue, rcv: ValueCommitTrapdoor) -> Self {
143        let cv = (VALUE_COMMITMENT_VALUE_GENERATOR * jubjub::Scalar::from(value.0))
144            + (VALUE_COMMITMENT_RANDOMNESS_GENERATOR * rcv.0);
145
146        ValueCommitment(cv.into())
147    }
148
149    /// Returns the inner Jubjub point representing this value commitment.
150    ///
151    /// This is public for access by `zcash_proofs`.
152    pub fn as_inner(&self) -> &jubjub::ExtendedPoint {
153        &self.0
154    }
155
156    /// Deserializes a value commitment from its byte representation.
157    ///
158    /// Returns `None` if `bytes` is an invalid representation of a Jubjub point, or the
159    /// resulting point is of small order.
160    ///
161    /// This method can be used to enforce the "not small order" consensus rules defined
162    /// in [Zcash Protocol Spec § 4.4: Spend Descriptions][spenddesc] and
163    /// [§ 4.5: Output Descriptions][outputdesc].
164    ///
165    /// [spenddesc]: https://zips.z.cash/protocol/protocol.pdf#spenddesc
166    /// [outputdesc]: https://zips.z.cash/protocol/protocol.pdf#outputdesc
167    pub fn from_bytes_not_small_order(bytes: &[u8; 32]) -> CtOption<ValueCommitment> {
168        jubjub::ExtendedPoint::from_bytes(bytes)
169            .and_then(|cv| CtOption::new(ValueCommitment(cv), !cv.is_small_order()))
170    }
171
172    /// Serializes this value commitment to its canonical byte representation.
173    pub fn to_bytes(&self) -> [u8; 32] {
174        self.0.to_bytes()
175    }
176}
177
178/// Generators for property testing.
179#[cfg(any(test, feature = "test-dependencies"))]
180#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
181pub mod testing {
182    use proptest::prelude::*;
183
184    use super::{NoteValue, ValueCommitTrapdoor, MAX_NOTE_VALUE};
185
186    prop_compose! {
187        /// Generate an arbitrary value in the range of valid nonnegative amounts.
188        pub fn arb_note_value()(value in 0u64..MAX_NOTE_VALUE) -> NoteValue {
189            NoteValue(value)
190        }
191    }
192
193    prop_compose! {
194        /// Generate an arbitrary value in the range of valid positive amounts less than a
195        /// specified value.
196        pub fn arb_note_value_bounded(max: u64)(value in 0u64..max) -> NoteValue {
197            NoteValue(value)
198        }
199    }
200
201    prop_compose! {
202        /// Generate an arbitrary value in the range of valid positive amounts less than a
203        /// specified value.
204        pub fn arb_positive_note_value(max: u64)(value in 1u64..max) -> NoteValue {
205            NoteValue(value)
206        }
207    }
208
209    prop_compose! {
210        /// Generate an arbitrary Jubjub scalar.
211        fn arb_scalar()(bytes in prop::array::uniform32(0u8..)) -> jubjub::Scalar {
212            // Instead of rejecting out-of-range bytes, let's reduce them.
213            let mut buf = [0; 64];
214            buf[..32].copy_from_slice(&bytes);
215            jubjub::Scalar::from_bytes_wide(&buf)
216        }
217    }
218
219    prop_compose! {
220        /// Generate an arbitrary ValueCommitTrapdoor
221        pub fn arb_trapdoor()(rcv in arb_scalar()) -> ValueCommitTrapdoor {
222            ValueCommitTrapdoor(rcv)
223        }
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use proptest::prelude::*;
230
231    use super::{
232        testing::{arb_note_value_bounded, arb_trapdoor},
233        CommitmentSum, OverflowError, TrapdoorSum, ValueCommitment, ValueSum,
234    };
235
236    proptest! {
237        #[test]
238        fn bsk_consistent_with_bvk(
239            values in (1usize..10).prop_flat_map(|n_values| prop::collection::vec(
240                (arb_note_value_bounded((i64::MAX as u64) / (n_values as u64)), arb_trapdoor()),
241                n_values,
242            ))
243        ) {
244            let value_balance: i64 = values
245                .iter()
246                .map(|(value, _)| value)
247                .sum::<Result<ValueSum, OverflowError>>()
248                .expect("we generate values that won't overflow")
249                .try_into()
250                .unwrap();
251
252            let bsk = values
253                .iter()
254                .map(|(_, rcv)| rcv)
255                .sum::<TrapdoorSum>()
256                .into_bsk();
257
258            let bvk = values
259                .into_iter()
260                .map(|(value, rcv)| ValueCommitment::derive(value, rcv))
261                .sum::<CommitmentSum>()
262                .into_bvk(value_balance);
263
264            assert_eq!(redjubjub::VerificationKey::from(&bsk), bvk);
265        }
266    }
267}