Add Sapling value types

These are modeled after the value types developed for the `orchard`
crate.
This commit is contained in:
Jack Grigg 2022-12-10 01:09:06 +00:00
parent fee0b6a18d
commit 23922ca290
20 changed files with 740 additions and 289 deletions

View File

@ -6,16 +6,40 @@ and this library adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- `zcash_primitives::sapling::value`, containing types for handling Sapling note
values and value commitments.
### Changed
- `zcash_primitives::transaction::components::sapling::builder`:
- `SaplingBuilder::add_output` now takes a
`zcash_primitives::sapling::value::NoteValue`.
- Value commitments now use `zcash_primitives::sapling::value::ValueCommitment`
instead of `jubjub::ExtendedPoint` in the following places:
- `zcash_primitives::sapling::note_encryption`:
- `prf_ock`
- `SaplingDomain::ValueCommitment`
- `zcash_primitives::sapling::prover`:
- `TxProver::{spend_proof, output_proof}` return type.
- `zcash_primitives::transaction::components`:
- `SpendDescription::cv`
- `OutputDescription::cv`
- `zcash_primitives::transaction::components`:
- `sapling::{Bundle, SpendDescription, OutputDescription}` have had their
fields replaced by getter methods.
### Removed
- `zcash_primitives::sapling`:
- `NoteValue` (use `zcash_primitives::sapling::value::NoteValue` instead).
- `ValueCommitment` (use `zcash_primitives::sapling::value::ValueCommitment`
or `zcash_proofs::circuit::sapling::ValueCommitmentPreimage` instead).
- `testing::{arb_note_value, arb_positive_note_value}` (use the methods in
`zcash_primitives::sapling::value::testing` instead).
- `zcash_primitives::transaction::components`:
- The fields of `sapling::{SpendDescriptionV5, OutputDescriptionV5}` (they are
now opaque types; use `sapling::{SpendDescription, OutputDescription}`
instead).
- `sapling::read_point`
## [0.9.1] - 2022-12-06
### Fixed

View File

@ -13,13 +13,11 @@ use zcash_primitives::{
PreparedIncomingViewingKey, SaplingDomain,
},
prover::mock::MockTxProver,
value::NoteValue,
Diversifier, PaymentAddress, SaplingIvk,
},
transaction::components::{
sapling::{
builder::SaplingBuilder, CompactOutputDescription, GrothProofBytes, OutputDescription,
},
Amount,
transaction::components::sapling::{
builder::SaplingBuilder, CompactOutputDescription, GrothProofBytes, OutputDescription,
},
};
@ -45,7 +43,7 @@ fn bench_note_decryption(c: &mut Criterion) {
&mut rng,
None,
pa,
Amount::from_u64(100).unwrap(),
NoteValue::from_raw(100),
MemoBytes::empty(),
)
.unwrap();

View File

@ -7,6 +7,7 @@ pub mod pedersen_hash;
pub mod prover;
pub mod redjubjub;
pub mod util;
pub mod value;
use bitvec::{order::Lsb0, view::AsBits};
use blake2s_simd::Params as Blake2sParams;
@ -24,7 +25,6 @@ use crate::{
constants::{self, SPENDING_KEY_GENERATOR},
keys::prf_expand,
merkle_tree::{HashSer, Hashable},
transaction::components::amount::MAX_MONEY,
};
use self::{
@ -169,19 +169,6 @@ pub(crate) fn spend_sig_internal<R: RngCore>(
rsk.sign(&data_to_be_signed, rng, SPENDING_KEY_GENERATOR)
}
#[derive(Clone)]
pub struct ValueCommitment {
pub value: u64,
pub randomness: jubjub::Fr,
}
impl ValueCommitment {
pub fn commitment(&self) -> jubjub::SubgroupPoint {
(constants::VALUE_COMMITMENT_VALUE_GENERATOR * jubjub::Fr::from(self.value))
+ (constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR * self.randomness)
}
}
#[derive(Clone)]
pub struct ProofGenerationKey {
pub ak: jubjub::SubgroupPoint,
@ -391,27 +378,6 @@ impl ConstantTimeEq for Nullifier {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NoteValue(u64);
impl TryFrom<u64> for NoteValue {
type Error = ();
fn try_from(value: u64) -> Result<Self, Self::Error> {
if value <= MAX_MONEY as u64 {
Ok(NoteValue(value))
} else {
Err(())
}
}
}
impl From<NoteValue> for u64 {
fn from(value: NoteValue) -> u64 {
value.0
}
}
#[derive(Clone, Debug)]
pub struct Note {
/// The value of the note
@ -540,30 +506,12 @@ impl Note {
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::prelude::*;
use std::cmp::min;
use crate::transaction::components::amount::MAX_MONEY;
use super::{
keys::testing::arb_full_viewing_key, Diversifier, Node, Note, NoteValue, PaymentAddress,
Rseed, SaplingIvk,
keys::testing::arb_full_viewing_key, value::NoteValue, Diversifier, Node, Note,
PaymentAddress, Rseed, SaplingIvk,
};
prop_compose! {
pub fn arb_note_value()(value in 0u64..=MAX_MONEY as u64) -> NoteValue {
NoteValue::try_from(value).unwrap()
}
}
prop_compose! {
/// The
pub fn arb_positive_note_value(bound: u64)(
value in 1u64..=(min(bound, MAX_MONEY as u64))
) -> NoteValue {
NoteValue::try_from(value).unwrap()
}
}
prop_compose! {
pub fn arb_incoming_viewing_key()(fvk in arb_full_viewing_key()) -> SaplingIvk {
fvk.vk.ivk()
@ -593,7 +541,7 @@ pub mod testing {
rseed in prop::array::uniform32(prop::num::u8::ANY).prop_map(Rseed::AfterZip212)
) -> Note {
Note {
value: value.into(),
value: value.inner(),
g_d: addr.g_d().unwrap(), // this unwrap is safe because arb_payment_address always generates an address with a valid g_d
pk_d: *addr.pk_d(),
rseed

View File

@ -20,7 +20,10 @@ use zcash_note_encryption::{
use crate::{
consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD},
memo::MemoBytes,
sapling::{keys::OutgoingViewingKey, Diversifier, Note, PaymentAddress, Rseed, SaplingIvk},
sapling::{
keys::OutgoingViewingKey, value::ValueCommitment, Diversifier, Note, PaymentAddress, Rseed,
SaplingIvk,
},
transaction::components::{
amount::Amount,
sapling::{self, OutputDescription},
@ -93,7 +96,7 @@ fn kdf_sapling(dhsecret: jubjub::SubgroupPoint, ephemeral_key: &EphemeralKeyByte
/// Implemented per section 5.4.2 of the Zcash Protocol Specification.
pub fn prf_ock(
ovk: &OutgoingViewingKey,
cv: &jubjub::ExtendedPoint,
cv: &ValueCommitment,
cmu_bytes: &[u8; 32],
ephemeral_key: &EphemeralKeyBytes,
) -> OutgoingCipherKey {
@ -191,7 +194,7 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
type DiversifiedTransmissionKey = jubjub::SubgroupPoint;
type IncomingViewingKey = PreparedIncomingViewingKey;
type OutgoingViewingKey = OutgoingViewingKey;
type ValueCommitment = jubjub::ExtendedPoint;
type ValueCommitment = ValueCommitment;
type ExtractedCommitment = bls12_381::Scalar;
type ExtractedCommitmentBytes = [u8; 32];
type Memo = MemoBytes;
@ -424,7 +427,8 @@ impl<P: consensus::Parameters> BatchDomain for SaplingDomain<P> {
/// sapling::{
/// note_encryption::sapling_note_encryption,
/// util::generate_random_rseed,
/// Diversifier, PaymentAddress, Rseed, ValueCommitment
/// value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
/// Diversifier, PaymentAddress, Rseed,
/// },
/// };
///
@ -435,20 +439,17 @@ impl<P: consensus::Parameters> BatchDomain for SaplingDomain<P> {
/// let to = PaymentAddress::from_parts(diversifier, pk_d).unwrap();
/// let ovk = Some(OutgoingViewingKey([0; 32]));
///
/// let value = 1000;
/// let rcv = jubjub::Fr::random(&mut rng);
/// let cv = ValueCommitment {
/// value,
/// randomness: rcv.clone(),
/// };
/// let value = NoteValue::from_raw(1000);
/// let rcv = ValueCommitTrapdoor::random(&mut rng);
/// let cv = ValueCommitment::derive(value, rcv);
/// let height = TEST_NETWORK.activation_height(NetworkUpgrade::Canopy).unwrap();
/// let rseed = generate_random_rseed(&TEST_NETWORK, height, &mut rng);
/// let note = to.create_note(value, rseed).unwrap();
/// let note = to.create_note(value.inner(), rseed).unwrap();
/// let cmu = note.cmu();
///
/// let mut enc = sapling_note_encryption::<_, TestNetwork>(ovk, note, to, MemoBytes::empty(), &mut rng);
/// let encCiphertext = enc.encrypt_note_plaintext();
/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu, &mut rng);
/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng);
/// ```
pub fn sapling_note_encryption<R: RngCore, P: consensus::Parameters>(
ovk: Option<OutgoingViewingKey>,
@ -595,10 +596,13 @@ mod tests {
},
keys::OutgoingViewingKey,
memo::MemoBytes,
sapling::{note_encryption::PreparedIncomingViewingKey, util::generate_random_rseed},
sapling::{Diversifier, PaymentAddress, Rseed, SaplingIvk, ValueCommitment},
sapling::{
note_encryption::PreparedIncomingViewingKey,
util::generate_random_rseed,
value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
Diversifier, PaymentAddress, Rseed, SaplingIvk,
},
transaction::components::{
amount::Amount,
sapling::{self, CompactOutputDescription, OutputDescription},
GROTH_PROOF_SIZE,
},
@ -654,16 +658,13 @@ mod tests {
let pa = PaymentAddress::from_parts_unchecked(diversifier, pk_d);
// Construct the value commitment for the proof instance
let value = Amount::from_u64(100).unwrap();
let value_commitment = ValueCommitment {
value: value.into(),
randomness: jubjub::Fr::random(&mut rng),
};
let cv = value_commitment.commitment().into();
let value = NoteValue::from_raw(100);
let rcv = ValueCommitTrapdoor::random(&mut rng);
let cv = ValueCommitment::derive(value, rcv);
let rseed = generate_random_rseed(&TEST_NETWORK, height, &mut rng);
let note = pa.create_note(value.into(), rseed).unwrap();
let note = pa.create_note(value.inner(), rseed).unwrap();
let cmu = note.cmu();
let ovk = OutgoingViewingKey([0; 32]);
@ -677,12 +678,13 @@ mod tests {
let epk = *ne.epk();
let ock = prf_ock(&ovk, &cv, &cmu.to_repr(), &epk_bytes(&epk));
let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng);
let output = OutputDescription::from_parts(
cv,
cmu,
epk.to_bytes().into(),
ne.encrypt_note_plaintext(),
ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng),
out_ciphertext,
[0u8; GROTH_PROOF_SIZE],
);
@ -691,7 +693,7 @@ mod tests {
fn reencrypt_enc_ciphertext(
ovk: &OutgoingViewingKey,
cv: &jubjub::ExtendedPoint,
cv: &ValueCommitment,
cmu: &bls12_381::Scalar,
ephemeral_key: &EphemeralKeyBytes,
enc_ciphertext: &[u8; ENC_CIPHERTEXT_SIZE],
@ -1165,7 +1167,10 @@ mod tests {
for &height in heights.iter() {
let (ovk, _, _, mut output) = random_enc_ciphertext(height, &mut rng);
*output.cv_mut() = jubjub::ExtendedPoint::random(&mut rng);
*output.cv_mut() = ValueCommitment::derive(
NoteValue::from_raw(7),
ValueCommitTrapdoor::random(&mut rng),
);
assert_eq!(
try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,),
@ -1409,6 +1414,12 @@ mod tests {
};
}
macro_rules! read_cv {
($field:expr) => {
ValueCommitment::from_bytes_not_small_order(&$field).unwrap()
};
}
let height = TEST_NETWORK.activation_height(Sapling).unwrap();
for tv in test_vectors {
@ -1419,7 +1430,7 @@ mod tests {
let ivk = PreparedIncomingViewingKey::new(&SaplingIvk(read_jubjub_scalar!(tv.ivk)));
let pk_d = read_point!(tv.default_pk_d).into_subgroup().unwrap();
let rcm = read_jubjub_scalar!(tv.rcm);
let cv = read_point!(tv.cv);
let cv = read_cv!(tv.cv);
let cmu = read_bls12_381_scalar!(tv.cmu);
let esk = read_jubjub_scalar!(tv.esk);
let ephemeral_key = EphemeralKeyBytes(tv.epk);
@ -1443,7 +1454,7 @@ mod tests {
assert_eq!(note.cmu(), cmu);
let output = OutputDescription::from_parts(
cv,
cv.clone(),
cmu,
ephemeral_key,
tv.c_enc,

View File

@ -4,6 +4,7 @@ use crate::{
merkle_tree::MerklePath,
sapling::{
redjubjub::{PublicKey, Signature},
value::ValueCommitment,
Node,
},
transaction::components::{Amount, GROTH_PROOF_SIZE},
@ -35,7 +36,7 @@ pub trait TxProver {
value: u64,
anchor: bls12_381::Scalar,
merkle_path: MerklePath<Node>,
) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint, PublicKey), ()>;
) -> Result<([u8; GROTH_PROOF_SIZE], ValueCommitment, PublicKey), ()>;
/// Create the value commitment and proof for a Sapling [`OutputDescription`],
/// while accumulating its value commitment randomness inside the context for later
@ -49,7 +50,7 @@ pub trait TxProver {
payment_address: PaymentAddress,
rcm: jubjub::Fr,
value: u64,
) -> ([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint);
) -> ([u8; GROTH_PROOF_SIZE], ValueCommitment);
/// Create the `bindingSig` for a Sapling transaction. All calls to
/// [`TxProver::spend_proof`] and [`TxProver::output_proof`] must be completed before
@ -64,7 +65,6 @@ pub trait TxProver {
#[cfg(any(test, feature = "test-dependencies"))]
pub mod mock {
use ff::Field;
use rand_core::OsRng;
use crate::{
@ -72,7 +72,8 @@ pub mod mock {
merkle_tree::MerklePath,
sapling::{
redjubjub::{PublicKey, Signature},
Diversifier, Node, PaymentAddress, ProofGenerationKey, Rseed, ValueCommitment,
value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
Diversifier, Node, PaymentAddress, ProofGenerationKey, Rseed,
},
transaction::components::{Amount, GROTH_PROOF_SIZE},
};
@ -96,15 +97,12 @@ pub mod mock {
value: u64,
_anchor: bls12_381::Scalar,
_merkle_path: MerklePath<Node>,
) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint, PublicKey), ()> {
) -> Result<([u8; GROTH_PROOF_SIZE], ValueCommitment, PublicKey), ()> {
let mut rng = OsRng;
let cv = ValueCommitment {
value,
randomness: jubjub::Fr::random(&mut rng),
}
.commitment()
.into();
let value = NoteValue::from_raw(value);
let rcv = ValueCommitTrapdoor::random(&mut rng);
let cv = ValueCommitment::derive(value, rcv);
let rk =
PublicKey(proof_generation_key.ak.into()).randomize(ar, SPENDING_KEY_GENERATOR);
@ -119,15 +117,12 @@ pub mod mock {
_payment_address: PaymentAddress,
_rcm: jubjub::Fr,
value: u64,
) -> ([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint) {
) -> ([u8; GROTH_PROOF_SIZE], ValueCommitment) {
let mut rng = OsRng;
let cv = ValueCommitment {
value,
randomness: jubjub::Fr::random(&mut rng),
}
.commitment()
.into();
let value = NoteValue::from_raw(value);
let rcv = ValueCommitTrapdoor::random(&mut rng);
let cv = ValueCommitment::derive(value, rcv);
([0u8; GROTH_PROOF_SIZE], cv)
}

View File

@ -0,0 +1,243 @@
//! Monetary values within the Sapling shielded pool.
//!
//! Values are represented in three places within the Sapling 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 a Sapling [`Bundle`]. It is represented
//! as an `i128` and places an upper bound on the maximum number of notes within a
//! single [`Bundle`].
//! - `valueBalanceSapling`, which is a signed 63-bit integer. This is represented
//! by a user-defined type parameter on [`Bundle`], returned by
//! [`Bundle::value_balance`] and [`SaplingBuilder::value_balance`].
//!
//! If your specific instantiation of the Sapling 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 `valueBalanceSapling` 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 [`SaplingBuilder::add_output`].
//!
//! Inside the circuit, note values are constrained to be unsigned 64-bit integers.
//!
//! # 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 `valueBalanceSapling` (which can be represented as an `i64`).
//!
//! [`Bundle`]: crate::transaction::components::sapling::Bundle
//! [`Bundle::value_balance`]: crate::transaction::components::sapling::Bundle::value_balance
//! [`SaplingBuilder::value_balance`]: crate::transaction::components::sapling::builder::SaplingBuilder::value_balance
//! [`SaplingBuilder::add_output`]: crate::transaction::components::sapling::builder::SaplingBuilder::add_output
//! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html
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};
/// Maximum note value.
pub const MAX_NOTE_VALUE: u64 = u64::MAX;
/// The non-negative value of an individual Sapling note.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct NoteValue(u64);
impl NoteValue {
/// Returns the raw underlying value.
pub fn inner(&self) -> u64 {
self.0
}
/// 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)
}
}
/// The blinding factor for a [`ValueCommitment`].
#[derive(Clone, Debug)]
pub struct ValueCommitTrapdoor(jubjub::Scalar);
impl ValueCommitTrapdoor {
/// Generates a new value commitment trapdoor.
///
/// This is public for access by `zcash_proofs`.
pub fn random(rng: impl RngCore) -> Self {
ValueCommitTrapdoor(jubjub::Scalar::random(rng))
}
/// Returns the inner Jubjub scalar representing this trapdoor.
///
/// This is public for access by `zcash_proofs`.
pub fn inner(&self) -> jubjub::Scalar {
self.0
}
}
/// A commitment to a [`ValueSum`].
///
/// # Consensus rules
///
/// The Zcash Protocol Spec requires Sapling Spend Descriptions and Output Descriptions to
/// not contain a small order `ValueCommitment`. However, the `ValueCommitment` type as
/// specified (and implemented here) may contain a small order point. In practice, it will
/// not occur:
/// - [`ValueCommitment::derive`] will only produce a small order point if both the given
/// [`NoteValue`] and [`ValueCommitTrapdoor`] are zero. However, the only constructor
/// available for `ValueCommitTrapdoor` is [`ValueCommitTrapdoor::random`], which will
/// produce zero with negligible probability (assuming a non-broken PRNG).
/// - [`ValueCommitment::from_bytes_not_small_order`] enforces this by definition, and is
/// the only constructor that can be used with data received over the network.
#[derive(Clone, Debug)]
pub struct ValueCommitment(jubjub::ExtendedPoint);
impl ValueCommitment {
/// Derives a `ValueCommitment` by $\mathsf{ValueCommit^{Sapling}}$.
///
/// Defined in [Zcash Protocol Spec § 5.4.8.3: Homomorphic Pedersen commitments (Sapling and Orchard)][concretehomomorphiccommit].
///
/// [concretehomomorphiccommit]: https://zips.z.cash/protocol/protocol.pdf#concretehomomorphiccommit
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())
}
/// Returns the inner Jubjub point representing this value commitment.
///
/// This is public for access by `zcash_proofs`.
pub fn as_inner(&self) -> &jubjub::ExtendedPoint {
&self.0
}
/// Deserializes a value commitment from its byte representation.
///
/// Returns `None` if `bytes` is an invalid representation of a Jubjub point, or the
/// resulting point is of small order.
///
/// This method can be used to enforce the "not small order" consensus rules defined
/// in [Zcash Protocol Spec § 4.4: Spend Descriptions][spenddesc] and
/// [§ 4.5: Output Descriptions][outputdesc].
///
/// [spenddesc]: https://zips.z.cash/protocol/protocol.pdf#spenddesc
/// [outputdesc]: https://zips.z.cash/protocol/protocol.pdf#outputdesc
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()))
}
/// Serializes this value commitment to its canonical byte representation.
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
}
/// Generators for property testing.
#[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! {
/// Generate an arbitrary value in the range of valid nonnegative amounts.
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 amounts less than a
/// specified value.
pub fn arb_note_value_bounded(max: u64)(value in 0u64..max) -> NoteValue {
NoteValue(value)
}
}
prop_compose! {
/// Generate an arbitrary value in the range of valid positive amounts less than a
/// specified value.
pub fn arb_positive_note_value(max: u64)(value in 1u64..max) -> NoteValue {
NoteValue(value)
}
}
prop_compose! {
/// Generate an arbitrary Jubjub scalar.
fn arb_scalar()(bytes in prop::array::uniform32(0u8..)) -> jubjub::Scalar {
// Instead of rejecting out-of-range bytes, let's reduce them.
let mut buf = [0; 64];
buf[..32].copy_from_slice(&bytes);
jubjub::Scalar::from_bytes_wide(&buf)
}
}
prop_compose! {
/// Generate an arbitrary ValueCommitTrapdoor
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);
}
}
}

View File

@ -0,0 +1,221 @@
use core::fmt::{self, Debug};
use core::iter::Sum;
use core::ops::{Add, AddAssign, Sub, SubAssign};
use super::{NoteValue, ValueCommitTrapdoor, ValueCommitment};
use crate::constants::VALUE_COMMITMENT_VALUE_GENERATOR;
use crate::sapling::redjubjub;
/// 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, "Sapling value operation overflowed")
}
}
impl std::error::Error for OverflowError {}
/// A sum of Sapling note values.
///
/// [Zcash Protocol Spec § 4.13: Balance and Binding Signature (Sapling)][saplingbalance]
/// constrains the range of this type to between `[-(r_J - 1)/2..(r_J - 1)/2]` in the
/// abstract protocol, and `[38913406623490299131842..104805176454780817500623]` in the
/// concrete Zcash protocol. We represent it as an `i128`, which has a range large enough
/// to handle Zcash transactions while small enough to ensure the abstract protocol bounds
/// are not breached.
///
/// [saplingbalance]: https://zips.z.cash/protocol/protocol.pdf#saplingbalance
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ValueSum(i128);
impl ValueSum {
/// Initializes a sum of `NoteValue`s to zero.
pub fn zero() -> Self {
ValueSum(0)
}
}
impl Add<NoteValue> for ValueSum {
type Output = Option<ValueSum>;
#[allow(clippy::suspicious_arithmetic_impl)]
fn add(self, rhs: NoteValue) -> Self::Output {
self.0.checked_add(rhs.0.into()).map(ValueSum)
}
}
impl Sub<NoteValue> for ValueSum {
type Output = Option<ValueSum>;
#[allow(clippy::suspicious_arithmetic_impl)]
fn sub(self, rhs: NoteValue) -> Self::Output {
self.0.checked_sub(rhs.0.into()).map(ValueSum)
}
}
impl<'a> Sum<&'a NoteValue> for Result<ValueSum, OverflowError> {
fn sum<I: Iterator<Item = &'a NoteValue>>(iter: I) -> Self {
iter.fold(Ok(ValueSum(0)), |acc, v| (acc? + *v).ok_or(OverflowError))
}
}
impl Sum<NoteValue> for Result<ValueSum, OverflowError> {
fn sum<I: Iterator<Item = NoteValue>>(iter: I) -> Self {
iter.fold(Ok(ValueSum(0)), |acc, v| (acc? + v).ok_or(OverflowError))
}
}
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)
}
}
/// A sum of Sapling value commitment blinding factors.
#[derive(Clone, Copy, Debug)]
pub struct TrapdoorSum(jubjub::Scalar);
impl TrapdoorSum {
/// Initializes a sum of `ValueCommitTrapdoor`s to zero.
pub fn zero() -> Self {
TrapdoorSum(jubjub::Scalar::zero())
}
/// Transform this trapdoor sum into the corresponding RedJubjub private key.
///
/// This is public for access by `zcash_proofs`.
pub fn into_bsk(self) -> redjubjub::PrivateKey {
redjubjub::PrivateKey(self.0)
}
}
impl Add<&ValueCommitTrapdoor> for ValueCommitTrapdoor {
type Output = TrapdoorSum;
fn add(self, rhs: &Self) -> Self::Output {
TrapdoorSum(self.0 + rhs.0)
}
}
impl Add<&ValueCommitTrapdoor> for TrapdoorSum {
type Output = TrapdoorSum;
fn add(self, rhs: &ValueCommitTrapdoor) -> Self::Output {
TrapdoorSum(self.0 + rhs.0)
}
}
impl AddAssign<&ValueCommitTrapdoor> for TrapdoorSum {
fn add_assign(&mut self, rhs: &ValueCommitTrapdoor) {
self.0 += rhs.0;
}
}
impl Sub<&ValueCommitTrapdoor> for ValueCommitTrapdoor {
type Output = TrapdoorSum;
fn sub(self, rhs: &Self) -> Self::Output {
TrapdoorSum(self.0 - rhs.0)
}
}
impl SubAssign<&ValueCommitTrapdoor> for TrapdoorSum {
fn sub_assign(&mut self, rhs: &ValueCommitTrapdoor) {
self.0 -= rhs.0;
}
}
impl<'a> Sum<&'a ValueCommitTrapdoor> for TrapdoorSum {
fn sum<I: Iterator<Item = &'a ValueCommitTrapdoor>>(iter: I) -> Self {
iter.fold(TrapdoorSum::zero(), |acc, cv| acc + cv)
}
}
/// A sum of Sapling value commitments.
#[derive(Clone, Copy, Debug)]
pub struct CommitmentSum(jubjub::ExtendedPoint);
impl CommitmentSum {
/// Initializes a sum of `ValueCommitment`s to zero.
pub fn zero() -> Self {
CommitmentSum(jubjub::ExtendedPoint::identity())
}
/// Transform this value commitment sum into the corresponding RedJubjub public key.
///
/// This is public for access by `zcash_proofs`.
pub fn into_bvk<V: Into<i64>>(self, value_balance: V) -> redjubjub::PublicKey {
let value: i64 = value_balance.into();
// Compute the absolute value.
let abs_value = match value.checked_abs() {
Some(v) => u64::try_from(v).expect("v is non-negative"),
None => 1u64 << 63,
};
// Construct the field representation of the signed value.
let value_balance = if value.is_negative() {
-jubjub::Scalar::from(abs_value)
} else {
jubjub::Scalar::from(abs_value)
};
// Subtract `value_balance` from the sum to get the final bvk.
let bvk = self.0 - VALUE_COMMITMENT_VALUE_GENERATOR * value_balance;
redjubjub::PublicKey(bvk)
}
}
impl Add<&ValueCommitment> for ValueCommitment {
type Output = CommitmentSum;
fn add(self, rhs: &Self) -> Self::Output {
CommitmentSum(self.0 + rhs.0)
}
}
impl Add<&ValueCommitment> for CommitmentSum {
type Output = CommitmentSum;
fn add(self, rhs: &ValueCommitment) -> Self::Output {
CommitmentSum(self.0 + rhs.0)
}
}
impl AddAssign<&ValueCommitment> for CommitmentSum {
fn add_assign(&mut self, rhs: &ValueCommitment) {
self.0 += rhs.0;
}
}
impl Sub<&ValueCommitment> for ValueCommitment {
type Output = CommitmentSum;
fn sub(self, rhs: &Self) -> Self::Output {
CommitmentSum(self.0 - rhs.0)
}
}
impl SubAssign<&ValueCommitment> for CommitmentSum {
fn sub_assign(&mut self, rhs: &ValueCommitment) {
self.0 -= rhs.0;
}
}
impl Sum<ValueCommitment> for CommitmentSum {
fn sum<I: Iterator<Item = ValueCommitment>>(iter: I) -> Self {
iter.fold(CommitmentSum::zero(), |acc, cv| acc + &cv)
}
}
impl<'a> Sum<&'a ValueCommitment> for CommitmentSum {
fn sum<I: Iterator<Item = &'a ValueCommitment>>(iter: I) -> Self {
iter.fold(CommitmentSum::zero(), |acc, cv| acc + cv)
}
}

View File

@ -16,7 +16,7 @@ use crate::{
legacy::TransparentAddress,
memo::MemoBytes,
merkle_tree::MerklePath,
sapling::{prover::TxProver, Diversifier, Node, Note, PaymentAddress},
sapling::{prover::TxProver, value::NoteValue, Diversifier, Node, Note, PaymentAddress},
transaction::{
components::{
amount::{Amount, BalanceError},
@ -254,8 +254,16 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
value: Amount,
memo: MemoBytes,
) -> Result<(), sapling::builder::Error> {
self.sapling_builder
.add_output(&mut self.rng, ovk, to, value, memo)
if value.is_negative() {
return Err(sapling::builder::Error::InvalidAmount);
}
self.sapling_builder.add_output(
&mut self.rng,
ovk,
to,
NoteValue::from_raw(value.into()),
memo,
)
}
/// Adds a transparent coin to be spent in this transaction.

View File

@ -1,7 +1,6 @@
use core::fmt::Debug;
use ff::PrimeField;
use group::GroupEncoding;
use memuse::DynamicUsage;
use std::io::{self, Read, Write};
@ -15,6 +14,7 @@ use crate::{
sapling::{
note_encryption::SaplingDomain,
redjubjub::{self, PublicKey, Signature},
value::ValueCommitment,
Nullifier,
},
};
@ -163,7 +163,7 @@ impl<A: Authorization> Bundle<A> {
#[derive(Clone)]
pub struct SpendDescription<A: Authorization> {
cv: jubjub::ExtendedPoint,
cv: ValueCommitment,
anchor: bls12_381::Scalar,
nullifier: Nullifier,
rk: PublicKey,
@ -183,7 +183,7 @@ impl<A: Authorization> std::fmt::Debug for SpendDescription<A> {
impl<A: Authorization> SpendDescription<A> {
/// Returns the commitment to the value consumed by this spend.
pub fn cv(&self) -> &jubjub::ExtendedPoint {
pub fn cv(&self) -> &ValueCommitment {
&self.cv
}
@ -215,20 +215,16 @@ impl<A: Authorization> SpendDescription<A> {
/// Consensus rules (§4.4) & (§4.5):
/// - Canonical encoding is enforced here.
/// - "Not small order" is enforced in SaplingVerificationContext::(check_spend()/check_output())
/// (located in zcash_proofs::sapling::verifier).
pub fn read_point<R: Read>(mut reader: R, field: &str) -> io::Result<jubjub::ExtendedPoint> {
/// - "Not small order" is enforced here.
fn read_value_commitment<R: Read>(mut reader: R) -> io::Result<ValueCommitment> {
let mut bytes = [0u8; 32];
reader.read_exact(&mut bytes)?;
let point = jubjub::ExtendedPoint::from_bytes(&bytes);
let cv = ValueCommitment::from_bytes_not_small_order(&bytes);
if point.is_none().into() {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("invalid {}", field),
))
if cv.is_none().into() {
Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid cv"))
} else {
Ok(point.unwrap())
Ok(cv.unwrap())
}
}
@ -283,7 +279,7 @@ impl SpendDescription<Authorized> {
// - Canonical encoding is enforced here.
// - "Not small order" is enforced in SaplingVerificationContext::(check_spend()/check_output())
// (located in zcash_proofs::sapling::verifier).
let cv = read_point(&mut reader, "cv")?;
let cv = read_value_commitment(&mut reader)?;
// Consensus rules (§7.3) & (§7.4):
// - Canonical encoding is enforced here
let anchor = read_base(&mut reader, "anchor")?;
@ -320,14 +316,14 @@ impl SpendDescription<Authorized> {
#[derive(Clone)]
pub struct SpendDescriptionV5 {
cv: jubjub::ExtendedPoint,
cv: ValueCommitment,
nullifier: Nullifier,
rk: PublicKey,
}
impl SpendDescriptionV5 {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
let cv = read_point(&mut reader, "cv")?;
let cv = read_value_commitment(&mut reader)?;
let nullifier = SpendDescription::read_nullifier(&mut reader)?;
let rk = SpendDescription::read_rk(&mut reader)?;
@ -353,7 +349,7 @@ impl SpendDescriptionV5 {
#[derive(Clone)]
pub struct OutputDescription<Proof> {
cv: jubjub::ExtendedPoint,
cv: ValueCommitment,
cmu: bls12_381::Scalar,
ephemeral_key: EphemeralKeyBytes,
enc_ciphertext: [u8; 580],
@ -363,7 +359,7 @@ pub struct OutputDescription<Proof> {
impl<Proof> OutputDescription<Proof> {
/// Returns the commitment to the value consumed by this output.
pub fn cv(&self) -> &jubjub::ExtendedPoint {
pub fn cv(&self) -> &ValueCommitment {
&self.cv
}
@ -395,7 +391,7 @@ impl<Proof> OutputDescription<Proof> {
#[cfg(test)]
impl<Proof> OutputDescription<Proof> {
pub(crate) fn from_parts(
cv: jubjub::ExtendedPoint,
cv: ValueCommitment,
cmu: bls12_381::Scalar,
ephemeral_key: EphemeralKeyBytes,
enc_ciphertext: [u8; 580],
@ -411,7 +407,7 @@ impl<Proof> OutputDescription<Proof> {
zkproof,
}
}
pub(crate) fn cv_mut(&mut self) -> &mut jubjub::ExtendedPoint {
pub(crate) fn cv_mut(&mut self) -> &mut ValueCommitment {
&mut self.cv
}
pub(crate) fn cmu_mut(&mut self) -> &mut bls12_381::Scalar {
@ -470,7 +466,7 @@ impl OutputDescription<GrothProofBytes> {
// - Canonical encoding is enforced here.
// - "Not small order" is enforced in SaplingVerificationContext::check_output()
// (located in zcash_proofs::sapling::verifier).
let cv = read_point(&mut reader, "cv")?;
let cv = read_value_commitment(&mut reader)?;
// Consensus rule (§7.4): Canonical encoding is enforced here
let cmu = read_base(&mut reader, "cmu")?;
@ -518,7 +514,7 @@ impl OutputDescription<GrothProofBytes> {
#[derive(Clone)]
pub struct OutputDescriptionV5 {
cv: jubjub::ExtendedPoint,
cv: ValueCommitment,
cmu: bls12_381::Scalar,
ephemeral_key: EphemeralKeyBytes,
enc_ciphertext: [u8; 580],
@ -529,7 +525,7 @@ memuse::impl_no_dynamic_usage!(OutputDescriptionV5);
impl OutputDescriptionV5 {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
let cv = read_point(&mut reader, "cv")?;
let cv = read_value_commitment(&mut reader)?;
let cmu = read_base(&mut reader, "cmu")?;
// Consensus rules (§4.5):
@ -614,6 +610,10 @@ pub mod testing {
constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR},
sapling::{
redjubjub::{PrivateKey, PublicKey},
value::{
testing::{arb_note_value_bounded, arb_trapdoor},
ValueCommitment, MAX_NOTE_VALUE,
},
Nullifier,
},
transaction::{
@ -635,8 +635,9 @@ pub mod testing {
prop_compose! {
/// produce a spend description with invalid data (useful only for serialization
/// roundtrip testing).
fn arb_spend_description()(
cv in arb_extended_point(),
fn arb_spend_description(n_spends: usize)(
value in arb_note_value_bounded(MAX_NOTE_VALUE.checked_div(n_spends as u64).unwrap_or(0)),
rcv in arb_trapdoor(),
anchor in vec(any::<u8>(), 64)
.prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap())
.prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)),
@ -650,6 +651,7 @@ pub mod testing {
let mut rng = StdRng::from_seed(rng_seed);
let sk1 = PrivateKey(jubjub::Fr::random(&mut rng));
let rk = PublicKey::from_private(&sk1, SPENDING_KEY_GENERATOR);
let cv = ValueCommitment::derive(value, rcv);
SpendDescription {
cv,
anchor,
@ -664,8 +666,9 @@ pub mod testing {
prop_compose! {
/// produce an output description with invalid data (useful only for serialization
/// roundtrip testing).
pub fn arb_output_description()(
cv in arb_extended_point(),
pub fn arb_output_description(n_outputs: usize)(
value in arb_note_value_bounded(MAX_NOTE_VALUE.checked_div(n_outputs as u64).unwrap_or(0)),
rcv in arb_trapdoor(),
cmu in vec(any::<u8>(), 64)
.prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap())
.prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)),
@ -677,6 +680,7 @@ pub mod testing {
zkproof in vec(any::<u8>(), GROTH_PROOF_SIZE)
.prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()),
) -> OutputDescription<GrothProofBytes> {
let cv = ValueCommitment::derive(value, rcv);
OutputDescription {
cv,
cmu,
@ -690,8 +694,11 @@ pub mod testing {
prop_compose! {
pub fn arb_bundle()(
shielded_spends in vec(arb_spend_description(), 0..30),
shielded_outputs in vec(arb_output_description(), 0..30),
n_spends in 0usize..30,
n_outputs in 0usize..30,
)(
shielded_spends in vec(arb_spend_description(n_spends), n_spends),
shielded_outputs in vec(arb_output_description(n_outputs), n_outputs),
value_balance in arb_amount(),
rng_seed in prop::array::uniform32(prop::num::u8::ANY),
fake_bvk_bytes in prop::array::uniform32(prop::num::u8::ANY),

View File

@ -18,6 +18,7 @@ use crate::{
redjubjub::{PrivateKey, Signature},
spend_sig_internal,
util::generate_random_rseed_internal,
value::{NoteValue, ValueSum},
Diversifier, Node, Note, PaymentAddress,
},
transaction::{
@ -99,20 +100,17 @@ impl SaplingOutputInfo {
target_height: BlockHeight,
ovk: Option<OutgoingViewingKey>,
to: PaymentAddress,
value: Amount,
value: NoteValue,
memo: MemoBytes,
) -> Result<Self, Error> {
let g_d = to.g_d().ok_or(Error::InvalidAddress)?;
if value.is_negative() {
return Err(Error::InvalidAmount);
}
let rseed = generate_random_rseed_internal(params, target_height, rng);
let note = Note {
g_d,
pk_d: *to.pk_d(),
value: value.into(),
value: value.inner(),
rseed,
};
@ -212,7 +210,7 @@ pub struct SaplingBuilder<P> {
params: P,
anchor: Option<bls12_381::Scalar>,
target_height: BlockHeight,
value_balance: Amount,
value_balance: ValueSum,
spends: Vec<SpendDescriptionInfo>,
outputs: Vec<SaplingOutputInfo>,
}
@ -239,7 +237,7 @@ impl<P> SaplingBuilder<P> {
params,
anchor: None,
target_height,
value_balance: Amount::zero(),
value_balance: ValueSum::zero(),
spends: vec![],
outputs: vec![],
}
@ -269,9 +267,21 @@ impl<P> SaplingBuilder<P> {
}
}
/// Returns the net value represented by the spends and outputs added to this builder,
/// or an error if the values added to this builder overflow the range of a Zcash
/// monetary amount.
fn try_value_balance(&self) -> Result<Amount, Error> {
self.value_balance
.try_into()
.map_err(|_| ())
.and_then(Amount::from_i64)
.map_err(|()| Error::InvalidAmount)
}
/// Returns the net value represented by the spends and outputs added to this builder.
pub fn value_balance(&self) -> Amount {
self.value_balance
self.try_value_balance()
.expect("we check this when mutating self.value_balance")
}
}
@ -301,7 +311,9 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
let alpha = jubjub::Fr::random(&mut rng);
self.value_balance += Amount::from_u64(note.value).map_err(|_| Error::InvalidAmount)?;
self.value_balance =
(self.value_balance + NoteValue::from_raw(note.value)).ok_or(Error::InvalidAddress)?;
self.try_value_balance()?;
self.spends.push(SpendDescriptionInfo {
extsk,
@ -321,7 +333,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
mut rng: R,
ovk: Option<OutgoingViewingKey>,
to: PaymentAddress,
value: Amount,
value: NoteValue,
memo: MemoBytes,
) -> Result<(), Error> {
let output = SaplingOutputInfo::new_internal(
@ -334,7 +346,8 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
memo,
)?;
self.value_balance -= value;
self.value_balance = (self.value_balance - value).ok_or(Error::InvalidAddress)?;
self.try_value_balance()?;
self.outputs.push(output);
@ -349,6 +362,8 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
target_height: BlockHeight,
progress_notifier: Option<&Sender<Progress>>,
) -> Result<Option<Bundle<Unauthorized>>, Error> {
let value_balance = self.try_value_balance()?;
// Record initial positions of spends and outputs
let params = self.params;
let mut indexed_spends: Vec<_> = self.spends.into_iter().enumerate().collect();
@ -527,7 +542,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
Some(Bundle {
shielded_spends,
shielded_outputs,
value_balance: self.value_balance,
value_balance,
authorization: Unauthorized { tx_metadata },
})
};
@ -539,7 +554,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
impl SpendDescription<Unauthorized> {
pub fn apply_signature(&self, spend_auth_sig: Signature) -> SpendDescription<Authorized> {
SpendDescription {
cv: self.cv,
cv: self.cv.clone(),
anchor: self.anchor,
nullifier: self.nullifier,
rk: self.rk.clone(),
@ -598,7 +613,8 @@ pub mod testing {
merkle_tree::{testing::arb_commitment_tree, IncrementalWitness},
sapling::{
prover::mock::MockTxProver,
testing::{arb_node, arb_note, arb_positive_note_value},
testing::{arb_node, arb_note},
value::testing::arb_positive_note_value,
Diversifier,
},
transaction::components::{

View File

@ -1,7 +1,6 @@
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
use byteorder::{LittleEndian, WriteBytesExt};
use ff::PrimeField;
use group::GroupEncoding;
use crate::consensus::BranchId;

View File

@ -5,7 +5,6 @@ use std::io::Write;
use blake2b_simd::{Hash as Blake2bHash, Params, State};
use byteorder::{LittleEndian, WriteBytesExt};
use ff::PrimeField;
use group::GroupEncoding;
use orchard::bundle::{self as orchard};
use crate::consensus::{BlockHeight, BranchId};

View File

@ -6,6 +6,21 @@ and this library adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- `zcash_proofs::circuit::sapling`:
- `ValueCommitmentOpening`
- A `value_commitment_opening` field on `Spend` and `Output`.
### Changed
- Value commitments now use `zcash_primitives::sapling::value::ValueCommitment`
instead of `jubjub::ExtendedPoint` in `zcash_proofs::sapling`:
- `SaplingProvingContext::{spend_proof, output_proof}`
- `SaplingVerificationContext::{check_spend, check_output}`
### Removed
- `zcash_proofs::circuit::sapling`:
- The `value_commitment` field of `Spend` and `Output` (use
`value_commitment_opening` instead).
## [0.9.0] - 2022-11-12
### Changed

View File

@ -7,8 +7,8 @@ use criterion::Criterion;
use group::{ff::Field, Group};
use rand_core::{RngCore, SeedableRng};
use rand_xorshift::XorShiftRng;
use zcash_primitives::sapling::{Diversifier, ProofGenerationKey, ValueCommitment};
use zcash_proofs::circuit::sapling::Spend;
use zcash_primitives::sapling::{Diversifier, ProofGenerationKey};
use zcash_proofs::circuit::sapling::{Spend, ValueCommitmentOpening};
#[cfg(unix)]
use pprof::criterion::{Output, PProfProfiler};
@ -23,7 +23,7 @@ fn criterion_benchmark(c: &mut Criterion) {
let groth_params = generate_random_parameters::<Bls12, _, _>(
Spend {
value_commitment: None,
value_commitment_opening: None,
proof_generation_key: None,
payment_address: None,
commitment_randomness: None,
@ -36,7 +36,7 @@ fn criterion_benchmark(c: &mut Criterion) {
.unwrap();
c.bench_function("sapling-spend-prove", |b| {
let value_commitment = ValueCommitment {
let value_commitment = ValueCommitmentOpening {
value: 1,
randomness: jubjub::Fr::random(&mut rng),
};
@ -72,7 +72,7 @@ fn criterion_benchmark(c: &mut Criterion) {
b.iter(|| {
create_random_proof(
Spend {
value_commitment: Some(value_commitment.clone()),
value_commitment_opening: Some(value_commitment.clone()),
proof_generation_key: Some(proof_generation_key.clone()),
payment_address: Some(payment_address.clone()),
commitment_randomness: Some(commitment_randomness),

View File

@ -7,7 +7,7 @@ use bellman::{Circuit, ConstraintSystem, SynthesisError};
use zcash_primitives::constants;
use zcash_primitives::sapling::{
PaymentAddress, ProofGenerationKey, ValueCommitment, SAPLING_COMMITMENT_TREE_DEPTH,
PaymentAddress, ProofGenerationKey, SAPLING_COMMITMENT_TREE_DEPTH,
};
use super::ecc;
@ -28,10 +28,26 @@ use group::ff::PrimeFieldBits;
pub const TREE_DEPTH: usize = SAPLING_COMMITMENT_TREE_DEPTH;
/// The opening (value and randomness) of a Sapling value commitment.
#[derive(Clone)]
pub struct ValueCommitmentOpening {
pub value: u64,
pub randomness: jubjub::Scalar,
}
#[cfg(test)]
impl ValueCommitmentOpening {
fn commitment(&self) -> jubjub::ExtendedPoint {
let cv = (constants::VALUE_COMMITMENT_VALUE_GENERATOR * jubjub::Fr::from(self.value))
+ (constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR * self.randomness);
cv.into()
}
}
/// This is an instance of the `Spend` circuit.
pub struct Spend {
/// Pedersen commitment to the value being spent
pub value_commitment: Option<ValueCommitment>,
/// The opening of a Pedersen commitment to the value being spent.
pub value_commitment_opening: Option<ValueCommitmentOpening>,
/// Key required to construct proofs for spending notes
/// for a particular spending key
@ -56,8 +72,8 @@ pub struct Spend {
/// This is an output circuit instance.
pub struct Output {
/// Pedersen commitment to the value being spent
pub value_commitment: Option<ValueCommitment>,
/// The opening of a Pedersen commitment to the value being spent.
pub value_commitment_opening: Option<ValueCommitmentOpening>,
/// The payment address of the recipient
pub payment_address: Option<PaymentAddress>,
@ -73,7 +89,7 @@ pub struct Output {
/// input to the circuit
fn expose_value_commitment<CS>(
mut cs: CS,
value_commitment: Option<ValueCommitment>,
value_commitment_opening: Option<ValueCommitmentOpening>,
) -> Result<Vec<boolean::Boolean>, SynthesisError>
where
CS: ConstraintSystem<bls12_381::Scalar>,
@ -81,7 +97,7 @@ where
// Booleanize the value into little-endian bit order
let value_bits = boolean::u64_into_boolean_vec_le(
cs.namespace(|| "value"),
value_commitment.as_ref().map(|c| c.value),
value_commitment_opening.as_ref().map(|c| c.value),
)?;
// Compute the note value in the exponent
@ -96,7 +112,7 @@ where
// it doesn't matter for security.
let rcv = boolean::field_into_boolean_vec_le(
cs.namespace(|| "rcv"),
value_commitment.as_ref().map(|c| c.randomness),
value_commitment_opening.as_ref().map(|c| c.randomness),
)?;
// Compute the randomness in the exponent
@ -230,7 +246,7 @@ impl Circuit<bls12_381::Scalar> for Spend {
// Get the value in little-endian bit order
let value_bits = expose_value_commitment(
cs.namespace(|| "value commitment"),
self.value_commitment,
self.value_commitment_opening,
)?;
// Compute the note's value as a linear combination
@ -404,7 +420,7 @@ impl Circuit<bls12_381::Scalar> for Output {
// in the note.
note_contents.extend(expose_value_commitment(
cs.namespace(|| "value commitment"),
self.value_commitment,
self.value_commitment_opening,
)?);
// Let's deal with g_d
@ -529,7 +545,7 @@ fn test_input_circuit_with_bls12_381() {
let tree_depth = 32;
for _ in 0..10 {
let value_commitment = ValueCommitment {
let value_commitment = ValueCommitmentOpening {
value: rng.next_u64(),
randomness: jubjub::Fr::random(&mut rng),
};
@ -564,8 +580,7 @@ fn test_input_circuit_with_bls12_381() {
{
let rk = jubjub::ExtendedPoint::from(viewing_key.rk(ar)).to_affine();
let expected_value_commitment =
jubjub::ExtendedPoint::from(value_commitment.commitment()).to_affine();
let expected_value_commitment = value_commitment.commitment().to_affine();
let note = Note {
value: value_commitment.value,
g_d,
@ -617,7 +632,7 @@ fn test_input_circuit_with_bls12_381() {
let mut cs = TestConstraintSystem::new();
let instance = Spend {
value_commitment: Some(value_commitment.clone()),
value_commitment_opening: Some(value_commitment.clone()),
proof_generation_key: Some(proof_generation_key.clone()),
payment_address: Some(payment_address.clone()),
commitment_randomness: Some(commitment_randomness),
@ -698,7 +713,7 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() {
];
for i in 0..10 {
let value_commitment = ValueCommitment {
let value_commitment = ValueCommitmentOpening {
value: i,
randomness: jubjub::Fr::from(1000 * (i + 1)),
};
@ -733,8 +748,7 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() {
{
let rk = jubjub::ExtendedPoint::from(viewing_key.rk(ar)).to_affine();
let expected_value_commitment =
jubjub::ExtendedPoint::from(value_commitment.commitment()).to_affine();
let expected_value_commitment = value_commitment.commitment().to_affine();
assert_eq!(
expected_value_commitment.get_u(),
bls12_381::Scalar::from_str_vartime(expected_commitment_us[i as usize]).unwrap()
@ -794,7 +808,7 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() {
let mut cs = TestConstraintSystem::new();
let instance = Spend {
value_commitment: Some(value_commitment.clone()),
value_commitment_opening: Some(value_commitment.clone()),
proof_generation_key: Some(proof_generation_key.clone()),
payment_address: Some(payment_address.clone()),
commitment_randomness: Some(commitment_randomness),
@ -847,7 +861,7 @@ fn test_output_circuit_with_bls12_381() {
]);
for _ in 0..100 {
let value_commitment = ValueCommitment {
let value_commitment = ValueCommitmentOpening {
value: rng.next_u64(),
randomness: jubjub::Fr::random(&mut rng),
};
@ -881,7 +895,7 @@ fn test_output_circuit_with_bls12_381() {
let mut cs = TestConstraintSystem::new();
let instance = Output {
value_commitment: Some(value_commitment.clone()),
value_commitment_opening: Some(value_commitment.clone()),
payment_address: Some(payment_address.clone()),
commitment_randomness: Some(commitment_randomness),
esk: Some(esk),
@ -904,8 +918,7 @@ fn test_output_circuit_with_bls12_381() {
.expect("should be valid")
.cmu();
let expected_value_commitment =
jubjub::ExtendedPoint::from(value_commitment.commitment()).to_affine();
let expected_value_commitment = value_commitment.commitment().to_affine();
let expected_epk =
jubjub::ExtendedPoint::from(payment_address.g_d().expect("should be valid") * esk)

View File

@ -8,6 +8,7 @@ use zcash_primitives::{
sapling::{
prover::TxProver,
redjubjub::{PublicKey, Signature},
value::ValueCommitment,
Diversifier, Node, PaymentAddress, ProofGenerationKey, Rseed,
},
transaction::components::{Amount, GROTH_PROOF_SIZE},
@ -154,7 +155,7 @@ impl TxProver for LocalTxProver {
value: u64,
anchor: bls12_381::Scalar,
merkle_path: MerklePath<Node>,
) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint, PublicKey), ()> {
) -> Result<([u8; GROTH_PROOF_SIZE], ValueCommitment, PublicKey), ()> {
let (proof, cv, rk) = ctx.spend_proof(
proof_generation_key,
diversifier,
@ -182,7 +183,7 @@ impl TxProver for LocalTxProver {
payment_address: PaymentAddress,
rcm: jubjub::Fr,
value: u64,
) -> ([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint) {
) -> ([u8; GROTH_PROOF_SIZE], ValueCommitment) {
let (proof, cv) = ctx.output_proof(esk, payment_address, rcm, value, &self.output_params);
let mut zkproof = [0u8; GROTH_PROOF_SIZE];

View File

@ -1,35 +1,7 @@
//! Helpers for creating Sapling proofs.
use zcash_primitives::{
constants::VALUE_COMMITMENT_VALUE_GENERATOR, transaction::components::Amount,
};
mod prover;
mod verifier;
pub use self::prover::SaplingProvingContext;
pub use self::verifier::{BatchValidator, SaplingVerificationContext};
// This function computes `value` in the exponent of the value commitment base
fn compute_value_balance(value: Amount) -> Option<jubjub::ExtendedPoint> {
// Compute the absolute value (failing if -i64::MAX is
// the value)
let abs = match i64::from(value).checked_abs() {
Some(a) => a as u64,
None => return None,
};
// Is it negative? We'll have to negate later if so.
let is_negative = value.is_negative();
// Compute it in the exponent
let mut value_balance = VALUE_COMMITMENT_VALUE_GENERATOR * jubjub::Fr::from(abs);
// Negate if necessary
if is_negative {
value_balance = -value_balance;
}
// Convert to unknown order point
Some(value_balance.into())
}

View File

@ -3,27 +3,26 @@ use bellman::{
groth16::{create_random_proof, verify_proof, Parameters, PreparedVerifyingKey, Proof},
};
use bls12_381::Bls12;
use group::{ff::Field, Curve, GroupEncoding};
use group::{Curve, GroupEncoding};
use rand_core::OsRng;
use std::ops::{AddAssign, Neg};
use zcash_primitives::{
constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR},
merkle_tree::MerklePath,
sapling::{
redjubjub::{PrivateKey, PublicKey, Signature},
Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed, ValueCommitment,
redjubjub::{PublicKey, Signature},
value::{CommitmentSum, NoteValue, TrapdoorSum, ValueCommitTrapdoor, ValueCommitment},
Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed,
},
transaction::components::Amount,
};
use super::compute_value_balance;
use crate::circuit::sapling::{Output, Spend};
use crate::circuit::sapling::{Output, Spend, ValueCommitmentOpening};
/// A context object for creating the Sapling components of a Zcash transaction.
pub struct SaplingProvingContext {
bsk: jubjub::Fr,
bsk: TrapdoorSum,
// (sum of the Spend value commitments) - (sum of the Output value commitments)
cv_sum: jubjub::ExtendedPoint,
cv_sum: CommitmentSum,
}
impl Default for SaplingProvingContext {
@ -36,8 +35,8 @@ impl SaplingProvingContext {
/// Construct a new context to be used with a single transaction.
pub fn new() -> Self {
SaplingProvingContext {
bsk: jubjub::Fr::zero(),
cv_sum: jubjub::ExtendedPoint::identity(),
bsk: TrapdoorSum::zero(),
cv_sum: CommitmentSum::zero(),
}
}
@ -56,27 +55,22 @@ impl SaplingProvingContext {
merkle_path: MerklePath<Node>,
proving_key: &Parameters<Bls12>,
verifying_key: &PreparedVerifyingKey<Bls12>,
) -> Result<(Proof<Bls12>, jubjub::ExtendedPoint, PublicKey), ()> {
) -> Result<(Proof<Bls12>, ValueCommitment, PublicKey), ()> {
// Initialize secure RNG
let mut rng = OsRng;
// We create the randomness of the value commitment
let rcv = jubjub::Fr::random(&mut rng);
let rcv = ValueCommitTrapdoor::random(&mut rng);
// Accumulate the value commitment randomness in the context
{
let mut tmp = rcv;
tmp.add_assign(&self.bsk);
// Update the context
self.bsk = tmp;
}
self.bsk += &rcv;
// Construct the value commitment
let value_commitment = ValueCommitment {
let value_commitment_opening = ValueCommitmentOpening {
value,
randomness: rcv,
randomness: rcv.inner(),
};
let value_commitment = ValueCommitment::derive(NoteValue::from_raw(value), rcv);
// Construct the viewing key
let viewing_key = proof_generation_key.to_viewing_key();
@ -99,7 +93,7 @@ impl SaplingProvingContext {
// We now have the full witness for our circuit
let instance = Spend {
value_commitment: Some(value_commitment.clone()),
value_commitment_opening: Some(value_commitment_opening),
proof_generation_key: Some(proof_generation_key),
payment_address: Some(payment_address),
commitment_randomness: Some(note.rcm()),
@ -126,7 +120,7 @@ impl SaplingProvingContext {
public_input[1] = v;
}
{
let affine = jubjub::ExtendedPoint::from(value_commitment.commitment()).to_affine();
let affine = value_commitment.as_inner().to_affine();
let (u, v) = (affine.get_u(), affine.get_v());
public_input[2] = u;
public_input[3] = v;
@ -147,11 +141,8 @@ impl SaplingProvingContext {
// Verify the proof
verify_proof(verifying_key, &proof, &public_input[..]).map_err(|_| ())?;
// Compute value commitment
let value_commitment: jubjub::ExtendedPoint = value_commitment.commitment().into();
// Accumulate the value commitment in the context
self.cv_sum += value_commitment;
self.cv_sum += &value_commitment;
Ok((proof, value_commitment, rk))
}
@ -166,33 +157,28 @@ impl SaplingProvingContext {
rcm: jubjub::Fr,
value: u64,
proving_key: &Parameters<Bls12>,
) -> (Proof<Bls12>, jubjub::ExtendedPoint) {
) -> (Proof<Bls12>, ValueCommitment) {
// Initialize secure RNG
let mut rng = OsRng;
// We construct ephemeral randomness for the value commitment. This
// randomness is not given back to the caller, but the synthetic
// blinding factor `bsk` is accumulated in the context.
let rcv = jubjub::Fr::random(&mut rng);
let rcv = ValueCommitTrapdoor::random(&mut rng);
// Accumulate the value commitment randomness in the context
{
let mut tmp = rcv.neg(); // Outputs subtract from the total.
tmp.add_assign(&self.bsk);
// Update the context
self.bsk = tmp;
}
self.bsk -= &rcv; // Outputs subtract from the total.
// Construct the value commitment for the proof instance
let value_commitment = ValueCommitment {
let value_commitment_opening = ValueCommitmentOpening {
value,
randomness: rcv,
randomness: rcv.inner(),
};
let value_commitment = ValueCommitment::derive(NoteValue::from_raw(value), rcv);
// We now have a full witness for the output proof.
let instance = Output {
value_commitment: Some(value_commitment.clone()),
value_commitment_opening: Some(value_commitment_opening),
payment_address: Some(payment_address),
commitment_randomness: Some(rcm),
esk: Some(esk),
@ -202,11 +188,8 @@ impl SaplingProvingContext {
let proof =
create_random_proof(instance, proving_key, &mut rng).expect("proving should not fail");
// Compute the actual value commitment
let value_commitment: jubjub::ExtendedPoint = value_commitment.commitment().into();
// Accumulate the value commitment in the context. We do this to check internal consistency.
self.cv_sum -= value_commitment; // Outputs subtract from the total.
self.cv_sum -= &value_commitment; // Outputs subtract from the total.
(proof, value_commitment)
}
@ -218,7 +201,7 @@ impl SaplingProvingContext {
let mut rng = OsRng;
// Grab the current `bsk` from the context
let bsk = PrivateKey(self.bsk);
let bsk = self.bsk.into_bsk();
// Grab the `bvk` using DerivePublic.
let bvk = PublicKey::from_private(&bsk, VALUE_COMMITMENT_RANDOMNESS_GENERATOR);
@ -227,14 +210,11 @@ impl SaplingProvingContext {
// commitments (as the verifier would) and apply value_balance to compare
// against our derived bvk.
{
// Compute value balance
let value_balance = compute_value_balance(value_balance).ok_or(())?;
// Subtract value_balance from cv_sum to get final bvk
let final_bvk = self.cv_sum - value_balance;
// Compute the final bvk.
let final_bvk = self.cv_sum.into_bvk(value_balance);
// The result should be the same, unless the provided valueBalance is wrong.
if bvk.0 != final_bvk {
if bvk.0 != final_bvk.0 {
return Err(());
}
}

View File

@ -2,12 +2,13 @@ use bellman::{gadgets::multipack, groth16::Proof};
use bls12_381::Bls12;
use group::{Curve, GroupEncoding};
use zcash_primitives::{
sapling::redjubjub::{PublicKey, Signature},
sapling::{
redjubjub::{PublicKey, Signature},
value::{CommitmentSum, ValueCommitment},
},
transaction::components::Amount,
};
use super::compute_value_balance;
mod single;
pub use single::SaplingVerificationContext;
@ -17,14 +18,14 @@ pub use batch::BatchValidator;
/// A context object for verifying the Sapling components of a Zcash transaction.
struct SaplingVerificationContextInner {
// (sum of the Spend value commitments) - (sum of the Output value commitments)
cv_sum: jubjub::ExtendedPoint,
cv_sum: CommitmentSum,
}
impl SaplingVerificationContextInner {
/// Construct a new context to be used with a single transaction.
fn new() -> Self {
SaplingVerificationContextInner {
cv_sum: jubjub::ExtendedPoint::identity(),
cv_sum: CommitmentSum::zero(),
}
}
@ -33,7 +34,7 @@ impl SaplingVerificationContextInner {
#[allow(clippy::too_many_arguments)]
fn check_spend<C>(
&mut self,
cv: &jubjub::ExtendedPoint,
cv: &ValueCommitment,
anchor: bls12_381::Scalar,
nullifier: &[u8; 32],
rk: &PublicKey,
@ -44,7 +45,10 @@ impl SaplingVerificationContextInner {
spend_auth_sig_verifier: impl FnOnce(&mut C, &PublicKey, [u8; 64], &Signature) -> bool,
proof_verifier: impl FnOnce(&mut C, Proof<Bls12>, [bls12_381::Scalar; 7]) -> bool,
) -> bool {
if (cv.is_small_order() | rk.0.is_small_order()).into() {
// The "cv is not small order" happens when a SpendDescription is deserialized.
// This happens when transactions or blocks are received over the network, or when
// mined blocks are introduced via the `submitblock` RPC method on full nodes.
if rk.0.is_small_order().into() {
return false;
}
@ -74,7 +78,7 @@ impl SaplingVerificationContextInner {
public_input[1] = v;
}
{
let affine = cv.to_affine();
let affine = cv.as_inner().to_affine();
let (u, v) = (affine.get_u(), affine.get_v());
public_input[2] = u;
public_input[3] = v;
@ -100,13 +104,16 @@ impl SaplingVerificationContextInner {
/// accumulating its value commitment inside the context for later use.
fn check_output(
&mut self,
cv: &jubjub::ExtendedPoint,
cv: &ValueCommitment,
cmu: bls12_381::Scalar,
epk: jubjub::ExtendedPoint,
zkproof: Proof<Bls12>,
proof_verifier: impl FnOnce(Proof<Bls12>, [bls12_381::Scalar; 5]) -> bool,
) -> bool {
if (cv.is_small_order() | epk.is_small_order()).into() {
// The "cv is not small order" happens when an OutputDescription is deserialized.
// This happens when transactions or blocks are received over the network, or when
// mined blocks are introduced via the `submitblock` RPC method on full nodes.
if epk.is_small_order().into() {
return false;
}
@ -116,7 +123,7 @@ impl SaplingVerificationContextInner {
// Construct public input for circuit
let mut public_input = [bls12_381::Scalar::zero(); 5];
{
let affine = cv.to_affine();
let affine = cv.as_inner().to_affine();
let (u, v) = (affine.get_u(), affine.get_v());
public_input[0] = u;
public_input[1] = v;
@ -143,17 +150,8 @@ impl SaplingVerificationContextInner {
binding_sig: Signature,
binding_sig_verifier: impl FnOnce(PublicKey, [u8; 64], Signature) -> bool,
) -> bool {
// Obtain current cv_sum from the context
let mut bvk = PublicKey(self.cv_sum);
// Compute value balance
let value_balance = match compute_value_balance(value_balance) {
Some(a) => a,
None => return false,
};
// Subtract value_balance from current cv_sum to get final bvk
bvk.0 -= value_balance;
// Compute the final bvk.
let bvk = self.cv_sum.into_bvk(value_balance);
// Compute the signature's message for bvk/binding_sig
let mut data_to_be_signed = [0u8; 64];

View File

@ -2,7 +2,10 @@ use bellman::groth16::{verify_proof, PreparedVerifyingKey, Proof};
use bls12_381::Bls12;
use zcash_primitives::{
constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR},
sapling::redjubjub::{PublicKey, Signature},
sapling::{
redjubjub::{PublicKey, Signature},
value::ValueCommitment,
},
transaction::components::Amount,
};
@ -28,7 +31,7 @@ impl SaplingVerificationContext {
#[allow(clippy::too_many_arguments)]
pub fn check_spend(
&mut self,
cv: jubjub::ExtendedPoint,
cv: &ValueCommitment,
anchor: bls12_381::Scalar,
nullifier: &[u8; 32],
rk: PublicKey,
@ -39,7 +42,7 @@ impl SaplingVerificationContext {
) -> bool {
let zip216_enabled = self.zip216_enabled;
self.inner.check_spend(
&cv,
cv,
anchor,
nullifier,
&rk,
@ -60,14 +63,14 @@ impl SaplingVerificationContext {
/// accumulating its value commitment inside the context for later use.
pub fn check_output(
&mut self,
cv: jubjub::ExtendedPoint,
cv: &ValueCommitment,
cmu: bls12_381::Scalar,
epk: jubjub::ExtendedPoint,
zkproof: Proof<Bls12>,
verifying_key: &PreparedVerifyingKey<Bls12>,
) -> bool {
self.inner
.check_output(&cv, cmu, epk, zkproof, |proof, public_inputs| {
.check_output(cv, cmu, epk, zkproof, |proof, public_inputs| {
verify_proof(verifying_key, &proof, &public_inputs[..]).is_ok()
})
}