Move Sapling components to a bundle within TransactionData
This commit is contained in:
parent
488d02aad3
commit
84e8952ec3
|
@ -44,11 +44,12 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
|
|||
) -> Vec<DecryptedOutput> {
|
||||
let mut decrypted = vec![];
|
||||
|
||||
if let Some(bundle) = tx.sapling_bundle.as_ref() {
|
||||
for (account, extfvk) in extfvks.iter() {
|
||||
let ivk = extfvk.fvk.vk.ivk();
|
||||
let ovk = extfvk.fvk.ovk;
|
||||
|
||||
for (index, output) in tx.shielded_outputs.iter().enumerate() {
|
||||
for (index, output) in bundle.shielded_outputs.iter().enumerate() {
|
||||
let ((note, to, memo), outgoing) =
|
||||
match try_sapling_note_decryption(params, height, &ivk, output) {
|
||||
Some(ret) => (ret, false),
|
||||
|
@ -57,6 +58,7 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
|
|||
None => continue,
|
||||
},
|
||||
};
|
||||
|
||||
decrypted.push(DecryptedOutput {
|
||||
index,
|
||||
note,
|
||||
|
@ -67,6 +69,7 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
|
|||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decrypted
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use zcash_primitives::{
|
|||
consensus::BlockHeight,
|
||||
sapling::Nullifier,
|
||||
transaction::{
|
||||
components::sapling::{CompactOutputDescription, OutputDescription},
|
||||
components::sapling::{self, CompactOutputDescription, OutputDescription},
|
||||
TxId,
|
||||
},
|
||||
};
|
||||
|
@ -113,8 +113,8 @@ impl compact_formats::CompactOutput {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<OutputDescription> for compact_formats::CompactOutput {
|
||||
fn from(out: OutputDescription) -> compact_formats::CompactOutput {
|
||||
impl<A: sapling::Authorization> From<OutputDescription<A>> for compact_formats::CompactOutput {
|
||||
fn from(out: OutputDescription<A>) -> compact_formats::CompactOutput {
|
||||
let mut result = compact_formats::CompactOutput::new();
|
||||
result.set_cmu(out.cmu.to_repr().to_vec());
|
||||
result.set_epk(out.ephemeral_key.to_bytes().to_vec());
|
||||
|
|
|
@ -489,9 +489,11 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
|
|||
//
|
||||
// Assumes that create_spend_to_address() will never be called in parallel, which is a
|
||||
// reasonable assumption for a light client such as a mobile phone.
|
||||
for spend in &sent_tx.tx.shielded_spends {
|
||||
if let Some(bundle) = sent_tx.tx.sapling_bundle.as_ref() {
|
||||
for spend in &bundle.shielded_spends {
|
||||
wallet::mark_spent(up, tx_ref, &spend.nullifier)?;
|
||||
}
|
||||
}
|
||||
|
||||
wallet::insert_sent_note(
|
||||
up,
|
||||
|
|
|
@ -631,7 +631,8 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let output = &tx.shielded_outputs[output_index as usize];
|
||||
let output =
|
||||
&tx.sapling_bundle.as_ref().unwrap().shielded_outputs[output_index as usize];
|
||||
|
||||
try_sapling_output_recovery(
|
||||
&network,
|
||||
|
|
|
@ -78,8 +78,7 @@ impl<'a> demo::Context for Context<'a> {
|
|||
fn is_tze_only(&self) -> bool {
|
||||
self.tx.vin.is_empty()
|
||||
&& self.tx.vout.is_empty()
|
||||
&& self.tx.shielded_spends.is_empty()
|
||||
&& self.tx.shielded_outputs.is_empty()
|
||||
&& self.tx.sapling_bundle.is_none()
|
||||
&& self.tx.joinsplits.is_empty()
|
||||
&& self.tx.orchard_bundle.is_none()
|
||||
}
|
||||
|
|
|
@ -472,8 +472,7 @@ mod tests {
|
|||
extensions::transparent::{self as tze, Extension, FromPayload, ToPayload},
|
||||
legacy::TransparentAddress,
|
||||
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||
sapling::Node,
|
||||
sapling::Rseed,
|
||||
sapling::{Node, Rseed},
|
||||
transaction::{
|
||||
builder::Builder,
|
||||
components::{
|
||||
|
@ -606,8 +605,7 @@ mod tests {
|
|||
fn is_tze_only(&self) -> bool {
|
||||
self.tx.vin.is_empty()
|
||||
&& self.tx.vout.is_empty()
|
||||
&& self.tx.shielded_spends.is_empty()
|
||||
&& self.tx.shielded_outputs.is_empty()
|
||||
&& self.tx.sapling_bundle.is_none()
|
||||
&& self.tx.joinsplits.is_empty()
|
||||
&& self.tx.orchard_bundle.is_none()
|
||||
}
|
||||
|
@ -659,7 +657,8 @@ mod tests {
|
|||
};
|
||||
|
||||
fn auth_and_freeze(tx: TransactionData<Unauthorized>) -> Transaction {
|
||||
Builder::<FutureNetwork, OsRng>::apply_authorization(
|
||||
Builder::<FutureNetwork, OsRng>::apply_signatures(
|
||||
BranchId::ZFuture,
|
||||
tx,
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
None,
|
||||
|
@ -667,7 +666,6 @@ mod tests {
|
|||
None,
|
||||
None,
|
||||
)
|
||||
.freeze(BranchId::ZFuture)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,10 @@ use zcash_primitives::{
|
|||
util::generate_random_rseed,
|
||||
Diversifier, PaymentAddress, SaplingIvk, ValueCommitment,
|
||||
},
|
||||
transaction::components::{OutputDescription, GROTH_PROOF_SIZE},
|
||||
transaction::components::{
|
||||
sapling::{Authorized, OutputDescription},
|
||||
GROTH_PROOF_SIZE,
|
||||
},
|
||||
};
|
||||
|
||||
fn bench_note_decryption(c: &mut Criterion) {
|
||||
|
@ -20,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 = {
|
||||
let output: OutputDescription<Authorized> = {
|
||||
let diversifier = Diversifier([0; 11]);
|
||||
let pk_d = diversifier.g_d().unwrap() * valid_ivk.0;
|
||||
let pa = PaymentAddress::from_parts(diversifier, pk_d).unwrap();
|
||||
|
|
|
@ -554,6 +554,10 @@ impl BranchId {
|
|||
.map(|lower| (lower, None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sprout_uses_groth_proofs(&self) -> bool {
|
||||
!matches!(self, BranchId::Sprout | BranchId::Overwinter)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
|
|
|
@ -47,7 +47,7 @@ impl<Node: Hashable> PathFiller<Node> {
|
|||
///
|
||||
/// The depth of the Merkle tree is fixed at 32, equal to the depth of the Sapling
|
||||
/// commitment tree.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommitmentTree<Node: Hashable> {
|
||||
left: Option<Node>,
|
||||
right: Option<Node>,
|
||||
|
@ -1057,3 +1057,25 @@ mod tests {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use core::fmt::Debug;
|
||||
use proptest::collection::vec;
|
||||
use proptest::prelude::*;
|
||||
|
||||
use super::{CommitmentTree, Hashable};
|
||||
|
||||
pub fn arb_commitment_tree<Node: Hashable + Debug, T: Strategy<Value = Node>>(
|
||||
min_size: usize,
|
||||
arb_node: T,
|
||||
) -> impl Strategy<Value = CommitmentTree<Node>> {
|
||||
vec(arb_node, min_size..(min_size + 100)).prop_map(|v| {
|
||||
let mut tree = CommitmentTree::empty();
|
||||
for node in v.into_iter() {
|
||||
tree.append(node).unwrap();
|
||||
}
|
||||
tree
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,10 @@ use crate::{
|
|||
consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD},
|
||||
memo::MemoBytes,
|
||||
sapling::{keys::OutgoingViewingKey, Diversifier, Note, PaymentAddress, Rseed, SaplingIvk},
|
||||
transaction::components::{amount::Amount, sapling::OutputDescription},
|
||||
transaction::components::{
|
||||
amount::Amount,
|
||||
sapling::{self, OutputDescription},
|
||||
},
|
||||
};
|
||||
|
||||
pub const KDF_SAPLING_PERSONALIZATION: &[u8; 16] = b"Zcash_SaplingKDF";
|
||||
|
@ -383,7 +386,7 @@ pub fn try_sapling_output_recovery_with_ock<P: consensus::Parameters>(
|
|||
params: &P,
|
||||
height: BlockHeight,
|
||||
ock: &OutgoingCipherKey,
|
||||
output: &OutputDescription,
|
||||
output: &OutputDescription<sapling::Authorized>,
|
||||
) -> Option<(Note, PaymentAddress, MemoBytes)> {
|
||||
let domain = SaplingDomain {
|
||||
params: params.clone(),
|
||||
|
@ -405,7 +408,7 @@ pub fn try_sapling_output_recovery<P: consensus::Parameters>(
|
|||
params: &P,
|
||||
height: BlockHeight,
|
||||
ovk: &OutgoingViewingKey,
|
||||
output: &OutputDescription,
|
||||
output: &OutputDescription<sapling::Authorized>,
|
||||
) -> Option<(Note, PaymentAddress, MemoBytes)> {
|
||||
let domain = SaplingDomain {
|
||||
params: params.clone(),
|
||||
|
@ -450,7 +453,7 @@ mod tests {
|
|||
},
|
||||
transaction::components::{
|
||||
amount::Amount,
|
||||
sapling::{CompactOutputDescription, OutputDescription},
|
||||
sapling::{self, CompactOutputDescription, OutputDescription},
|
||||
GROTH_PROOF_SIZE,
|
||||
},
|
||||
};
|
||||
|
@ -462,7 +465,7 @@ mod tests {
|
|||
OutgoingViewingKey,
|
||||
OutgoingCipherKey,
|
||||
SaplingIvk,
|
||||
OutputDescription,
|
||||
OutputDescription<sapling::Authorized>,
|
||||
) {
|
||||
let ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
|
||||
|
||||
|
@ -492,7 +495,11 @@ mod tests {
|
|||
height: BlockHeight,
|
||||
ivk: &SaplingIvk,
|
||||
mut rng: &mut R,
|
||||
) -> (OutgoingViewingKey, OutgoingCipherKey, OutputDescription) {
|
||||
) -> (
|
||||
OutgoingViewingKey,
|
||||
OutgoingCipherKey,
|
||||
OutputDescription<sapling::Authorized>,
|
||||
) {
|
||||
let diversifier = Diversifier([0; 11]);
|
||||
let pk_d = diversifier.g_d().unwrap() * ivk.0;
|
||||
let pa = PaymentAddress::from_parts_unchecked(diversifier, pk_d);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
use core::array;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use rand::{rngs::OsRng, CryptoRng, RngCore};
|
||||
|
@ -21,11 +22,14 @@ use crate::{
|
|||
transaction::{
|
||||
components::{
|
||||
amount::{Amount, DEFAULT_FEE},
|
||||
sapling::builder::{self as sapling, SaplingBuilder, SaplingMetadata},
|
||||
transparent::builder::{self as transparent, TransparentBuilder},
|
||||
sapling::{
|
||||
self,
|
||||
builder::{SaplingBuilder, SaplingMetadata},
|
||||
},
|
||||
signature_hash_data, Authorized, SignableInput, Transaction, TransactionData, TxVersion,
|
||||
Unauthorized, SIGHASH_ALL,
|
||||
transparent::{self, builder::TransparentBuilder, TxIn},
|
||||
},
|
||||
signature_hash_data, SignableInput, Transaction, TransactionData, TxVersion, Unauthorized,
|
||||
SIGHASH_ALL,
|
||||
},
|
||||
zip32::ExtendedSpendingKey,
|
||||
};
|
||||
|
@ -34,17 +38,14 @@ use crate::{
|
|||
use std::marker::PhantomData;
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use crate::{
|
||||
legacy::Script,
|
||||
transaction::components::{OutPoint, TxOut},
|
||||
};
|
||||
use crate::{legacy::Script, transaction::components::transparent::TxOut};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use crate::{
|
||||
extensions::transparent::{ExtensionTxBuilder, ToPayload},
|
||||
transaction::components::{
|
||||
tze::builder::{self as tzebuilder, TzeBuilder, WitnessData},
|
||||
TzeOut, TzeOutPoint,
|
||||
tze::builder::{TzeBuilder, WitnessData},
|
||||
tze::{self, TzeIn, TzeOut, TzeOutPoint},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -58,10 +59,10 @@ pub enum Error {
|
|||
ChangeIsNegative(Amount),
|
||||
InvalidAmount,
|
||||
NoChangeAddress,
|
||||
TransparentBuild(transparent::Error),
|
||||
SaplingBuild(sapling::Error),
|
||||
TransparentBuild(transparent::builder::Error),
|
||||
SaplingBuild(sapling::builder::Error),
|
||||
#[cfg(feature = "zfuture")]
|
||||
TzeBuild(tzebuilder::Error),
|
||||
TzeBuild(tze::builder::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
|
@ -175,7 +176,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
expiry_height: target_height + DEFAULT_TX_EXPIRY_DELTA,
|
||||
fee: DEFAULT_FEE,
|
||||
transparent_builder: TransparentBuilder::empty(),
|
||||
sapling_builder: SaplingBuilder::empty(params, target_height),
|
||||
sapling_builder: SaplingBuilder::new(params, target_height),
|
||||
change_address: None,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_builder: TzeBuilder::empty(),
|
||||
|
@ -220,7 +221,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
pub fn add_transparent_input(
|
||||
&mut self,
|
||||
sk: secp256k1::SecretKey,
|
||||
utxo: OutPoint,
|
||||
utxo: transparent::OutPoint,
|
||||
coin: TxOut,
|
||||
) -> Result<(), Error> {
|
||||
self.transparent_builder
|
||||
|
@ -326,7 +327,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
let (vin, vout) = self.transparent_builder.build();
|
||||
|
||||
let mut ctx = prover.new_sapling_proving_context();
|
||||
let (spend_descs, output_descs, tx_metadata) = self
|
||||
let (sapling_bundle, tx_metadata) = self
|
||||
.sapling_builder
|
||||
.build(
|
||||
prover,
|
||||
|
@ -350,13 +351,10 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
tze_outputs,
|
||||
lock_time: 0,
|
||||
expiry_height: self.expiry_height,
|
||||
value_balance: self.sapling_builder.value_balance(),
|
||||
shielded_spends: spend_descs,
|
||||
shielded_outputs: output_descs,
|
||||
joinsplits: vec![],
|
||||
joinsplit_pubkey: None,
|
||||
joinsplit_sig: None,
|
||||
binding_sig: None,
|
||||
sapling_bundle,
|
||||
orchard_bundle: None,
|
||||
};
|
||||
|
||||
|
@ -373,9 +371,10 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
));
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
let transparent_sigs = self
|
||||
.transparent_builder
|
||||
.create_signatures(&unauthed_tx, consensus_branch_id);
|
||||
let transparent_sigs = Some(
|
||||
self.transparent_builder
|
||||
.create_signatures(&unauthed_tx, consensus_branch_id),
|
||||
);
|
||||
|
||||
let sapling_sigs = self
|
||||
.sapling_builder
|
||||
|
@ -389,26 +388,25 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
.map_err(Error::TzeBuild)?,
|
||||
);
|
||||
|
||||
let authorized_tx = Self::apply_authorization(
|
||||
Ok((
|
||||
Self::apply_signatures(
|
||||
consensus_branch_id,
|
||||
unauthed_tx,
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
Some(transparent_sigs),
|
||||
transparent_sigs,
|
||||
sapling_sigs,
|
||||
None,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_witnesses,
|
||||
);
|
||||
|
||||
Ok((
|
||||
authorized_tx
|
||||
.freeze(consensus_branch_id)
|
||||
.expect("Transaction should be complete"),
|
||||
)
|
||||
.expect("An IO error occurred applying signatures."),
|
||||
tx_metadata,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn apply_authorization(
|
||||
mut mtx: TransactionData<Unauthorized>,
|
||||
pub fn apply_signatures(
|
||||
consensus_branch_id: consensus::BranchId,
|
||||
unauthed_tx: TransactionData<Unauthorized>,
|
||||
#[cfg(feature = "transparent-inputs")] transparent_sigs: Option<Vec<Script>>,
|
||||
sapling_sigs: Option<(Vec<redjubjub::Signature>, redjubjub::Signature)>,
|
||||
_orchard_auth: Option<(
|
||||
|
@ -416,46 +414,59 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
orchard::Authorized,
|
||||
)>,
|
||||
#[cfg(feature = "zfuture")] tze_witnesses: Option<Vec<WitnessData>>,
|
||||
) -> TransactionData<Authorized> {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
if let Some(script_sigs) = transparent_sigs {
|
||||
for (i, sig) in script_sigs.into_iter().enumerate() {
|
||||
mtx.vin[i].script_sig = sig;
|
||||
) -> 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.");
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((sapling_spend_auth_sigs, sapling_binding_sig)) = sapling_sigs {
|
||||
for (i, spend_auth_sig) in sapling_spend_auth_sigs.into_iter().enumerate() {
|
||||
mtx.shielded_spends[i].spend_auth_sig = Some(spend_auth_sig);
|
||||
}
|
||||
mtx.binding_sig = Some(sapling_binding_sig);
|
||||
}
|
||||
|
||||
let mut authorized_tx = TransactionData {
|
||||
version: unauthed_tx.version,
|
||||
vin: unauthed_tx.vin,
|
||||
vout: unauthed_tx.vout,
|
||||
#[cfg(feature = "zfuture")]
|
||||
if let Some(tze_witnesses) = tze_witnesses {
|
||||
for (i, payload) in tze_witnesses.into_iter().enumerate() {
|
||||
mtx.tze_inputs[i].witness.payload = payload.0;
|
||||
}
|
||||
}
|
||||
|
||||
TransactionData {
|
||||
version: mtx.version,
|
||||
vin: mtx.vin,
|
||||
vout: mtx.vout,
|
||||
tze_inputs: unauthed_tx.tze_inputs,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_inputs: mtx.tze_inputs,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_outputs: mtx.tze_outputs,
|
||||
lock_time: mtx.lock_time,
|
||||
expiry_height: mtx.expiry_height,
|
||||
value_balance: mtx.value_balance,
|
||||
shielded_spends: mtx.shielded_spends,
|
||||
shielded_outputs: mtx.shielded_outputs,
|
||||
joinsplits: mtx.joinsplits,
|
||||
joinsplit_pubkey: mtx.joinsplit_pubkey,
|
||||
joinsplit_sig: mtx.joinsplit_sig,
|
||||
binding_sig: mtx.binding_sig,
|
||||
tze_outputs: unauthed_tx.tze_outputs,
|
||||
lock_time: unauthed_tx.lock_time,
|
||||
expiry_height: unauthed_tx.expiry_height,
|
||||
joinsplits: unauthed_tx.joinsplits,
|
||||
joinsplit_pubkey: unauthed_tx.joinsplit_pubkey,
|
||||
joinsplit_sig: unauthed_tx.joinsplit_sig,
|
||||
sapling_bundle: signed_sapling_bundle,
|
||||
orchard_bundle: None,
|
||||
};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
Self::apply_transparent_sigs(&mut authorized_tx.vin, transparent_sigs);
|
||||
#[cfg(feature = "zfuture")]
|
||||
Self::apply_tze_sigs(&mut authorized_tx.tze_inputs, tze_witnesses);
|
||||
|
||||
authorized_tx.freeze(consensus_branch_id)
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
pub fn apply_transparent_sigs(vin: &mut [TxIn], transparent_sigs: Option<Vec<Script>>) {
|
||||
if let Some(script_sigs) = transparent_sigs {
|
||||
assert!(vin.len() == script_sigs.len());
|
||||
for (i, sig) in script_sigs.into_iter().enumerate() {
|
||||
vin[i].script_sig = sig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub fn apply_tze_sigs(vtzein: &mut [TzeIn], tze_witnesses: Option<Vec<WitnessData>>) {
|
||||
if let Some(tze_witnesses) = tze_witnesses {
|
||||
assert!(vtzein.len() == tze_witnesses.len());
|
||||
for (i, payload) in tze_witnesses.into_iter().enumerate() {
|
||||
vtzein[i].witness.payload = payload.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -465,7 +476,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> ExtensionTxBuilder<'a
|
|||
for Builder<'a, P, R>
|
||||
{
|
||||
type BuildCtx = TransactionData<Unauthorized>;
|
||||
type BuildError = tzebuilder::Error;
|
||||
type BuildError = tze::builder::Error;
|
||||
|
||||
fn add_tze_input<WBuilder, W: ToPayload>(
|
||||
&mut self,
|
||||
|
@ -475,7 +486,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> ExtensionTxBuilder<'a
|
|||
witness_builder: WBuilder,
|
||||
) -> Result<(), Self::BuildError>
|
||||
where
|
||||
WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result<W, tzebuilder::Error>),
|
||||
WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result<W, tze::builder::Error>),
|
||||
{
|
||||
self.tze_builder
|
||||
.add_input(extension_id, mode, prevout, witness_builder);
|
||||
|
@ -552,7 +563,7 @@ mod tests {
|
|||
sapling::{prover::mock::MockTxProver, Node, Rseed},
|
||||
transaction::components::{
|
||||
amount::{Amount, DEFAULT_FEE},
|
||||
sapling::builder::{self as sapling},
|
||||
sapling::builder::{self as build_s},
|
||||
transparent::builder::{self as transparent},
|
||||
},
|
||||
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||
|
@ -580,7 +591,7 @@ mod tests {
|
|||
let mut builder = Builder::new(TEST_NETWORK, sapling_activation_height);
|
||||
assert_eq!(
|
||||
builder.add_sapling_output(Some(ovk), to, Amount::from_i64(-1).unwrap(), None),
|
||||
Err(Error::SaplingBuild(sapling::Error::InvalidAmount))
|
||||
Err(Error::SaplingBuild(build_s::Error::InvalidAmount))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -601,7 +612,7 @@ mod tests {
|
|||
expiry_height: sapling_activation_height + DEFAULT_TX_EXPIRY_DELTA,
|
||||
fee: Amount::zero(),
|
||||
transparent_builder: TransparentBuilder::empty(),
|
||||
sapling_builder: SaplingBuilder::empty(TEST_NETWORK, sapling_activation_height),
|
||||
sapling_builder: SaplingBuilder::new(TEST_NETWORK, sapling_activation_height),
|
||||
change_address: None,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_builder: TzeBuilder::empty(),
|
||||
|
@ -617,7 +628,7 @@ mod tests {
|
|||
|
||||
let (tx, _) = builder.build(&MockTxProver).unwrap();
|
||||
// No binding signature, because only t input and outputs
|
||||
assert!(tx.binding_sig.is_none());
|
||||
assert!(tx.sapling_bundle.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -654,7 +665,7 @@ mod tests {
|
|||
// that a binding signature was attempted
|
||||
assert_eq!(
|
||||
builder.build(&MockTxProver),
|
||||
Err(Error::SaplingBuild(sapling::Error::BindingSig))
|
||||
Err(Error::SaplingBuild(build_s::Error::BindingSig))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -804,7 +815,7 @@ mod tests {
|
|||
.unwrap();
|
||||
assert_eq!(
|
||||
builder.build(&MockTxProver),
|
||||
Err(Error::SaplingBuild(sapling::Error::BindingSig))
|
||||
Err(Error::SaplingBuild(build_s::Error::BindingSig))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +1,113 @@
|
|||
use core::fmt::Debug;
|
||||
use ff::PrimeField;
|
||||
use group::GroupEncoding;
|
||||
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use zcash_note_encryption::ShieldedOutput;
|
||||
use zcash_note_encryption::{ShieldedOutput, COMPACT_NOTE_SIZE};
|
||||
|
||||
use crate::{
|
||||
consensus,
|
||||
sapling::{
|
||||
note_encryption::SaplingDomain,
|
||||
redjubjub::{PublicKey, Signature},
|
||||
redjubjub::{self, PublicKey, Signature},
|
||||
Nullifier,
|
||||
},
|
||||
};
|
||||
|
||||
use zcash_note_encryption::COMPACT_NOTE_SIZE;
|
||||
use super::{amount::Amount, GROTH_PROOF_SIZE};
|
||||
|
||||
use super::GROTH_PROOF_SIZE;
|
||||
pub type GrothProofBytes = [u8; GROTH_PROOF_SIZE];
|
||||
|
||||
pub mod builder;
|
||||
|
||||
pub trait Authorization: Debug {
|
||||
type Proof: Clone + Debug;
|
||||
type AuthSig: Clone + Debug;
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Unproven;
|
||||
|
||||
impl Authorization for Unproven {
|
||||
type Proof = ();
|
||||
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,
|
||||
}
|
||||
|
||||
impl Authorization for Authorized {
|
||||
type Proof = GrothProofBytes;
|
||||
type AuthSig = redjubjub::Signature;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Bundle<A: Authorization> {
|
||||
pub shielded_spends: Vec<SpendDescription<A>>,
|
||||
pub shielded_outputs: Vec<OutputDescription<A>>,
|
||||
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 {
|
||||
pub struct SpendDescription<A: Authorization> {
|
||||
pub cv: jubjub::ExtendedPoint,
|
||||
pub anchor: bls12_381::Scalar,
|
||||
pub nullifier: Nullifier,
|
||||
pub rk: PublicKey,
|
||||
pub zkproof: [u8; GROTH_PROOF_SIZE],
|
||||
pub spend_auth_sig: Option<Signature>,
|
||||
pub zkproof: A::Proof,
|
||||
pub spend_auth_sig: A::AuthSig,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SpendDescription {
|
||||
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!(
|
||||
f,
|
||||
|
@ -40,49 +117,84 @@ impl std::fmt::Debug for SpendDescription {
|
|||
}
|
||||
}
|
||||
|
||||
impl SpendDescription {
|
||||
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
|
||||
// Consensus rules (§4.4):
|
||||
// - Canonical encoding is enforced here.
|
||||
// - "Not small order" is enforced in SaplingVerificationContext::check_spend()
|
||||
// (located in zcash_proofs::sapling::verifier).
|
||||
let cv = {
|
||||
/// Consensus rules (§4.4) & (§4.5):
|
||||
/// - Canonical encoding is enforced here.
|
||||
/// - "Not small order" is enforced in SaplingVerificationContext::(check_spend()/check_output())
|
||||
/// (located in zcash_proofs::sapling::verifier).
|
||||
pub fn read_point<R: Read>(mut reader: R, field: &str) -> io::Result<jubjub::ExtendedPoint> {
|
||||
let mut bytes = [0u8; 32];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
let cv = jubjub::ExtendedPoint::from_bytes(&bytes);
|
||||
if cv.is_none().into() {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid cv"));
|
||||
}
|
||||
cv.unwrap()
|
||||
};
|
||||
let point = jubjub::ExtendedPoint::from_bytes(&bytes);
|
||||
|
||||
// Consensus rule (§7.3): Canonical encoding is enforced here
|
||||
let anchor = {
|
||||
if point.is_none().into() {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid ".to_owned() + field,
|
||||
))
|
||||
} else {
|
||||
Ok(point.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// Consensus rules (§7.3) & (§7.4):
|
||||
/// - Canonical encoding is enforced here
|
||||
pub fn read_scalar<R: Read>(mut reader: R, field: &str) -> io::Result<bls12_381::Scalar> {
|
||||
let mut f = [0u8; 32];
|
||||
reader.read_exact(&mut f)?;
|
||||
bls12_381::Scalar::from_repr(f)
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "anchor not in field"))?
|
||||
};
|
||||
bls12_381::Scalar::from_repr(f).ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
field.to_owned() + " not in field",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
let mut nullifier = Nullifier([0u8; 32]);
|
||||
reader.read_exact(&mut nullifier.0)?;
|
||||
|
||||
// Consensus rules (§4.4):
|
||||
// - Canonical encoding is enforced here.
|
||||
// - "Not small order" is enforced in SaplingVerificationContext::check_spend()
|
||||
let rk = PublicKey::read(&mut reader)?;
|
||||
|
||||
// Consensus rules (§4.4):
|
||||
// - Canonical encoding is enforced by the API of SaplingVerificationContext::check_spend()
|
||||
// due to the need to parse this into a bellman::groth16::Proof.
|
||||
// - Proof validity is enforced in SaplingVerificationContext::check_spend()
|
||||
/// Consensus rules (§4.4) & (§4.5):
|
||||
/// - Canonical encoding is enforced by the API of SaplingVerificationContext::check_spend()
|
||||
/// and SaplingVerificationContext::check_output() due to the need to parse this into a
|
||||
/// bellman::groth16::Proof.
|
||||
/// - Proof validity is enforced in SaplingVerificationContext::check_spend()
|
||||
/// and SaplingVerificationContext::check_output()
|
||||
pub fn read_zkproof<R: Read>(mut reader: R) -> io::Result<GrothProofBytes> {
|
||||
let mut zkproof = [0u8; GROTH_PROOF_SIZE];
|
||||
reader.read_exact(&mut zkproof)?;
|
||||
Ok(zkproof)
|
||||
}
|
||||
|
||||
// Consensus rules (§4.4):
|
||||
impl SpendDescription<Authorized> {
|
||||
pub fn read_nullifier<R: Read>(mut reader: R) -> io::Result<Nullifier> {
|
||||
let mut nullifier = Nullifier([0u8; 32]);
|
||||
reader.read_exact(&mut nullifier.0)?;
|
||||
Ok(nullifier)
|
||||
}
|
||||
|
||||
/// Consensus rules (§4.4):
|
||||
/// - Canonical encoding is enforced here.
|
||||
/// - "Not small order" is enforced in SaplingVerificationContext::check_spend()
|
||||
pub fn read_rk<R: Read>(mut reader: R) -> io::Result<PublicKey> {
|
||||
PublicKey::read(&mut reader)
|
||||
}
|
||||
|
||||
/// Consensus rules (§4.4):
|
||||
/// - Canonical encoding is enforced here.
|
||||
/// - Signature validity is enforced in SaplingVerificationContext::check_spend()
|
||||
pub fn read_spend_auth_sig<R: Read>(mut reader: R) -> io::Result<Signature> {
|
||||
Signature::read(&mut reader)
|
||||
}
|
||||
|
||||
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||
// Consensus rules (§4.4) & (§4.5):
|
||||
// - Canonical encoding is enforced here.
|
||||
// - Signature validity is enforced in SaplingVerificationContext::check_spend()
|
||||
let spend_auth_sig = Some(Signature::read(&mut reader)?);
|
||||
// - "Not small order" is enforced in SaplingVerificationContext::(check_spend()/check_output())
|
||||
// (located in zcash_proofs::sapling::verifier).
|
||||
let cv = read_point(&mut reader, "cv")?;
|
||||
// Consensus rules (§7.3) & (§7.4):
|
||||
// - Canonical encoding is enforced here
|
||||
let anchor = read_scalar(&mut reader, "anchor")?;
|
||||
let nullifier = Self::read_nullifier(&mut reader)?;
|
||||
let rk = Self::read_rk(&mut reader)?;
|
||||
let zkproof = read_zkproof(&mut reader)?;
|
||||
let spend_auth_sig = Self::read_spend_auth_sig(&mut reader)?;
|
||||
|
||||
Ok(SpendDescription {
|
||||
cv,
|
||||
|
@ -100,27 +212,23 @@ impl SpendDescription {
|
|||
writer.write_all(&self.nullifier.0)?;
|
||||
self.rk.write(&mut writer)?;
|
||||
writer.write_all(&self.zkproof)?;
|
||||
match self.spend_auth_sig {
|
||||
Some(sig) => sig.write(&mut writer),
|
||||
None => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Missing spend auth signature",
|
||||
)),
|
||||
}
|
||||
self.spend_auth_sig.write(&mut writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OutputDescription {
|
||||
pub struct OutputDescription<A: Authorization> {
|
||||
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: [u8; GROTH_PROOF_SIZE],
|
||||
pub zkproof: A::Proof,
|
||||
}
|
||||
|
||||
impl<P: consensus::Parameters> ShieldedOutput<SaplingDomain<P>> for OutputDescription {
|
||||
impl<P: consensus::Parameters, A: Authorization> ShieldedOutput<SaplingDomain<P>>
|
||||
for OutputDescription<A>
|
||||
{
|
||||
fn epk(&self) -> &jubjub::ExtendedPoint {
|
||||
&self.ephemeral_key
|
||||
}
|
||||
|
@ -134,7 +242,7 @@ impl<P: consensus::Parameters> ShieldedOutput<SaplingDomain<P>> for OutputDescri
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for OutputDescription {
|
||||
impl<A: Authorization> std::fmt::Debug for OutputDescription<A> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
|
@ -144,7 +252,7 @@ impl std::fmt::Debug for OutputDescription {
|
|||
}
|
||||
}
|
||||
|
||||
impl OutputDescription {
|
||||
impl OutputDescription<Authorized> {
|
||||
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
|
||||
// Consensus rules (§4.5):
|
||||
// - Canonical encoding is enforced here.
|
||||
|
@ -205,7 +313,9 @@ impl OutputDescription {
|
|||
zkproof,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Authorization<Proof = GrothProofBytes>> OutputDescription<A> {
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_all(&self.cv.to_bytes())?;
|
||||
writer.write_all(self.cmu.to_repr().as_ref())?;
|
||||
|
@ -216,14 +326,27 @@ impl OutputDescription {
|
|||
}
|
||||
}
|
||||
|
||||
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 From<OutputDescription> for CompactOutputDescription {
|
||||
fn from(out: OutputDescription) -> CompactOutputDescription {
|
||||
impl<A: Authorization> From<OutputDescription<A>> for CompactOutputDescription {
|
||||
fn from(out: OutputDescription<A>) -> CompactOutputDescription {
|
||||
CompactOutputDescription {
|
||||
epk: out.ephemeral_key,
|
||||
cmu: out.cmu,
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
//! Types and functions for building Sapling transaction components.
|
||||
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use ff::Field;
|
||||
use rand::{seq::SliceRandom, CryptoRng, RngCore};
|
||||
use rand::{seq::SliceRandom, RngCore};
|
||||
|
||||
use crate::{
|
||||
consensus::{self, BlockHeight},
|
||||
|
@ -22,7 +21,10 @@ use crate::{
|
|||
},
|
||||
transaction::{
|
||||
builder::Progress,
|
||||
components::{amount::Amount, OutputDescription, SpendDescription},
|
||||
components::{
|
||||
amount::Amount,
|
||||
sapling::{Bundle, OutputDescription, SpendDescription, Unauthorized},
|
||||
},
|
||||
},
|
||||
zip32::ExtendedSpendingKey,
|
||||
};
|
||||
|
@ -63,32 +65,19 @@ struct SpendDescriptionInfo {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SaplingOutput<P: consensus::Parameters> {
|
||||
struct SaplingOutput {
|
||||
/// `None` represents the `ovk = ⊥` case.
|
||||
ovk: Option<OutgoingViewingKey>,
|
||||
to: PaymentAddress,
|
||||
note: Note,
|
||||
memo: MemoBytes,
|
||||
_params: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: consensus::Parameters> SaplingOutput<P> {
|
||||
pub fn new<R: RngCore + CryptoRng>(
|
||||
impl SaplingOutput {
|
||||
fn new_internal<P: consensus::Parameters, R: RngCore>(
|
||||
params: &P,
|
||||
target_height: BlockHeight,
|
||||
rng: &mut R,
|
||||
ovk: Option<OutgoingViewingKey>,
|
||||
to: PaymentAddress,
|
||||
value: Amount,
|
||||
memo: Option<MemoBytes>,
|
||||
) -> Result<Self, Error> {
|
||||
Self::new_internal(params, target_height, rng, ovk, to, value, memo)
|
||||
}
|
||||
|
||||
fn new_internal<R: RngCore>(
|
||||
params: &P,
|
||||
target_height: BlockHeight,
|
||||
rng: &mut R,
|
||||
ovk: Option<OutgoingViewingKey>,
|
||||
to: PaymentAddress,
|
||||
value: Amount,
|
||||
|
@ -113,25 +102,15 @@ impl<P: consensus::Parameters> SaplingOutput<P> {
|
|||
to,
|
||||
note,
|
||||
memo: memo.unwrap_or_else(MemoBytes::empty),
|
||||
_params: PhantomData::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build<Pr: TxProver, R: RngCore + CryptoRng>(
|
||||
fn build<P: consensus::Parameters, Pr: TxProver, R: RngCore>(
|
||||
self,
|
||||
prover: &Pr,
|
||||
ctx: &mut Pr::SaplingProvingContext,
|
||||
rng: &mut R,
|
||||
) -> OutputDescription {
|
||||
self.build_internal(prover, ctx, rng)
|
||||
}
|
||||
|
||||
fn build_internal<Pr: TxProver, R: RngCore>(
|
||||
self,
|
||||
prover: &Pr,
|
||||
ctx: &mut Pr::SaplingProvingContext,
|
||||
rng: &mut R,
|
||||
) -> OutputDescription {
|
||||
) -> OutputDescription<Unauthorized> {
|
||||
let encryptor = sapling_note_encryption::<R, P>(
|
||||
self.ovk,
|
||||
self.note.clone(),
|
||||
|
@ -204,17 +183,17 @@ impl SaplingMetadata {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct SaplingBuilder<P: consensus::Parameters> {
|
||||
pub struct SaplingBuilder<P> {
|
||||
params: P,
|
||||
anchor: Option<bls12_381::Scalar>,
|
||||
target_height: BlockHeight,
|
||||
value_balance: Amount,
|
||||
spends: Vec<SpendDescriptionInfo>,
|
||||
outputs: Vec<SaplingOutput<P>>,
|
||||
outputs: Vec<SaplingOutput>,
|
||||
}
|
||||
|
||||
impl<P: consensus::Parameters> SaplingBuilder<P> {
|
||||
pub fn empty(params: P, target_height: BlockHeight) -> Self {
|
||||
pub fn new(params: P, target_height: BlockHeight) -> Self {
|
||||
SaplingBuilder {
|
||||
params,
|
||||
anchor: None,
|
||||
|
@ -280,8 +259,8 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
|||
) -> Result<(), Error> {
|
||||
let output = SaplingOutput::new_internal(
|
||||
&self.params,
|
||||
self.target_height,
|
||||
&mut rng,
|
||||
self.target_height,
|
||||
ovk,
|
||||
to,
|
||||
value,
|
||||
|
@ -311,14 +290,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
|||
mut rng: R,
|
||||
target_height: BlockHeight,
|
||||
progress_notifier: Option<&Sender<Progress>>,
|
||||
) -> Result<
|
||||
(
|
||||
Vec<SpendDescription>,
|
||||
Vec<OutputDescription>,
|
||||
SaplingMetadata,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
) -> Result<(Option<Bundle<Unauthorized>>, SaplingMetadata), Error> {
|
||||
// Record initial positions of spends and outputs
|
||||
let mut indexed_spends: Vec<_> = self.spends.iter().enumerate().collect();
|
||||
let mut indexed_outputs: Vec<_> = self
|
||||
|
@ -350,7 +322,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
|||
let mut progress = 0u32;
|
||||
|
||||
// Create Sapling SpendDescriptions
|
||||
let spend_descs = if !indexed_spends.is_empty() {
|
||||
let shielded_spends: Vec<SpendDescription<Unauthorized>> = if !indexed_spends.is_empty() {
|
||||
let anchor = self
|
||||
.anchor
|
||||
.expect("Sapling anchor must be set if Sapling spends are present.");
|
||||
|
@ -397,7 +369,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
|||
nullifier,
|
||||
rk,
|
||||
zkproof,
|
||||
spend_auth_sig: None,
|
||||
spend_auth_sig: (),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?
|
||||
|
@ -406,7 +378,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
|||
};
|
||||
|
||||
// Create Sapling OutputDescriptions
|
||||
let output_descs = indexed_outputs
|
||||
let shielded_outputs: Vec<OutputDescription<Unauthorized>> = indexed_outputs
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, output)| {
|
||||
|
@ -414,7 +386,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
|||
// Record the post-randomized output location
|
||||
tx_metadata.output_indices[pos] = i;
|
||||
|
||||
output.clone().build_internal(prover, ctx, &mut rng)
|
||||
output.clone().build::<P, _, _>(prover, ctx, &mut rng)
|
||||
} else {
|
||||
// This is a dummy output
|
||||
let (dummy_to, dummy_note) = {
|
||||
|
@ -491,7 +463,18 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
|||
})
|
||||
.collect();
|
||||
|
||||
Ok((spend_descs, output_descs, tx_metadata))
|
||||
let bundle = if shielded_spends.is_empty() && shielded_outputs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Bundle {
|
||||
shielded_spends,
|
||||
shielded_outputs,
|
||||
value_balance: self.value_balance,
|
||||
authorization: Unauthorized,
|
||||
})
|
||||
};
|
||||
|
||||
Ok((bundle, tx_metadata))
|
||||
}
|
||||
|
||||
pub fn create_signatures<Pr: TxProver, R: RngCore>(
|
||||
|
@ -523,10 +506,91 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
|||
.unwrap_or_default();
|
||||
|
||||
let binding_sig = prover
|
||||
.binding_sig(ctx, self.value_balance, &sighash_bytes)
|
||||
.binding_sig(ctx, self.value_balance, sighash_bytes)
|
||||
.map_err(|_| Error::BindingSig)?;
|
||||
|
||||
Ok(Some((spend_sigs, binding_sig)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use proptest::collection::vec;
|
||||
use proptest::prelude::*;
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
use crate::{
|
||||
consensus::{
|
||||
testing::{arb_branch_id, arb_height},
|
||||
TEST_NETWORK,
|
||||
},
|
||||
merkle_tree::{testing::arb_commitment_tree, IncrementalWitness},
|
||||
sapling::{
|
||||
prover::{mock::MockTxProver, TxProver},
|
||||
testing::{arb_node, arb_note, arb_positive_note_value},
|
||||
Diversifier,
|
||||
},
|
||||
transaction::components::{
|
||||
amount::MAX_MONEY,
|
||||
sapling::{Authorized, Bundle},
|
||||
},
|
||||
zip32::testing::arb_extended_spending_key,
|
||||
};
|
||||
|
||||
use super::SaplingBuilder;
|
||||
|
||||
prop_compose! {
|
||||
fn arb_bundle()(n_notes in 1..30usize)(
|
||||
extsk in arb_extended_spending_key(),
|
||||
spendable_notes in vec(
|
||||
arb_positive_note_value(MAX_MONEY as u64 / 10000).prop_flat_map(arb_note),
|
||||
n_notes
|
||||
),
|
||||
commitment_trees in vec(
|
||||
arb_commitment_tree(n_notes, arb_node()).prop_map(
|
||||
|t| IncrementalWitness::from_tree(&t).path().unwrap()
|
||||
),
|
||||
n_notes
|
||||
),
|
||||
diversifiers in vec(prop::array::uniform11(any::<u8>()).prop_map(Diversifier), n_notes),
|
||||
target_height in arb_branch_id().prop_flat_map(|b| arb_height(b, &TEST_NETWORK)),
|
||||
rng_seed in prop::array::uniform32(any::<u8>()),
|
||||
fake_sighash_bytes in prop::array::uniform32(any::<u8>()),
|
||||
) -> Bundle<Authorized> {
|
||||
let mut builder = SaplingBuilder::new(TEST_NETWORK, target_height.unwrap());
|
||||
let mut rng = StdRng::from_seed(rng_seed);
|
||||
|
||||
for ((note, path), diversifier) in spendable_notes.into_iter().zip(commitment_trees.into_iter()).zip(diversifiers.into_iter()) {
|
||||
builder.add_spend(
|
||||
&mut rng,
|
||||
extsk.clone(),
|
||||
diversifier,
|
||||
note,
|
||||
path
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
let prover = MockTxProver;
|
||||
let mut ctx = prover.new_sapling_proving_context();
|
||||
|
||||
let (bundle, meta) = builder.build(
|
||||
&prover,
|
||||
&mut ctx,
|
||||
&mut rng,
|
||||
target_height.unwrap(),
|
||||
None
|
||||
).unwrap();
|
||||
|
||||
let (spend_auth_sigs, binding_sig) = builder.create_signatures(
|
||||
&prover,
|
||||
&mut ctx,
|
||||
&mut rng,
|
||||
&fake_sighash_bytes,
|
||||
&meta
|
||||
).unwrap().unwrap();
|
||||
|
||||
bundle.unwrap().apply_signatures(spend_auth_sigs, binding_sig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,14 @@ use orchard;
|
|||
|
||||
use crate::{
|
||||
consensus::{BlockHeight, BranchId},
|
||||
sapling::redjubjub::Signature,
|
||||
sapling::redjubjub,
|
||||
serialize::Vector,
|
||||
};
|
||||
|
||||
use self::{
|
||||
components::{Amount, JsDescription, OutputDescription, SpendDescription, TxIn, TxOut},
|
||||
components::{
|
||||
sapling, Amount, JsDescription, OutputDescription, SpendDescription, TxIn, TxOut,
|
||||
},
|
||||
sighash::{signature_hash_data, SignableInput, SIGHASH_ALL},
|
||||
util::sha256d::{HashReader, HashWriter},
|
||||
};
|
||||
|
@ -167,7 +169,7 @@ impl TxVersion {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn uses_groth_proofs(&self) -> bool {
|
||||
pub fn has_sapling(&self) -> bool {
|
||||
match self {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter => false,
|
||||
TxVersion::Sapling => true,
|
||||
|
@ -192,18 +194,21 @@ impl TxVersion {
|
|||
|
||||
/// Authorization state for a bundle of transaction data.
|
||||
pub trait Authorization {
|
||||
type SaplingAuth: sapling::Authorization;
|
||||
type OrchardAuth: orchard::bundle::Authorization;
|
||||
}
|
||||
|
||||
pub struct Authorized;
|
||||
|
||||
impl Authorization for Authorized {
|
||||
type SaplingAuth = sapling::Authorized;
|
||||
type OrchardAuth = orchard::bundle::Authorized;
|
||||
}
|
||||
|
||||
pub struct Unauthorized;
|
||||
|
||||
impl Authorization for Unauthorized {
|
||||
type SaplingAuth = sapling::Unauthorized;
|
||||
type OrchardAuth = orchard::builder::Unauthorized;
|
||||
}
|
||||
|
||||
|
@ -238,13 +243,10 @@ pub struct TransactionData<A: Authorization> {
|
|||
pub tze_outputs: Vec<TzeOut>,
|
||||
pub lock_time: u32,
|
||||
pub expiry_height: BlockHeight,
|
||||
pub value_balance: Amount,
|
||||
pub shielded_spends: Vec<SpendDescription>,
|
||||
pub shielded_outputs: Vec<OutputDescription>,
|
||||
pub joinsplits: Vec<JsDescription>,
|
||||
pub joinsplit_pubkey: Option<[u8; 32]>,
|
||||
pub joinsplit_sig: Option<[u8; 64]>,
|
||||
pub binding_sig: Option<Signature>,
|
||||
pub sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
|
||||
pub orchard_bundle: Option<orchard::Bundle<A::OrchardAuth, Amount>>,
|
||||
}
|
||||
|
||||
|
@ -282,19 +284,33 @@ impl<A: Authorization> std::fmt::Debug for TransactionData<A> {
|
|||
},
|
||||
self.lock_time,
|
||||
self.expiry_height,
|
||||
self.value_balance,
|
||||
self.shielded_spends,
|
||||
self.shielded_outputs,
|
||||
self.sapling_bundle
|
||||
.as_ref()
|
||||
.map_or(Amount::zero(), |b| b.value_balance),
|
||||
self.sapling_bundle
|
||||
.as_ref()
|
||||
.map_or(&vec![], |b| &b.shielded_spends),
|
||||
self.sapling_bundle
|
||||
.as_ref()
|
||||
.map_or(&vec![], |b| &b.shielded_outputs),
|
||||
self.joinsplits,
|
||||
self.joinsplit_pubkey,
|
||||
self.binding_sig
|
||||
self.sapling_bundle.as_ref().map(|b| &b.authorization)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Authorization> TransactionData<A> {
|
||||
pub fn sapling_value_balance(&self) -> Amount {
|
||||
self.sapling_bundle
|
||||
.as_ref()
|
||||
.map_or(Amount::zero(), |b| b.value_balance)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TransactionData<Unauthorized> {
|
||||
fn default() -> Self {
|
||||
TransactionData::new()
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -310,13 +326,10 @@ impl TransactionData<Unauthorized> {
|
|||
tze_outputs: vec![],
|
||||
lock_time: 0,
|
||||
expiry_height: 0u32.into(),
|
||||
value_balance: Amount::zero(),
|
||||
shielded_spends: vec![],
|
||||
shielded_outputs: vec![],
|
||||
joinsplits: vec![],
|
||||
joinsplit_pubkey: None,
|
||||
joinsplit_sig: None,
|
||||
binding_sig: None,
|
||||
sapling_bundle: None,
|
||||
orchard_bundle: None,
|
||||
}
|
||||
}
|
||||
|
@ -331,13 +344,10 @@ impl TransactionData<Unauthorized> {
|
|||
tze_outputs: vec![],
|
||||
lock_time: 0,
|
||||
expiry_height: 0u32.into(),
|
||||
value_balance: Amount::zero(),
|
||||
shielded_spends: vec![],
|
||||
shielded_outputs: vec![],
|
||||
joinsplits: vec![],
|
||||
joinsplit_pubkey: None,
|
||||
joinsplit_sig: None,
|
||||
binding_sig: None,
|
||||
sapling_bundle: None,
|
||||
orchard_bundle: None,
|
||||
}
|
||||
}
|
||||
|
@ -365,6 +375,13 @@ impl Transaction {
|
|||
self.txid
|
||||
}
|
||||
|
||||
fn read_amount<R: Read>(mut reader: R) -> io::Result<Amount> {
|
||||
let mut tmp = [0; 8];
|
||||
reader.read_exact(&mut tmp)?;
|
||||
Amount::from_i64_le_bytes(tmp)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "valueBalance out of range"))
|
||||
}
|
||||
|
||||
pub fn read<R: Read>(reader: R) -> io::Result<Self> {
|
||||
let mut reader = HashReader::new(reader);
|
||||
|
||||
|
@ -396,15 +413,14 @@ impl Transaction {
|
|||
0u32.into()
|
||||
};
|
||||
|
||||
let (value_balance, shielded_spends, shielded_outputs) = if is_sapling_v4 || has_tze {
|
||||
let vb = {
|
||||
let mut tmp = [0; 8];
|
||||
reader.read_exact(&mut tmp)?;
|
||||
Amount::from_i64_le_bytes(tmp)
|
||||
}
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "valueBalance out of range"))?;
|
||||
let ss = Vector::read(&mut reader, SpendDescription::read)?;
|
||||
let so = Vector::read(&mut reader, OutputDescription::read)?;
|
||||
let (value_balance, shielded_spends, shielded_outputs) = if version.has_sapling() {
|
||||
let vb = Self::read_amount(&mut reader)?;
|
||||
#[allow(clippy::redundant_closure)]
|
||||
let ss: Vec<SpendDescription<sapling::Authorized>> =
|
||||
Vector::read(&mut reader, |r| SpendDescription::read(r))?;
|
||||
#[allow(clippy::redundant_closure)]
|
||||
let so: Vec<OutputDescription<sapling::Authorized>> =
|
||||
Vector::read(&mut reader, |r| OutputDescription::read(r))?;
|
||||
(vb, ss, so)
|
||||
} else {
|
||||
(Amount::zero(), vec![], vec![])
|
||||
|
@ -412,7 +428,7 @@ impl Transaction {
|
|||
|
||||
let (joinsplits, joinsplit_pubkey, joinsplit_sig) = if version.has_sprout() {
|
||||
let jss = Vector::read(&mut reader, |r| {
|
||||
JsDescription::read(r, version.uses_groth_proofs())
|
||||
JsDescription::read(r, version.has_sapling())
|
||||
})?;
|
||||
let (pubkey, sig) = if !jss.is_empty() {
|
||||
let mut joinsplit_pubkey = [0; 32];
|
||||
|
@ -431,7 +447,7 @@ impl Transaction {
|
|||
let binding_sig = if (is_sapling_v4 || has_tze)
|
||||
&& !(shielded_spends.is_empty() && shielded_outputs.is_empty())
|
||||
{
|
||||
Some(Signature::read(&mut reader)?)
|
||||
Some(redjubjub::Signature::read(&mut reader)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -451,13 +467,15 @@ impl Transaction {
|
|||
tze_outputs,
|
||||
lock_time,
|
||||
expiry_height,
|
||||
value_balance,
|
||||
shielded_spends,
|
||||
shielded_outputs,
|
||||
joinsplits,
|
||||
joinsplit_pubkey,
|
||||
joinsplit_sig,
|
||||
binding_sig,
|
||||
sapling_bundle: binding_sig.map(|binding_sig| sapling::Bundle {
|
||||
value_balance,
|
||||
shielded_spends,
|
||||
shielded_outputs,
|
||||
authorization: sapling::Authorized { binding_sig },
|
||||
}),
|
||||
orchard_bundle: None,
|
||||
},
|
||||
})
|
||||
|
@ -486,10 +504,28 @@ impl Transaction {
|
|||
writer.write_u32::<LittleEndian>(u32::from(self.expiry_height))?;
|
||||
}
|
||||
|
||||
if is_sapling_v4 || has_tze {
|
||||
writer.write_all(&self.value_balance.to_i64_le_bytes())?;
|
||||
Vector::write(&mut writer, &self.shielded_spends, |w, e| e.write(w))?;
|
||||
Vector::write(&mut writer, &self.shielded_outputs, |w, e| e.write(w))?;
|
||||
if self.version.has_sapling() {
|
||||
writer.write_all(
|
||||
&self
|
||||
.sapling_bundle
|
||||
.as_ref()
|
||||
.map_or(Amount::zero(), |b| b.value_balance)
|
||||
.to_i64_le_bytes(),
|
||||
)?;
|
||||
Vector::write(
|
||||
&mut writer,
|
||||
self.sapling_bundle
|
||||
.as_ref()
|
||||
.map_or(&[], |b| &b.shielded_spends),
|
||||
|w, e| e.write(w),
|
||||
)?;
|
||||
Vector::write(
|
||||
&mut writer,
|
||||
self.sapling_bundle
|
||||
.as_ref()
|
||||
.map_or(&[], |b| &b.shielded_outputs),
|
||||
|w, e| e.write(w),
|
||||
)?;
|
||||
}
|
||||
|
||||
if self.version.has_sprout() {
|
||||
|
@ -531,19 +567,16 @@ impl Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
if (is_sapling_v4 || has_tze)
|
||||
&& !(self.shielded_spends.is_empty() && self.shielded_outputs.is_empty())
|
||||
{
|
||||
match self.binding_sig {
|
||||
Some(sig) => sig.write(&mut writer)?,
|
||||
None => {
|
||||
if self.version.has_sapling() {
|
||||
if let Some(bundle) = self.sapling_bundle.as_ref() {
|
||||
bundle.authorization.binding_sig.write(&mut writer)?;
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Missing binding signature",
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if self.binding_sig.is_some() {
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Binding signature should not be present",
|
||||
|
@ -686,7 +719,6 @@ pub mod testing {
|
|||
tze_outputs in vec(arb_tzeout(), 0..10),
|
||||
lock_time in any::<u32>(),
|
||||
expiry_height in any::<u32>(),
|
||||
value_balance in arb_amount(),
|
||||
) -> TransactionData<Authorized> {
|
||||
TransactionData {
|
||||
version,
|
||||
|
@ -695,16 +727,10 @@ pub mod testing {
|
|||
tze_outputs: if branch_id == BranchId::ZFuture { tze_outputs } else { vec![] },
|
||||
lock_time,
|
||||
expiry_height: expiry_height.into(),
|
||||
value_balance: match version {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter => Amount::zero(),
|
||||
_ => value_balance,
|
||||
},
|
||||
shielded_spends: vec![], //FIXME
|
||||
shielded_outputs: vec![], //FIXME
|
||||
joinsplits: vec![], //FIXME
|
||||
joinsplit_pubkey: None, //FIXME
|
||||
joinsplit_sig: None, //FIXME
|
||||
binding_sig: None, //FIXME
|
||||
sapling_bundle: None, //FIXME
|
||||
orchard_bundle: None, //FIXME
|
||||
}
|
||||
}
|
||||
|
@ -718,23 +744,16 @@ pub mod testing {
|
|||
vout in vec(arb_txout(), 0..10),
|
||||
lock_time in any::<u32>(),
|
||||
expiry_height in any::<u32>(),
|
||||
value_balance in arb_amount(),
|
||||
) -> TransactionData<Authorized> {
|
||||
TransactionData {
|
||||
version,
|
||||
vin, vout,
|
||||
lock_time,
|
||||
expiry_height: expiry_height.into(),
|
||||
value_balance: match version {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter => Amount::zero(),
|
||||
_ => value_balance,
|
||||
},
|
||||
shielded_spends: vec![], //FIXME
|
||||
shielded_outputs: vec![], //FIXME
|
||||
joinsplits: vec![], //FIXME
|
||||
joinsplit_pubkey: None, //FIXME
|
||||
joinsplit_sig: None, //FIXME
|
||||
binding_sig: None, //FIXME
|
||||
sapling_bundle: None, //FIXME
|
||||
orchard_bundle: None, //FIXME
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,10 @@ use byteorder::{LittleEndian, WriteBytesExt};
|
|||
use ff::PrimeField;
|
||||
use group::GroupEncoding;
|
||||
|
||||
use crate::{consensus, legacy::Script};
|
||||
use crate::{
|
||||
consensus::{self, BranchId},
|
||||
legacy::Script,
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use crate::{
|
||||
|
@ -15,7 +18,10 @@ use crate::{
|
|||
};
|
||||
|
||||
use super::{
|
||||
components::{Amount, JsDescription, OutputDescription, SpendDescription, TxIn, TxOut},
|
||||
components::{
|
||||
sapling::{self, GrothProofBytes},
|
||||
Amount, JsDescription, OutputDescription, SpendDescription, TxIn, TxOut,
|
||||
},
|
||||
Authorization, Transaction, TransactionData, TxVersion,
|
||||
};
|
||||
|
||||
|
@ -67,10 +73,6 @@ fn has_overwinter_components(version: &TxVersion) -> bool {
|
|||
!matches!(version, TxVersion::Sprout(_))
|
||||
}
|
||||
|
||||
fn has_sapling_components(version: &TxVersion) -> bool {
|
||||
!matches!(version, TxVersion::Sprout(_) | TxVersion::Overwinter)
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn has_tze_components(version: &TxVersion) -> bool {
|
||||
matches!(version, TxVersion::ZFuture)
|
||||
|
@ -121,13 +123,13 @@ fn single_output_hash(tx_out: &TxOut) -> Blake2bHash {
|
|||
}
|
||||
|
||||
fn joinsplits_hash(
|
||||
txversion: TxVersion,
|
||||
consensus_branch_id: BranchId,
|
||||
joinsplits: &[JsDescription],
|
||||
joinsplit_pubkey: &[u8; 32],
|
||||
) -> Blake2bHash {
|
||||
let mut data = Vec::with_capacity(
|
||||
joinsplits.len()
|
||||
* if txversion.uses_groth_proofs() {
|
||||
* if consensus_branch_id.sprout_uses_groth_proofs() {
|
||||
1698 // JSDescription with Groth16 proof
|
||||
} else {
|
||||
1802 // JSDescription with PHGR13 proof
|
||||
|
@ -143,7 +145,9 @@ fn joinsplits_hash(
|
|||
.hash(&data)
|
||||
}
|
||||
|
||||
fn shielded_spends_hash(shielded_spends: &[SpendDescription]) -> Blake2bHash {
|
||||
fn shielded_spends_hash<A: sapling::Authorization<Proof = GrothProofBytes>>(
|
||||
shielded_spends: &[SpendDescription<A>],
|
||||
) -> Blake2bHash {
|
||||
let mut data = Vec::with_capacity(shielded_spends.len() * 384);
|
||||
for s_spend in shielded_spends {
|
||||
data.extend_from_slice(&s_spend.cv.to_bytes());
|
||||
|
@ -158,7 +162,9 @@ fn shielded_spends_hash(shielded_spends: &[SpendDescription]) -> Blake2bHash {
|
|||
.hash(&data)
|
||||
}
|
||||
|
||||
fn shielded_outputs_hash(shielded_outputs: &[OutputDescription]) -> Blake2bHash {
|
||||
fn shielded_outputs_hash<A: sapling::Authorization<Proof = GrothProofBytes>>(
|
||||
shielded_outputs: &[OutputDescription<A>],
|
||||
) -> Blake2bHash {
|
||||
let mut data = Vec::with_capacity(shielded_outputs.len() * 948);
|
||||
for s_out in shielded_outputs {
|
||||
s_out.write(&mut data).unwrap();
|
||||
|
@ -227,7 +233,10 @@ impl<'a> SignableInput<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn signature_hash_data<A: Authorization>(
|
||||
pub fn signature_hash_data<
|
||||
SA: sapling::Authorization<Proof = GrothProofBytes>,
|
||||
A: Authorization<SaplingAuth = SA>,
|
||||
>(
|
||||
tx: &TransactionData<A>,
|
||||
consensus_branch_id: consensus::BranchId,
|
||||
hash_type: u32,
|
||||
|
@ -291,24 +300,44 @@ pub fn signature_hash_data<A: Authorization>(
|
|||
update_hash!(
|
||||
h,
|
||||
!tx.joinsplits.is_empty(),
|
||||
joinsplits_hash(tx.version, &tx.joinsplits, &tx.joinsplit_pubkey.unwrap())
|
||||
joinsplits_hash(
|
||||
consensus_branch_id,
|
||||
&tx.joinsplits,
|
||||
&tx.joinsplit_pubkey.unwrap()
|
||||
)
|
||||
);
|
||||
if has_sapling_components(&tx.version) {
|
||||
if tx.version.has_sapling() {
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.shielded_spends.is_empty(),
|
||||
shielded_spends_hash(&tx.shielded_spends)
|
||||
!tx.sapling_bundle
|
||||
.as_ref()
|
||||
.map_or(true, |b| b.shielded_spends.is_empty()),
|
||||
shielded_spends_hash(
|
||||
tx.sapling_bundle
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.shielded_spends
|
||||
.as_slice()
|
||||
)
|
||||
);
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.shielded_outputs.is_empty(),
|
||||
shielded_outputs_hash(&tx.shielded_outputs)
|
||||
!tx.sapling_bundle
|
||||
.as_ref()
|
||||
.map_or(true, |b| b.shielded_outputs.is_empty()),
|
||||
shielded_outputs_hash(
|
||||
tx.sapling_bundle
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.shielded_outputs
|
||||
.as_slice()
|
||||
)
|
||||
);
|
||||
}
|
||||
update_u32!(h, tx.lock_time, tmp);
|
||||
update_u32!(h, tx.expiry_height.into(), tmp);
|
||||
if has_sapling_components(&tx.version) {
|
||||
h.update(&tx.value_balance.to_i64_le_bytes());
|
||||
if tx.version.has_sapling() {
|
||||
h.update(&tx.sapling_value_balance().to_i64_le_bytes());
|
||||
}
|
||||
update_u32!(h, hash_type, tmp);
|
||||
|
||||
|
|
|
@ -1,41 +1,13 @@
|
|||
use ff::Field;
|
||||
use rand_core::OsRng;
|
||||
use std::io;
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::{
|
||||
consensus::{BranchId, TestNetwork},
|
||||
constants::SPENDING_KEY_GENERATOR,
|
||||
sapling::redjubjub::PrivateKey,
|
||||
};
|
||||
|
||||
use super::{
|
||||
builder::Builder,
|
||||
components::Amount,
|
||||
sighash::{signature_hash, SignableInput},
|
||||
Transaction, TransactionData, Unauthorized,
|
||||
Transaction,
|
||||
};
|
||||
|
||||
use super::testing::{arb_branch_id, arb_tx};
|
||||
|
||||
fn auth_and_freeze(
|
||||
txdata: TransactionData<Unauthorized>,
|
||||
branch_id: BranchId,
|
||||
) -> io::Result<Transaction> {
|
||||
Builder::<TestNetwork, OsRng>::apply_authorization(
|
||||
txdata,
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
None,
|
||||
None,
|
||||
#[cfg(any(feature = "nu5", feature = "zfuture"))]
|
||||
None,
|
||||
#[cfg(feature = "zfuture")]
|
||||
None,
|
||||
)
|
||||
.freeze(branch_id)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_read_write() {
|
||||
let data = &self::data::tx_read_write::TX_READ_WRITE;
|
||||
|
@ -50,49 +22,6 @@ fn tx_read_write() {
|
|||
assert_eq!(&data[..], &encoded[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_write_rejects_unexpected_joinsplit_pubkey() {
|
||||
// Succeeds without a JoinSplit pubkey
|
||||
assert!(auth_and_freeze(TransactionData::new(), BranchId::Canopy).is_ok());
|
||||
|
||||
// Fails with an unexpected JoinSplit pubkey
|
||||
{
|
||||
let mut tx = TransactionData::new();
|
||||
tx.joinsplit_pubkey = Some([0; 32]);
|
||||
assert!(auth_and_freeze(tx, BranchId::Canopy).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_write_rejects_unexpected_joinsplit_sig() {
|
||||
// Succeeds without a JoinSplit signature
|
||||
assert!(auth_and_freeze(TransactionData::new(), BranchId::Canopy).is_ok());
|
||||
|
||||
// Fails with an unexpected JoinSplit signature
|
||||
{
|
||||
let mut tx = TransactionData::new();
|
||||
tx.joinsplit_sig = Some([0; 64]);
|
||||
assert!(auth_and_freeze(tx, BranchId::Canopy).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_write_rejects_unexpected_binding_sig() {
|
||||
// Succeeds without a binding signature
|
||||
assert!(auth_and_freeze(TransactionData::new(), BranchId::Canopy).is_ok());
|
||||
|
||||
// Fails with an unexpected binding signature
|
||||
{
|
||||
let mut rng = OsRng;
|
||||
let sk = PrivateKey(jubjub::Fr::random(&mut rng));
|
||||
let sig = sk.sign(b"Foo bar", &mut rng, SPENDING_KEY_GENERATOR);
|
||||
|
||||
let mut tx = TransactionData::new();
|
||||
tx.binding_sig = Some(sig);
|
||||
assert!(auth_and_freeze(tx, BranchId::Canopy).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn tx_serialization_roundtrip(tx in arb_branch_id().prop_flat_map(arb_tx)) {
|
||||
|
@ -109,7 +38,7 @@ proptest! {
|
|||
#[cfg(feature = "zfuture")]
|
||||
assert_eq!(tx.tze_outputs, txo.tze_outputs);
|
||||
assert_eq!(tx.lock_time, txo.lock_time);
|
||||
assert_eq!(tx.value_balance, txo.value_balance);
|
||||
assert_eq!(tx.sapling_value_balance(), txo.sapling_value_balance());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,7 +85,7 @@ fn zip_0143() {
|
|||
};
|
||||
|
||||
assert_eq!(
|
||||
signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, signable_input),
|
||||
signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, signable_input).as_ref(),
|
||||
tv.sighash
|
||||
);
|
||||
}
|
||||
|
@ -176,7 +105,7 @@ fn zip_0243() {
|
|||
};
|
||||
|
||||
assert_eq!(
|
||||
signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, signable_input),
|
||||
signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, signable_input).as_ref(),
|
||||
tv.sighash
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue