Store patial authorizing data for sapling components in bundle authorization.

This commit is contained in:
Kris Nuttycombe 2021-06-03 20:21:10 -06:00
parent 6635895e55
commit 6348400cf4
8 changed files with 124 additions and 173 deletions

View File

@ -10,7 +10,7 @@ use zcash_primitives::{
Diversifier, PaymentAddress, SaplingIvk, ValueCommitment,
},
transaction::components::{
sapling::{Authorized, OutputDescription},
sapling::{GrothProofBytes, OutputDescription},
GROTH_PROOF_SIZE,
},
};
@ -23,7 +23,7 @@ fn bench_note_decryption(c: &mut Criterion) {
let invalid_ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
// Construct a fake Sapling output as if we had just deserialized a transaction.
let output: OutputDescription<Authorized> = {
let output: OutputDescription<GrothProofBytes> = {
let diversifier = Diversifier([0; 11]);
let pk_d = diversifier.g_d().unwrap() * valid_ivk.0;
let pa = PaymentAddress::from_parts(diversifier, pk_d).unwrap();

View File

@ -386,7 +386,7 @@ pub fn try_sapling_output_recovery_with_ock<P: consensus::Parameters>(
params: &P,
height: BlockHeight,
ock: &OutgoingCipherKey,
output: &OutputDescription<sapling::Authorized>,
output: &OutputDescription<sapling::GrothProofBytes>,
) -> Option<(Note, PaymentAddress, MemoBytes)> {
let domain = SaplingDomain {
params: params.clone(),
@ -408,7 +408,7 @@ pub fn try_sapling_output_recovery<P: consensus::Parameters>(
params: &P,
height: BlockHeight,
ovk: &OutgoingViewingKey,
output: &OutputDescription<sapling::Authorized>,
output: &OutputDescription<sapling::GrothProofBytes>,
) -> Option<(Note, PaymentAddress, MemoBytes)> {
let domain = SaplingDomain {
params: params.clone(),
@ -465,7 +465,7 @@ mod tests {
OutgoingViewingKey,
OutgoingCipherKey,
SaplingIvk,
OutputDescription<sapling::Authorized>,
OutputDescription<sapling::GrothProofBytes>,
) {
let ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
@ -498,7 +498,7 @@ mod tests {
) -> (
OutgoingViewingKey,
OutgoingCipherKey,
OutputDescription<sapling::Authorized>,
OutputDescription<sapling::GrothProofBytes>,
) {
let diversifier = Diversifier([0; 11]);
let pk_d = diversifier.g_d().unwrap() * ivk.0;

View File

@ -3,7 +3,6 @@
use std::array;
use std::error;
use std::fmt;
use std::io;
use std::sync::mpsc::Sender;
#[cfg(not(feature = "zfuture"))]
@ -17,8 +16,7 @@ use crate::{
memo::MemoBytes,
merkle_tree::MerklePath,
sapling::{
keys::OutgoingViewingKey, prover::TxProver, redjubjub, Diversifier, Node, Note,
PaymentAddress,
keys::OutgoingViewingKey, prover::TxProver, Diversifier, Node, Note, PaymentAddress,
},
transaction::{
components::{
@ -320,13 +318,14 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
let transparent_bundle = self.transparent_builder.build();
let mut rng = self.rng;
let mut ctx = prover.new_sapling_proving_context();
let (sapling_bundle, tx_metadata) = self
let sapling_bundle = self
.sapling_builder
.build(
prover,
&mut ctx,
&mut self.rng,
&mut rng,
self.target_height,
self.progress_notifier.as_ref(),
)
@ -335,7 +334,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
#[cfg(feature = "zfuture")]
let (tze_bundle, tze_signers) = self.tze_builder.build();
let unauthed_tx = TransactionData {
let unauthed_tx: TransactionData<Unauthorized> = TransactionData {
version,
consensus_branch_id: BranchId::for_height(&self.params, self.target_height),
lock_time: 0,
@ -362,6 +361,14 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
)
});
#[cfg(feature = "zfuture")]
let tze_bundle = unauthed_tx
.tze_bundle
.clone()
.map(|b| b.into_authorized(&unauthed_tx, tze_signers))
.transpose()
.map_err(Error::TzeBuild)?;
// the commitment being signed is shared across all Sapling inputs; once
// V4 transactions are deprecated this should just be the txid, but
// for now we need to continue to compute it here.
@ -372,52 +379,16 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
SIGHASH_ALL,
);
let sapling_sigs = self
.sapling_builder
.create_signatures(
prover,
&mut ctx,
&mut self.rng,
shielded_sig_commitment.as_ref(),
&tx_metadata,
)
.map_err(Error::SaplingBuild)?;
#[cfg(feature = "zfuture")]
let tze_bundle = unauthed_tx
.tze_bundle
.clone()
.map(|b| b.into_authorized(&unauthed_tx, tze_signers))
let (sapling_bundle, tx_metadata) = match unauthed_tx
.sapling_bundle
.map(|b| {
b.apply_signatures(prover, &mut ctx, &mut rng, shielded_sig_commitment.as_ref())
})
.transpose()
.map_err(Error::TzeBuild)?;
Ok((
Self::apply_signatures(
unauthed_tx,
sapling_sigs,
transparent_bundle,
#[cfg(feature = "zfuture")]
tze_bundle,
)
.expect("An IO error occurred applying signatures."),
tx_metadata,
))
}
pub fn apply_signatures(
unauthed_tx: TransactionData<Unauthorized>,
sapling_sigs: Option<(Vec<redjubjub::Signature>, redjubjub::Signature)>,
transparent_bundle: Option<transparent::Bundle<transparent::Authorized>>,
#[cfg(feature = "zfuture")] tze_bundle: Option<tze::Bundle<tze::Authorized>>,
) -> io::Result<Transaction> {
let signed_sapling_bundle = match (unauthed_tx.sapling_bundle, sapling_sigs) {
(Some(bundle), Some((spend_sigs, binding_sig))) => {
Some(bundle.apply_signatures(spend_sigs, binding_sig))
}
(None, None) => None,
_ => {
panic!("Mismatch between sapling bundle and signatures.");
}
.map_err(Error::SaplingBuild)?
{
Some((bundle, meta)) => (Some(bundle), meta),
None => (None, SaplingMetadata::empty()),
};
let authorized_tx = TransactionData {
@ -427,13 +398,15 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
expiry_height: unauthed_tx.expiry_height,
transparent_bundle,
sprout_bundle: unauthed_tx.sprout_bundle,
sapling_bundle: signed_sapling_bundle,
sapling_bundle,
orchard_bundle: None,
#[cfg(feature = "zfuture")]
tze_bundle,
};
authorized_tx.freeze()
// The unwrap() here is safe because the txid hashing
// of freeze() should be infalliable.
Ok((authorized_tx.freeze().unwrap(), tx_metadata))
}
}

View File

@ -33,14 +33,6 @@ impl Authorization for Unproven {
type AuthSig = ();
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Unauthorized;
impl Authorization for Unauthorized {
type Proof = GrothProofBytes;
type AuthSig = ();
}
#[derive(Debug, Copy, Clone)]
pub struct Authorized {
pub binding_sig: redjubjub::Signature,
@ -54,36 +46,11 @@ impl Authorization for Authorized {
#[derive(Debug, Clone)]
pub struct Bundle<A: Authorization> {
pub shielded_spends: Vec<SpendDescription<A>>,
pub shielded_outputs: Vec<OutputDescription<A>>,
pub shielded_outputs: Vec<OutputDescription<A::Proof>>,
pub value_balance: Amount,
pub authorization: A,
}
impl Bundle<Unauthorized> {
pub fn apply_signatures(
self,
spend_auth_sigs: Vec<Signature>,
binding_sig: Signature,
) -> Bundle<Authorized> {
assert!(self.shielded_spends.len() == spend_auth_sigs.len());
Bundle {
shielded_spends: self
.shielded_spends
.iter()
.zip(spend_auth_sigs.iter())
.map(|(spend, sig)| spend.apply_signature(*sig))
.collect(),
shielded_outputs: self
.shielded_outputs
.into_iter()
.map(|o| o.into_authorized())
.collect(), //TODO, is there a zero-cost way to do this?
value_balance: self.value_balance,
authorization: Authorized { binding_sig },
}
}
}
#[derive(Clone)]
pub struct SpendDescription<A: Authorization> {
pub cv: jubjub::ExtendedPoint,
@ -94,19 +61,6 @@ pub struct SpendDescription<A: Authorization> {
pub spend_auth_sig: A::AuthSig,
}
impl SpendDescription<Unauthorized> {
pub fn apply_signature(&self, spend_auth_sig: Signature) -> SpendDescription<Authorized> {
SpendDescription {
cv: self.cv,
anchor: self.anchor,
nullifier: self.nullifier,
rk: self.rk.clone(),
zkproof: self.zkproof,
spend_auth_sig,
}
}
}
impl<A: Authorization> std::fmt::Debug for SpendDescription<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
@ -256,18 +210,16 @@ impl SpendDescriptionV5 {
}
#[derive(Clone)]
pub struct OutputDescription<A: Authorization> {
pub struct OutputDescription<Proof> {
pub cv: jubjub::ExtendedPoint,
pub cmu: bls12_381::Scalar,
pub ephemeral_key: jubjub::ExtendedPoint,
pub enc_ciphertext: [u8; 580],
pub out_ciphertext: [u8; 80],
pub zkproof: A::Proof,
pub zkproof: Proof,
}
impl<P: consensus::Parameters, A: Authorization> ShieldedOutput<SaplingDomain<P>>
for OutputDescription<A>
{
impl<P: consensus::Parameters, A> ShieldedOutput<SaplingDomain<P>> for OutputDescription<A> {
fn epk(&self) -> &jubjub::ExtendedPoint {
&self.ephemeral_key
}
@ -281,7 +233,7 @@ impl<P: consensus::Parameters, A: Authorization> ShieldedOutput<SaplingDomain<P>
}
}
impl<A: Authorization> std::fmt::Debug for OutputDescription<A> {
impl<A> std::fmt::Debug for OutputDescription<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
@ -291,7 +243,7 @@ impl<A: Authorization> std::fmt::Debug for OutputDescription<A> {
}
}
impl OutputDescription<Authorized> {
impl OutputDescription<GrothProofBytes> {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
// Consensus rules (§4.5):
// - Canonical encoding is enforced here.
@ -323,9 +275,7 @@ impl OutputDescription<Authorized> {
zkproof,
})
}
}
impl<A: Authorization<Proof = GrothProofBytes>> OutputDescription<A> {
pub fn write_v4<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.cv.to_bytes())?;
writer.write_all(self.cmu.to_repr().as_ref())?;
@ -376,7 +326,7 @@ impl OutputDescriptionV5 {
pub fn into_output_description(
self,
zkproof: GrothProofBytes,
) -> OutputDescription<Authorized> {
) -> OutputDescription<GrothProofBytes> {
OutputDescription {
cv: self.cv,
cmu: self.cmu,
@ -388,26 +338,13 @@ impl OutputDescriptionV5 {
}
}
impl OutputDescription<Unauthorized> {
pub fn into_authorized(self) -> OutputDescription<Authorized> {
OutputDescription {
cv: self.cv,
cmu: self.cmu,
ephemeral_key: self.ephemeral_key,
enc_ciphertext: self.enc_ciphertext,
out_ciphertext: self.out_ciphertext,
zkproof: self.zkproof,
}
}
}
pub struct CompactOutputDescription {
pub epk: jubjub::ExtendedPoint,
pub cmu: bls12_381::Scalar,
pub enc_ciphertext: Vec<u8>,
}
impl<A: Authorization> From<OutputDescription<A>> for CompactOutputDescription {
impl<A> From<OutputDescription<A>> for CompactOutputDescription {
fn from(out: OutputDescription<A>) -> CompactOutputDescription {
CompactOutputDescription {
epk: out.ephemeral_key,
@ -452,7 +389,7 @@ pub mod testing {
},
};
use super::{Authorized, Bundle, OutputDescription, SpendDescription};
use super::{Authorized, Bundle, GrothProofBytes, OutputDescription, SpendDescription};
prop_compose! {
/// produce a spend description with invalid data (useful only for serialization
@ -507,7 +444,7 @@ pub mod testing {
.prop_map(|v| <[u8;80]>::try_from(v.as_slice()).unwrap()),
zkproof in vec(any::<u8>(), GROTH_PROOF_SIZE)
.prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()),
) -> OutputDescription<Authorized> {
) -> OutputDescription<GrothProofBytes> {
OutputDescription {
cv,
cmu,

View File

@ -23,7 +23,10 @@ use crate::{
builder::Progress,
components::{
amount::Amount,
sapling::{Bundle, OutputDescription, SpendDescription, Unauthorized},
sapling::{
Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription,
SpendDescription,
},
},
},
zip32::ExtendedSpendingKey,
@ -56,6 +59,7 @@ impl fmt::Display for Error {
}
}
#[derive(Clone)]
struct SpendDescriptionInfo {
extsk: ExtendedSpendingKey,
diversifier: Diversifier,
@ -110,7 +114,7 @@ impl SaplingOutput {
prover: &Pr,
ctx: &mut Pr::SaplingProvingContext,
rng: &mut R,
) -> OutputDescription<Unauthorized> {
) -> OutputDescription<GrothProofBytes> {
let encryptor = sapling_note_encryption::<R, P>(
self.ovk,
self.note.clone(),
@ -146,7 +150,7 @@ impl SaplingOutput {
}
/// Metadata about a transaction created by a [`SaplingBuilder`].
#[derive(Debug, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct SaplingMetadata {
spend_indices: Vec<usize>,
output_indices: Vec<usize>,
@ -192,6 +196,23 @@ pub struct SaplingBuilder<P> {
outputs: Vec<SaplingOutput>,
}
#[derive(Clone)]
pub struct Unauthorized {
spends: Vec<SpendDescriptionInfo>,
tx_metadata: SaplingMetadata,
}
impl std::fmt::Debug for Unauthorized {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "Unauthorized")
}
}
impl Authorization for Unauthorized {
type Proof = GrothProofBytes;
type AuthSig = ();
}
impl<P: consensus::Parameters> SaplingBuilder<P> {
pub fn new(params: P, target_height: BlockHeight) -> Self {
SaplingBuilder {
@ -284,13 +305,13 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
}
pub fn build<Pr: TxProver, R: RngCore>(
&self,
self,
prover: &Pr,
ctx: &mut Pr::SaplingProvingContext,
mut rng: R,
target_height: BlockHeight,
progress_notifier: Option<&Sender<Progress>>,
) -> Result<(Option<Bundle<Unauthorized>>, SaplingMetadata), Error> {
) -> Result<Option<Bundle<Unauthorized>>, Error> {
// Record initial positions of spends and outputs
let mut indexed_spends: Vec<_> = self.spends.iter().enumerate().collect();
let mut indexed_outputs: Vec<_> = self
@ -378,7 +399,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
};
// Create Sapling OutputDescriptions
let shielded_outputs: Vec<OutputDescription<Unauthorized>> = indexed_outputs
let shielded_outputs: Vec<OutputDescription<GrothProofBytes>> = indexed_outputs
.into_iter()
.enumerate()
.map(|(i, output)| {
@ -404,7 +425,6 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
}
(diversifier, g_d)
};
let (pk_d, payment_address) = loop {
let dummy_ivk = jubjub::Fr::random(&mut rng);
let pk_d = g_d * dummy_ivk;
@ -470,26 +490,43 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
shielded_spends,
shielded_outputs,
value_balance: self.value_balance,
authorization: Unauthorized,
authorization: Unauthorized {
spends: self.spends,
tx_metadata,
},
})
};
Ok((bundle, tx_metadata))
Ok(bundle)
}
}
pub fn create_signatures<Pr: TxProver, R: RngCore>(
impl SpendDescription<Unauthorized> {
pub fn apply_signature(&self, spend_auth_sig: Signature) -> SpendDescription<Authorized> {
SpendDescription {
cv: self.cv,
anchor: self.anchor,
nullifier: self.nullifier,
rk: self.rk.clone(),
zkproof: self.zkproof,
spend_auth_sig,
}
}
}
impl Bundle<Unauthorized> {
pub fn apply_signatures<Pr: TxProver, R: RngCore>(
self,
prover: &Pr,
ctx: &mut Pr::SaplingProvingContext,
rng: &mut R,
sighash_bytes: &[u8; 32],
tx_metadata: &SaplingMetadata,
) -> Result<Option<(Vec<Signature>, Signature)>, Error> {
) -> Result<(Bundle<Authorized>, SaplingMetadata), Error> {
// Create Sapling spendAuth signatures. These must be properly ordered with respect to the
// shuffle that is described by tx_metadata.
let mut spend_sigs = vec![None; self.spends.len()];
for (i, spend) in self.spends.into_iter().enumerate() {
spend_sigs[tx_metadata.spend_indices[i]] = Some(spend_sig_internal(
let mut spend_sigs = vec![None; self.authorization.spends.len()];
for (i, spend) in self.authorization.spends.into_iter().enumerate() {
spend_sigs[self.authorization.tx_metadata.spend_indices[i]] = Some(spend_sig_internal(
PrivateKey(spend.extsk.expsk.ask),
spend.alpha,
sighash_bytes,
@ -497,20 +534,29 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
));
}
if tx_metadata.spend_indices.is_empty() && tx_metadata.output_indices.is_empty() {
Ok(None)
} else {
let spend_sigs = spend_sigs
.into_iter()
.collect::<Option<Vec<Signature>>>()
.unwrap_or_default();
let spend_sigs = spend_sigs
.into_iter()
.collect::<Option<Vec<Signature>>>()
.unwrap_or_default();
let binding_sig = prover
.binding_sig(ctx, self.value_balance, sighash_bytes)
.map_err(|_| Error::BindingSig)?;
let binding_sig = prover
.binding_sig(ctx, self.value_balance, sighash_bytes)
.map_err(|_| Error::BindingSig)?;
Ok(Some((spend_sigs, binding_sig)))
}
Ok((
Bundle {
shielded_spends: self
.shielded_spends
.iter()
.zip(spend_sigs.iter())
.map(|(spend, sig)| spend.apply_signature(*sig))
.collect(),
shielded_outputs: self.shielded_outputs,
value_balance: self.value_balance,
authorization: Authorized { binding_sig },
},
self.authorization.tx_metadata,
))
}
}
@ -574,23 +620,22 @@ pub mod testing {
let prover = MockTxProver;
let mut ctx = prover.new_sapling_proving_context();
let (bundle, meta) = builder.build(
let bundle = builder.build(
&prover,
&mut ctx,
&mut rng,
target_height.unwrap(),
None
).unwrap();
).unwrap().unwrap();
let (spend_auth_sigs, binding_sig) = builder.create_signatures(
let (bundle, _) = bundle.apply_signatures(
&prover,
&mut ctx,
&mut rng,
&fake_sighash_bytes,
&meta
).unwrap().unwrap();
).unwrap();
bundle.unwrap().apply_signatures(spend_auth_sigs, binding_sig)
bundle
}
}
}

View File

@ -257,7 +257,7 @@ pub struct Unauthorized;
impl Authorization for Unauthorized {
type TransparentAuth = transparent::builder::Unauthorized;
type SaplingAuth = sapling::Unauthorized;
type SaplingAuth = sapling::builder::Unauthorized;
type OrchardAuth = orchard::builder::Unauthorized;
#[cfg(feature = "zfuture")]
@ -547,7 +547,7 @@ impl Transaction {
let ss: Vec<SpendDescription<sapling::Authorized>> =
Vector::read(&mut reader, |r| SpendDescription::read(r))?;
#[allow(clippy::redundant_closure)]
let so: Vec<OutputDescription<sapling::Authorized>> =
let so: Vec<OutputDescription<sapling::GrothProofBytes>> =
Vector::read(&mut reader, |r| OutputDescription::read(r))?;
(vb, ss, so)
} else {

View File

@ -124,9 +124,7 @@ fn shielded_spends_hash<A: sapling::Authorization<Proof = GrothProofBytes>>(
.hash(&data)
}
fn shielded_outputs_hash<A: sapling::Authorization<Proof = GrothProofBytes>>(
shielded_outputs: &[OutputDescription<A>],
) -> Blake2bHash {
fn shielded_outputs_hash(shielded_outputs: &[OutputDescription<GrothProofBytes>]) -> Blake2bHash {
let mut data = Vec::with_capacity(shielded_outputs.len() * 948);
for s_out in shielded_outputs {
s_out.write_v4(&mut data).unwrap();

View File

@ -168,9 +168,7 @@ pub(crate) fn hash_sapling_spends<A: sapling::Authorization>(
/// * [(cv, enc_ciphertext[564..], out_ciphertext, zkproof)*] personalized with ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION
///
/// Then, hash these together personalized with ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION
pub(crate) fn hash_sapling_outputs<A: sapling::Authorization>(
shielded_outputs: &[OutputDescription<A>],
) -> Blake2bHash {
pub(crate) fn hash_sapling_outputs<A>(shielded_outputs: &[OutputDescription<A>]) -> Blake2bHash {
let mut ch = hasher(ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION);
let mut mh = hasher(ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION);
let mut nh = hasher(ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION);