Add the capability to generate dummy spends (internal to the Builder)

This commit is contained in:
Kris Nuttycombe 2023-12-21 16:08:51 -07:00
parent 954a27ee9b
commit 6f02b62c8e
4 changed files with 147 additions and 136 deletions

View File

@ -26,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
@ -33,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`
@ -42,9 +43,6 @@ The entries below are relative to the `zcash_primitives::sapling` module as of
- `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`
@ -93,6 +91,7 @@ The entries below are relative to the `zcash_primitives::sapling` module as of
argument as a `NoteValue` instead of as a bare `u64`.
- `sapling_crypto::builder`:
- `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.
- `Builder::new` now takes a `Zip212Enforcement` argument instead of a
@ -121,6 +120,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
@ -131,7 +133,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`:
@ -175,6 +176,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},
@ -23,6 +23,7 @@ use crate::{
},
zip32::ExtendedSpendingKey,
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
@ -39,7 +40,7 @@ pub enum BundleType {
/// with the minimum required outputs irrespective of whether any outputs have been
/// requested; if no explicit outputs have been added, all of the outputs in the resulting
/// bundle will be dummies.
outputs_required: bool,
bundle_required: bool,
},
/// A coinbase bundle is required to have no spends. No output padding is performed.
Coinbase,
@ -49,7 +50,7 @@ impl BundleType {
/// The default bundle type has all flags enabled, 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 {
outputs_required: false,
bundle_required: false,
};
/// Returns the number of logical outputs that a builder will produce in constructing a bundle
@ -63,8 +64,8 @@ impl BundleType {
num_outputs: usize,
) -> Result<usize, &'static str> {
match self {
BundleType::Transactional { outputs_required } => {
Ok(if *outputs_required || num_outputs > 0 {
BundleType::Transactional { bundle_required } => {
Ok(if *bundle_required || num_outputs > 0 {
core::cmp::max(num_outputs, MIN_SHIELDED_OUTPUTS)
} else {
0
@ -125,6 +126,7 @@ pub struct SpendInfo {
proof_generation_key: ProofGenerationKey,
note: Note,
merkle_path: MerklePath,
dummy_ask: Option<SpendAuthorizingKey>,
}
impl SpendInfo {
@ -138,6 +140,7 @@ impl SpendInfo {
proof_generation_key,
note,
merkle_path,
dummy_ask: None,
}
}
@ -146,6 +149,27 @@ impl SpendInfo {
self.note.value()
}
/// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Sapling)][orcharddummynotes].
///
/// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
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(rng.next_u64()),
)
.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
@ -161,6 +185,7 @@ impl SpendInfo {
note: self.note,
merkle_path: self.merkle_path,
rcv: ValueCommitTrapdoor::random(rng),
dummy_ask: self.dummy_ask,
}
}
}
@ -171,6 +196,7 @@ struct PreparedSpendInfo {
note: Note,
merkle_path: MerklePath,
rcv: ValueCommitTrapdoor,
dummy_ask: Option<SpendAuthorizingKey>,
}
impl PreparedSpendInfo {
@ -213,7 +239,10 @@ impl PreparedSpendInfo {
nullifier,
rk,
zkproof,
SigningParts { ak, alpha },
SigningMetadata {
dummy_ask: self.dummy_ask,
parts: SigningParts { ak, alpha },
},
))
}
}
@ -742,17 +771,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();
@ -765,11 +784,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(),
@ -788,13 +806,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),
)
}
}
@ -811,7 +837,7 @@ impl fmt::Debug for Unsigned {
}
impl InProgressSignatures for Unsigned {
type AuthSig = SigningParts;
type AuthSig = SigningMetadata;
}
/// The parts needed to sign a [`SpendDescription`].
@ -831,6 +857,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;
}
@ -841,7 +879,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>),
}
@ -864,18 +902,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(),
},
))
)
}
}
@ -906,17 +950,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,
))
|_rng, partial| partial,
)
}
/// Appends externally computed [`redjubjub::Signature`]s.
@ -934,24 +979,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),
@ -1028,13 +1074,10 @@ pub mod testing {
let anchor = spendable_notes
.first()
.zip(commitment_trees.first())
.map_or_else(
|| Anchor::empty_tree(),
|(note, tree)| {
let node = Node::from_cmu(&note.cmu());
Anchor::from(*tree.root(node).inner())
},
);
.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);

View File

@ -1,6 +1,7 @@
use core::fmt::Debug;
use memuse::DynamicUsage;
use redjubjub::{Binding, SpendAuth};
use zcash_note_encryption::{
@ -38,75 +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`].
@ -200,7 +132,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,
mut spend_proof: impl FnMut(&mut R, A::SpendProof) -> B::SpendProof,
mut output_proof: impl FnMut(&mut R, A::OutputProof) -> B::OutputProof,
mut auth_sig: impl FnMut(&mut R, A::AuthSig) -> B::AuthSig,
mut auth: impl FnMut(&mut R, A) -> B,
) -> Bundle<B, V> {
Bundle {
shielded_spends: self
.shielded_spends
@ -210,8 +149,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,11 +162,11 @@ 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),
}
}

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"))]