Merge pull request #114 from zcash/sapling_builder_cleanup

A few minor cleanups for consistency with the `orchard` builder API
This commit is contained in:
str4d 2024-01-02 19:21:12 +00:00 committed by GitHub
commit 71711b9e4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 315 additions and 238 deletions

View File

@ -10,6 +10,7 @@ The entries below are relative to the `zcash_primitives::sapling` module as of
`zcash_primitives 0.13.0`.
### Added
- `sapling_crypto::Anchor`
- `sapling_crypto::BatchValidator` (moved from `zcash_proofs::sapling`).
- `sapling_crypto::SaplingVerificationContext` (moved from
`zcash_proofs::sapling`).
@ -25,6 +26,7 @@ The entries below are relative to the `zcash_primitives::sapling` module as of
- `OutputInfo`
- `ProverProgress`
- `BundleType`
- `SigningMetadata`
- `bundle` bundle builder function.
- `sapling_crypto::bundle` module:
- The following types moved from
@ -32,7 +34,7 @@ The entries below are relative to the `zcash_primitives::sapling` module as of
- `Bundle`
- `SpendDescription, SpendDescriptionV5`
- `OutputDescription, OutputDescriptionV5`
- `Authorization, Authorized, MapAuth`
- `Authorization, Authorized`
- `GrothProofBytes`
- `Bundle::<InProgress<Unproven, _>>::create_proofs`
- `Bundle::<InProgress<_, Unsigned>>::prepare`
@ -40,10 +42,6 @@ The entries below are relative to the `zcash_primitives::sapling` module as of
- `Bundle::<InProgress<Proven, PartiallyAuthorized>>::finalize`
- `Bundle::<InProgress<Proven, Unsigned>>::apply_signatures`
- `Bundle::try_map_authorization`
- `TryMapAuth`
- `impl {MapAuth, TryMapAuth} for (FnMut, FnMut, FnMut, FnMut)`
helpers to enable calling `Bundle::{map_authorization, try_map_authorization}`
with a set of closures.
- `testing` module, containing the following functions moved from
`zcash_primitives::transaction::components::sapling::testing`:
- `arb_output_description`
@ -91,21 +89,26 @@ The entries below are relative to the `zcash_primitives::sapling` module as of
- `sapling_crypto::address::PaymentAddress::create_note` now takes its `value`
argument as a `NoteValue` instead of as a bare `u64`.
- `sapling_crypto::builder`:
- `SaplingBuilder` no longer has a `P: zcash_primitives::consensus::Parameters`
- `SaplingBuilder` has been renamed to `Builder`
- `MaybeSigned::SigningMetadata` has been renamed to `MaybeSigned::SigningParts`
- `Builder` no longer has a `P: zcash_primitives::consensus::Parameters`
type parameter.
- `SaplingBuilder::new` now takes a `Zip212Enforcement` argument instead of a
- `Builder::new` now takes a `Zip212Enforcement` argument instead of a
`P: zcash_primitives::consensus::Parameters` argument and a target height.
- `SaplingBuilder::add_spend` now takes `extsk` by reference. Also, it no
It also now takes as an argument the Sapling anchor to be used for all
spends in the bundle.
- `Builder::add_spend` now takes `extsk` by reference. Also, it no
longer takes a `diversifier` argument as the diversifier may be obtained
from the note.
- `SaplingBuilder::add_output` now takes an `Option<[u8; 512]>` memo instead
from the note. All calls to `add_spend` are now required to use an anchor
that corresponds to the anchor provided at builder construction.
- `Builder::add_output` now takes an `Option<[u8; 512]>` memo instead
of a `MemoBytes`.
- `SaplingBuilder::build` no longer takes a prover, proving context, progress
- `Builder::build` no longer takes a prover, proving context, progress
notifier, or target height. Instead, it has `SpendProver, OutputProver`
generic parameters and returns `(UnauthorizedBundle, SaplingMetadata)`. The
caller can then use `Bundle::<InProgress<Unproven, _>>::create_proofs` to
create spend and output proofs for the bundle.
- `SaplingBuilder::build` now takes a `BundleType` argument that instructs
- `Builder::build` now takes a `BundleType` argument that instructs
it how to pad the bundle with dummy outputs.
- `Error` has new error variants:
- `Error::DuplicateSignature`
@ -116,6 +119,9 @@ The entries below are relative to the `zcash_primitives::sapling` module as of
- `Bundle` now has a second generic parameter `V`.
- `Bundle::value_balance` now returns `&V` instead of
`&zcash_primitives::transaction::components::Amount`.
- `Bundle::map_authorization` now takes a context argument and explicit
functions for each mappable field, rather than a `MapAuth` value, in
order to simplify handling of context values.
- `Authorized::binding_sig` now has type `redjubjub::Signature<Binding>`.
- `Authorized::AuthSig` now has type `redjubjub::Signature<SpendAuth>`.
- `SpendDescription::temporary_zcashd_from_parts` now takes `rk` as
@ -126,7 +132,6 @@ The entries below are relative to the `zcash_primitives::sapling` module as of
`redjubjub::Signature<SpendAuth>` instead of
`sapling_crypto::redjubjub::Signature`.
- `testing::arb_bundle` now takes a `value_balance: V` argument.
- `MapAuth` trait methods now take `&mut self` instead of `&self`.
- `sapling_crypto::circuit::ValueCommitmentOpening::value` is now represented as
a `NoteValue` instead of as a bare `u64`.
- `sapling_crypto::keys`:
@ -170,6 +175,7 @@ The entries below are relative to the `zcash_primitives::sapling` module as of
- `OutputDescription::read`
- `OutputDescription::{write_v4, write_v5_without_proof}`
- `OutputDescriptionV5::read`
- `MapAuth` trait
- `sapling_crypto::builder`:
- `SpendDescriptionInfo`
- `sapling_crypto::note_encryption::SaplingDomain::for_height` (use

View File

@ -4,14 +4,14 @@ use core::fmt;
use std::{iter, marker::PhantomData};
use group::ff::Field;
use incrementalmerkletree::Position;
use rand::{seq::SliceRandom, RngCore};
use rand_core::CryptoRng;
use redjubjub::{Binding, SpendAuth};
use crate::{
bundle::{
Authorization, Authorized, Bundle, GrothProofBytes, MapAuth, OutputDescription,
SpendDescription,
Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription, SpendDescription,
},
circuit,
keys::{OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey},
@ -22,7 +22,8 @@ use crate::{
CommitmentSum, NoteValue, TrapdoorSum, ValueCommitTrapdoor, ValueCommitment, ValueSum,
},
zip32::ExtendedSpendingKey,
Diversifier, MerklePath, Node, Note, PaymentAddress, ProofGenerationKey, SaplingIvk,
Anchor, Diversifier, MerklePath, Node, Note, PaymentAddress, ProofGenerationKey, SaplingIvk,
NOTE_COMMITMENT_TREE_DEPTH,
};
/// If there are any shielded inputs, always have at least two shielded outputs, padding
@ -34,12 +35,50 @@ const MIN_SHIELDED_OUTPUTS: usize = 2;
pub enum BundleType {
/// A transactional bundle will be padded if necessary to contain at least 2 outputs,
/// irrespective of whether any genuine outputs are required.
Transactional { anchor: Node },
Transactional {
/// A flag that, when set to `true`, indicates that the resulting bundle should be
/// produced with the minimum required number of spends (1) and outputs (2 with
/// padding) to be usable on its own in a transaction, irrespective of whether any
/// spends or outputs have been requested. If no explicit spends or outputs have
/// been added, all of the spends and outputs in the resulting bundle will be
/// dummies.
bundle_required: bool,
},
/// A coinbase bundle is required to have no spends. No output padding is performed.
Coinbase,
}
impl BundleType {
/// The default bundle type allows both spends and outputs, and does not require a
/// bundle to be produced if no spends or outputs have been added to the bundle.
pub const DEFAULT: BundleType = BundleType::Transactional {
bundle_required: false,
};
/// Returns the number of logical spends that a builder will produce in constructing a bundle
/// of this type, given the specified numbers of spends and outputs.
///
/// Returns an error if the specified number of spends and outputs is incompatible with
/// this bundle type.
pub fn num_spends(&self, requested_spends: usize) -> Result<usize, &'static str> {
match self {
BundleType::Transactional { bundle_required } => {
Ok(if *bundle_required || requested_spends > 0 {
core::cmp::max(requested_spends, 1)
} else {
0
})
}
BundleType::Coinbase => {
if requested_spends == 0 {
Ok(0)
} else {
Err("Spends not allowed in coinbase bundles")
}
}
}
}
/// Returns the number of logical outputs that a builder will produce in constructing a bundle
/// of this type, given the specified numbers of spends and outputs.
///
@ -47,16 +86,20 @@ impl BundleType {
/// this bundle type.
pub fn num_outputs(
&self,
num_spends: usize,
num_outputs: usize,
requested_spends: usize,
requested_outputs: usize,
) -> Result<usize, &'static str> {
match self {
BundleType::Transactional { .. } => {
Ok(core::cmp::max(num_outputs, MIN_SHIELDED_OUTPUTS))
BundleType::Transactional { bundle_required } => {
Ok(if *bundle_required || requested_outputs > 0 {
core::cmp::max(requested_outputs, MIN_SHIELDED_OUTPUTS)
} else {
0
})
}
BundleType::Coinbase => {
if num_spends == 0 {
Ok(num_outputs)
if requested_spends == 0 {
Ok(requested_outputs)
} else {
Err("Spends not allowed in coinbase bundles")
}
@ -109,6 +152,7 @@ pub struct SpendInfo {
proof_generation_key: ProofGenerationKey,
note: Note,
merkle_path: MerklePath,
dummy_ask: Option<SpendAuthorizingKey>,
}
impl SpendInfo {
@ -122,6 +166,7 @@ impl SpendInfo {
proof_generation_key,
note,
merkle_path,
dummy_ask: None,
}
}
@ -130,12 +175,33 @@ impl SpendInfo {
self.note.value()
}
fn has_matching_anchor(&self, anchor: Node) -> bool {
/// Defined in [Zcash Protocol Spec § 4.8.2: Dummy Notes (Sapling)][saplingdummynotes].
///
/// [saplingdummynotes]: https://zips.z.cash/protocol/protocol.pdf#saplingdummynotes
fn dummy<R: RngCore>(mut rng: R) -> Self {
let (sk, _, note) = Note::dummy(&mut rng);
let merkle_path = MerklePath::from_parts(
iter::repeat_with(|| Node::from_scalar(jubjub::Base::random(&mut rng)))
.take(NOTE_COMMITMENT_TREE_DEPTH.into())
.collect(),
Position::from(0),
)
.expect("The path length corresponds to the length of the generated vector.");
SpendInfo {
proof_generation_key: sk.proof_generation_key(),
note,
merkle_path,
dummy_ask: Some(sk.ask),
}
}
fn has_matching_anchor(&self, anchor: &Anchor) -> bool {
if self.note.value() == NoteValue::ZERO {
true
} else {
let node = Node::from_cmu(&self.note.cmu());
self.merkle_path.root(node) == anchor
&Anchor::from(self.merkle_path.root(node)) == anchor
}
}
@ -145,6 +211,7 @@ impl SpendInfo {
note: self.note,
merkle_path: self.merkle_path,
rcv: ValueCommitTrapdoor::random(rng),
dummy_ask: self.dummy_ask,
}
}
}
@ -155,6 +222,7 @@ struct PreparedSpendInfo {
note: Note,
merkle_path: MerklePath,
rcv: ValueCommitTrapdoor,
dummy_ask: Option<SpendAuthorizingKey>,
}
impl PreparedSpendInfo {
@ -197,7 +265,10 @@ impl PreparedSpendInfo {
nullifier,
rk,
zkproof,
SigningParts { ak, alpha },
SigningMetadata {
dummy_ask: self.dummy_ask,
parts: SigningParts { ak, alpha },
},
))
}
}
@ -323,7 +394,7 @@ impl PreparedOutputInfo {
}
}
/// Metadata about a transaction created by a [`SaplingBuilder`].
/// Metadata about a transaction created by a [`Builder`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SaplingMetadata {
spend_indices: Vec<usize>,
@ -339,22 +410,22 @@ impl SaplingMetadata {
}
/// Returns the index within the transaction of the [`SpendDescription`] corresponding
/// to the `n`-th call to [`SaplingBuilder::add_spend`].
/// to the `n`-th call to [`Builder::add_spend`].
///
/// Note positions are randomized when building transactions for indistinguishability.
/// This means that the transaction consumer cannot assume that e.g. the first spend
/// they added (via the first call to [`SaplingBuilder::add_spend`]) is the first
/// they added (via the first call to [`Builder::add_spend`]) is the first
/// [`SpendDescription`] in the transaction.
pub fn spend_index(&self, n: usize) -> Option<usize> {
self.spend_indices.get(n).copied()
}
/// Returns the index within the transaction of the [`OutputDescription`] corresponding
/// to the `n`-th call to [`SaplingBuilder::add_output`].
/// to the `n`-th call to [`Builder::add_output`].
///
/// Note positions are randomized when building transactions for indistinguishability.
/// This means that the transaction consumer cannot assume that e.g. the first output
/// they added (via the first call to [`SaplingBuilder::add_output`]) is the first
/// they added (via the first call to [`Builder::add_output`]) is the first
/// [`OutputDescription`] in the transaction.
pub fn output_index(&self, n: usize) -> Option<usize> {
self.output_indices.get(n).copied()
@ -362,22 +433,28 @@ impl SaplingMetadata {
}
/// A mutable builder type for constructing Sapling bundles.
pub struct SaplingBuilder {
pub struct Builder {
value_balance: ValueSum,
spends: Vec<SpendInfo>,
outputs: Vec<OutputInfo>,
zip212_enforcement: Zip212Enforcement,
bundle_type: BundleType,
anchor: Anchor,
}
impl SaplingBuilder {
pub fn new(zip212_enforcement: Zip212Enforcement, bundle_type: BundleType) -> Self {
SaplingBuilder {
impl Builder {
pub fn new(
zip212_enforcement: Zip212Enforcement,
bundle_type: BundleType,
anchor: Anchor,
) -> Self {
Builder {
value_balance: ValueSum::zero(),
spends: vec![],
outputs: vec![],
zip212_enforcement,
bundle_type,
anchor,
}
}
@ -422,8 +499,8 @@ impl SaplingBuilder {
// Consistency check: all anchors must equal the first one
match self.bundle_type {
BundleType::Transactional { anchor } => {
if !spend.has_matching_anchor(anchor) {
BundleType::Transactional { .. } => {
if !spend.has_matching_anchor(&self.anchor) {
return Err(Error::AnchorMismatch);
}
}
@ -441,8 +518,7 @@ impl SaplingBuilder {
}
/// Adds a Sapling address to send funds to.
#[allow(clippy::too_many_arguments)]
pub fn add_output<R: RngCore>(
pub fn add_output(
&mut self,
ovk: Option<OutgoingViewingKey>,
to: PaymentAddress,
@ -466,10 +542,11 @@ impl SaplingBuilder {
) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
bundle::<SP, OP, _, _>(
rng,
self.spends,
self.outputs,
self.bundle_type,
self.zip212_enforcement,
self.anchor,
self.spends,
self.outputs,
)
}
}
@ -478,15 +555,16 @@ impl SaplingBuilder {
/// and outputs.
pub fn bundle<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
mut rng: R,
spends: Vec<SpendInfo>,
outputs: Vec<OutputInfo>,
bundle_type: BundleType,
zip212_enforcement: Zip212Enforcement,
anchor: Anchor,
spends: Vec<SpendInfo>,
outputs: Vec<OutputInfo>,
) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
match bundle_type {
BundleType::Transactional { anchor } => {
BundleType::Transactional { .. } => {
for spend in &spends {
if !spend.has_matching_anchor(anchor) {
if !spend.has_matching_anchor(&anchor) {
return Err(Error::AnchorMismatch);
}
}
@ -498,6 +576,11 @@ pub fn bundle<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
}
}
let requested_spend_count = spends.len();
let bundle_spend_count = bundle_type
.num_spends(requested_spend_count)
.map_err(|_| Error::BundleTypeNotSatisfiable)?;
let requested_output_count = outputs.len();
let bundle_output_count = bundle_type
.num_outputs(spends.len(), requested_output_count)
@ -510,8 +593,14 @@ pub fn bundle<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
tx_metadata.spend_indices.resize(spends.len(), 0);
tx_metadata.output_indices.resize(requested_output_count, 0);
// Record initial spend positions
let mut indexed_spends: Vec<_> = spends.into_iter().enumerate().collect();
// Create any required dummy spends and record initial spend positions
let mut indexed_spends: Vec<_> = spends
.into_iter()
.chain(iter::repeat_with(|| SpendInfo::dummy(&mut rng)))
.enumerate()
.take(bundle_spend_count)
.collect();
// Create any required dummy outputs and record initial output positions
let mut indexed_outputs: Vec<_> = outputs
.into_iter()
@ -530,7 +619,9 @@ pub fn bundle<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
.enumerate()
.map(|(i, (pos, spend))| {
// Record the post-randomized spend location
tx_metadata.spend_indices[pos] = i;
if pos < requested_spend_count {
tx_metadata.spend_indices[pos] = i;
}
spend.prepare(&mut rng)
})
@ -607,7 +698,7 @@ pub fn bundle<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
/// Type alias for an in-progress bundle that has no proofs or signatures.
///
/// This is returned by [`SaplingBuilder::build`].
/// This is returned by [`Builder::build`].
pub type UnauthorizedBundle<V> = Bundle<InProgress<Unproven, Unsigned>, V>;
/// Marker trait representing bundle proofs in the process of being created.
@ -719,17 +810,7 @@ impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress>
self.progress_notifier
.update(self.progress, self.total_progress);
}
}
impl<
'a,
S: InProgressSignatures,
SP: SpendProver,
OP: OutputProver,
R: RngCore,
U: ProverProgress,
> MapAuth<InProgress<Unproven, S>, InProgress<Proven, S>> for CreateProofs<'a, SP, OP, R, U>
{
fn map_spend_proof(&mut self, spend: circuit::Spend) -> GrothProofBytes {
let proof = self.spend_prover.create_proof(spend, &mut self.rng);
self.update_progress();
@ -742,11 +823,10 @@ impl<
OP::encode_proof(proof)
}
fn map_auth_sig(&mut self, s: S::AuthSig) -> S::AuthSig {
s
}
fn map_authorization(&mut self, a: InProgress<Unproven, S>) -> InProgress<Proven, S> {
fn map_authorization<S: InProgressSignatures>(
&mut self,
a: InProgress<Unproven, S>,
) -> InProgress<Proven, S> {
InProgress {
sigs: a.sigs,
_proof_state: PhantomData::default(),
@ -765,13 +845,21 @@ impl<S: InProgressSignatures, V> Bundle<InProgress<Unproven, S>, V> {
) -> 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(
let mut cp = CreateProofs::new(
spend_prover,
output_prover,
rng,
progress_notifier,
total_progress,
))
);
self.map_authorization(
&mut cp,
|cp, spend| cp.map_spend_proof(spend),
|cp, output| cp.map_output_proof(output),
|_cp, sig| sig,
|cp, auth| cp.map_authorization(auth),
)
}
}
@ -788,7 +876,7 @@ impl fmt::Debug for Unsigned {
}
impl InProgressSignatures for Unsigned {
type AuthSig = SigningParts;
type AuthSig = SigningMetadata;
}
/// The parts needed to sign a [`SpendDescription`].
@ -808,6 +896,18 @@ pub struct PartiallyAuthorized {
sighash: [u8; 32],
}
/// Container for metadata needed to sign a Sapling input.
#[derive(Clone, Debug)]
pub struct SigningMetadata {
/// If this action is spending a dummy note, this field holds that note's spend
/// authorizing key.
///
/// These keys are used automatically in [`Bundle<Unauthorized>::prepare`] or
/// [`Bundle<Unauthorized>::apply_signatures`] to sign dummy spends.
dummy_ask: Option<SpendAuthorizingKey>,
parts: SigningParts,
}
impl InProgressSignatures for PartiallyAuthorized {
type AuthSig = MaybeSigned;
}
@ -818,7 +918,7 @@ impl InProgressSignatures for PartiallyAuthorized {
#[derive(Clone, Debug)]
pub enum MaybeSigned {
/// The information needed to sign this [`SpendDescription`].
SigningMetadata(SigningParts),
SigningParts(SigningParts),
/// The signature for this [`SpendDescription`].
Signature(redjubjub::Signature<SpendAuth>),
}
@ -841,18 +941,24 @@ impl<P: InProgressProofs, V> Bundle<InProgress<P, Unsigned>, V> {
mut rng: R,
sighash: [u8; 32],
) -> Bundle<InProgress<P, PartiallyAuthorized>, V> {
self.map_authorization((
|proof| proof,
|proof| proof,
MaybeSigned::SigningMetadata,
|auth: InProgress<P, Unsigned>| InProgress {
self.map_authorization(
&mut rng,
|_, proof| proof,
|_, proof| proof,
|rng, SigningMetadata { dummy_ask, parts }| match dummy_ask {
None => MaybeSigned::SigningParts(parts),
Some(ask) => {
MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(rng, &sighash))
}
},
|rng, auth: InProgress<P, Unsigned>| InProgress {
sigs: PartiallyAuthorized {
binding_signature: auth.sigs.bsk.sign(&mut rng, &sighash),
binding_signature: auth.sigs.bsk.sign(rng, &sighash),
sighash,
},
_proof_state: PhantomData::default(),
},
))
)
}
}
@ -883,17 +989,18 @@ impl<P: InProgressProofs, V> Bundle<InProgress<P, PartiallyAuthorized>, V> {
pub fn sign<R: RngCore + CryptoRng>(self, mut rng: R, ask: &SpendAuthorizingKey) -> Self {
let expected_ak = ask.into();
let sighash = self.authorization().sigs.sighash;
self.map_authorization((
|proof| proof,
|proof| proof,
|maybe| match maybe {
MaybeSigned::SigningMetadata(parts) if parts.ak == expected_ak => {
MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(&mut rng, &sighash))
self.map_authorization(
&mut rng,
|_, proof| proof,
|_, proof| proof,
|rng, maybe| match maybe {
MaybeSigned::SigningParts(parts) if parts.ak == expected_ak => {
MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(rng, &sighash))
}
s => s,
},
|partial| partial,
))
|_, partial| partial,
)
}
/// Appends externally computed [`redjubjub::Signature`]s.
@ -911,24 +1018,25 @@ impl<P: InProgressProofs, V> Bundle<InProgress<P, PartiallyAuthorized>, V> {
fn append_signature(self, signature: &redjubjub::Signature<SpendAuth>) -> Result<Self, Error> {
let sighash = self.authorization().sigs.sighash;
let mut signature_valid_for = 0usize;
let bundle = self.map_authorization((
|proof| proof,
|proof| proof,
|maybe| match maybe {
MaybeSigned::SigningMetadata(parts) => {
let bundle = self.map_authorization(
&mut signature_valid_for,
|_, proof| proof,
|_, proof| proof,
|ctx, maybe| match maybe {
MaybeSigned::SigningParts(parts) => {
let rk = parts.ak.randomize(&parts.alpha);
if rk.verify(&sighash, signature).is_ok() {
signature_valid_for += 1;
**ctx += 1;
MaybeSigned::Signature(*signature)
} else {
// Signature isn't for this input.
MaybeSigned::SigningMetadata(parts)
MaybeSigned::SigningParts(parts)
}
}
s => s,
},
|partial| partial,
));
|_, partial| partial,
);
match signature_valid_for {
0 => Err(Error::InvalidExternalSignature),
1 => Ok(bundle),
@ -942,16 +1050,17 @@ impl<V> Bundle<InProgress<Proven, PartiallyAuthorized>, V> {
///
/// Returns an error if any signatures are missing.
pub fn finalize(self) -> Result<Bundle<Authorized, V>, Error> {
self.try_map_authorization((
Ok,
Ok,
|maybe: MaybeSigned| maybe.finalize(),
|partial: InProgress<Proven, PartiallyAuthorized>| {
self.try_map_authorization(
(),
|_, v| Ok(v),
|_, v| Ok(v),
|_, maybe: MaybeSigned| maybe.finalize(),
|_, partial: InProgress<Proven, PartiallyAuthorized>| {
Ok(Authorized {
binding_sig: partial.sigs.binding_signature,
})
},
))
)
}
}
@ -970,13 +1079,13 @@ pub mod testing {
testing::{arb_node, arb_note},
value::testing::arb_positive_note_value,
zip32::testing::arb_extended_spending_key,
Node, NOTE_COMMITMENT_TREE_DEPTH,
Anchor, Node,
};
use incrementalmerkletree::{
frontier::testing::arb_commitment_tree, witness::IncrementalWitness, Hashable, Level,
frontier::testing::arb_commitment_tree, witness::IncrementalWitness,
};
use super::{BundleType, SaplingBuilder};
use super::{Builder, BundleType};
#[allow(dead_code)]
fn arb_bundle<V: fmt::Debug + From<i64>>(
@ -1005,17 +1114,11 @@ pub mod testing {
let anchor = spendable_notes
.first()
.zip(commitment_trees.first())
.map_or_else(
|| Node::empty_root(Level::from(NOTE_COMMITMENT_TREE_DEPTH)),
|(note, tree)| {
let node = Node::from_cmu(&note.cmu());
Node::from_scalar(*tree.root(node).inner())
},
);
let mut builder = SaplingBuilder::new(
zip212_enforcement,
BundleType::Transactional { anchor },
);
.map_or_else(Anchor::empty_tree, |(note, tree)| {
let node = Node::from_cmu(&note.cmu());
Anchor::from(*tree.root(node).inner())
});
let mut builder = Builder::new(zip212_enforcement, BundleType::DEFAULT, anchor);
let mut rng = StdRng::from_seed(rng_seed);
for (note, path) in spendable_notes

View File

@ -1,6 +1,7 @@
use core::fmt::Debug;
use memuse::DynamicUsage;
use redjubjub::{Binding, SpendAuth};
use zcash_note_encryption::{
@ -38,115 +39,6 @@ impl Authorization for Authorized {
type AuthSig = redjubjub::Signature<SpendAuth>;
}
/// A map from one bundle authorization to another.
///
/// For use with [`Bundle::map_authorization`].
pub trait MapAuth<A: Authorization, B: Authorization> {
fn map_spend_proof(&mut self, p: A::SpendProof) -> B::SpendProof;
fn map_output_proof(&mut self, p: A::OutputProof) -> B::OutputProof;
fn map_auth_sig(&mut self, s: A::AuthSig) -> B::AuthSig;
fn map_authorization(&mut self, a: A) -> B;
}
/// The identity map.
///
/// This can be used with [`Bundle::map_authorization`] when you want to map the
/// authorization of a subset of a transaction's bundles (excluding the Sapling bundle) in
/// a higher-level transaction type.
impl MapAuth<Authorized, Authorized> for () {
fn map_spend_proof(
&mut self,
p: <Authorized as Authorization>::SpendProof,
) -> <Authorized as Authorization>::SpendProof {
p
}
fn map_output_proof(
&mut self,
p: <Authorized as Authorization>::OutputProof,
) -> <Authorized as Authorization>::OutputProof {
p
}
fn map_auth_sig(
&mut self,
s: <Authorized as Authorization>::AuthSig,
) -> <Authorized as Authorization>::AuthSig {
s
}
fn map_authorization(&mut self, a: Authorized) -> Authorized {
a
}
}
/// A helper for implementing `MapAuth` with a set of closures.
impl<A, B, F, G, H, I> MapAuth<A, B> for (F, G, H, I)
where
A: Authorization,
B: Authorization,
F: FnMut(A::SpendProof) -> B::SpendProof,
G: FnMut(A::OutputProof) -> B::OutputProof,
H: FnMut(A::AuthSig) -> B::AuthSig,
I: FnMut(A) -> B,
{
fn map_spend_proof(&mut self, p: A::SpendProof) -> B::SpendProof {
self.0(p)
}
fn map_output_proof(&mut self, p: A::OutputProof) -> B::OutputProof {
self.1(p)
}
fn map_auth_sig(&mut self, s: A::AuthSig) -> B::AuthSig {
self.2(s)
}
fn map_authorization(&mut self, a: A) -> B {
self.3(a)
}
}
/// A fallible map from one bundle authorization to another.
///
/// For use with [`Bundle::try_map_authorization`].
pub trait TryMapAuth<A: Authorization, B: Authorization> {
type Error;
fn try_map_spend_proof(&mut self, p: A::SpendProof) -> Result<B::SpendProof, Self::Error>;
fn try_map_output_proof(&mut self, p: A::OutputProof) -> Result<B::OutputProof, Self::Error>;
fn try_map_auth_sig(&mut self, s: A::AuthSig) -> Result<B::AuthSig, Self::Error>;
fn try_map_authorization(&mut self, a: A) -> Result<B, Self::Error>;
}
/// A helper for implementing `TryMapAuth` with a set of closures.
impl<A, B, E, F, G, H, I> TryMapAuth<A, B> for (F, G, H, I)
where
A: Authorization,
B: Authorization,
F: FnMut(A::SpendProof) -> Result<B::SpendProof, E>,
G: FnMut(A::OutputProof) -> Result<B::OutputProof, E>,
H: FnMut(A::AuthSig) -> Result<B::AuthSig, E>,
I: FnMut(A) -> Result<B, E>,
{
type Error = E;
fn try_map_spend_proof(&mut self, p: A::SpendProof) -> Result<B::SpendProof, Self::Error> {
self.0(p)
}
fn try_map_output_proof(&mut self, p: A::OutputProof) -> Result<B::OutputProof, Self::Error> {
self.1(p)
}
fn try_map_auth_sig(&mut self, s: A::AuthSig) -> Result<B::AuthSig, Self::Error> {
self.2(s)
}
fn try_map_authorization(&mut self, a: A) -> Result<B, Self::Error> {
self.3(a)
}
}
#[derive(Debug, Clone)]
pub struct Bundle<A: Authorization, V> {
shielded_spends: Vec<SpendDescription<A>>,
@ -200,7 +92,14 @@ impl<A: Authorization, V> Bundle<A, V> {
}
/// 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, V> {
pub fn map_authorization<R, B: Authorization>(
self,
mut context: R,
spend_proof: impl Fn(&mut R, A::SpendProof) -> B::SpendProof,
output_proof: impl Fn(&mut R, A::OutputProof) -> B::OutputProof,
auth_sig: impl Fn(&mut R, A::AuthSig) -> B::AuthSig,
auth: impl FnOnce(&mut R, A) -> B,
) -> Bundle<B, V> {
Bundle {
shielded_spends: self
.shielded_spends
@ -210,8 +109,8 @@ impl<A: Authorization, V> Bundle<A, V> {
anchor: d.anchor,
nullifier: d.nullifier,
rk: d.rk,
zkproof: f.map_spend_proof(d.zkproof),
spend_auth_sig: f.map_auth_sig(d.spend_auth_sig),
zkproof: spend_proof(&mut context, d.zkproof),
spend_auth_sig: auth_sig(&mut context, d.spend_auth_sig),
})
.collect(),
shielded_outputs: self
@ -223,19 +122,23 @@ impl<A: Authorization, V> Bundle<A, V> {
ephemeral_key: o.ephemeral_key,
enc_ciphertext: o.enc_ciphertext,
out_ciphertext: o.out_ciphertext,
zkproof: f.map_output_proof(o.zkproof),
zkproof: output_proof(&mut context, o.zkproof),
})
.collect(),
value_balance: self.value_balance,
authorization: f.map_authorization(self.authorization),
authorization: auth(&mut context, self.authorization),
}
}
/// Transitions this bundle from one authorization state to another.
pub fn try_map_authorization<B: Authorization, F: TryMapAuth<A, B>>(
pub fn try_map_authorization<R, B: Authorization, Error>(
self,
mut f: F,
) -> Result<Bundle<B, V>, F::Error> {
mut context: R,
spend_proof: impl Fn(&mut R, A::SpendProof) -> Result<B::SpendProof, Error>,
output_proof: impl Fn(&mut R, A::OutputProof) -> Result<B::OutputProof, Error>,
auth_sig: impl Fn(&mut R, A::AuthSig) -> Result<B::AuthSig, Error>,
auth: impl Fn(&mut R, A) -> Result<B, Error>,
) -> Result<Bundle<B, V>, Error> {
Ok(Bundle {
shielded_spends: self
.shielded_spends
@ -246,8 +149,8 @@ impl<A: Authorization, V> Bundle<A, V> {
anchor: d.anchor,
nullifier: d.nullifier,
rk: d.rk,
zkproof: f.try_map_spend_proof(d.zkproof)?,
spend_auth_sig: f.try_map_auth_sig(d.spend_auth_sig)?,
zkproof: spend_proof(&mut context, d.zkproof)?,
spend_auth_sig: auth_sig(&mut context, d.spend_auth_sig)?,
})
})
.collect::<Result<_, _>>()?,
@ -261,12 +164,12 @@ impl<A: Authorization, V> Bundle<A, V> {
ephemeral_key: o.ephemeral_key,
enc_ciphertext: o.enc_ciphertext,
out_ciphertext: o.out_ciphertext,
zkproof: f.try_map_output_proof(o.zkproof)?,
zkproof: output_proof(&mut context, o.zkproof)?,
})
})
.collect::<Result<_, _>>()?,
value_balance: self.value_balance,
authorization: f.try_map_authorization(self.authorization)?,
authorization: auth(&mut context, self.authorization)?,
})
}
}

View File

@ -34,7 +34,8 @@ pub use bundle::Bundle;
pub use keys::{Diversifier, NullifierDerivingKey, ProofGenerationKey, SaplingIvk, ViewingKey};
pub use note::{nullifier::Nullifier, Note, Rseed};
pub use tree::{
merkle_hash, CommitmentTree, IncrementalWitness, MerklePath, Node, NOTE_COMMITMENT_TREE_DEPTH,
merkle_hash, Anchor, CommitmentTree, IncrementalWitness, MerklePath, Node,
NOTE_COMMITMENT_TREE_DEPTH,
};
pub use verifier::{BatchValidator, SaplingVerificationContext};

View File

@ -2,6 +2,11 @@ use group::{ff::Field, GroupEncoding};
use rand_core::{CryptoRng, RngCore};
use zcash_spec::PrfExpand;
use crate::{
keys::{ExpandedSpendingKey, FullViewingKey},
zip32::ExtendedSpendingKey,
};
use super::{
keys::EphemeralSecretKey, value::NoteValue, Nullifier, NullifierDerivingKey, PaymentAddress,
};
@ -148,6 +153,28 @@ impl Note {
))),
}
}
/// Generates a dummy spent note.
///
/// Defined in [Zcash Protocol Spec § 4.8.2: Dummy Notes (Sapling)][saplingdummynotes].
///
/// [saplingdummynotes]: https://zips.z.cash/protocol/nu5.pdf#saplingdummynotes
pub(crate) fn dummy<R: RngCore>(mut rng: R) -> (ExpandedSpendingKey, FullViewingKey, Self) {
let mut sk_bytes = [0; 32];
rng.fill_bytes(&mut sk_bytes);
let extsk = ExtendedSpendingKey::master(&sk_bytes[..]);
let fvk = extsk.to_diversifiable_full_viewing_key().fvk().clone();
let recipient = extsk.default_address();
let mut rseed_bytes = [0; 32];
rng.fill_bytes(&mut rseed_bytes);
let rseed = Rseed::AfterZip212(rseed_bytes);
let note = Note::from_parts(recipient.1, NoteValue::ZERO, rseed);
(extsk.expsk, fvk, note)
}
}
#[cfg(any(test, feature = "test-dependencies"))]

View File

@ -67,6 +67,43 @@ fn merkle_hash_field(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> jubjub::Ba
.get_u()
}
/// The root of a Sapling commitment tree.
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub struct Anchor(jubjub::Base);
impl From<jubjub::Base> for Anchor {
fn from(anchor_field: jubjub::Base) -> Anchor {
Anchor(anchor_field)
}
}
impl From<Node> for Anchor {
fn from(anchor: Node) -> Anchor {
Anchor(anchor.0)
}
}
impl Anchor {
/// The anchor of the empty Orchard note commitment tree.
///
/// This anchor does not correspond to any valid anchor for a spend, so it
/// may only be used for coinbase bundles or in circumstances where Orchard
/// functionality is not active.
pub fn empty_tree() -> Anchor {
Anchor(Node::empty_root(NOTE_COMMITMENT_TREE_DEPTH.into()).0)
}
/// Parses an Orchard anchor from a byte encoding.
pub fn from_bytes(bytes: [u8; 32]) -> CtOption<Anchor> {
jubjub::Base::from_repr(bytes).map(Self)
}
/// Returns the byte encoding of this anchor.
pub fn to_bytes(self) -> [u8; 32] {
self.0.to_repr()
}
}
/// A node within the Sapling commitment tree.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Node(jubjub::Base);

View File

@ -8,7 +8,7 @@
//! 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`].
//! [`Bundle::value_balance`] and [`Builder::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
@ -17,7 +17,7 @@
//! - 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`].
//! to calling [`Builder::add_output`].
//!
//! Inside the circuit, note values are constrained to be unsigned 64-bit integers.
//!
@ -33,8 +33,8 @@
//!
//! [`Bundle`]: crate::Bundle
//! [`Bundle::value_balance`]: crate::Bundle::value_balance
//! [`SaplingBuilder::value_balance`]: crate::builder::SaplingBuilder::value_balance
//! [`SaplingBuilder::add_output`]: crate::builder::SaplingBuilder::add_output
//! [`Builder::value_balance`]: crate::builder::Builder::value_balance
//! [`Builder::add_output`]: crate::builder::Builder::add_output
//! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html
use bitvec::{array::BitArray, order::Lsb0};