Merge pull request #1023 from zcash/741-sapling-builder-refactor
Refactor Sapling builder to separate out proof generation
This commit is contained in:
commit
44c64e1fb9
|
@ -8,7 +8,7 @@ use zcash_primitives::{
|
||||||
sapling::{
|
sapling::{
|
||||||
self,
|
self,
|
||||||
note_encryption::{try_sapling_note_decryption, PreparedIncomingViewingKey},
|
note_encryption::{try_sapling_note_decryption, PreparedIncomingViewingKey},
|
||||||
prover::TxProver as SaplingProver,
|
prover::{OutputProver, SpendProver},
|
||||||
Node,
|
Node,
|
||||||
},
|
},
|
||||||
transaction::{
|
transaction::{
|
||||||
|
@ -191,7 +191,8 @@ where
|
||||||
pub fn create_spend_to_address<DbT, ParamsT>(
|
pub fn create_spend_to_address<DbT, ParamsT>(
|
||||||
wallet_db: &mut DbT,
|
wallet_db: &mut DbT,
|
||||||
params: &ParamsT,
|
params: &ParamsT,
|
||||||
prover: impl SaplingProver,
|
spend_prover: &impl SpendProver,
|
||||||
|
output_prover: &impl OutputProver,
|
||||||
usk: &UnifiedSpendingKey,
|
usk: &UnifiedSpendingKey,
|
||||||
to: &RecipientAddress,
|
to: &RecipientAddress,
|
||||||
amount: NonNegativeAmount,
|
amount: NonNegativeAmount,
|
||||||
|
@ -231,7 +232,15 @@ where
|
||||||
change_memo,
|
change_memo,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
create_proposed_transaction(wallet_db, params, prover, usk, ovk_policy, &proposal)
|
create_proposed_transaction(
|
||||||
|
wallet_db,
|
||||||
|
params,
|
||||||
|
spend_prover,
|
||||||
|
output_prover,
|
||||||
|
usk,
|
||||||
|
ovk_policy,
|
||||||
|
&proposal,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a transaction that sends funds as specified by the `request` argument
|
/// Constructs a transaction that sends funds as specified by the `request` argument
|
||||||
|
@ -289,7 +298,8 @@ where
|
||||||
pub fn spend<DbT, ParamsT, InputsT>(
|
pub fn spend<DbT, ParamsT, InputsT>(
|
||||||
wallet_db: &mut DbT,
|
wallet_db: &mut DbT,
|
||||||
params: &ParamsT,
|
params: &ParamsT,
|
||||||
prover: impl SaplingProver,
|
spend_prover: &impl SpendProver,
|
||||||
|
output_prover: &impl OutputProver,
|
||||||
input_selector: &InputsT,
|
input_selector: &InputsT,
|
||||||
usk: &UnifiedSpendingKey,
|
usk: &UnifiedSpendingKey,
|
||||||
request: zip321::TransactionRequest,
|
request: zip321::TransactionRequest,
|
||||||
|
@ -324,7 +334,15 @@ where
|
||||||
min_confirmations,
|
min_confirmations,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
create_proposed_transaction(wallet_db, params, prover, usk, ovk_policy, &proposal)
|
create_proposed_transaction(
|
||||||
|
wallet_db,
|
||||||
|
params,
|
||||||
|
spend_prover,
|
||||||
|
output_prover,
|
||||||
|
usk,
|
||||||
|
ovk_policy,
|
||||||
|
&proposal,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select transaction inputs, compute fees, and construct a proposal for a transaction
|
/// Select transaction inputs, compute fees, and construct a proposal for a transaction
|
||||||
|
@ -475,7 +493,8 @@ where
|
||||||
pub fn create_proposed_transaction<DbT, ParamsT, InputsErrT, FeeRuleT>(
|
pub fn create_proposed_transaction<DbT, ParamsT, InputsErrT, FeeRuleT>(
|
||||||
wallet_db: &mut DbT,
|
wallet_db: &mut DbT,
|
||||||
params: &ParamsT,
|
params: &ParamsT,
|
||||||
prover: impl SaplingProver,
|
spend_prover: &impl SpendProver,
|
||||||
|
output_prover: &impl OutputProver,
|
||||||
usk: &UnifiedSpendingKey,
|
usk: &UnifiedSpendingKey,
|
||||||
ovk_policy: OvkPolicy,
|
ovk_policy: OvkPolicy,
|
||||||
proposal: &Proposal<FeeRuleT, DbT::NoteRef>,
|
proposal: &Proposal<FeeRuleT, DbT::NoteRef>,
|
||||||
|
@ -663,7 +682,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the transaction with the specified fee rule
|
// Build the transaction with the specified fee rule
|
||||||
let (tx, sapling_build_meta) = builder.build(&prover, proposal.fee_rule())?;
|
let (tx, sapling_build_meta) =
|
||||||
|
builder.build(spend_prover, output_prover, proposal.fee_rule())?;
|
||||||
|
|
||||||
let internal_ivk = PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::Internal));
|
let internal_ivk = PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::Internal));
|
||||||
let sapling_outputs =
|
let sapling_outputs =
|
||||||
|
@ -768,7 +788,8 @@ where
|
||||||
pub fn shield_transparent_funds<DbT, ParamsT, InputsT>(
|
pub fn shield_transparent_funds<DbT, ParamsT, InputsT>(
|
||||||
wallet_db: &mut DbT,
|
wallet_db: &mut DbT,
|
||||||
params: &ParamsT,
|
params: &ParamsT,
|
||||||
prover: impl SaplingProver,
|
spend_prover: &impl SpendProver,
|
||||||
|
output_prover: &impl OutputProver,
|
||||||
input_selector: &InputsT,
|
input_selector: &InputsT,
|
||||||
shielding_threshold: NonNegativeAmount,
|
shielding_threshold: NonNegativeAmount,
|
||||||
usk: &UnifiedSpendingKey,
|
usk: &UnifiedSpendingKey,
|
||||||
|
@ -798,7 +819,15 @@ where
|
||||||
min_confirmations,
|
min_confirmations,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
create_proposed_transaction(wallet_db, params, prover, usk, OvkPolicy::Sender, &proposal)
|
create_proposed_transaction(
|
||||||
|
wallet_db,
|
||||||
|
params,
|
||||||
|
spend_prover,
|
||||||
|
output_prover,
|
||||||
|
usk,
|
||||||
|
OvkPolicy::Sender,
|
||||||
|
&proposal,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
|
|
|
@ -448,10 +448,12 @@ impl<Cache> TestState<Cache> {
|
||||||
>,
|
>,
|
||||||
> {
|
> {
|
||||||
let params = self.network();
|
let params = self.network();
|
||||||
|
let prover = test_prover();
|
||||||
create_spend_to_address(
|
create_spend_to_address(
|
||||||
&mut self.db_data,
|
&mut self.db_data,
|
||||||
¶ms,
|
¶ms,
|
||||||
test_prover(),
|
&prover,
|
||||||
|
&prover,
|
||||||
usk,
|
usk,
|
||||||
to,
|
to,
|
||||||
amount,
|
amount,
|
||||||
|
@ -484,10 +486,12 @@ impl<Cache> TestState<Cache> {
|
||||||
InputsT: InputSelector<DataSource = WalletDb<Connection, Network>>,
|
InputsT: InputSelector<DataSource = WalletDb<Connection, Network>>,
|
||||||
{
|
{
|
||||||
let params = self.network();
|
let params = self.network();
|
||||||
|
let prover = test_prover();
|
||||||
spend(
|
spend(
|
||||||
&mut self.db_data,
|
&mut self.db_data,
|
||||||
¶ms,
|
¶ms,
|
||||||
test_prover(),
|
&prover,
|
||||||
|
&prover,
|
||||||
input_selector,
|
input_selector,
|
||||||
usk,
|
usk,
|
||||||
request,
|
request,
|
||||||
|
@ -614,10 +618,12 @@ impl<Cache> TestState<Cache> {
|
||||||
FeeRuleT: FeeRule,
|
FeeRuleT: FeeRule,
|
||||||
{
|
{
|
||||||
let params = self.network();
|
let params = self.network();
|
||||||
|
let prover = test_prover();
|
||||||
create_proposed_transaction(
|
create_proposed_transaction(
|
||||||
&mut self.db_data,
|
&mut self.db_data,
|
||||||
¶ms,
|
¶ms,
|
||||||
test_prover(),
|
&prover,
|
||||||
|
&prover,
|
||||||
usk,
|
usk,
|
||||||
ovk_policy,
|
ovk_policy,
|
||||||
proposal,
|
proposal,
|
||||||
|
@ -647,10 +653,12 @@ impl<Cache> TestState<Cache> {
|
||||||
InputsT: InputSelector<DataSource = WalletDb<Connection, Network>>,
|
InputsT: InputSelector<DataSource = WalletDb<Connection, Network>>,
|
||||||
{
|
{
|
||||||
let params = self.network();
|
let params = self.network();
|
||||||
|
let prover = test_prover();
|
||||||
shield_transparent_funds(
|
shield_transparent_funds(
|
||||||
&mut self.db_data,
|
&mut self.db_data,
|
||||||
¶ms,
|
¶ms,
|
||||||
test_prover(),
|
&prover,
|
||||||
|
&prover,
|
||||||
input_selector,
|
input_selector,
|
||||||
shielding_threshold,
|
shielding_threshold,
|
||||||
usk,
|
usk,
|
||||||
|
|
|
@ -451,8 +451,9 @@ pub(crate) mod tests {
|
||||||
legacy::TransparentAddress,
|
legacy::TransparentAddress,
|
||||||
memo::{Memo, MemoBytes},
|
memo::{Memo, MemoBytes},
|
||||||
sapling::{
|
sapling::{
|
||||||
note_encryption::try_sapling_output_recovery, prover::TxProver, Node, Note,
|
note_encryption::try_sapling_output_recovery,
|
||||||
PaymentAddress,
|
prover::{OutputProver, SpendProver},
|
||||||
|
Node, Note, PaymentAddress,
|
||||||
},
|
},
|
||||||
transaction::{
|
transaction::{
|
||||||
components::{amount::NonNegativeAmount, Amount},
|
components::{amount::NonNegativeAmount, Amount},
|
||||||
|
@ -497,7 +498,7 @@ pub(crate) mod tests {
|
||||||
zcash_primitives::transaction::components::{OutPoint, TxOut},
|
zcash_primitives::transaction::components::{OutPoint, TxOut},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) fn test_prover() -> impl TxProver {
|
pub(crate) fn test_prover() -> impl SpendProver + OutputProver {
|
||||||
match LocalTxProver::with_default_location() {
|
match LocalTxProver::with_default_location() {
|
||||||
Some(tx_prover) => tx_prover,
|
Some(tx_prover) => tx_prover,
|
||||||
None => {
|
None => {
|
||||||
|
|
|
@ -839,7 +839,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (tx_a, _) = builder_a
|
let (tx_a, _) = builder_a
|
||||||
.txn_builder
|
.txn_builder
|
||||||
.build_zfuture(&prover, &fee_rule)
|
.build_zfuture(&prover, &prover, &fee_rule)
|
||||||
.map_err(|e| format!("build failure: {:?}", e))
|
.map_err(|e| format!("build failure: {:?}", e))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tze_a = tx_a.tze_bundle().unwrap();
|
let tze_a = tx_a.tze_bundle().unwrap();
|
||||||
|
@ -857,7 +857,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (tx_b, _) = builder_b
|
let (tx_b, _) = builder_b
|
||||||
.txn_builder
|
.txn_builder
|
||||||
.build_zfuture(&prover, &fee_rule)
|
.build_zfuture(&prover, &prover, &fee_rule)
|
||||||
.map_err(|e| format!("build failure: {:?}", e))
|
.map_err(|e| format!("build failure: {:?}", e))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tze_b = tx_b.tze_bundle().unwrap();
|
let tze_b = tx_b.tze_bundle().unwrap();
|
||||||
|
@ -882,7 +882,7 @@ mod tests {
|
||||||
|
|
||||||
let (tx_c, _) = builder_c
|
let (tx_c, _) = builder_c
|
||||||
.txn_builder
|
.txn_builder
|
||||||
.build_zfuture(&prover, &fee_rule)
|
.build_zfuture(&prover, &prover, &fee_rule)
|
||||||
.map_err(|e| format!("build failure: {:?}", e))
|
.map_err(|e| format!("build failure: {:?}", e))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tze_c = tx_c.tze_bundle().unwrap();
|
let tze_c = tx_c.tze_bundle().unwrap();
|
||||||
|
|
|
@ -12,13 +12,11 @@ use zcash_primitives::{
|
||||||
try_sapling_compact_note_decryption, try_sapling_note_decryption,
|
try_sapling_compact_note_decryption, try_sapling_note_decryption,
|
||||||
PreparedIncomingViewingKey, SaplingDomain,
|
PreparedIncomingViewingKey, SaplingDomain,
|
||||||
},
|
},
|
||||||
prover::mock::MockTxProver,
|
prover::mock::{MockOutputProver, MockSpendProver},
|
||||||
value::NoteValue,
|
value::NoteValue,
|
||||||
Diversifier, SaplingIvk,
|
Diversifier, SaplingIvk,
|
||||||
},
|
},
|
||||||
transaction::components::sapling::{
|
transaction::components::sapling::{builder::SaplingBuilder, CompactOutputDescription},
|
||||||
builder::SaplingBuilder, CompactOutputDescription, GrothProofBytes, OutputDescription,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -32,7 +30,7 @@ fn bench_note_decryption(c: &mut Criterion) {
|
||||||
let invalid_ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
|
let invalid_ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
|
||||||
|
|
||||||
// Construct a Sapling output.
|
// Construct a Sapling output.
|
||||||
let output: OutputDescription<GrothProofBytes> = {
|
let output = {
|
||||||
let diversifier = Diversifier([0; 11]);
|
let diversifier = Diversifier([0; 11]);
|
||||||
let pa = valid_ivk.to_payment_address(diversifier).unwrap();
|
let pa = valid_ivk.to_payment_address(diversifier).unwrap();
|
||||||
|
|
||||||
|
@ -46,8 +44,8 @@ fn bench_note_decryption(c: &mut Criterion) {
|
||||||
MemoBytes::empty(),
|
MemoBytes::empty(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let bundle = builder
|
let (bundle, _) = builder
|
||||||
.build(&MockTxProver, &mut (), &mut rng, height, None)
|
.build::<MockSpendProver, MockOutputProver, _>(&mut rng, height)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
bundle.shielded_outputs()[0].clone()
|
bundle.shielded_outputs()[0].clone()
|
||||||
|
|
|
@ -36,11 +36,11 @@ pub fn spend_sig<R: RngCore + CryptoRng>(
|
||||||
sighash: &[u8; 32],
|
sighash: &[u8; 32],
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
) -> Signature {
|
) -> Signature {
|
||||||
spend_sig_internal(ask, ar, sighash, rng)
|
spend_sig_internal(&ask, ar, sighash, rng)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn spend_sig_internal<R: RngCore>(
|
pub(crate) fn spend_sig_internal<R: RngCore>(
|
||||||
ask: PrivateKey,
|
ask: &PrivateKey,
|
||||||
ar: jubjub::Fr,
|
ar: jubjub::Fr,
|
||||||
sighash: &[u8; 32],
|
sighash: &[u8; 32],
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
|
@ -60,6 +60,29 @@ pub(crate) fn spend_sig_internal<R: RngCore>(
|
||||||
rsk.sign(&data_to_be_signed, rng, SPENDING_KEY_GENERATOR)
|
rsk.sign(&data_to_be_signed, rng, SPENDING_KEY_GENERATOR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verifies a spendAuthSig.
|
||||||
|
///
|
||||||
|
/// This only exists because the RedJubjub implementation inside `zcash_primitives` does
|
||||||
|
/// not implement key prefixing (which was added in response to a Sapling audit). This
|
||||||
|
/// will be fixed by migrating to the redjubjub crate.
|
||||||
|
pub(crate) fn verify_spend_sig(
|
||||||
|
ak: &PublicKey,
|
||||||
|
alpha: jubjub::Fr,
|
||||||
|
sighash: &[u8; 32],
|
||||||
|
sig: &Signature,
|
||||||
|
) -> bool {
|
||||||
|
// We compute `rk` (needed for key prefixing)
|
||||||
|
let rk = ak.randomize(alpha, SPENDING_KEY_GENERATOR);
|
||||||
|
|
||||||
|
// Compute the signature's message for rk/spend_auth_sig
|
||||||
|
let mut data_to_be_signed = [0u8; 64];
|
||||||
|
data_to_be_signed[0..32].copy_from_slice(&rk.0.to_bytes());
|
||||||
|
data_to_be_signed[32..64].copy_from_slice(&sighash[..]);
|
||||||
|
|
||||||
|
// Do the verifying
|
||||||
|
rk.verify(&data_to_be_signed, sig, SPENDING_KEY_GENERATOR)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-dependencies"))]
|
#[cfg(any(test, feature = "test-dependencies"))]
|
||||||
pub mod testing {
|
pub mod testing {
|
||||||
pub use super::{
|
pub use super::{
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
//! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html
|
//! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html
|
||||||
|
|
||||||
use bitvec::{array::BitArray, order::Lsb0};
|
use bitvec::{array::BitArray, order::Lsb0};
|
||||||
use ff::Field;
|
use ff::{Field, PrimeField};
|
||||||
use group::GroupEncoding;
|
use group::GroupEncoding;
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
use subtle::CtOption;
|
use subtle::CtOption;
|
||||||
|
@ -86,6 +86,21 @@ impl ValueCommitTrapdoor {
|
||||||
ValueCommitTrapdoor(jubjub::Scalar::random(rng))
|
ValueCommitTrapdoor(jubjub::Scalar::random(rng))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs `ValueCommitTrapdoor` from the byte representation of a scalar.
|
||||||
|
///
|
||||||
|
/// Returns a `None` [`CtOption`] if `bytes` is not a canonical representation of a
|
||||||
|
/// Jubjub scalar.
|
||||||
|
///
|
||||||
|
/// This is a low-level API, requiring a detailed understanding of the
|
||||||
|
/// [use of value commitment trapdoors][saplingbalance] in the Zcash protocol
|
||||||
|
/// to use correctly and securely. It is intended to be used in combination
|
||||||
|
/// with [`ValueCommitment::derive`].
|
||||||
|
///
|
||||||
|
/// [saplingbalance]: https://zips.z.cash/protocol/protocol.pdf#saplingbalance
|
||||||
|
pub fn from_bytes(bytes: [u8; 32]) -> CtOption<Self> {
|
||||||
|
jubjub::Scalar::from_repr(bytes).map(ValueCommitTrapdoor)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the inner Jubjub scalar representing this trapdoor.
|
/// Returns the inner Jubjub scalar representing this trapdoor.
|
||||||
///
|
///
|
||||||
/// This is public for access by `zcash_proofs`.
|
/// This is public for access by `zcash_proofs`.
|
||||||
|
|
|
@ -123,6 +123,14 @@ impl Sub<&ValueCommitTrapdoor> for ValueCommitTrapdoor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sub<TrapdoorSum> for TrapdoorSum {
|
||||||
|
type Output = TrapdoorSum;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
TrapdoorSum(self.0 - rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SubAssign<&ValueCommitTrapdoor> for TrapdoorSum {
|
impl SubAssign<&ValueCommitTrapdoor> for TrapdoorSum {
|
||||||
fn sub_assign(&mut self, rhs: &ValueCommitTrapdoor) {
|
fn sub_assign(&mut self, rhs: &ValueCommitTrapdoor) {
|
||||||
self.0 -= rhs.0;
|
self.0 -= rhs.0;
|
||||||
|
@ -207,6 +215,14 @@ impl SubAssign<&ValueCommitment> for CommitmentSum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sub<CommitmentSum> for CommitmentSum {
|
||||||
|
type Output = CommitmentSum;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
CommitmentSum(self.0 - rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Sum<ValueCommitment> for CommitmentSum {
|
impl Sum<ValueCommitment> for CommitmentSum {
|
||||||
fn sum<I: Iterator<Item = ValueCommitment>>(iter: I) -> Self {
|
fn sum<I: Iterator<Item = ValueCommitment>>(iter: I) -> Self {
|
||||||
iter.fold(CommitmentSum::zero(), |acc, cv| acc + &cv)
|
iter.fold(CommitmentSum::zero(), |acc, cv| acc + &cv)
|
||||||
|
|
|
@ -12,7 +12,11 @@ use crate::{
|
||||||
keys::OutgoingViewingKey,
|
keys::OutgoingViewingKey,
|
||||||
legacy::TransparentAddress,
|
legacy::TransparentAddress,
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
sapling::{self, prover::TxProver, Diversifier, Note, PaymentAddress},
|
sapling::{
|
||||||
|
self,
|
||||||
|
prover::{OutputProver, SpendProver},
|
||||||
|
redjubjub, Diversifier, Note, PaymentAddress,
|
||||||
|
},
|
||||||
transaction::{
|
transaction::{
|
||||||
components::{
|
components::{
|
||||||
amount::{Amount, BalanceError},
|
amount::{Amount, BalanceError},
|
||||||
|
@ -162,6 +166,7 @@ pub struct Builder<'a, P, R> {
|
||||||
// `add_sapling_spend` or `add_orchard_spend`, we will build an unauthorized, unproven
|
// `add_sapling_spend` or `add_orchard_spend`, we will build an unauthorized, unproven
|
||||||
// transaction, and then the caller will be responsible for using the spending keys or their
|
// transaction, and then the caller will be responsible for using the spending keys or their
|
||||||
// derivatives for proving and signing to complete transaction creation.
|
// derivatives for proving and signing to complete transaction creation.
|
||||||
|
sapling_asks: Vec<redjubjub::PrivateKey>,
|
||||||
orchard_saks: Vec<orchard::keys::SpendAuthorizingKey>,
|
orchard_saks: Vec<orchard::keys::SpendAuthorizingKey>,
|
||||||
#[cfg(feature = "zfuture")]
|
#[cfg(feature = "zfuture")]
|
||||||
tze_builder: TzeBuilder<'a, TransactionData<Unauthorized>>,
|
tze_builder: TzeBuilder<'a, TransactionData<Unauthorized>>,
|
||||||
|
@ -266,6 +271,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
||||||
transparent_builder: TransparentBuilder::empty(),
|
transparent_builder: TransparentBuilder::empty(),
|
||||||
sapling_builder: SaplingBuilder::new(params, target_height),
|
sapling_builder: SaplingBuilder::new(params, target_height),
|
||||||
orchard_builder,
|
orchard_builder,
|
||||||
|
sapling_asks: vec![],
|
||||||
orchard_saks: Vec::new(),
|
orchard_saks: Vec::new(),
|
||||||
#[cfg(feature = "zfuture")]
|
#[cfg(feature = "zfuture")]
|
||||||
tze_builder: TzeBuilder::empty(),
|
tze_builder: TzeBuilder::empty(),
|
||||||
|
@ -329,7 +335,12 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
||||||
merkle_path: sapling::MerklePath,
|
merkle_path: sapling::MerklePath,
|
||||||
) -> Result<(), sapling_builder::Error> {
|
) -> Result<(), sapling_builder::Error> {
|
||||||
self.sapling_builder
|
self.sapling_builder
|
||||||
.add_spend(&mut self.rng, extsk, diversifier, note, merkle_path)
|
.add_spend(&mut self.rng, &extsk, diversifier, note, merkle_path)?;
|
||||||
|
|
||||||
|
self.sapling_asks
|
||||||
|
.push(redjubjub::PrivateKey(extsk.expsk.ask));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a Sapling address to send funds to.
|
/// Adds a Sapling address to send funds to.
|
||||||
|
@ -432,13 +443,14 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
||||||
///
|
///
|
||||||
/// Upon success, returns a tuple containing the final transaction, and the
|
/// Upon success, returns a tuple containing the final transaction, and the
|
||||||
/// [`SaplingMetadata`] generated during the build process.
|
/// [`SaplingMetadata`] generated during the build process.
|
||||||
pub fn build<FR: FeeRule>(
|
pub fn build<SP: SpendProver, OP: OutputProver, FR: FeeRule>(
|
||||||
self,
|
self,
|
||||||
prover: &impl TxProver,
|
spend_prover: &SP,
|
||||||
|
output_prover: &OP,
|
||||||
fee_rule: &FR,
|
fee_rule: &FR,
|
||||||
) -> Result<(Transaction, SaplingMetadata), Error<FR::Error>> {
|
) -> Result<(Transaction, SaplingMetadata), Error<FR::Error>> {
|
||||||
let fee = self.get_fee(fee_rule).map_err(Error::Fee)?;
|
let fee = self.get_fee(fee_rule).map_err(Error::Fee)?;
|
||||||
self.build_internal(prover, fee.into())
|
self.build_internal(spend_prover, output_prover, fee.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a transaction from the configured spends and outputs.
|
/// Builds a transaction from the configured spends and outputs.
|
||||||
|
@ -446,9 +458,10 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
||||||
/// Upon success, returns a tuple containing the final transaction, and the
|
/// Upon success, returns a tuple containing the final transaction, and the
|
||||||
/// [`SaplingMetadata`] generated during the build process.
|
/// [`SaplingMetadata`] generated during the build process.
|
||||||
#[cfg(feature = "zfuture")]
|
#[cfg(feature = "zfuture")]
|
||||||
pub fn build_zfuture<FR: FutureFeeRule>(
|
pub fn build_zfuture<SP: SpendProver, OP: OutputProver, FR: FutureFeeRule>(
|
||||||
self,
|
self,
|
||||||
prover: &impl TxProver,
|
spend_prover: &SP,
|
||||||
|
output_prover: &OP,
|
||||||
fee_rule: &FR,
|
fee_rule: &FR,
|
||||||
) -> Result<(Transaction, SaplingMetadata), Error<FR::Error>> {
|
) -> Result<(Transaction, SaplingMetadata), Error<FR::Error>> {
|
||||||
let fee = fee_rule
|
let fee = fee_rule
|
||||||
|
@ -464,12 +477,13 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
||||||
)
|
)
|
||||||
.map_err(Error::Fee)?;
|
.map_err(Error::Fee)?;
|
||||||
|
|
||||||
self.build_internal(prover, fee.into())
|
self.build_internal(spend_prover, output_prover, fee.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_internal<FE>(
|
fn build_internal<SP: SpendProver, OP: OutputProver, FE>(
|
||||||
self,
|
self,
|
||||||
prover: &impl TxProver,
|
spend_prover: &SP,
|
||||||
|
output_prover: &OP,
|
||||||
fee: Amount,
|
fee: Amount,
|
||||||
) -> Result<(Transaction, SaplingMetadata), Error<FE>> {
|
) -> Result<(Transaction, SaplingMetadata), Error<FE>> {
|
||||||
let consensus_branch_id = BranchId::for_height(&self.params, self.target_height);
|
let consensus_branch_id = BranchId::for_height(&self.params, self.target_height);
|
||||||
|
@ -497,17 +511,27 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
||||||
let transparent_bundle = self.transparent_builder.build();
|
let transparent_bundle = self.transparent_builder.build();
|
||||||
|
|
||||||
let mut rng = self.rng;
|
let mut rng = self.rng;
|
||||||
let mut ctx = prover.new_sapling_proving_context();
|
let (sapling_bundle, tx_metadata) = match self
|
||||||
let sapling_bundle = self
|
|
||||||
.sapling_builder
|
.sapling_builder
|
||||||
.build(
|
.build::<SP, OP, _>(&mut rng, self.target_height)
|
||||||
prover,
|
.map_err(Error::SaplingBuild)?
|
||||||
&mut ctx,
|
.map(|(bundle, tx_metadata)| {
|
||||||
&mut rng,
|
// We need to create proofs before signatures, because we still support
|
||||||
self.target_height,
|
// creating V4 transactions, which commit to the Sapling proofs in the
|
||||||
self.progress_notifier.as_ref(),
|
// transaction digest.
|
||||||
)
|
(
|
||||||
.map_err(Error::SaplingBuild)?;
|
bundle.create_proofs(
|
||||||
|
spend_prover,
|
||||||
|
output_prover,
|
||||||
|
&mut rng,
|
||||||
|
self.progress_notifier.as_ref(),
|
||||||
|
),
|
||||||
|
tx_metadata,
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
Some((bundle, meta)) => (Some(bundle), meta),
|
||||||
|
None => (None, SaplingMetadata::empty()),
|
||||||
|
};
|
||||||
|
|
||||||
let orchard_bundle: Option<orchard::Bundle<_, Amount>> =
|
let orchard_bundle: Option<orchard::Bundle<_, Amount>> =
|
||||||
if let Some(builder) = self.orchard_builder {
|
if let Some(builder) = self.orchard_builder {
|
||||||
|
@ -560,17 +584,17 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
||||||
let shielded_sig_commitment =
|
let shielded_sig_commitment =
|
||||||
signature_hash(&unauthed_tx, &SignableInput::Shielded, &txid_parts);
|
signature_hash(&unauthed_tx, &SignableInput::Shielded, &txid_parts);
|
||||||
|
|
||||||
let (sapling_bundle, tx_metadata) = match unauthed_tx
|
let sapling_bundle = unauthed_tx
|
||||||
.sapling_bundle
|
.sapling_bundle
|
||||||
.map(|b| {
|
.map(|b| {
|
||||||
b.apply_signatures(prover, &mut ctx, &mut rng, shielded_sig_commitment.as_ref())
|
b.apply_signatures(
|
||||||
|
&mut rng,
|
||||||
|
*shielded_sig_commitment.as_ref(),
|
||||||
|
&self.sapling_asks,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
.map_err(Error::SaplingBuild)?
|
.map_err(Error::SaplingBuild)?;
|
||||||
{
|
|
||||||
Some((bundle, meta)) => (Some(bundle), meta),
|
|
||||||
None => (None, SaplingMetadata::empty()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let orchard_bundle = unauthed_tx
|
let orchard_bundle = unauthed_tx
|
||||||
.orchard_bundle
|
.orchard_bundle
|
||||||
|
@ -648,7 +672,7 @@ mod testing {
|
||||||
use super::{Builder, Error, SaplingMetadata};
|
use super::{Builder, Error, SaplingMetadata};
|
||||||
use crate::{
|
use crate::{
|
||||||
consensus::{self, BlockHeight},
|
consensus::{self, BlockHeight},
|
||||||
sapling::prover::mock::MockTxProver,
|
sapling::prover::mock::{MockOutputProver, MockSpendProver},
|
||||||
transaction::fees::fixed,
|
transaction::fees::fixed,
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
|
@ -693,7 +717,11 @@ mod testing {
|
||||||
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
||||||
pub fn mock_build(self) -> Result<(Transaction, SaplingMetadata), Error<Infallible>> {
|
pub fn mock_build(self) -> Result<(Transaction, SaplingMetadata), Error<Infallible>> {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
self.build(&MockTxProver, &fixed::FeeRule::standard())
|
self.build(
|
||||||
|
&MockSpendProver,
|
||||||
|
&MockOutputProver,
|
||||||
|
&fixed::FeeRule::standard(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -710,10 +738,7 @@ mod tests {
|
||||||
legacy::TransparentAddress,
|
legacy::TransparentAddress,
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
sapling::{self, Node, Rseed},
|
sapling::{self, Node, Rseed},
|
||||||
transaction::components::{
|
transaction::components::amount::{Amount, BalanceError, NonNegativeAmount},
|
||||||
amount::{Amount, NonNegativeAmount},
|
|
||||||
sapling::builder::{self as sapling_builder},
|
|
||||||
},
|
|
||||||
zip32::ExtendedSpendingKey,
|
zip32::ExtendedSpendingKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -759,6 +784,7 @@ mod tests {
|
||||||
tze_builder: std::marker::PhantomData,
|
tze_builder: std::marker::PhantomData,
|
||||||
progress_notifier: None,
|
progress_notifier: None,
|
||||||
orchard_builder: None,
|
orchard_builder: None,
|
||||||
|
sapling_asks: vec![],
|
||||||
orchard_saks: Vec::new(),
|
orchard_saks: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -828,12 +854,9 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Expect a binding signature error, because our inputs aren't valid, but this shows
|
// A binding signature (and bundle) is present because there is a Sapling spend.
|
||||||
// that a binding signature was attempted
|
let (tx, _) = builder.mock_build().unwrap();
|
||||||
assert_matches!(
|
assert!(tx.sapling_bundle().is_some());
|
||||||
builder.mock_build(),
|
|
||||||
Err(Error::SaplingBuild(sapling_builder::Error::BindingSig))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -950,9 +973,6 @@ mod tests {
|
||||||
|
|
||||||
// Succeeds if there is sufficient input
|
// Succeeds if there is sufficient input
|
||||||
// 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.0006 z-ZEC in
|
// 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.0006 z-ZEC in
|
||||||
//
|
|
||||||
// (Still fails because we are using a MockTxProver which doesn't correctly
|
|
||||||
// compute bindingSig.)
|
|
||||||
{
|
{
|
||||||
let mut builder = Builder::new(TEST_NETWORK, tx_height, None);
|
let mut builder = Builder::new(TEST_NETWORK, tx_height, None);
|
||||||
builder
|
builder
|
||||||
|
@ -982,8 +1002,8 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
builder.mock_build(),
|
builder.mock_build(),
|
||||||
Err(Error::SaplingBuild(sapling_builder::Error::BindingSig))
|
Ok((tx, _)) if tx.fee_paid(|_| Err(BalanceError::Overflow)).unwrap() == Amount::const_from_i64(10_000)
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,19 +34,11 @@ pub trait Authorization: Debug {
|
||||||
type AuthSig: Clone + Debug;
|
type AuthSig: Clone + Debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Unproven;
|
|
||||||
|
|
||||||
impl Authorization for Unproven {
|
|
||||||
type SpendProof = ();
|
|
||||||
type OutputProof = ();
|
|
||||||
type AuthSig = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Authorizing data for a bundle of Sapling spends and outputs, ready to be committed to
|
/// Authorizing data for a bundle of Sapling spends and outputs, ready to be committed to
|
||||||
/// the ledger.
|
/// the ledger.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct Authorized {
|
pub struct Authorized {
|
||||||
|
// TODO: Make this private.
|
||||||
pub binding_sig: redjubjub::Signature,
|
pub binding_sig: redjubjub::Signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,37 @@
|
||||||
//! Types and functions for building Sapling transaction components.
|
//! Types and functions for building Sapling transaction components.
|
||||||
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::sync::mpsc::Sender;
|
use std::{marker::PhantomData, sync::mpsc::Sender};
|
||||||
|
|
||||||
use ff::Field;
|
use ff::Field;
|
||||||
use rand::{seq::SliceRandom, RngCore};
|
use rand::{seq::SliceRandom, RngCore};
|
||||||
|
use rand_core::CryptoRng;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consensus::{self, BlockHeight},
|
consensus::{self, BlockHeight},
|
||||||
keys::OutgoingViewingKey,
|
keys::OutgoingViewingKey,
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
sapling::{
|
sapling::{
|
||||||
keys::SaplingIvk,
|
self,
|
||||||
|
constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR},
|
||||||
note_encryption::sapling_note_encryption,
|
note_encryption::sapling_note_encryption,
|
||||||
prover::TxProver,
|
prover::{OutputProver, SpendProver},
|
||||||
redjubjub::{PrivateKey, Signature},
|
redjubjub::{PrivateKey, PublicKey, Signature},
|
||||||
spend_sig_internal,
|
spend_sig_internal,
|
||||||
util::generate_random_rseed_internal,
|
util::generate_random_rseed_internal,
|
||||||
value::{NoteValue, ValueSum},
|
value::{
|
||||||
Diversifier, MerklePath, Node, Note, PaymentAddress,
|
CommitmentSum, NoteValue, TrapdoorSum, ValueCommitTrapdoor, ValueCommitment, ValueSum,
|
||||||
|
},
|
||||||
|
verify_spend_sig, Diversifier, MerklePath, Node, Note, PaymentAddress, ProofGenerationKey,
|
||||||
|
SaplingIvk,
|
||||||
},
|
},
|
||||||
transaction::{
|
transaction::{
|
||||||
builder::Progress,
|
builder::Progress,
|
||||||
components::{
|
components::{
|
||||||
amount::{Amount, NonNegativeAmount},
|
amount::{Amount, NonNegativeAmount},
|
||||||
sapling::{
|
sapling::{
|
||||||
fees, Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription,
|
fees, Authorization, Authorized, Bundle, GrothProofBytes, MapAuth,
|
||||||
SpendDescription,
|
OutputDescription, SpendDescription,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -41,8 +46,15 @@ const MIN_SHIELDED_OUTPUTS: usize = 2;
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
AnchorMismatch,
|
AnchorMismatch,
|
||||||
BindingSig,
|
BindingSig,
|
||||||
|
/// A signature is valid for more than one input. This should never happen if `alpha`
|
||||||
|
/// is sampled correctly, and indicates a critical failure in randomness generation.
|
||||||
|
DuplicateSignature,
|
||||||
InvalidAddress,
|
InvalidAddress,
|
||||||
InvalidAmount,
|
InvalidAmount,
|
||||||
|
/// External signature is not valid.
|
||||||
|
InvalidExternalSignature,
|
||||||
|
/// A bundle could not be built because required signatures were missing.
|
||||||
|
MissingSignatures,
|
||||||
SpendProof,
|
SpendProof,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,8 +65,11 @@ impl fmt::Display for Error {
|
||||||
write!(f, "Anchor mismatch (anchors for all spends must be equal)")
|
write!(f, "Anchor mismatch (anchors for all spends must be equal)")
|
||||||
}
|
}
|
||||||
Error::BindingSig => write!(f, "Failed to create bindingSig"),
|
Error::BindingSig => write!(f, "Failed to create bindingSig"),
|
||||||
|
Error::DuplicateSignature => write!(f, "Signature valid for more than one input"),
|
||||||
Error::InvalidAddress => write!(f, "Invalid address"),
|
Error::InvalidAddress => write!(f, "Invalid address"),
|
||||||
Error::InvalidAmount => write!(f, "Invalid amount"),
|
Error::InvalidAmount => write!(f, "Invalid amount"),
|
||||||
|
Error::InvalidExternalSignature => write!(f, "External signature was invalid"),
|
||||||
|
Error::MissingSignatures => write!(f, "Required signatures were missing during build"),
|
||||||
Error::SpendProof => write!(f, "Failed to create Sapling spend proof"),
|
Error::SpendProof => write!(f, "Failed to create Sapling spend proof"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,11 +77,12 @@ impl fmt::Display for Error {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SpendDescriptionInfo {
|
pub struct SpendDescriptionInfo {
|
||||||
extsk: ExtendedSpendingKey,
|
proof_generation_key: ProofGenerationKey,
|
||||||
diversifier: Diversifier,
|
diversifier: Diversifier,
|
||||||
note: Note,
|
note: Note,
|
||||||
alpha: jubjub::Fr,
|
alpha: jubjub::Fr,
|
||||||
merkle_path: MerklePath,
|
merkle_path: MerklePath,
|
||||||
|
rcv: ValueCommitTrapdoor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fees::InputView<()> for SpendDescriptionInfo {
|
impl fees::InputView<()> for SpendDescriptionInfo {
|
||||||
|
@ -81,6 +97,70 @@ impl fees::InputView<()> for SpendDescriptionInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SpendDescriptionInfo {
|
||||||
|
fn new_internal<R: RngCore>(
|
||||||
|
mut rng: &mut R,
|
||||||
|
extsk: &ExtendedSpendingKey,
|
||||||
|
diversifier: Diversifier,
|
||||||
|
note: Note,
|
||||||
|
merkle_path: MerklePath,
|
||||||
|
) -> Self {
|
||||||
|
SpendDescriptionInfo {
|
||||||
|
proof_generation_key: extsk.expsk.proof_generation_key(),
|
||||||
|
diversifier,
|
||||||
|
note,
|
||||||
|
alpha: jubjub::Fr::random(&mut rng),
|
||||||
|
merkle_path,
|
||||||
|
rcv: ValueCommitTrapdoor::random(rng),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build<Pr: SpendProver>(
|
||||||
|
self,
|
||||||
|
anchor: Option<bls12_381::Scalar>,
|
||||||
|
) -> Result<SpendDescription<InProgress<Unproven, Unsigned>>, Error> {
|
||||||
|
let anchor = anchor.expect("Sapling anchor must be set if Sapling spends are present.");
|
||||||
|
|
||||||
|
// Construct the value commitment.
|
||||||
|
let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
|
||||||
|
|
||||||
|
let ak = PublicKey(self.proof_generation_key.ak.into());
|
||||||
|
|
||||||
|
// This is the result of the re-randomization, we compute it for the caller
|
||||||
|
let rk = ak.randomize(self.alpha, SPENDING_KEY_GENERATOR);
|
||||||
|
|
||||||
|
let nullifier = self.note.nf(
|
||||||
|
&self.proof_generation_key.to_viewing_key().nk,
|
||||||
|
u64::try_from(self.merkle_path.position())
|
||||||
|
.expect("Sapling note commitment tree position must fit into a u64"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let zkproof = Pr::prepare_circuit(
|
||||||
|
self.proof_generation_key,
|
||||||
|
self.diversifier,
|
||||||
|
*self.note.rseed(),
|
||||||
|
self.note.value(),
|
||||||
|
self.alpha,
|
||||||
|
self.rcv,
|
||||||
|
anchor,
|
||||||
|
self.merkle_path.clone(),
|
||||||
|
)
|
||||||
|
.ok_or(Error::SpendProof)?;
|
||||||
|
|
||||||
|
Ok(SpendDescription {
|
||||||
|
cv,
|
||||||
|
anchor,
|
||||||
|
nullifier,
|
||||||
|
rk,
|
||||||
|
zkproof,
|
||||||
|
spend_auth_sig: SigningParts {
|
||||||
|
ak,
|
||||||
|
alpha: self.alpha,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A struct containing the information required in order to construct a
|
/// A struct containing the information required in order to construct a
|
||||||
/// Sapling output to a transaction.
|
/// Sapling output to a transaction.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -89,9 +169,38 @@ struct SaplingOutputInfo {
|
||||||
ovk: Option<OutgoingViewingKey>,
|
ovk: Option<OutgoingViewingKey>,
|
||||||
note: Note,
|
note: Note,
|
||||||
memo: MemoBytes,
|
memo: MemoBytes,
|
||||||
|
rcv: ValueCommitTrapdoor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SaplingOutputInfo {
|
impl SaplingOutputInfo {
|
||||||
|
fn dummy<P: consensus::Parameters, R: RngCore>(
|
||||||
|
params: &P,
|
||||||
|
mut rng: &mut R,
|
||||||
|
target_height: BlockHeight,
|
||||||
|
) -> Self {
|
||||||
|
// This is a dummy output
|
||||||
|
let dummy_to = {
|
||||||
|
let mut diversifier = Diversifier([0; 11]);
|
||||||
|
loop {
|
||||||
|
rng.fill_bytes(&mut diversifier.0);
|
||||||
|
let dummy_ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
|
||||||
|
if let Some(addr) = dummy_ivk.to_payment_address(diversifier) {
|
||||||
|
break addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::new_internal(
|
||||||
|
params,
|
||||||
|
rng,
|
||||||
|
target_height,
|
||||||
|
None,
|
||||||
|
dummy_to,
|
||||||
|
NoteValue::from_raw(0),
|
||||||
|
MemoBytes::empty(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn new_internal<P: consensus::Parameters, R: RngCore>(
|
fn new_internal<P: consensus::Parameters, R: RngCore>(
|
||||||
params: &P,
|
params: &P,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
|
@ -105,24 +214,31 @@ impl SaplingOutputInfo {
|
||||||
|
|
||||||
let note = Note::from_parts(to, value, rseed);
|
let note = Note::from_parts(to, value, rseed);
|
||||||
|
|
||||||
SaplingOutputInfo { ovk, note, memo }
|
SaplingOutputInfo {
|
||||||
|
ovk,
|
||||||
|
note,
|
||||||
|
memo,
|
||||||
|
rcv: ValueCommitTrapdoor::random(rng),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build<P: consensus::Parameters, Pr: TxProver, R: RngCore>(
|
fn build<P: consensus::Parameters, Pr: OutputProver, R: RngCore>(
|
||||||
self,
|
self,
|
||||||
prover: &Pr,
|
|
||||||
ctx: &mut Pr::SaplingProvingContext,
|
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
) -> OutputDescription<GrothProofBytes> {
|
) -> OutputDescription<sapling::circuit::Output> {
|
||||||
let encryptor =
|
let encryptor =
|
||||||
sapling_note_encryption::<R, P>(self.ovk, self.note.clone(), self.memo, rng);
|
sapling_note_encryption::<R, P>(self.ovk, self.note.clone(), self.memo, rng);
|
||||||
|
|
||||||
let (zkproof, cv) = prover.output_proof(
|
// Construct the value commitment.
|
||||||
ctx,
|
let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
|
||||||
|
|
||||||
|
// Prepare the circuit that will be used to construct the proof.
|
||||||
|
let zkproof = Pr::prepare_circuit(
|
||||||
encryptor.esk().0,
|
encryptor.esk().0,
|
||||||
self.note.recipient(),
|
self.note.recipient(),
|
||||||
self.note.rcm(),
|
self.note.rcm(),
|
||||||
self.note.value().inner(),
|
self.note.value(),
|
||||||
|
self.rcv,
|
||||||
);
|
);
|
||||||
|
|
||||||
let cmu = self.note.cmu();
|
let cmu = self.note.cmu();
|
||||||
|
@ -197,23 +313,6 @@ pub struct SaplingBuilder<P> {
|
||||||
outputs: Vec<SaplingOutputInfo>,
|
outputs: Vec<SaplingOutputInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Unauthorized {
|
|
||||||
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 SpendProof = GrothProofBytes;
|
|
||||||
type OutputProof = GrothProofBytes;
|
|
||||||
type AuthSig = SpendDescriptionInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P> SaplingBuilder<P> {
|
impl<P> SaplingBuilder<P> {
|
||||||
pub fn new(params: P, target_height: BlockHeight) -> Self {
|
pub fn new(params: P, target_height: BlockHeight) -> Self {
|
||||||
SaplingBuilder {
|
SaplingBuilder {
|
||||||
|
@ -276,7 +375,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
||||||
pub fn add_spend<R: RngCore>(
|
pub fn add_spend<R: RngCore>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut rng: R,
|
mut rng: R,
|
||||||
extsk: ExtendedSpendingKey,
|
extsk: &ExtendedSpendingKey,
|
||||||
diversifier: Diversifier,
|
diversifier: Diversifier,
|
||||||
note: Note,
|
note: Note,
|
||||||
merkle_path: MerklePath,
|
merkle_path: MerklePath,
|
||||||
|
@ -292,18 +391,13 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
||||||
self.anchor = Some(merkle_path.root(node).into())
|
self.anchor = Some(merkle_path.root(node).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
let alpha = jubjub::Fr::random(&mut rng);
|
|
||||||
|
|
||||||
self.value_balance = (self.value_balance + note.value()).ok_or(Error::InvalidAmount)?;
|
self.value_balance = (self.value_balance + note.value()).ok_or(Error::InvalidAmount)?;
|
||||||
self.try_value_balance()?;
|
self.try_value_balance()?;
|
||||||
|
|
||||||
self.spends.push(SpendDescriptionInfo {
|
let spend =
|
||||||
extsk,
|
SpendDescriptionInfo::new_internal(&mut rng, extsk, diversifier, note, merkle_path);
|
||||||
diversifier,
|
|
||||||
note,
|
self.spends.push(spend);
|
||||||
alpha,
|
|
||||||
merkle_path,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -336,22 +430,18 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build<Pr: TxProver, R: RngCore>(
|
pub fn build<SP: SpendProver, OP: OutputProver, R: RngCore>(
|
||||||
self,
|
self,
|
||||||
prover: &Pr,
|
|
||||||
ctx: &mut Pr::SaplingProvingContext,
|
|
||||||
mut rng: R,
|
mut rng: R,
|
||||||
target_height: BlockHeight,
|
target_height: BlockHeight,
|
||||||
progress_notifier: Option<&Sender<Progress>>,
|
) -> Result<Option<(UnauthorizedBundle, SaplingMetadata)>, Error> {
|
||||||
) -> Result<Option<Bundle<Unauthorized>>, Error> {
|
|
||||||
let value_balance = self.try_value_balance()?;
|
let value_balance = self.try_value_balance()?;
|
||||||
|
|
||||||
// Record initial positions of spends and outputs
|
// Record initial positions of spends and outputs
|
||||||
let params = self.params;
|
|
||||||
let mut indexed_spends: Vec<_> = self.spends.into_iter().enumerate().collect();
|
let mut indexed_spends: Vec<_> = self.spends.into_iter().enumerate().collect();
|
||||||
let mut indexed_outputs: Vec<_> = self
|
let mut indexed_outputs: Vec<_> = self
|
||||||
.outputs
|
.outputs
|
||||||
.iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, o)| Some((i, o)))
|
.map(|(i, o)| Some((i, o)))
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -373,204 +463,403 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
||||||
indexed_spends.shuffle(&mut rng);
|
indexed_spends.shuffle(&mut rng);
|
||||||
indexed_outputs.shuffle(&mut rng);
|
indexed_outputs.shuffle(&mut rng);
|
||||||
|
|
||||||
// Keep track of the total number of steps computed
|
// Record the transaction metadata and create dummy outputs.
|
||||||
let total_progress = indexed_spends.len() as u32 + indexed_outputs.len() as u32;
|
let spend_infos = indexed_spends
|
||||||
let mut progress = 0u32;
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, (pos, spend))| {
|
||||||
|
// Record the post-randomized spend location
|
||||||
|
tx_metadata.spend_indices[pos] = i;
|
||||||
|
|
||||||
// Create Sapling SpendDescriptions
|
spend
|
||||||
let shielded_spends: Vec<SpendDescription<Unauthorized>> = if !indexed_spends.is_empty() {
|
})
|
||||||
let anchor = self
|
.collect::<Vec<_>>();
|
||||||
.anchor
|
let output_infos = indexed_outputs
|
||||||
.expect("Sapling anchor must be set if Sapling spends are present.");
|
|
||||||
|
|
||||||
indexed_spends
|
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, (pos, spend))| {
|
|
||||||
let proof_generation_key = spend.extsk.expsk.proof_generation_key();
|
|
||||||
|
|
||||||
let nullifier = spend.note.nf(
|
|
||||||
&proof_generation_key.to_viewing_key().nk,
|
|
||||||
u64::try_from(spend.merkle_path.position())
|
|
||||||
.expect("Sapling note commitment tree position must fit into a u64"),
|
|
||||||
);
|
|
||||||
|
|
||||||
let (zkproof, cv, rk) = prover
|
|
||||||
.spend_proof(
|
|
||||||
ctx,
|
|
||||||
proof_generation_key,
|
|
||||||
spend.diversifier,
|
|
||||||
*spend.note.rseed(),
|
|
||||||
spend.alpha,
|
|
||||||
spend.note.value().inner(),
|
|
||||||
anchor,
|
|
||||||
spend.merkle_path.clone(),
|
|
||||||
)
|
|
||||||
.map_err(|_| Error::SpendProof)?;
|
|
||||||
|
|
||||||
// Record the post-randomized spend location
|
|
||||||
tx_metadata.spend_indices[pos] = i;
|
|
||||||
|
|
||||||
// Update progress and send a notification on the channel
|
|
||||||
progress += 1;
|
|
||||||
if let Some(sender) = progress_notifier {
|
|
||||||
// If the send fails, we should ignore the error, not crash.
|
|
||||||
sender
|
|
||||||
.send(Progress::new(progress, Some(total_progress)))
|
|
||||||
.unwrap_or(());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(SpendDescription {
|
|
||||||
cv,
|
|
||||||
anchor,
|
|
||||||
nullifier,
|
|
||||||
rk,
|
|
||||||
zkproof,
|
|
||||||
spend_auth_sig: spend,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, Error>>()?
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create Sapling OutputDescriptions
|
|
||||||
let shielded_outputs: Vec<OutputDescription<GrothProofBytes>> = indexed_outputs
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, output)| {
|
.map(|(i, output)| {
|
||||||
let result = if let Some((pos, output)) = output {
|
if let Some((pos, output)) = output {
|
||||||
// Record the post-randomized output location
|
// Record the post-randomized output location
|
||||||
tx_metadata.output_indices[pos] = i;
|
tx_metadata.output_indices[pos] = i;
|
||||||
|
|
||||||
output.clone().build::<P, _, _>(prover, ctx, &mut rng)
|
output
|
||||||
} else {
|
} else {
|
||||||
// This is a dummy output
|
// This is a dummy output
|
||||||
let dummy_note = {
|
SaplingOutputInfo::dummy(&self.params, &mut rng, target_height)
|
||||||
let payment_address = {
|
|
||||||
let mut diversifier = Diversifier([0; 11]);
|
|
||||||
loop {
|
|
||||||
rng.fill_bytes(&mut diversifier.0);
|
|
||||||
let dummy_ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
|
|
||||||
if let Some(addr) = dummy_ivk.to_payment_address(diversifier) {
|
|
||||||
break addr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let rseed =
|
|
||||||
generate_random_rseed_internal(¶ms, target_height, &mut rng);
|
|
||||||
|
|
||||||
Note::from_parts(payment_address, NoteValue::from_raw(0), rseed)
|
|
||||||
};
|
|
||||||
|
|
||||||
let esk = dummy_note.generate_or_derive_esk_internal(&mut rng);
|
|
||||||
let epk = esk.derive_public(
|
|
||||||
dummy_note
|
|
||||||
.recipient()
|
|
||||||
.diversifier()
|
|
||||||
.g_d()
|
|
||||||
.expect("checked at construction")
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let (zkproof, cv) = prover.output_proof(
|
|
||||||
ctx,
|
|
||||||
esk.0,
|
|
||||||
dummy_note.recipient(),
|
|
||||||
dummy_note.rcm(),
|
|
||||||
dummy_note.value().inner(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let cmu = dummy_note.cmu();
|
|
||||||
|
|
||||||
let mut enc_ciphertext = [0u8; 580];
|
|
||||||
let mut out_ciphertext = [0u8; 80];
|
|
||||||
rng.fill_bytes(&mut enc_ciphertext[..]);
|
|
||||||
rng.fill_bytes(&mut out_ciphertext[..]);
|
|
||||||
|
|
||||||
OutputDescription {
|
|
||||||
cv,
|
|
||||||
cmu,
|
|
||||||
ephemeral_key: epk.to_bytes(),
|
|
||||||
enc_ciphertext,
|
|
||||||
out_ciphertext,
|
|
||||||
zkproof,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update progress and send a notification on the channel
|
|
||||||
progress += 1;
|
|
||||||
if let Some(sender) = progress_notifier {
|
|
||||||
// If the send fails, we should ignore the error, not crash.
|
|
||||||
sender
|
|
||||||
.send(Progress::new(progress, Some(total_progress)))
|
|
||||||
.unwrap_or(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Compute the transaction binding signing key.
|
||||||
|
let bsk = {
|
||||||
|
let spends: TrapdoorSum = spend_infos.iter().map(|spend| &spend.rcv).sum();
|
||||||
|
let outputs: TrapdoorSum = output_infos.iter().map(|output| &output.rcv).sum();
|
||||||
|
(spends - outputs).into_bsk()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the unauthorized Spend and Output descriptions.
|
||||||
|
let shielded_spends = spend_infos
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| a.build::<SP>(self.anchor))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
let shielded_outputs = output_infos
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| a.build::<P, OP, _>(&mut rng))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Verify that bsk and bvk are consistent.
|
||||||
|
let bvk = {
|
||||||
|
let spends = shielded_spends
|
||||||
|
.iter()
|
||||||
|
.map(|spend| spend.cv())
|
||||||
|
.sum::<CommitmentSum>();
|
||||||
|
let outputs = shielded_outputs
|
||||||
|
.iter()
|
||||||
|
.map(|output| output.cv())
|
||||||
|
.sum::<CommitmentSum>();
|
||||||
|
(spends - outputs)
|
||||||
|
.into_bvk(i64::try_from(self.value_balance).map_err(|_| Error::InvalidAmount)?)
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
PublicKey::from_private(&bsk, VALUE_COMMITMENT_RANDOMNESS_GENERATOR).0,
|
||||||
|
bvk.0,
|
||||||
|
);
|
||||||
|
|
||||||
let bundle = if shielded_spends.is_empty() && shielded_outputs.is_empty() {
|
let bundle = if shielded_spends.is_empty() && shielded_outputs.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(Bundle {
|
Some((
|
||||||
shielded_spends,
|
Bundle {
|
||||||
shielded_outputs,
|
shielded_spends,
|
||||||
value_balance,
|
shielded_outputs,
|
||||||
authorization: Unauthorized { tx_metadata },
|
value_balance,
|
||||||
})
|
authorization: InProgress {
|
||||||
|
sigs: Unsigned { bsk },
|
||||||
|
_proof_state: PhantomData::default(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tx_metadata,
|
||||||
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(bundle)
|
Ok(bundle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpendDescription<Unauthorized> {
|
/// Type alias for an in-progress bundle that has no proofs or signatures.
|
||||||
pub fn apply_signature(&self, spend_auth_sig: Signature) -> SpendDescription<Authorized> {
|
///
|
||||||
SpendDescription {
|
/// This is returned by [`SaplingBuilder::build`].
|
||||||
cv: self.cv.clone(),
|
pub type UnauthorizedBundle = Bundle<InProgress<Unproven, Unsigned>>;
|
||||||
anchor: self.anchor,
|
|
||||||
nullifier: self.nullifier,
|
/// Marker trait representing bundle proofs in the process of being created.
|
||||||
rk: self.rk.clone(),
|
pub trait InProgressProofs: fmt::Debug {
|
||||||
zkproof: self.zkproof,
|
/// The proof type of a Sapling spend in the process of being proven.
|
||||||
spend_auth_sig,
|
type SpendProof: Clone + fmt::Debug;
|
||||||
|
/// The proof type of a Sapling output in the process of being proven.
|
||||||
|
type OutputProof: Clone + fmt::Debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marker trait representing bundle signatures in the process of being created.
|
||||||
|
pub trait InProgressSignatures: fmt::Debug {
|
||||||
|
/// The authorization type of a Sapling spend or output in the process of being
|
||||||
|
/// authorized.
|
||||||
|
type AuthSig: Clone + fmt::Debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marker for a bundle in the process of being built.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct InProgress<P: InProgressProofs, S: InProgressSignatures> {
|
||||||
|
sigs: S,
|
||||||
|
_proof_state: PhantomData<P>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: InProgressProofs, S: InProgressSignatures> Authorization for InProgress<P, S> {
|
||||||
|
type SpendProof = P::SpendProof;
|
||||||
|
type OutputProof = P::OutputProof;
|
||||||
|
type AuthSig = S::AuthSig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marker for a [`Bundle`] without proofs.
|
||||||
|
///
|
||||||
|
/// The [`SpendDescription`]s and [`OutputDescription`]s within the bundle contain the
|
||||||
|
/// private data needed to create proofs.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Unproven;
|
||||||
|
|
||||||
|
impl InProgressProofs for Unproven {
|
||||||
|
type SpendProof = sapling::circuit::Spend;
|
||||||
|
type OutputProof = sapling::circuit::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marker for a [`Bundle`] with proofs.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Proven;
|
||||||
|
|
||||||
|
impl InProgressProofs for Proven {
|
||||||
|
type SpendProof = GrothProofBytes;
|
||||||
|
type OutputProof = GrothProofBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CreateProofs<'a, SP: SpendProver, OP: OutputProver, R: RngCore> {
|
||||||
|
spend_prover: &'a SP,
|
||||||
|
output_prover: &'a OP,
|
||||||
|
rng: R,
|
||||||
|
progress_notifier: Option<&'a Sender<Progress>>,
|
||||||
|
total_progress: u32,
|
||||||
|
progress: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore> CreateProofs<'a, SP, OP, R> {
|
||||||
|
fn new(
|
||||||
|
spend_prover: &'a SP,
|
||||||
|
output_prover: &'a OP,
|
||||||
|
rng: R,
|
||||||
|
progress_notifier: Option<&'a Sender<Progress>>,
|
||||||
|
total_progress: u32,
|
||||||
|
) -> Self {
|
||||||
|
// Keep track of the total number of steps computed
|
||||||
|
Self {
|
||||||
|
spend_prover,
|
||||||
|
output_prover,
|
||||||
|
rng,
|
||||||
|
progress_notifier,
|
||||||
|
total_progress,
|
||||||
|
progress: 0u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_progress(&mut self) {
|
||||||
|
// Update progress and send a notification on the channel
|
||||||
|
self.progress += 1;
|
||||||
|
if let Some(sender) = self.progress_notifier {
|
||||||
|
// If the send fails, we should ignore the error, not crash.
|
||||||
|
sender
|
||||||
|
.send(Progress::new(self.progress, Some(self.total_progress)))
|
||||||
|
.unwrap_or(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bundle<Unauthorized> {
|
impl<'a, S: InProgressSignatures, SP: SpendProver, OP: OutputProver, R: RngCore>
|
||||||
pub fn apply_signatures<Pr: TxProver, R: RngCore>(
|
MapAuth<InProgress<Unproven, S>, InProgress<Proven, S>> for CreateProofs<'a, SP, OP, R>
|
||||||
self,
|
{
|
||||||
prover: &Pr,
|
fn map_spend_proof(&mut self, spend: sapling::circuit::Spend) -> GrothProofBytes {
|
||||||
ctx: &mut Pr::SaplingProvingContext,
|
let proof = self.spend_prover.create_proof(spend, &mut self.rng);
|
||||||
rng: &mut R,
|
self.update_progress();
|
||||||
sighash_bytes: &[u8; 32],
|
SP::encode_proof(proof)
|
||||||
) -> Result<(Bundle<Authorized>, SaplingMetadata), Error> {
|
}
|
||||||
let binding_sig = prover
|
|
||||||
.binding_sig(ctx, self.value_balance, sighash_bytes)
|
|
||||||
.map_err(|_| Error::BindingSig)?;
|
|
||||||
|
|
||||||
Ok((
|
fn map_output_proof(&mut self, output: sapling::circuit::Output) -> GrothProofBytes {
|
||||||
Bundle {
|
let proof = self.output_prover.create_proof(output, &mut self.rng);
|
||||||
shielded_spends: self
|
self.update_progress();
|
||||||
.shielded_spends
|
OP::encode_proof(proof)
|
||||||
.iter()
|
}
|
||||||
.map(|spend| {
|
|
||||||
spend.apply_signature(spend_sig_internal(
|
fn map_auth_sig(&mut self, s: S::AuthSig) -> S::AuthSig {
|
||||||
PrivateKey(spend.spend_auth_sig.extsk.expsk.ask),
|
s
|
||||||
spend.spend_auth_sig.alpha,
|
}
|
||||||
sighash_bytes,
|
|
||||||
rng,
|
fn map_authorization(&mut self, a: InProgress<Unproven, S>) -> InProgress<Proven, S> {
|
||||||
))
|
InProgress {
|
||||||
})
|
sigs: a.sigs,
|
||||||
.collect(),
|
_proof_state: PhantomData::default(),
|
||||||
shielded_outputs: self.shielded_outputs,
|
}
|
||||||
value_balance: self.value_balance,
|
}
|
||||||
authorization: Authorized { binding_sig },
|
}
|
||||||
|
|
||||||
|
impl<S: InProgressSignatures> Bundle<InProgress<Unproven, S>> {
|
||||||
|
/// Creates the proofs for this bundle.
|
||||||
|
pub fn create_proofs<SP: SpendProver, OP: OutputProver>(
|
||||||
|
self,
|
||||||
|
spend_prover: &SP,
|
||||||
|
output_prover: &OP,
|
||||||
|
rng: impl RngCore,
|
||||||
|
progress_notifier: Option<&Sender<Progress>>,
|
||||||
|
) -> Bundle<InProgress<Proven, S>> {
|
||||||
|
let total_progress = self.shielded_spends.len() as u32 + self.shielded_outputs.len() as u32;
|
||||||
|
self.map_authorization(CreateProofs::new(
|
||||||
|
spend_prover,
|
||||||
|
output_prover,
|
||||||
|
rng,
|
||||||
|
progress_notifier,
|
||||||
|
total_progress,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marker for an unauthorized bundle with no signatures.
|
||||||
|
pub struct Unsigned {
|
||||||
|
bsk: PrivateKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Unsigned {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Unsigned").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InProgressSignatures for Unsigned {
|
||||||
|
type AuthSig = SigningParts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The parts needed to sign a [`SpendDescription`].
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SigningParts {
|
||||||
|
/// The spend validating key for this spend description. Used to match spend
|
||||||
|
/// authorizing keys to spend descriptions they can create signatures for.
|
||||||
|
ak: PublicKey,
|
||||||
|
/// The randomization needed to derive the actual signing key for this note.
|
||||||
|
alpha: jubjub::Scalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marker for a partially-authorized bundle, in the process of being signed.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PartiallyAuthorized {
|
||||||
|
binding_signature: Signature,
|
||||||
|
sighash: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InProgressSignatures for PartiallyAuthorized {
|
||||||
|
type AuthSig = MaybeSigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A heisen[`Signature`] for a particular [`SpendDescription`].
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum MaybeSigned {
|
||||||
|
/// The information needed to sign this [`SpendDescription`].
|
||||||
|
SigningMetadata(SigningParts),
|
||||||
|
/// The signature for this [`SpendDescription`].
|
||||||
|
Signature(Signature),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaybeSigned {
|
||||||
|
fn finalize(self) -> Result<Signature, Error> {
|
||||||
|
match self {
|
||||||
|
Self::Signature(sig) => Ok(sig),
|
||||||
|
_ => Err(Error::MissingSignatures),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: InProgressProofs> Bundle<InProgress<P, Unsigned>> {
|
||||||
|
/// Loads the sighash into this bundle, preparing it for signing.
|
||||||
|
///
|
||||||
|
/// This API ensures that all signatures are created over the same sighash.
|
||||||
|
pub fn prepare<R: RngCore + CryptoRng>(
|
||||||
|
self,
|
||||||
|
mut rng: R,
|
||||||
|
sighash: [u8; 32],
|
||||||
|
) -> Bundle<InProgress<P, PartiallyAuthorized>> {
|
||||||
|
self.map_authorization((
|
||||||
|
|proof| proof,
|
||||||
|
|proof| proof,
|
||||||
|
MaybeSigned::SigningMetadata,
|
||||||
|
|auth: InProgress<P, Unsigned>| InProgress {
|
||||||
|
sigs: PartiallyAuthorized {
|
||||||
|
binding_signature: auth.sigs.bsk.sign(
|
||||||
|
&sighash,
|
||||||
|
&mut rng,
|
||||||
|
VALUE_COMMITMENT_RANDOMNESS_GENERATOR,
|
||||||
|
),
|
||||||
|
sighash,
|
||||||
|
},
|
||||||
|
_proof_state: PhantomData::default(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bundle<InProgress<Proven, Unsigned>> {
|
||||||
|
/// Applies signatures to this bundle, in order to authorize it.
|
||||||
|
///
|
||||||
|
/// This is a helper method that wraps [`Bundle::prepare`], [`Bundle::sign`], and
|
||||||
|
/// [`Bundle::finalize`].
|
||||||
|
pub fn apply_signatures<R: RngCore + CryptoRng>(
|
||||||
|
self,
|
||||||
|
mut rng: R,
|
||||||
|
sighash: [u8; 32],
|
||||||
|
signing_keys: &[PrivateKey],
|
||||||
|
) -> Result<Bundle<Authorized>, Error> {
|
||||||
|
signing_keys
|
||||||
|
.iter()
|
||||||
|
.fold(self.prepare(&mut rng, sighash), |partial, ask| {
|
||||||
|
partial.sign(&mut rng, ask)
|
||||||
|
})
|
||||||
|
.finalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: InProgressProofs> Bundle<InProgress<P, PartiallyAuthorized>> {
|
||||||
|
/// Signs this bundle with the given [`PrivateKey`].
|
||||||
|
///
|
||||||
|
/// This will apply signatures for all notes controlled by this spending key.
|
||||||
|
pub fn sign<R: RngCore + CryptoRng>(self, mut rng: R, ask: &PrivateKey) -> Self {
|
||||||
|
let expected_ak = PublicKey::from_private(ask, SPENDING_KEY_GENERATOR);
|
||||||
|
let sighash = self.authorization.sigs.sighash;
|
||||||
|
self.map_authorization((
|
||||||
|
|proof| proof,
|
||||||
|
|proof| proof,
|
||||||
|
|maybe| match maybe {
|
||||||
|
MaybeSigned::SigningMetadata(parts) if parts.ak.0 == expected_ak.0 => {
|
||||||
|
MaybeSigned::Signature(spend_sig_internal(ask, parts.alpha, &sighash, &mut rng))
|
||||||
|
}
|
||||||
|
s => s,
|
||||||
|
},
|
||||||
|
|partial| partial,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends externally computed [`Signature`]s.
|
||||||
|
///
|
||||||
|
/// Each signature will be applied to the one input for which it is valid. An error
|
||||||
|
/// will be returned if the signature is not valid for any inputs, or if it is valid
|
||||||
|
/// for more than one input.
|
||||||
|
pub fn append_signatures(self, signatures: &[Signature]) -> Result<Self, Error> {
|
||||||
|
signatures.iter().try_fold(self, Self::append_signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_signature(self, signature: &Signature) -> 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) => {
|
||||||
|
if verify_spend_sig(&parts.ak, parts.alpha, &sighash, signature) {
|
||||||
|
signature_valid_for += 1;
|
||||||
|
MaybeSigned::Signature(*signature)
|
||||||
|
} else {
|
||||||
|
// Signature isn't for this input.
|
||||||
|
MaybeSigned::SigningMetadata(parts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s => s,
|
||||||
|
},
|
||||||
|
|partial| partial,
|
||||||
|
));
|
||||||
|
match signature_valid_for {
|
||||||
|
0 => Err(Error::InvalidExternalSignature),
|
||||||
|
1 => Ok(bundle),
|
||||||
|
_ => Err(Error::DuplicateSignature),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bundle<InProgress<Proven, PartiallyAuthorized>> {
|
||||||
|
/// 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> {
|
||||||
|
self.try_map_authorization((
|
||||||
|
Ok,
|
||||||
|
Ok,
|
||||||
|
|maybe: MaybeSigned| maybe.finalize(),
|
||||||
|
|partial: InProgress<Proven, PartiallyAuthorized>| {
|
||||||
|
Ok(Authorized {
|
||||||
|
binding_sig: partial.sigs.binding_signature,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
self.authorization.tx_metadata,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -587,7 +876,8 @@ pub mod testing {
|
||||||
TEST_NETWORK,
|
TEST_NETWORK,
|
||||||
},
|
},
|
||||||
sapling::{
|
sapling::{
|
||||||
prover::mock::MockTxProver,
|
prover::mock::{MockOutputProver, MockSpendProver},
|
||||||
|
redjubjub::PrivateKey,
|
||||||
testing::{arb_node, arb_note},
|
testing::{arb_node, arb_note},
|
||||||
value::testing::arb_positive_note_value,
|
value::testing::arb_positive_note_value,
|
||||||
Diversifier,
|
Diversifier,
|
||||||
|
@ -628,31 +918,30 @@ pub mod testing {
|
||||||
for ((note, path), diversifier) in spendable_notes.into_iter().zip(commitment_trees.into_iter()).zip(diversifiers.into_iter()) {
|
for ((note, path), diversifier) in spendable_notes.into_iter().zip(commitment_trees.into_iter()).zip(diversifiers.into_iter()) {
|
||||||
builder.add_spend(
|
builder.add_spend(
|
||||||
&mut rng,
|
&mut rng,
|
||||||
extsk.clone(),
|
&extsk,
|
||||||
diversifier,
|
diversifier,
|
||||||
note,
|
note,
|
||||||
path
|
path
|
||||||
).unwrap();
|
).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let prover = MockTxProver;
|
let (bundle, _) = builder.build::<MockSpendProver, MockOutputProver, _>(
|
||||||
|
|
||||||
let bundle = builder.build(
|
|
||||||
&prover,
|
|
||||||
&mut (),
|
|
||||||
&mut rng,
|
&mut rng,
|
||||||
target_height.unwrap(),
|
target_height.unwrap(),
|
||||||
None
|
|
||||||
).unwrap().unwrap();
|
).unwrap().unwrap();
|
||||||
|
|
||||||
let (bundle, _) = bundle.apply_signatures(
|
let bundle = bundle.create_proofs(
|
||||||
&prover,
|
&MockSpendProver,
|
||||||
&mut (),
|
&MockOutputProver,
|
||||||
&mut rng,
|
&mut rng,
|
||||||
&fake_sighash_bytes,
|
None,
|
||||||
).unwrap();
|
);
|
||||||
|
|
||||||
bundle
|
bundle.apply_signatures(
|
||||||
|
&mut rng,
|
||||||
|
fake_sighash_bytes,
|
||||||
|
&[PrivateKey(extsk.expsk.ask)],
|
||||||
|
).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,7 +275,8 @@ pub struct Unauthorized;
|
||||||
|
|
||||||
impl Authorization for Unauthorized {
|
impl Authorization for Unauthorized {
|
||||||
type TransparentAuth = transparent::builder::Unauthorized;
|
type TransparentAuth = transparent::builder::Unauthorized;
|
||||||
type SaplingAuth = sapling::builder::Unauthorized;
|
type SaplingAuth =
|
||||||
|
sapling::builder::InProgress<sapling::builder::Proven, sapling::builder::Unsigned>;
|
||||||
type OrchardAuth =
|
type OrchardAuth =
|
||||||
orchard::builder::InProgress<orchard::builder::Unproven, orchard::builder::Unauthorized>;
|
orchard::builder::InProgress<orchard::builder::Unproven, orchard::builder::Unauthorized>;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue