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}