mirror of https://github.com/zcash/halo2.git
Merge pull request #59 from zcash/valuecommit
Implement ValueCommit^Orchard
This commit is contained in:
commit
b88e77dd56
|
@ -25,6 +25,7 @@ blake2b_simd = "0.5"
|
||||||
ff = "0.9"
|
ff = "0.9"
|
||||||
fpe = "0.4"
|
fpe = "0.4"
|
||||||
group = "0.9"
|
group = "0.9"
|
||||||
|
rand = "0.8"
|
||||||
nonempty = "0.6"
|
nonempty = "0.6"
|
||||||
subtle = "2.3"
|
subtle = "2.3"
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ rev = "f8ff124a52d86e122e0705e8e9272f2099fe4c46"
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
proptest = "1.0.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
|
@ -67,6 +67,12 @@ impl<'a, T: SigType> From<&'a SigningKey<T>> for VerificationKey<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: SigType> PartialEq for VerificationKey<T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
<[u8; 32]>::from(self).eq(&<[u8; 32]>::from(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A RedPallas signature.
|
/// A RedPallas signature.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Signature<T: SigType>(reddsa::Signature<T>);
|
pub struct Signature<T: SigType>(reddsa::Signature<T>);
|
||||||
|
|
231
src/value.rs
231
src/value.rs
|
@ -1,34 +1,241 @@
|
||||||
//! Monetary values within the Orchard shielded pool.
|
//! Monetary values within the Orchard shielded pool.
|
||||||
//!
|
//!
|
||||||
//! Values are represented in two places within Orchard:
|
//! Values are represented in two places within Orchard:
|
||||||
//! - The value of an individual note, which is unsigned.
|
//! - The value of an individual note, which is an unsigned 63-bit integer.
|
||||||
//! - The sum of note values within an Orchard [`Action`] or [`Bundle`], which is signed.
|
//! - The sum of note values within an Orchard [`Action`] or [`Bundle`], which is a signed
|
||||||
|
//! 63-bit integer.
|
||||||
//!
|
//!
|
||||||
//! We give these separate types within this crate. Users should map these types to their
|
//! We give these separate types within this crate. Users should map these types to their
|
||||||
//! own general "amount" type as appropriate.
|
//! own general "amount" type as appropriate, and apply their own bounds checks if smaller
|
||||||
|
//! than the Orchard protocol supports.
|
||||||
//!
|
//!
|
||||||
//! Inside the circuit, values are constrained to be 63-bit integers.
|
//! Inside the circuit, note values are constrained to be unsigned 64-bit integers.
|
||||||
//! - TODO: Should this be constrained further to 53 bits? To Zcash's MAX_MONEY?
|
|
||||||
//!
|
//!
|
||||||
//! [`Action`]: crate::bundle::Action
|
//! [`Action`]: crate::bundle::Action
|
||||||
//! [`Bundle`]: crate::bundle::Bundle
|
//! [`Bundle`]: crate::bundle::Bundle
|
||||||
|
|
||||||
use bitvec::{array::BitArray, order::Lsb0};
|
use std::convert::TryInto;
|
||||||
|
use std::fmt;
|
||||||
|
use std::iter::Sum;
|
||||||
|
use std::ops::{Add, Sub};
|
||||||
|
|
||||||
/// The value of an individual Orchard note.
|
use bitvec::{array::BitArray, order::Lsb0};
|
||||||
#[derive(Clone, Copy, Debug)]
|
use ff::{Field, PrimeField};
|
||||||
|
use group::{Group, GroupEncoding};
|
||||||
|
use pasta_curves::{
|
||||||
|
arithmetic::{CurveExt, FieldExt},
|
||||||
|
pallas,
|
||||||
|
};
|
||||||
|
use rand::RngCore;
|
||||||
|
|
||||||
|
use crate::primitives::redpallas::{self, Binding};
|
||||||
|
|
||||||
|
/// 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)]
|
||||||
pub struct NoteValue(u64);
|
pub struct NoteValue(u64);
|
||||||
|
|
||||||
impl NoteValue {
|
impl NoteValue {
|
||||||
pub(crate) fn to_le_bits(&self) -> BitArray<Lsb0, [u8; 8]> {
|
pub(crate) fn to_le_bits(self) -> BitArray<Lsb0, [u8; 8]> {
|
||||||
BitArray::<Lsb0, _>::new(self.0.to_le_bytes())
|
BitArray::<Lsb0, _>::new(self.0.to_le_bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sub for NoteValue {
|
||||||
|
type Output = Result<ValueSum, OverflowError>;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
let a: i64 = self.0.try_into().map_err(|_| OverflowError)?;
|
||||||
|
let b: i64 = rhs.0.try_into().map_err(|_| OverflowError)?;
|
||||||
|
Ok(ValueSum(a - b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A sum of Orchard note values.
|
/// A sum of Orchard note values.
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub struct ValueSum(i64);
|
pub struct ValueSum(i64);
|
||||||
|
|
||||||
|
impl Add for ValueSum {
|
||||||
|
type Output = Result<ValueSum, OverflowError>;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
self.0.checked_add(rhs.0).map(ValueSum).ok_or(OverflowError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Sum<&'a ValueSum> for Result<ValueSum, OverflowError> {
|
||||||
|
fn sum<I: Iterator<Item = &'a ValueSum>>(iter: I) -> Self {
|
||||||
|
iter.fold(Ok(ValueSum(0)), |acc, cv| acc? + *cv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The blinding factor for a [`ValueCommitment`].
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ValueCommitTrapdoor(pallas::Scalar);
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A commitment to a [`ValueSum`].
|
/// A commitment to a [`ValueSum`].
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ValueCommitment;
|
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 {
|
||||||
|
/// $ValueCommit^Orchard$.
|
||||||
|
///
|
||||||
|
/// 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)]
|
||||||
|
pub(crate) fn derive(value: ValueSum, rcv: ValueCommitTrapdoor) -> Self {
|
||||||
|
let hasher = pallas::Point::hash_to_curve("z.cash:Orchard-cv");
|
||||||
|
let V = hasher(b"v");
|
||||||
|
let R = hasher(b"r");
|
||||||
|
|
||||||
|
let value = if value.0.is_negative() {
|
||||||
|
-pallas::Scalar::from_u64((-value.0) as u64)
|
||||||
|
} else {
|
||||||
|
pallas::Scalar::from_u64(value.0 as u64)
|
||||||
|
};
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||||
|
use proptest::prelude::*;
|
||||||
|
|
||||||
|
use super::{OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum};
|
||||||
|
use crate::primitives::redpallas;
|
||||||
|
|
||||||
|
/// Zcash's maximum money amount. Used as a bound in proptests so we don't artifically
|
||||||
|
/// overflow `ValueSum`'s size.
|
||||||
|
const MAX_MONEY: i64 = 21_000_000 * 1_0000_0000;
|
||||||
|
|
||||||
|
prop_compose! {
|
||||||
|
fn arb_scalar()(bytes in prop::array::uniform32(0u8..)) -> pallas::Scalar {
|
||||||
|
// 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! {
|
||||||
|
fn arb_value_sum(bound: i64)(value in -bound..bound) -> ValueSum {
|
||||||
|
ValueSum(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prop_compose! {
|
||||||
|
fn arb_trapdoor()(rcv in arb_scalar()) -> ValueCommitTrapdoor {
|
||||||
|
ValueCommitTrapdoor(rcv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn bsk_consistent_with_bvk(
|
||||||
|
values in prop::collection::vec((arb_value_sum(MAX_MONEY), arb_trapdoor()), 1..10),
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue