Merge pull request #1047 from zcash/1044-sapling-bundle-generic-valuebalance

zcash_primitives: Make `value_balance` generic in `sapling::Bundle`
This commit is contained in:
str4d 2023-11-28 01:31:34 +00:00 committed by GitHub
commit 1607d013d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 208 additions and 154 deletions

View File

@ -96,6 +96,8 @@ and this library adheres to Rust's notion of
- `SaplingVerificationContext::{check_spend, check_output}` now take
the `PreparedSpendVerifyingKey` and `PreparedOutputVerifyingKey`
newtypes.
- `SaplingVerificationContext::final_check` now takes its `value_balance`
argument as `V: Into<i64>` instead of `Amount`.
- `address::PaymentAddress::create_note` now takes its `value` argument as a
`NoteValue` instead of as a bare `u64`.
- `builder::SaplingBuilder` no longer has a `P: consensus::Parameters` type
@ -112,6 +114,9 @@ and this library adheres to Rust's notion of
- `Error::DuplicateSignature`
- `Error::InvalidExternalSignature`
- `Error::MissingSignatures`
- `bundle::Bundle` now has a second generic parameter `V`.
- `bundle::Bundle::value_balance` now returns `&V` instead of `&Amount`.
- `bundle::testing::arb_bundle` now takes a `value_balance: V` argument.
- `bundle::MapAuth` trait methods now take `&mut self` instead of `&self`.
- `circuit::ValueCommitmentOpening::value` is now represented as a `NoteValue`
instead of as a bare `u64`.

View File

@ -17,6 +17,7 @@ use zcash_primitives::{
value::NoteValue,
Diversifier, SaplingIvk,
},
transaction::components::Amount,
};
#[cfg(unix)]
@ -46,7 +47,7 @@ fn bench_note_decryption(c: &mut Criterion) {
)
.unwrap();
let (bundle, _) = builder
.build::<MockSpendProver, MockOutputProver, _>(&mut rng)
.build::<MockSpendProver, MockOutputProver, _, Amount>(&mut rng)
.unwrap()
.unwrap();
bundle.shielded_outputs()[0].clone()

View File

@ -30,10 +30,7 @@ use crate::{
},
transaction::{
builder::Progress,
components::{
amount::{Amount, NonNegativeAmount},
sapling::fees,
},
components::{amount::NonNegativeAmount, sapling::fees},
},
zip32::ExtendedSpendingKey,
};
@ -343,16 +340,16 @@ impl SaplingBuilder {
/// 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> {
fn try_value_balance<V: TryFrom<i64>>(&self) -> Result<V, Error> {
self.value_balance
.try_into()
.map_err(|_| ())
.and_then(Amount::from_i64)
.and_then(|vb| V::try_from(vb).map_err(|_| ()))
.map_err(|()| Error::InvalidAmount)
}
/// Returns the net value represented by the spends and outputs added to this builder.
pub fn value_balance(&self) -> Amount {
pub fn value_balance<V: TryFrom<i64>>(&self) -> V {
self.try_value_balance()
.expect("we check this when mutating self.value_balance")
}
@ -381,7 +378,7 @@ impl SaplingBuilder {
}
self.value_balance = (self.value_balance + note.value()).ok_or(Error::InvalidAmount)?;
self.try_value_balance()?;
self.try_value_balance::<i64>()?;
let spend =
SpendDescriptionInfo::new_internal(&mut rng, extsk, diversifier, note, merkle_path);
@ -411,17 +408,17 @@ impl SaplingBuilder {
);
self.value_balance = (self.value_balance - value).ok_or(Error::InvalidAddress)?;
self.try_value_balance()?;
self.try_value_balance::<i64>()?;
self.outputs.push(output);
Ok(())
}
pub fn build<SP: SpendProver, OP: OutputProver, R: RngCore>(
pub fn build<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
self,
mut rng: R,
) -> Result<Option<(UnauthorizedBundle, SaplingMetadata)>, Error> {
) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
let value_balance = self.try_value_balance()?;
// Record initial positions of spends and outputs
@ -536,7 +533,7 @@ impl SaplingBuilder {
/// Type alias for an in-progress bundle that has no proofs or signatures.
///
/// This is returned by [`SaplingBuilder::build`].
pub type UnauthorizedBundle = Bundle<InProgress<Unproven, Unsigned>>;
pub type UnauthorizedBundle<V> = Bundle<InProgress<Unproven, Unsigned>, V>;
/// Marker trait representing bundle proofs in the process of being created.
pub trait InProgressProofs: fmt::Debug {
@ -654,7 +651,7 @@ impl<'a, S: InProgressSignatures, SP: SpendProver, OP: OutputProver, R: RngCore>
}
}
impl<S: InProgressSignatures> Bundle<InProgress<Unproven, S>> {
impl<S: InProgressSignatures, V> Bundle<InProgress<Unproven, S>, V> {
/// Creates the proofs for this bundle.
pub fn create_proofs<SP: SpendProver, OP: OutputProver>(
self,
@ -662,7 +659,7 @@ impl<S: InProgressSignatures> Bundle<InProgress<Unproven, S>> {
output_prover: &OP,
rng: impl RngCore,
progress_notifier: Option<&Sender<Progress>>,
) -> Bundle<InProgress<Proven, S>> {
) -> Bundle<InProgress<Proven, S>, V> {
let total_progress =
self.shielded_spends().len() as u32 + self.shielded_outputs().len() as u32;
self.map_authorization(CreateProofs::new(
@ -737,7 +734,7 @@ impl MaybeSigned {
}
}
impl<P: InProgressProofs> Bundle<InProgress<P, Unsigned>> {
impl<P: InProgressProofs, V> Bundle<InProgress<P, Unsigned>, V> {
/// Loads the sighash into this bundle, preparing it for signing.
///
/// This API ensures that all signatures are created over the same sighash.
@ -745,7 +742,7 @@ impl<P: InProgressProofs> Bundle<InProgress<P, Unsigned>> {
self,
mut rng: R,
sighash: [u8; 32],
) -> Bundle<InProgress<P, PartiallyAuthorized>> {
) -> Bundle<InProgress<P, PartiallyAuthorized>, V> {
self.map_authorization((
|proof| proof,
|proof| proof,
@ -765,7 +762,7 @@ impl<P: InProgressProofs> Bundle<InProgress<P, Unsigned>> {
}
}
impl Bundle<InProgress<Proven, Unsigned>> {
impl<V> Bundle<InProgress<Proven, Unsigned>, V> {
/// Applies signatures to this bundle, in order to authorize it.
///
/// This is a helper method that wraps [`Bundle::prepare`], [`Bundle::sign`], and
@ -775,7 +772,7 @@ impl Bundle<InProgress<Proven, Unsigned>> {
mut rng: R,
sighash: [u8; 32],
signing_keys: &[PrivateKey],
) -> Result<Bundle<Authorized>, Error> {
) -> Result<Bundle<Authorized, V>, Error> {
signing_keys
.iter()
.fold(self.prepare(&mut rng, sighash), |partial, ask| {
@ -785,7 +782,7 @@ impl Bundle<InProgress<Proven, Unsigned>> {
}
}
impl<P: InProgressProofs> Bundle<InProgress<P, PartiallyAuthorized>> {
impl<P: InProgressProofs, V> Bundle<InProgress<P, PartiallyAuthorized>, V> {
/// Signs this bundle with the given [`PrivateKey`].
///
/// This will apply signatures for all notes controlled by this spending key.
@ -842,11 +839,11 @@ impl<P: InProgressProofs> Bundle<InProgress<P, PartiallyAuthorized>> {
}
}
impl Bundle<InProgress<Proven, PartiallyAuthorized>> {
impl<V> Bundle<InProgress<Proven, PartiallyAuthorized>, V> {
/// Finalizes this bundle, enabling it to be included in a transaction.
///
/// Returns an error if any signatures are missing.
pub fn finalize(self) -> Result<Bundle<Authorized>, Error> {
pub fn finalize(self) -> Result<Bundle<Authorized, V>, Error> {
self.try_map_authorization((
Ok,
Ok,
@ -862,6 +859,8 @@ impl Bundle<InProgress<Proven, PartiallyAuthorized>> {
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use std::fmt;
use proptest::collection::vec;
use proptest::prelude::*;
use rand::{rngs::StdRng, SeedableRng};
@ -876,7 +875,6 @@ pub mod testing {
value::testing::arb_positive_note_value,
Diversifier,
},
transaction::components::amount::MAX_MONEY,
zip32::sapling::testing::arb_extended_spending_key,
};
use incrementalmerkletree::{
@ -885,53 +883,70 @@ pub mod testing {
use super::SaplingBuilder;
prop_compose! {
fn arb_bundle(zip212_enforcement: Zip212Enforcement)(n_notes in 1..30usize)(
extsk in arb_extended_spending_key(),
spendable_notes in vec(
arb_positive_note_value(MAX_MONEY as u64 / 10000).prop_flat_map(arb_note),
n_notes
),
commitment_trees in vec(
arb_commitment_tree::<_, _, 32>(n_notes, arb_node()).prop_map(
|t| IncrementalWitness::from_tree(t).path().unwrap()
),
n_notes
),
diversifiers in vec(prop::array::uniform11(any::<u8>()).prop_map(Diversifier), n_notes),
rng_seed in prop::array::uniform32(any::<u8>()),
fake_sighash_bytes in prop::array::uniform32(any::<u8>()),
) -> Bundle<Authorized> {
let mut builder = SaplingBuilder::new(zip212_enforcement);
let mut rng = StdRng::from_seed(rng_seed);
#[allow(dead_code)]
fn arb_bundle<V: fmt::Debug + From<i64>>(
max_money: u64,
zip212_enforcement: Zip212Enforcement,
) -> impl Strategy<Value = Bundle<Authorized, V>> {
(1..30usize)
.prop_flat_map(move |n_notes| {
(
arb_extended_spending_key(),
vec(
arb_positive_note_value(max_money / 10000).prop_flat_map(arb_note),
n_notes,
),
vec(
arb_commitment_tree::<_, _, 32>(n_notes, arb_node())
.prop_map(|t| IncrementalWitness::from_tree(t).path().unwrap()),
n_notes,
),
vec(
prop::array::uniform11(any::<u8>()).prop_map(Diversifier),
n_notes,
),
prop::array::uniform32(any::<u8>()),
prop::array::uniform32(any::<u8>()),
)
})
.prop_map(
move |(
extsk,
spendable_notes,
commitment_trees,
diversifiers,
rng_seed,
fake_sighash_bytes,
)| {
let mut builder = SaplingBuilder::new(zip212_enforcement);
let mut rng = StdRng::from_seed(rng_seed);
for ((note, path), diversifier) in spendable_notes.into_iter().zip(commitment_trees.into_iter()).zip(diversifiers.into_iter()) {
builder.add_spend(
&mut rng,
&extsk,
diversifier,
note,
path
).unwrap();
}
for ((note, path), diversifier) in spendable_notes
.into_iter()
.zip(commitment_trees.into_iter())
.zip(diversifiers.into_iter())
{
builder
.add_spend(&mut rng, &extsk, diversifier, note, path)
.unwrap();
}
let (bundle, _) = builder
.build::<MockSpendProver, MockOutputProver, _>(&mut rng)
.unwrap()
.unwrap();
let (bundle, _) = builder
.build::<MockSpendProver, MockOutputProver, _, _>(&mut rng)
.unwrap()
.unwrap();
let bundle = bundle.create_proofs(
&MockSpendProver,
&MockOutputProver,
&mut rng,
None,
);
let bundle =
bundle.create_proofs(&MockSpendProver, &MockOutputProver, &mut rng, None);
bundle.apply_signatures(
&mut rng,
fake_sighash_bytes,
&[PrivateKey(extsk.expsk.ask)],
).unwrap()
}
bundle
.apply_signatures(
&mut rng,
fake_sighash_bytes,
&[PrivateKey(extsk.expsk.ask)],
)
.unwrap()
},
)
}
}

View File

@ -14,7 +14,7 @@ use crate::{
value::ValueCommitment,
Nullifier,
},
transaction::components::{Amount, GROTH_PROOF_SIZE},
transaction::components::GROTH_PROOF_SIZE,
};
pub type GrothProofBytes = [u8; GROTH_PROOF_SIZE];
@ -151,20 +151,20 @@ where
}
#[derive(Debug, Clone)]
pub struct Bundle<A: Authorization> {
pub struct Bundle<A: Authorization, V> {
shielded_spends: Vec<SpendDescription<A>>,
shielded_outputs: Vec<OutputDescription<A::OutputProof>>,
value_balance: Amount,
value_balance: V,
authorization: A,
}
impl<A: Authorization> Bundle<A> {
impl<A: Authorization, V> Bundle<A, V> {
/// Constructs a `Bundle` from its constituent parts.
#[cfg(feature = "temporary-zcashd")]
pub fn temporary_zcashd_from_parts(
shielded_spends: Vec<SpendDescription<A>>,
shielded_outputs: Vec<OutputDescription<A::OutputProof>>,
value_balance: Amount,
value_balance: V,
authorization: A,
) -> Self {
Self::from_parts(
@ -179,7 +179,7 @@ impl<A: Authorization> Bundle<A> {
pub(crate) fn from_parts(
shielded_spends: Vec<SpendDescription<A>>,
shielded_outputs: Vec<OutputDescription<A::OutputProof>>,
value_balance: Amount,
value_balance: V,
authorization: A,
) -> Self {
Bundle {
@ -203,7 +203,7 @@ impl<A: Authorization> Bundle<A> {
/// Returns the net value moved into or out of the Sapling shielded pool.
///
/// This is the sum of Sapling spends minus the sum of Sapling outputs.
pub fn value_balance(&self) -> &Amount {
pub fn value_balance(&self) -> &V {
&self.value_balance
}
@ -215,7 +215,7 @@ impl<A: Authorization> Bundle<A> {
}
/// Transitions this bundle from one authorization state to another.
pub fn map_authorization<B: Authorization, F: MapAuth<A, B>>(self, mut f: F) -> Bundle<B> {
pub fn map_authorization<B: Authorization, F: MapAuth<A, B>>(self, mut f: F) -> Bundle<B, V> {
Bundle {
shielded_spends: self
.shielded_spends
@ -250,7 +250,7 @@ impl<A: Authorization> Bundle<A> {
pub fn try_map_authorization<B: Authorization, F: TryMapAuth<A, B>>(
self,
mut f: F,
) -> Result<Bundle<B>, F::Error> {
) -> Result<Bundle<B, V>, F::Error> {
Ok(Bundle {
shielded_spends: self
.shielded_spends
@ -286,20 +286,28 @@ impl<A: Authorization> Bundle<A> {
}
}
impl DynamicUsage for Bundle<Authorized> {
impl<V: DynamicUsage> DynamicUsage for Bundle<Authorized, V> {
fn dynamic_usage(&self) -> usize {
self.shielded_spends.dynamic_usage() + self.shielded_outputs.dynamic_usage()
self.shielded_spends.dynamic_usage()
+ self.shielded_outputs.dynamic_usage()
+ self.value_balance.dynamic_usage()
}
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
let bounds = (
self.shielded_spends.dynamic_usage_bounds(),
self.shielded_outputs.dynamic_usage_bounds(),
self.value_balance.dynamic_usage_bounds(),
);
(
bounds.0 .0 + bounds.1 .0,
bounds.0 .1.zip(bounds.1 .1).map(|(a, b)| a + b),
bounds.0 .0 + bounds.1 .0 + bounds.2 .0,
bounds
.0
.1
.zip(bounds.1 .1)
.zip(bounds.2 .1)
.map(|((a, b), c)| a + b + c),
)
}
}
@ -611,6 +619,8 @@ impl<A> From<OutputDescription<A>> for CompactOutputDescription {
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use std::fmt;
use ff::Field;
use group::{Group, GroupEncoding};
use proptest::collection::vec;
@ -628,7 +638,7 @@ pub mod testing {
},
Nullifier,
},
transaction::components::{amount::testing::arb_amount, GROTH_PROOF_SIZE},
transaction::components::GROTH_PROOF_SIZE,
};
use super::{
@ -705,32 +715,40 @@ pub mod testing {
}
}
prop_compose! {
pub fn arb_bundle()(
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),
) -> Option<Bundle<Authorized>> {
if shielded_spends.is_empty() && shielded_outputs.is_empty() {
None
} else {
let mut rng = StdRng::from_seed(rng_seed);
let bsk = PrivateKey(jubjub::Fr::random(&mut rng));
Some(
Bundle {
shielded_spends,
shielded_outputs,
value_balance,
authorization: Authorized { binding_sig: bsk.sign(&fake_bvk_bytes, &mut rng, VALUE_COMMITMENT_RANDOMNESS_GENERATOR) },
}
pub fn arb_bundle<V: Copy + fmt::Debug + 'static>(
value_balance: V,
) -> impl Strategy<Value = Option<Bundle<Authorized, V>>> {
(0usize..30, 0usize..30)
.prop_flat_map(|(n_spends, n_outputs)| {
(
vec(arb_spend_description(n_spends), n_spends),
vec(arb_output_description(n_outputs), n_outputs),
prop::array::uniform32(prop::num::u8::ANY),
prop::array::uniform32(prop::num::u8::ANY),
)
}
}
})
.prop_map(
move |(shielded_spends, shielded_outputs, rng_seed, fake_bvk_bytes)| {
if shielded_spends.is_empty() && shielded_outputs.is_empty() {
None
} else {
let mut rng = StdRng::from_seed(rng_seed);
let bsk = PrivateKey(jubjub::Fr::random(&mut rng));
Some(Bundle {
shielded_spends,
shielded_outputs,
value_balance,
authorization: Authorized {
binding_sig: bsk.sign(
&fake_bvk_bytes,
&mut rng,
VALUE_COMMITMENT_RANDOMNESS_GENERATOR,
),
},
})
}
},
)
}
}

View File

@ -23,10 +23,9 @@ use crate::{
DiversifiedTransmissionKey, EphemeralPublicKey, EphemeralSecretKey, OutgoingViewingKey,
SharedSecret,
},
value::ValueCommitment,
value::{NoteValue, ValueCommitment},
Diversifier, Note, PaymentAddress, Rseed,
},
transaction::components::amount::NonNegativeAmount,
};
use super::note::ExtractedNoteCommitment;
@ -93,12 +92,11 @@ where
.try_into()
.expect("Note plaintext is checked to have length >= COMPACT_NOTE_SIZE."),
);
let value = NonNegativeAmount::from_u64_le_bytes(
let value = NoteValue::from_bytes(
plaintext[12..20]
.try_into()
.expect("Note plaintext is checked to have length >= COMPACT_NOTE_SIZE."),
)
.ok()?;
);
let r: [u8; 32] = plaintext[20..COMPACT_NOTE_SIZE]
.try_into()
.expect("Note plaintext is checked to have length >= COMPACT_NOTE_SIZE.");
@ -114,7 +112,7 @@ where
// `diversifier` was checked by `get_pk_d`.
let to = PaymentAddress::from_parts_unchecked(diversifier, pk_d)?;
let note = to.create_note(value.into(), rseed);
let note = to.create_note(value, rseed);
Some((note, to))
}

View File

@ -69,6 +69,10 @@ impl NoteValue {
NoteValue(value)
}
pub(crate) fn from_bytes(bytes: [u8; 8]) -> Self {
NoteValue(u64::from_le_bytes(bytes))
}
pub(crate) fn to_le_bits(self) -> BitArray<[u8; 8], Lsb0> {
BitArray::<_, Lsb0>::new(self.0.to_le_bytes())
}

View File

@ -2,13 +2,10 @@ use bellman::{gadgets::multipack, groth16::Proof};
use bls12_381::Bls12;
use group::{ff::PrimeField, Curve, GroupEncoding};
use crate::{
sapling::{
note::ExtractedNoteCommitment,
redjubjub::{PublicKey, Signature},
value::{CommitmentSum, ValueCommitment},
},
transaction::components::Amount,
use crate::sapling::{
note::ExtractedNoteCommitment,
redjubjub::{PublicKey, Signature},
value::{CommitmentSum, ValueCommitment},
};
mod single;
@ -145,9 +142,9 @@ impl SaplingVerificationContextInner {
/// Perform consensus checks on the valueBalance and bindingSig parts of a
/// Sapling transaction. All SpendDescriptions and OutputDescriptions must
/// have been checked before calling this function.
fn final_check(
fn final_check<V: Into<i64>>(
&self,
value_balance: Amount,
value_balance: V,
sighash_value: &[u8; 32],
binding_sig: Signature,
binding_sig_verifier: impl FnOnce(PublicKey, [u8; 64], Signature) -> bool,

View File

@ -45,7 +45,11 @@ impl BatchValidator {
/// `BatchValidator` can continue to be used regardless, but some or all of the proofs
/// and signatures from this bundle may have already been added to the batch even if
/// it fails other consensus rules.
pub fn check_bundle(&mut self, bundle: Bundle<Authorized>, sighash: [u8; 32]) -> bool {
pub fn check_bundle<V: Copy + Into<i64>>(
&mut self,
bundle: Bundle<Authorized, V>,
sighash: [u8; 32],
) -> bool {
self.bundles_added = true;
let mut ctx = SaplingVerificationContextInner::new();

View File

@ -2,15 +2,12 @@ use bellman::groth16::{verify_proof, Proof};
use bls12_381::Bls12;
use super::SaplingVerificationContextInner;
use crate::{
sapling::{
circuit::{PreparedOutputVerifyingKey, PreparedSpendVerifyingKey},
constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR},
note::ExtractedNoteCommitment,
redjubjub::{PublicKey, Signature},
value::ValueCommitment,
},
transaction::components::Amount,
use crate::sapling::{
circuit::{PreparedOutputVerifyingKey, PreparedSpendVerifyingKey},
constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR},
note::ExtractedNoteCommitment,
redjubjub::{PublicKey, Signature},
value::ValueCommitment,
};
/// A context object for verifying the Sapling components of a single Zcash transaction.
@ -80,9 +77,9 @@ impl SaplingVerificationContext {
/// Perform consensus checks on the valueBalance and bindingSig parts of a
/// Sapling transaction. All SpendDescriptions and OutputDescriptions must
/// have been checked before calling this function.
pub fn final_check(
pub fn final_check<V: Into<i64>>(
&self,
value_balance: Amount,
value_balance: V,
sighash_value: &[u8; 32],
binding_sig: Signature,
) -> bool {

View File

@ -513,7 +513,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
let mut rng = self.rng;
let (sapling_bundle, tx_metadata) = match self
.sapling_builder
.build::<SP, OP, _>(&mut rng)
.build::<SP, OP, _, _>(&mut rng)
.map_err(Error::SaplingBuild)?
.map(|(bundle, tx_metadata)| {
// We need to create proofs before signatures, because we still support

View File

@ -286,7 +286,7 @@ pub(crate) fn read_v4_components<R: Read>(
#[cfg(feature = "temporary-zcashd")]
pub fn temporary_zcashd_write_v4_components<W: Write>(
writer: W,
bundle: Option<&Bundle<Authorized>>,
bundle: Option<&Bundle<Authorized, Amount>>,
tx_has_sapling: bool,
) -> io::Result<()> {
write_v4_components(writer, bundle, tx_has_sapling)
@ -295,7 +295,7 @@ pub fn temporary_zcashd_write_v4_components<W: Write>(
/// Writes the Sapling components of a v4 transaction.
pub(crate) fn write_v4_components<W: Write>(
mut writer: W,
bundle: Option<&Bundle<Authorized>>,
bundle: Option<&Bundle<Authorized, Amount>>,
tx_has_sapling: bool,
) -> io::Result<()> {
if tx_has_sapling {
@ -326,7 +326,9 @@ pub(crate) fn write_v4_components<W: Write>(
/// Reads a [`Bundle`] from a v5 transaction format.
#[allow(clippy::redundant_closure)]
pub(crate) fn read_v5_bundle<R: Read>(mut reader: R) -> io::Result<Option<Bundle<Authorized>>> {
pub(crate) fn read_v5_bundle<R: Read>(
mut reader: R,
) -> io::Result<Option<Bundle<Authorized, Amount>>> {
let sd_v5s = Vector::read(&mut reader, read_spend_v5)?;
let od_v5s = Vector::read(&mut reader, read_output_v5)?;
let n_spends = sd_v5s.len();
@ -385,7 +387,7 @@ pub(crate) fn read_v5_bundle<R: Read>(mut reader: R) -> io::Result<Option<Bundle
/// Writes a [`Bundle`] in the v5 transaction format.
pub(crate) fn write_v5_bundle<W: Write>(
mut writer: W,
sapling_bundle: Option<&Bundle<Authorized>>,
sapling_bundle: Option<&Bundle<Authorized, Amount>>,
) -> io::Result<()> {
if let Some(bundle) = sapling_bundle {
Vector::write(&mut writer, bundle.shielded_spends(), |w, e| {
@ -436,13 +438,26 @@ pub mod testing {
use proptest::prelude::*;
use crate::{
sapling::bundle::{testing::arb_bundle, Authorized, Bundle},
transaction::TxVersion,
sapling::bundle::{testing as t_sap, Authorized, Bundle},
transaction::{
components::{amount::testing::arb_amount, Amount},
TxVersion,
},
};
prop_compose! {
fn arb_bundle()(
value_balance in arb_amount()
)(
bundle in t_sap::arb_bundle(value_balance)
) -> Option<Bundle<Authorized, Amount>> {
bundle
}
}
pub fn arb_bundle_for_version(
v: TxVersion,
) -> impl Strategy<Value = Option<Bundle<Authorized>>> {
) -> impl Strategy<Value = Option<Bundle<Authorized, Amount>>> {
if v.has_sapling() {
Strategy::boxed(arb_bundle())
} else {

View File

@ -309,7 +309,7 @@ pub struct TransactionData<A: Authorization> {
expiry_height: BlockHeight,
transparent_bundle: Option<transparent::Bundle<A::TransparentAuth>>,
sprout_bundle: Option<sprout::Bundle>,
sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
sapling_bundle: Option<sapling::Bundle<A::SaplingAuth, Amount>>,
orchard_bundle: Option<orchard::bundle::Bundle<A::OrchardAuth, Amount>>,
#[cfg(feature = "zfuture")]
tze_bundle: Option<tze::Bundle<A::TzeAuth>>,
@ -324,7 +324,7 @@ impl<A: Authorization> TransactionData<A> {
expiry_height: BlockHeight,
transparent_bundle: Option<transparent::Bundle<A::TransparentAuth>>,
sprout_bundle: Option<sprout::Bundle>,
sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
sapling_bundle: Option<sapling::Bundle<A::SaplingAuth, Amount>>,
orchard_bundle: Option<orchard::Bundle<A::OrchardAuth, Amount>>,
) -> Self {
TransactionData {
@ -350,7 +350,7 @@ impl<A: Authorization> TransactionData<A> {
expiry_height: BlockHeight,
transparent_bundle: Option<transparent::Bundle<A::TransparentAuth>>,
sprout_bundle: Option<sprout::Bundle>,
sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
sapling_bundle: Option<sapling::Bundle<A::SaplingAuth, Amount>>,
orchard_bundle: Option<orchard::Bundle<A::OrchardAuth, Amount>>,
tze_bundle: Option<tze::Bundle<A::TzeAuth>>,
) -> Self {
@ -391,7 +391,7 @@ impl<A: Authorization> TransactionData<A> {
self.sprout_bundle.as_ref()
}
pub fn sapling_bundle(&self) -> Option<&sapling::Bundle<A::SaplingAuth>> {
pub fn sapling_bundle(&self) -> Option<&sapling::Bundle<A::SaplingAuth, Amount>> {
self.sapling_bundle.as_ref()
}
@ -460,8 +460,8 @@ impl<A: Authorization> TransactionData<A> {
Option<transparent::Bundle<A::TransparentAuth>>,
) -> Option<transparent::Bundle<B::TransparentAuth>>,
f_sapling: impl FnOnce(
Option<sapling::Bundle<A::SaplingAuth>>,
) -> Option<sapling::Bundle<B::SaplingAuth>>,
Option<sapling::Bundle<A::SaplingAuth, Amount>>,
) -> Option<sapling::Bundle<B::SaplingAuth, Amount>>,
f_orchard: impl FnOnce(
Option<orchard::bundle::Bundle<A::OrchardAuth, Amount>>,
) -> Option<orchard::bundle::Bundle<B::OrchardAuth, Amount>>,
@ -727,7 +727,7 @@ impl Transaction {
#[cfg(feature = "temporary-zcashd")]
pub fn temporary_zcashd_read_v5_sapling<R: Read>(
reader: R,
) -> io::Result<Option<sapling::Bundle<sapling::bundle::Authorized>>> {
) -> io::Result<Option<sapling::Bundle<sapling::bundle::Authorized, Amount>>> {
sapling_serialization::read_v5_bundle(reader)
}
@ -836,7 +836,7 @@ impl Transaction {
#[cfg(feature = "temporary-zcashd")]
pub fn temporary_zcashd_write_v5_sapling<W: Write>(
sapling_bundle: Option<&sapling::Bundle<sapling::bundle::Authorized>>,
sapling_bundle: Option<&sapling::Bundle<sapling::bundle::Authorized, Amount>>,
writer: W,
) -> io::Result<()> {
sapling_serialization::write_v5_bundle(writer, sapling_bundle)
@ -915,7 +915,7 @@ pub trait TransactionDigest<A: Authorization> {
fn digest_sapling(
&self,
sapling_bundle: Option<&sapling::Bundle<A::SaplingAuth>>,
sapling_bundle: Option<&sapling::Bundle<A::SaplingAuth, Amount>>,
) -> Self::SaplingDigest;
fn digest_orchard(

View File

@ -256,7 +256,7 @@ pub(crate) fn hash_transparent_txid_data(
/// Implements [ZIP 244 section T.3](https://zips.z.cash/zip-0244#t-3-sapling-digest)
fn hash_sapling_txid_data<A: sapling::bundle::Authorization>(
bundle: &sapling::Bundle<A>,
bundle: &sapling::Bundle<A, Amount>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_HASH_PERSONALIZATION);
if !(bundle.shielded_spends().is_empty() && bundle.shielded_outputs().is_empty()) {
@ -328,7 +328,7 @@ impl<A: Authorization> TransactionDigest<A> for TxIdDigester {
fn digest_sapling(
&self,
sapling_bundle: Option<&sapling::Bundle<A::SaplingAuth>>,
sapling_bundle: Option<&sapling::Bundle<A::SaplingAuth, Amount>>,
) -> Self::SaplingDigest {
sapling_bundle.map(hash_sapling_txid_data)
}
@ -467,7 +467,7 @@ impl TransactionDigest<Authorized> for BlockTxCommitmentDigester {
fn digest_sapling(
&self,
sapling_bundle: Option<&sapling::Bundle<sapling::bundle::Authorized>>,
sapling_bundle: Option<&sapling::Bundle<sapling::bundle::Authorized, Amount>>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_SIGS_HASH_PERSONALIZATION);
if let Some(bundle) = sapling_bundle {