use bitvec::{array::BitArray, order::Lsb0};
use ff::Field;
use group::GroupEncoding;
use rand::RngCore;
use subtle::CtOption;
use crate::constants::{VALUE_COMMITMENT_RANDOMNESS_GENERATOR, VALUE_COMMITMENT_VALUE_GENERATOR};
mod sums;
pub use sums::{CommitmentSum, OverflowError, TrapdoorSum, ValueSum};
pub const MAX_NOTE_VALUE: u64 = u64::MAX;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct NoteValue(u64);
impl NoteValue {
pub fn inner(&self) -> u64 {
self.0
}
pub fn from_raw(value: u64) -> Self {
NoteValue(value)
}
pub(crate) fn to_le_bits(self) -> BitArray<[u8; 8], Lsb0> {
BitArray::<_, Lsb0>::new(self.0.to_le_bytes())
}
}
#[derive(Clone, Debug)]
pub struct ValueCommitTrapdoor(jubjub::Scalar);
impl ValueCommitTrapdoor {
pub fn random(rng: impl RngCore) -> Self {
ValueCommitTrapdoor(jubjub::Scalar::random(rng))
}
pub fn inner(&self) -> jubjub::Scalar {
self.0
}
}
#[derive(Clone, Debug)]
pub struct ValueCommitment(jubjub::ExtendedPoint);
impl ValueCommitment {
pub fn derive(value: NoteValue, rcv: ValueCommitTrapdoor) -> Self {
let cv = (VALUE_COMMITMENT_VALUE_GENERATOR * jubjub::Scalar::from(value.0))
+ (VALUE_COMMITMENT_RANDOMNESS_GENERATOR * rcv.0);
ValueCommitment(cv.into())
}
pub fn as_inner(&self) -> &jubjub::ExtendedPoint {
&self.0
}
pub fn from_bytes_not_small_order(bytes: &[u8; 32]) -> CtOption<ValueCommitment> {
jubjub::ExtendedPoint::from_bytes(bytes)
.and_then(|cv| CtOption::new(ValueCommitment(cv), !cv.is_small_order()))
}
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
}
#[cfg(any(test, feature = "test-dependencies"))]
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
pub mod testing {
use proptest::prelude::*;
use super::{NoteValue, ValueCommitTrapdoor, MAX_NOTE_VALUE};
prop_compose! {
pub fn arb_note_value()(value in 0u64..MAX_NOTE_VALUE) -> NoteValue {
NoteValue(value)
}
}
prop_compose! {
pub fn arb_note_value_bounded(max: u64)(value in 0u64..max) -> NoteValue {
NoteValue(value)
}
}
prop_compose! {
pub fn arb_positive_note_value(max: u64)(value in 1u64..max) -> NoteValue {
NoteValue(value)
}
}
prop_compose! {
fn arb_scalar()(bytes in prop::array::uniform32(0u8..)) -> jubjub::Scalar {
let mut buf = [0; 64];
buf[..32].copy_from_slice(&bytes);
jubjub::Scalar::from_bytes_wide(&buf)
}
}
prop_compose! {
pub fn arb_trapdoor()(rcv in arb_scalar()) -> ValueCommitTrapdoor {
ValueCommitTrapdoor(rcv)
}
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::{
testing::{arb_note_value_bounded, arb_trapdoor},
CommitmentSum, OverflowError, TrapdoorSum, ValueCommitment, ValueSum,
VALUE_COMMITMENT_RANDOMNESS_GENERATOR,
};
use crate::sapling::redjubjub;
proptest! {
#[test]
fn bsk_consistent_with_bvk(
values in (1usize..10).prop_flat_map(|n_values| prop::collection::vec(
(arb_note_value_bounded((i64::MAX as u64) / (n_values as u64)), arb_trapdoor()),
n_values,
))
) {
let value_balance: i64 = values
.iter()
.map(|(value, _)| value)
.sum::<Result<ValueSum, OverflowError>>()
.expect("we generate values that won't overflow")
.try_into()
.unwrap();
let bsk = values
.iter()
.map(|(_, rcv)| rcv)
.sum::<TrapdoorSum>()
.into_bsk();
let bvk = values
.into_iter()
.map(|(value, rcv)| ValueCommitment::derive(value, rcv))
.sum::<CommitmentSum>()
.into_bvk(value_balance);
assert_eq!(redjubjub::PublicKey::from_private(
&bsk, VALUE_COMMITMENT_RANDOMNESS_GENERATOR).0, bvk.0);
}
}
}