Add Sapling value types
These are modeled after the value types developed for the `orchard` crate.
This commit is contained in:
parent
fee0b6a18d
commit
23922ca290
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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::{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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(());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue