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