2021-01-20 12:09:09 -08:00
|
|
|
//! Monetary values within the Orchard shielded pool.
|
|
|
|
//!
|
2021-12-20 08:05:01 -08:00
|
|
|
//! Values are represented in three places within the Orchard protocol:
|
|
|
|
//! - [`NoteValue`], the value of an individual note. It is an unsigned 64-bit integer
|
|
|
|
//! (with maximum value [`MAX_NOTE_VALUE`]), and is serialized in a note plaintext.
|
|
|
|
//! - [`ValueSum`], the sum of note values within an Orchard [`Action`] or [`Bundle`].
|
|
|
|
//! It is a signed 64-bit integer (with range [`VALUE_SUM_RANGE`]).
|
2022-08-23 12:23:15 -07:00
|
|
|
//! - `valueBalanceOrchard`, which is a signed 63-bit integer. This is represented
|
|
|
|
//! by a user-defined type parameter on [`Bundle`], returned by
|
|
|
|
//! [`Bundle::value_balance`] and [`Builder::value_balance`].
|
2021-01-20 12:09:09 -08:00
|
|
|
//!
|
2021-12-20 08:05:01 -08:00
|
|
|
//! If your specific instantiation of the Orchard protocol requires a smaller bound on
|
|
|
|
//! valid note values (for example, Zcash's `MAX_MONEY` fits into a 51-bit integer), you
|
|
|
|
//! should enforce this in two ways:
|
|
|
|
//!
|
|
|
|
//! - Define your `valueBalanceOrchard` type to enforce your valid value range. This can
|
|
|
|
//! be checked in its `TryFrom<i64>` implementation.
|
|
|
|
//! - Define your own "amount" type for note values, and convert it to `NoteValue` prior
|
|
|
|
//! to calling [`Builder::add_recipient`].
|
2021-01-20 12:09:09 -08:00
|
|
|
//!
|
2021-04-14 21:13:41 -07:00
|
|
|
//! Inside the circuit, note values are constrained to be unsigned 64-bit integers.
|
2021-01-20 12:09:09 -08:00
|
|
|
//!
|
2021-12-20 08:05:01 -08:00
|
|
|
//! # Caution!
|
|
|
|
//!
|
|
|
|
//! An `i64` is _not_ a signed 64-bit integer! The [Rust documentation] calls `i64` the
|
|
|
|
//! 64-bit signed integer type, which is true in the sense that its encoding in memory
|
|
|
|
//! takes up 64 bits. Numerically, however, `i64` is a signed 63-bit integer.
|
|
|
|
//!
|
|
|
|
//! Fortunately, users of this crate should never need to construct [`ValueSum`] directly;
|
|
|
|
//! you should only need to interact with [`NoteValue`] (which can be safely constructed
|
|
|
|
//! from a `u64`) and `valueBalanceOrchard` (which can be represented as an `i64`).
|
|
|
|
//!
|
2022-04-28 14:46:24 -07:00
|
|
|
//! [`Action`]: crate::action::Action
|
2021-01-20 12:09:09 -08:00
|
|
|
//! [`Bundle`]: crate::bundle::Bundle
|
2021-12-20 08:05:01 -08:00
|
|
|
//! [`Bundle::value_balance`]: crate::bundle::Bundle::value_balance
|
2022-08-23 12:23:15 -07:00
|
|
|
//! [`Builder::value_balance`]: crate::builder::Builder::value_balance
|
2021-12-20 08:05:01 -08:00
|
|
|
//! [`Builder::add_recipient`]: crate::builder::Builder::add_recipient
|
|
|
|
//! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html
|
2021-01-20 12:09:09 -08:00
|
|
|
|
2022-04-28 13:20:23 -07:00
|
|
|
use core::fmt::{self, Debug};
|
|
|
|
use core::iter::Sum;
|
|
|
|
use core::ops::{Add, RangeInclusive, Sub};
|
2021-04-14 21:13:41 -07:00
|
|
|
|
2021-03-12 16:04:13 -08:00
|
|
|
use bitvec::{array::BitArray, order::Lsb0};
|
2021-04-14 21:13:41 -07:00
|
|
|
use ff::{Field, PrimeField};
|
2021-07-19 09:42:22 -07:00
|
|
|
use group::{Curve, Group, GroupEncoding};
|
2022-05-03 13:26:41 -07:00
|
|
|
use halo2_proofs::plonk::Assigned;
|
2021-04-14 21:13:41 -07:00
|
|
|
use pasta_curves::{
|
2021-12-07 09:47:03 -08:00
|
|
|
arithmetic::{CurveAffine, CurveExt},
|
2021-04-14 21:13:41 -07:00
|
|
|
pallas,
|
|
|
|
};
|
|
|
|
use rand::RngCore;
|
2021-04-23 11:53:13 -07:00
|
|
|
use subtle::CtOption;
|
2021-04-14 21:13:41 -07:00
|
|
|
|
2021-06-23 16:07:12 -07:00
|
|
|
use crate::{
|
2021-08-19 21:21:46 -07:00
|
|
|
constants::fixed_bases::{
|
2021-06-23 16:07:12 -07:00
|
|
|
VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_R_BYTES, VALUE_COMMITMENT_V_BYTES,
|
|
|
|
},
|
|
|
|
primitives::redpallas::{self, Binding},
|
|
|
|
};
|
2021-03-12 16:04:13 -08:00
|
|
|
|
2021-05-05 16:19:42 -07:00
|
|
|
/// Maximum note value.
|
|
|
|
pub const MAX_NOTE_VALUE: u64 = u64::MAX;
|
|
|
|
|
2021-05-05 10:10:52 -07:00
|
|
|
/// The valid range of the scalar multiplication used in ValueCommit^Orchard.
|
|
|
|
///
|
|
|
|
/// Defined in a note in [Zcash Protocol Spec § 4.17.4: Action Statement (Orchard)][actionstatement].
|
|
|
|
///
|
|
|
|
/// [actionstatement]: https://zips.z.cash/protocol/nu5.pdf#actionstatement
|
2021-05-05 16:19:42 -07:00
|
|
|
pub const VALUE_SUM_RANGE: RangeInclusive<i128> =
|
|
|
|
-(MAX_NOTE_VALUE as i128)..=MAX_NOTE_VALUE as i128;
|
2021-05-05 10:10:52 -07:00
|
|
|
|
2021-04-14 21:13:41 -07:00
|
|
|
/// A value operation overflowed.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct OverflowError;
|
|
|
|
|
|
|
|
impl fmt::Display for OverflowError {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(f, "Orchard value operation overflowed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::error::Error for OverflowError {}
|
|
|
|
|
|
|
|
/// The non-negative value of an individual Orchard note.
|
|
|
|
#[derive(Clone, Copy, Debug, Default)]
|
2021-01-21 04:16:50 -08:00
|
|
|
pub struct NoteValue(u64);
|
2021-01-20 12:09:09 -08:00
|
|
|
|
2021-03-12 16:04:13 -08:00
|
|
|
impl NoteValue {
|
2021-04-20 16:00:19 -07:00
|
|
|
pub(crate) fn zero() -> Self {
|
|
|
|
// Default for u64 is zero.
|
|
|
|
Default::default()
|
|
|
|
}
|
|
|
|
|
2021-08-04 12:11:20 -07:00
|
|
|
/// Returns the raw underlying value.
|
|
|
|
pub fn inner(&self) -> u64 {
|
2021-06-06 04:13:20 -07:00
|
|
|
self.0
|
|
|
|
}
|
|
|
|
|
2021-04-26 17:27:23 -07:00
|
|
|
/// Creates a note value from its raw numeric value.
|
|
|
|
///
|
|
|
|
/// This only enforces that the value is an unsigned 64-bit integer. Callers should
|
|
|
|
/// enforce any additional constraints on the value's valid range themselves.
|
|
|
|
pub fn from_raw(value: u64) -> Self {
|
|
|
|
NoteValue(value)
|
|
|
|
}
|
|
|
|
|
2021-06-02 15:22:22 -07:00
|
|
|
pub(crate) fn from_bytes(bytes: [u8; 8]) -> Self {
|
|
|
|
NoteValue(u64::from_le_bytes(bytes))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn to_bytes(self) -> [u8; 8] {
|
|
|
|
self.0.to_le_bytes()
|
|
|
|
}
|
|
|
|
|
2022-05-05 09:47:56 -07:00
|
|
|
pub(crate) fn to_le_bits(self) -> BitArray<[u8; 8], Lsb0> {
|
|
|
|
BitArray::<_, Lsb0>::new(self.0.to_le_bytes())
|
2021-03-12 16:04:13 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-03 13:26:41 -07:00
|
|
|
impl From<&NoteValue> for Assigned<pallas::Base> {
|
|
|
|
fn from(v: &NoteValue) -> Self {
|
|
|
|
pallas::Base::from(v.inner()).into()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:13:41 -07:00
|
|
|
impl Sub for NoteValue {
|
2022-04-29 06:54:42 -07:00
|
|
|
type Output = ValueSum;
|
2021-04-14 21:13:41 -07:00
|
|
|
|
2021-05-04 15:33:08 -07:00
|
|
|
#[allow(clippy::suspicious_arithmetic_impl)]
|
2021-04-14 21:13:41 -07:00
|
|
|
fn sub(self, rhs: Self) -> Self::Output {
|
2021-04-23 11:47:22 -07:00
|
|
|
let a = self.0 as i128;
|
|
|
|
let b = rhs.0 as i128;
|
2021-04-27 12:56:36 -07:00
|
|
|
a.checked_sub(b)
|
2021-05-05 10:10:52 -07:00
|
|
|
.filter(|v| VALUE_SUM_RANGE.contains(v))
|
2021-04-27 12:56:36 -07:00
|
|
|
.map(ValueSum)
|
2022-04-29 06:54:42 -07:00
|
|
|
.expect("u64 - u64 result is always in VALUE_SUM_RANGE")
|
2021-04-14 21:13:41 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-29 06:54:42 -07:00
|
|
|
pub(crate) enum Sign {
|
|
|
|
Positive,
|
|
|
|
Negative,
|
|
|
|
}
|
|
|
|
|
2021-12-20 08:08:44 -08:00
|
|
|
/// A sum of Orchard note values.
|
2021-04-14 21:14:34 -07:00
|
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
2021-04-23 11:47:22 -07:00
|
|
|
pub struct ValueSum(i128);
|
2021-01-20 12:09:09 -08:00
|
|
|
|
2021-04-26 17:27:23 -07:00
|
|
|
impl ValueSum {
|
2021-04-27 14:06:33 -07:00
|
|
|
pub(crate) fn zero() -> Self {
|
2022-04-29 06:54:42 -07:00
|
|
|
// Default for i128 is zero.
|
2021-04-27 14:06:33 -07:00
|
|
|
Default::default()
|
|
|
|
}
|
2022-02-15 14:47:05 -08:00
|
|
|
|
2022-04-29 06:54:42 -07:00
|
|
|
/// Creates a value sum from a raw i64 (which is always in range for this type).
|
2022-02-15 14:47:05 -08:00
|
|
|
///
|
|
|
|
/// This only enforces that the value is a signed 63-bit integer. We use it internally
|
|
|
|
/// in `Bundle::binding_validating_key`, where we are converting from the user-defined
|
|
|
|
/// `valueBalance` type that enforces any additional constraints on the value's valid
|
|
|
|
/// range.
|
|
|
|
pub(crate) fn from_raw(value: i64) -> Self {
|
|
|
|
ValueSum(value as i128)
|
|
|
|
}
|
2022-04-29 06:54:42 -07:00
|
|
|
|
|
|
|
/// Splits this value sum into its magnitude and sign.
|
|
|
|
pub(crate) fn magnitude_sign(&self) -> (u64, Sign) {
|
|
|
|
let (magnitude, sign) = if self.0.is_negative() {
|
|
|
|
(-self.0, Sign::Negative)
|
|
|
|
} else {
|
|
|
|
(self.0, Sign::Positive)
|
|
|
|
};
|
|
|
|
(
|
|
|
|
u64::try_from(magnitude)
|
|
|
|
.expect("ValueSum magnitude is in range for u64 by construction"),
|
|
|
|
sign,
|
|
|
|
)
|
|
|
|
}
|
2021-04-26 17:27:23 -07:00
|
|
|
}
|
|
|
|
|
2021-04-14 21:13:41 -07:00
|
|
|
impl Add for ValueSum {
|
2021-04-23 11:47:22 -07:00
|
|
|
type Output = Option<ValueSum>;
|
2021-04-14 21:13:41 -07:00
|
|
|
|
2021-05-04 15:33:08 -07:00
|
|
|
#[allow(clippy::suspicious_arithmetic_impl)]
|
2021-04-14 21:13:41 -07:00
|
|
|
fn add(self, rhs: Self) -> Self::Output {
|
2021-04-27 12:56:36 -07:00
|
|
|
self.0
|
|
|
|
.checked_add(rhs.0)
|
2021-05-05 10:10:52 -07:00
|
|
|
.filter(|v| VALUE_SUM_RANGE.contains(v))
|
2021-04-27 12:56:36 -07:00
|
|
|
.map(ValueSum)
|
2021-04-14 21:13:41 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Sum<&'a ValueSum> for Result<ValueSum, OverflowError> {
|
|
|
|
fn sum<I: Iterator<Item = &'a ValueSum>>(iter: I) -> Self {
|
2021-04-30 06:59:46 -07:00
|
|
|
iter.fold(Ok(ValueSum(0)), |acc, v| (acc? + *v).ok_or(OverflowError))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Sum<ValueSum> for Result<ValueSum, OverflowError> {
|
|
|
|
fn sum<I: Iterator<Item = ValueSum>>(iter: I) -> Self {
|
|
|
|
iter.fold(Ok(ValueSum(0)), |acc, v| (acc? + v).ok_or(OverflowError))
|
2021-04-23 11:47:22 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<ValueSum> for i64 {
|
|
|
|
type Error = OverflowError;
|
|
|
|
|
|
|
|
fn try_from(v: ValueSum) -> Result<i64, Self::Error> {
|
|
|
|
i64::try_from(v.0).map_err(|_| OverflowError)
|
2021-04-14 21:13:41 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The blinding factor for a [`ValueCommitment`].
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct ValueCommitTrapdoor(pallas::Scalar);
|
|
|
|
|
2021-07-22 07:14:34 -07:00
|
|
|
impl ValueCommitTrapdoor {
|
|
|
|
pub(crate) fn inner(&self) -> pallas::Scalar {
|
|
|
|
self.0
|
2021-06-06 04:13:20 -07:00
|
|
|
}
|
2022-08-11 03:24:15 -07:00
|
|
|
|
|
|
|
/// Constructs `ValueCommitTrapdoor` from the byte reprsentation of a scalar
|
|
|
|
pub fn from_bytes(bytes: [u8; 32]) -> CtOption<Self> {
|
|
|
|
pallas::Scalar::from_repr(bytes).map(ValueCommitTrapdoor)
|
|
|
|
}
|
2021-06-06 04:13:20 -07:00
|
|
|
}
|
|
|
|
|
2021-04-14 21:13:41 -07:00
|
|
|
impl Add<&ValueCommitTrapdoor> for ValueCommitTrapdoor {
|
|
|
|
type Output = ValueCommitTrapdoor;
|
|
|
|
|
|
|
|
fn add(self, rhs: &Self) -> Self::Output {
|
|
|
|
ValueCommitTrapdoor(self.0 + rhs.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Sum<&'a ValueCommitTrapdoor> for ValueCommitTrapdoor {
|
|
|
|
fn sum<I: Iterator<Item = &'a ValueCommitTrapdoor>>(iter: I) -> Self {
|
|
|
|
iter.fold(ValueCommitTrapdoor::zero(), |acc, cv| acc + cv)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ValueCommitTrapdoor {
|
|
|
|
/// Generates a new value commitment trapdoor.
|
|
|
|
pub(crate) fn random(rng: impl RngCore) -> Self {
|
|
|
|
ValueCommitTrapdoor(pallas::Scalar::random(rng))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the zero trapdoor, which provides no blinding.
|
|
|
|
pub(crate) fn zero() -> Self {
|
|
|
|
ValueCommitTrapdoor(pallas::Scalar::zero())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn into_bsk(self) -> redpallas::SigningKey<Binding> {
|
|
|
|
// TODO: impl From<pallas::Scalar> for redpallas::SigningKey.
|
|
|
|
self.0.to_repr().try_into().unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-20 12:09:09 -08:00
|
|
|
/// A commitment to a [`ValueSum`].
|
2021-04-14 21:13:41 -07:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct ValueCommitment(pallas::Point);
|
|
|
|
|
|
|
|
impl Add<&ValueCommitment> for ValueCommitment {
|
|
|
|
type Output = ValueCommitment;
|
|
|
|
|
|
|
|
fn add(self, rhs: &Self) -> Self::Output {
|
|
|
|
ValueCommitment(self.0 + rhs.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Sub for ValueCommitment {
|
|
|
|
type Output = ValueCommitment;
|
|
|
|
|
|
|
|
fn sub(self, rhs: Self) -> Self::Output {
|
|
|
|
ValueCommitment(self.0 - rhs.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Sum for ValueCommitment {
|
|
|
|
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
|
|
|
iter.fold(ValueCommitment(pallas::Point::identity()), |acc, cv| {
|
|
|
|
acc + &cv
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Sum<&'a ValueCommitment> for ValueCommitment {
|
|
|
|
fn sum<I: Iterator<Item = &'a ValueCommitment>>(iter: I) -> Self {
|
|
|
|
iter.fold(ValueCommitment(pallas::Point::identity()), |acc, cv| {
|
|
|
|
acc + cv
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ValueCommitment {
|
2022-09-19 06:49:16 -07:00
|
|
|
/// Derives a `ValueCommitment` by $\mathsf{ValueCommit^{Orchard}}$.
|
2021-04-14 21:13:41 -07:00
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 5.4.8.3: Homomorphic Pedersen commitments (Sapling and Orchard)][concretehomomorphiccommit].
|
|
|
|
///
|
|
|
|
/// [concretehomomorphiccommit]: https://zips.z.cash/protocol/nu5.pdf#concretehomomorphiccommit
|
|
|
|
#[allow(non_snake_case)]
|
2022-08-13 06:35:01 -07:00
|
|
|
pub fn derive(value: ValueSum, rcv: ValueCommitTrapdoor) -> Self {
|
2021-06-23 16:07:12 -07:00
|
|
|
let hasher = pallas::Point::hash_to_curve(VALUE_COMMITMENT_PERSONALIZATION);
|
|
|
|
let V = hasher(&VALUE_COMMITMENT_V_BYTES);
|
|
|
|
let R = hasher(&VALUE_COMMITMENT_R_BYTES);
|
2021-05-05 10:55:06 -07:00
|
|
|
let abs_value = u64::try_from(value.0.abs()).expect("value must be in valid range");
|
2021-04-14 21:13:41 -07:00
|
|
|
|
2021-05-05 10:55:06 -07:00
|
|
|
let value = if value.0.is_negative() {
|
2021-12-07 09:47:03 -08:00
|
|
|
-pallas::Scalar::from(abs_value)
|
2021-04-14 21:13:41 -07:00
|
|
|
} else {
|
2021-12-07 09:47:03 -08:00
|
|
|
pallas::Scalar::from(abs_value)
|
2021-04-14 21:13:41 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
ValueCommitment(V * value + R * rcv.0)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn into_bvk(self) -> redpallas::VerificationKey<Binding> {
|
|
|
|
// TODO: impl From<pallas::Point> for redpallas::VerificationKey.
|
|
|
|
self.0.to_bytes().try_into().unwrap()
|
|
|
|
}
|
2021-04-23 11:53:13 -07:00
|
|
|
|
|
|
|
/// Deserialize a value commitment from its byte representation
|
|
|
|
pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<ValueCommitment> {
|
|
|
|
pallas::Point::from_bytes(bytes).map(ValueCommitment)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Serialize this value commitment to its canonical byte representation.
|
|
|
|
pub fn to_bytes(&self) -> [u8; 32] {
|
|
|
|
self.0.to_bytes()
|
|
|
|
}
|
2021-07-19 09:42:22 -07:00
|
|
|
|
|
|
|
/// x-coordinate of this value commitment.
|
2021-07-22 07:14:34 -07:00
|
|
|
pub(crate) fn x(&self) -> pallas::Base {
|
2021-07-19 09:42:22 -07:00
|
|
|
if self.0 == pallas::Point::identity() {
|
|
|
|
pallas::Base::zero()
|
|
|
|
} else {
|
|
|
|
*self.0.to_affine().coordinates().unwrap().x()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// y-coordinate of this value commitment.
|
2021-07-22 07:14:34 -07:00
|
|
|
pub(crate) fn y(&self) -> pallas::Base {
|
2021-07-19 09:42:22 -07:00
|
|
|
if self.0 == pallas::Point::identity() {
|
|
|
|
pallas::Base::zero()
|
|
|
|
} else {
|
|
|
|
*self.0.to_affine().coordinates().unwrap().y()
|
|
|
|
}
|
|
|
|
}
|
2021-04-14 21:13:41 -07:00
|
|
|
}
|
|
|
|
|
2021-04-27 06:49:49 -07:00
|
|
|
/// Generators for property testing.
|
|
|
|
#[cfg(any(test, feature = "test-dependencies"))]
|
2021-12-17 14:08:58 -08:00
|
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
|
2021-04-27 06:49:49 -07:00
|
|
|
pub mod testing {
|
2021-04-14 21:13:41 -07:00
|
|
|
use pasta_curves::{arithmetic::FieldExt, pallas};
|
|
|
|
use proptest::prelude::*;
|
|
|
|
|
2021-05-05 16:19:42 -07:00
|
|
|
use super::{NoteValue, ValueCommitTrapdoor, ValueSum, MAX_NOTE_VALUE, VALUE_SUM_RANGE};
|
2021-04-14 21:13:41 -07:00
|
|
|
|
|
|
|
prop_compose! {
|
2021-04-27 06:49:49 -07:00
|
|
|
/// Generate an arbitrary Pallas scalar.
|
|
|
|
pub fn arb_scalar()(bytes in prop::array::uniform32(0u8..)) -> pallas::Scalar {
|
2021-04-14 21:13:41 -07:00
|
|
|
// Instead of rejecting out-of-range bytes, let's reduce them.
|
|
|
|
let mut buf = [0; 64];
|
|
|
|
buf[..32].copy_from_slice(&bytes);
|
|
|
|
pallas::Scalar::from_bytes_wide(&buf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
prop_compose! {
|
2021-04-27 06:49:49 -07:00
|
|
|
/// Generate an arbitrary [`ValueSum`] in the range of valid Zcash values.
|
2021-05-05 10:55:06 -07:00
|
|
|
pub fn arb_value_sum()(value in VALUE_SUM_RANGE) -> ValueSum {
|
2021-04-27 06:49:49 -07:00
|
|
|
ValueSum(value as i128)
|
2021-04-14 21:13:41 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-05 16:12:31 -07:00
|
|
|
prop_compose! {
|
|
|
|
/// Generate an arbitrary [`ValueSum`] in the range of valid Zcash values.
|
|
|
|
pub fn arb_value_sum_bounded(bound: NoteValue)(value in -(bound.0 as i128)..=(bound.0 as i128)) -> ValueSum {
|
|
|
|
ValueSum(value as i128)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:13:41 -07:00
|
|
|
prop_compose! {
|
2021-04-27 06:49:49 -07:00
|
|
|
/// Generate an arbitrary ValueCommitTrapdoor
|
|
|
|
pub fn arb_trapdoor()(rcv in arb_scalar()) -> ValueCommitTrapdoor {
|
2021-04-14 21:13:41 -07:00
|
|
|
ValueCommitTrapdoor(rcv)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 06:49:49 -07:00
|
|
|
prop_compose! {
|
|
|
|
/// Generate an arbitrary value in the range of valid nonnegative Zcash amounts.
|
2021-05-05 10:10:52 -07:00
|
|
|
pub fn arb_note_value()(value in 0u64..MAX_NOTE_VALUE) -> NoteValue {
|
|
|
|
NoteValue(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
prop_compose! {
|
|
|
|
/// Generate an arbitrary value in the range of valid positive Zcash amounts
|
|
|
|
/// less than a specified value.
|
2021-05-05 16:23:21 -07:00
|
|
|
pub fn arb_note_value_bounded(max: u64)(value in 0u64..max) -> NoteValue {
|
2021-04-27 06:49:49 -07:00
|
|
|
NoteValue(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
prop_compose! {
|
|
|
|
/// Generate an arbitrary value in the range of valid positive Zcash amounts
|
|
|
|
/// less than a specified value.
|
|
|
|
pub fn arb_positive_note_value(max: u64)(value in 1u64..max) -> NoteValue {
|
|
|
|
NoteValue(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use proptest::prelude::*;
|
|
|
|
|
|
|
|
use super::{
|
2021-05-05 16:23:21 -07:00
|
|
|
testing::{arb_note_value_bounded, arb_trapdoor, arb_value_sum_bounded},
|
2021-05-05 16:19:42 -07:00
|
|
|
OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum, MAX_NOTE_VALUE,
|
2021-04-27 06:49:49 -07:00
|
|
|
};
|
|
|
|
use crate::primitives::redpallas;
|
|
|
|
|
2021-04-14 21:13:41 -07:00
|
|
|
proptest! {
|
|
|
|
#[test]
|
|
|
|
fn bsk_consistent_with_bvk(
|
2021-05-05 16:12:31 -07:00
|
|
|
values in (1usize..10).prop_flat_map(|n_values|
|
2021-05-05 16:23:21 -07:00
|
|
|
arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64).prop_flat_map(move |bound|
|
2021-05-05 16:12:31 -07:00
|
|
|
prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor()), n_values)
|
|
|
|
)
|
|
|
|
)
|
2021-04-14 21:13:41 -07:00
|
|
|
) {
|
|
|
|
let value_balance = values
|
|
|
|
.iter()
|
|
|
|
.map(|(value, _)| value)
|
|
|
|
.sum::<Result<ValueSum, OverflowError>>()
|
|
|
|
.expect("we generate values that won't overflow");
|
|
|
|
|
|
|
|
let bsk = values
|
|
|
|
.iter()
|
|
|
|
.map(|(_, rcv)| rcv)
|
|
|
|
.sum::<ValueCommitTrapdoor>()
|
|
|
|
.into_bsk();
|
|
|
|
|
|
|
|
let bvk = (values
|
|
|
|
.into_iter()
|
|
|
|
.map(|(value, rcv)| ValueCommitment::derive(value, rcv))
|
|
|
|
.sum::<ValueCommitment>()
|
|
|
|
- ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero()))
|
|
|
|
.into_bvk();
|
|
|
|
|
|
|
|
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|