Merge pull request #392 from nuttycom/feature/zip-225-bundles
ZIP-225/244 #3: Create separate bundles for each part of the transaction.
This commit is contained in:
commit
4c4d226f40
|
@ -238,10 +238,14 @@ where
|
|||
},
|
||||
RecipientAddress::Transparent(addr) => {
|
||||
let script = addr.script();
|
||||
tx.vout
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, tx_out)| tx_out.script_pubkey == script)
|
||||
tx.transparent_bundle
|
||||
.as_ref()
|
||||
.and_then(|b| {
|
||||
b.vout
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, tx_out)| tx_out.script_pubkey == script)
|
||||
})
|
||||
.map(|(index, _)| index)
|
||||
.expect("we sent to this address")
|
||||
}
|
||||
|
|
|
@ -44,27 +44,30 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
|
|||
) -> Vec<DecryptedOutput> {
|
||||
let mut decrypted = vec![];
|
||||
|
||||
for (account, extfvk) in extfvks.iter() {
|
||||
let ivk = extfvk.fvk.vk.ivk();
|
||||
let ovk = extfvk.fvk.ovk;
|
||||
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() {
|
||||
let ((note, to, memo), outgoing) =
|
||||
match try_sapling_note_decryption(params, height, &ivk, output) {
|
||||
Some(ret) => (ret, false),
|
||||
None => match try_sapling_output_recovery(params, height, &ovk, output) {
|
||||
Some(ret) => (ret, true),
|
||||
None => continue,
|
||||
},
|
||||
};
|
||||
decrypted.push(DecryptedOutput {
|
||||
index,
|
||||
note,
|
||||
account: *account,
|
||||
to,
|
||||
memo,
|
||||
outgoing,
|
||||
})
|
||||
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),
|
||||
None => match try_sapling_output_recovery(params, height, &ovk, output) {
|
||||
Some(ret) => (ret, true),
|
||||
None => continue,
|
||||
},
|
||||
};
|
||||
|
||||
decrypted.push(DecryptedOutput {
|
||||
index,
|
||||
note,
|
||||
account: *account,
|
||||
to,
|
||||
memo,
|
||||
outgoing,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,8 +489,10 @@ 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 {
|
||||
wallet::mark_spent(up, tx_ref, &spend.nullifier)?;
|
||||
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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -17,3 +17,9 @@ ff = "0.9"
|
|||
jubjub = "0.6"
|
||||
rand_core = "0.6"
|
||||
zcash_proofs = { version = "0.5", path = "../zcash_proofs" }
|
||||
orchard = { git = "https://github.com/zcash/orchard", branch = "main" }
|
||||
secp256k1 = { version = "0.20", features = ["rand", "bitcoin_hashes"] }
|
||||
|
||||
[features]
|
||||
transparent-inputs = []
|
||||
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
use std::convert::TryFrom;
|
||||
use zcash_primitives::consensus::{BlockHeight, BranchId};
|
||||
use zcash_primitives::extensions::transparent::{Error, Extension, Precondition, Witness};
|
||||
use zcash_primitives::transaction::{components::TzeOut, Transaction};
|
||||
use zcash_primitives::extensions::transparent::{
|
||||
AuthData, Error, Extension, Precondition, Witness,
|
||||
};
|
||||
use zcash_primitives::transaction::{components::tze::TzeOut, Transaction};
|
||||
|
||||
use crate::transparent::demo;
|
||||
|
||||
|
@ -67,7 +69,7 @@ pub trait Epoch {
|
|||
fn verify<'a>(
|
||||
&self,
|
||||
precondition: &Precondition,
|
||||
witness: &Witness,
|
||||
witness: &Witness<AuthData>,
|
||||
ctx: &Context<'a>,
|
||||
) -> Result<(), Error<Self::Error>>;
|
||||
}
|
||||
|
@ -76,15 +78,18 @@ pub trait Epoch {
|
|||
/// by the context.
|
||||
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.joinsplits.is_empty()
|
||||
self.tx.transparent_bundle.is_none()
|
||||
&& self.tx.sapling_bundle.is_none()
|
||||
&& self.tx.sprout_bundle.is_none()
|
||||
&& self.tx.orchard_bundle.is_none()
|
||||
}
|
||||
|
||||
fn tx_tze_outputs(&self) -> &[TzeOut] {
|
||||
&self.tx.tze_outputs
|
||||
if let Some(bundle) = &self.tx.tze_bundle {
|
||||
&bundle.vout
|
||||
} else {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,7 +103,7 @@ impl Epoch for EpochVTest {
|
|||
fn verify<'a>(
|
||||
&self,
|
||||
precondition: &Precondition,
|
||||
witness: &Witness,
|
||||
witness: &Witness<AuthData>,
|
||||
ctx: &Context<'a>,
|
||||
) -> Result<(), Error<Self::Error>> {
|
||||
let ext_id = ExtensionId::try_from(precondition.extension_id)
|
||||
|
|
|
@ -21,12 +21,16 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use blake2b_simd::Params;
|
||||
|
||||
use zcash_primitives::{
|
||||
extensions::transparent::{Extension, ExtensionTxBuilder, FromPayload, ToPayload},
|
||||
transaction::components::{amount::Amount, TzeOut, TzeOutPoint},
|
||||
transaction::components::{
|
||||
amount::Amount,
|
||||
tze::{OutPoint, TzeOut},
|
||||
},
|
||||
};
|
||||
|
||||
/// Types and constants used for Mode 0 (open a channel)
|
||||
|
@ -336,6 +340,20 @@ pub struct DemoBuilder<B> {
|
|||
pub extension_id: u32,
|
||||
}
|
||||
|
||||
impl<B> Deref for DemoBuilder<B> {
|
||||
type Target = B;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.txn_builder
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> DerefMut for DemoBuilder<B> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.txn_builder
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur in construction of transactions using `DemoBuilder`.
|
||||
#[derive(Debug)]
|
||||
pub enum DemoBuildError<E> {
|
||||
|
@ -356,7 +374,7 @@ pub enum DemoBuildError<E> {
|
|||
|
||||
/// Convenience methods for use with [`zcash_primitives::transaction::builder::Builder`]
|
||||
/// for constructing transactions that utilize the features of the demo extension.
|
||||
impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<&mut B> {
|
||||
impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<B> {
|
||||
/// Add a channel-opening precondition to the outputs of the transaction under
|
||||
/// construction.
|
||||
pub fn demo_open(
|
||||
|
@ -374,7 +392,7 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<&mut B> {
|
|||
/// precondition to the transaction under construction.
|
||||
pub fn demo_transfer_to_close(
|
||||
&mut self,
|
||||
prevout: (TzeOutPoint, TzeOut),
|
||||
prevout: (OutPoint, TzeOut),
|
||||
transfer_amount: Amount,
|
||||
preimage_1: [u8; 32],
|
||||
hash_2: [u8; 32],
|
||||
|
@ -416,7 +434,7 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<&mut B> {
|
|||
/// Add a channel-closing witness to the transaction under construction.
|
||||
pub fn demo_close(
|
||||
&mut self,
|
||||
prevout: (TzeOutPoint, TzeOut),
|
||||
prevout: (OutPoint, TzeOut),
|
||||
preimage_2: [u8; 32],
|
||||
) -> Result<(), DemoBuildError<B::BuildError>> {
|
||||
let hash_2 = {
|
||||
|
@ -467,20 +485,19 @@ mod tests {
|
|||
use zcash_proofs::prover::LocalTxProver;
|
||||
|
||||
use zcash_primitives::{
|
||||
consensus::{BlockHeight, NetworkUpgrade, Parameters},
|
||||
consensus::{BlockHeight, BranchId, NetworkUpgrade, Parameters},
|
||||
constants,
|
||||
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::{
|
||||
amount::{Amount, DEFAULT_FEE},
|
||||
TzeIn, TzeOut, TzeOutPoint,
|
||||
tze::{Bundle, OutPoint, TzeIn, TzeOut},
|
||||
},
|
||||
Transaction, TransactionData,
|
||||
Transaction, TransactionData, TxVersion,
|
||||
},
|
||||
zip32::ExtendedSpendingKey,
|
||||
};
|
||||
|
@ -604,15 +621,24 @@ mod tests {
|
|||
/// by the context.
|
||||
impl<'a> Context for Ctx<'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.joinsplits.is_empty()
|
||||
self.tx.transparent_bundle.is_none()
|
||||
&& self.tx.sprout_bundle.is_none()
|
||||
&& self.tx.sapling_bundle.is_none()
|
||||
&& self.tx.orchard_bundle.is_none()
|
||||
}
|
||||
|
||||
fn tx_tze_outputs(&self) -> &[TzeOut] {
|
||||
&self.tx.tze_outputs
|
||||
match &self.tx.tze_bundle {
|
||||
Some(b) => &b.vout,
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn demo_builder<'a>(height: BlockHeight) -> DemoBuilder<Builder<'a, FutureNetwork, OsRng>> {
|
||||
DemoBuilder {
|
||||
txn_builder: Builder::new(FutureNetwork, height),
|
||||
extension_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -657,47 +683,83 @@ mod tests {
|
|||
precondition: tze::Precondition::from(0, &Precondition::open(hash_1)),
|
||||
};
|
||||
|
||||
let mut mtx_a = TransactionData::zfuture();
|
||||
mtx_a.tze_outputs.push(out_a);
|
||||
let tx_a = mtx_a.freeze().unwrap();
|
||||
let tx_a = TransactionData {
|
||||
version: TxVersion::ZFuture,
|
||||
lock_time: 0,
|
||||
expiry_height: 0u32.into(),
|
||||
transparent_bundle: None,
|
||||
sprout_bundle: None,
|
||||
sapling_bundle: None,
|
||||
orchard_bundle: None,
|
||||
tze_bundle: Some(Bundle {
|
||||
vin: vec![],
|
||||
vout: vec![out_a],
|
||||
}),
|
||||
}
|
||||
.freeze(BranchId::ZFuture)
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// Transfer
|
||||
//
|
||||
|
||||
let in_b = TzeIn {
|
||||
prevout: TzeOutPoint::new(tx_a.txid(), 0),
|
||||
prevout: OutPoint::new(tx_a.txid(), 0),
|
||||
witness: tze::Witness::from(0, &Witness::open(preimage_1)),
|
||||
};
|
||||
let out_b = TzeOut {
|
||||
value: Amount::from_u64(1).unwrap(),
|
||||
precondition: tze::Precondition::from(0, &Precondition::close(hash_2)),
|
||||
};
|
||||
let mut mtx_b = TransactionData::zfuture();
|
||||
mtx_b.tze_inputs.push(in_b);
|
||||
mtx_b.tze_outputs.push(out_b);
|
||||
let tx_b = mtx_b.freeze().unwrap();
|
||||
|
||||
let tx_b = TransactionData {
|
||||
version: TxVersion::ZFuture,
|
||||
lock_time: 0,
|
||||
expiry_height: 0u32.into(),
|
||||
transparent_bundle: None,
|
||||
sprout_bundle: None,
|
||||
sapling_bundle: None,
|
||||
orchard_bundle: None,
|
||||
tze_bundle: Some(Bundle {
|
||||
vin: vec![in_b],
|
||||
vout: vec![out_b],
|
||||
}),
|
||||
}
|
||||
.freeze(BranchId::ZFuture)
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// Closing transaction
|
||||
//
|
||||
|
||||
let in_c = TzeIn {
|
||||
prevout: TzeOutPoint::new(tx_b.txid(), 0),
|
||||
prevout: OutPoint::new(tx_b.txid(), 0),
|
||||
witness: tze::Witness::from(0, &Witness::close(preimage_2)),
|
||||
};
|
||||
|
||||
let mut mtx_c = TransactionData::zfuture();
|
||||
mtx_c.tze_inputs.push(in_c);
|
||||
let tx_c = mtx_c.freeze().unwrap();
|
||||
let tx_c = TransactionData {
|
||||
version: TxVersion::ZFuture,
|
||||
lock_time: 0,
|
||||
expiry_height: 0u32.into(),
|
||||
transparent_bundle: None,
|
||||
sprout_bundle: None,
|
||||
sapling_bundle: None,
|
||||
orchard_bundle: None,
|
||||
tze_bundle: Some(Bundle {
|
||||
vin: vec![in_c],
|
||||
vout: vec![],
|
||||
}),
|
||||
}
|
||||
.freeze(BranchId::ZFuture)
|
||||
.unwrap();
|
||||
|
||||
// Verify tx_b
|
||||
{
|
||||
let ctx = Ctx { tx: &tx_b };
|
||||
assert_eq!(
|
||||
Program.verify(
|
||||
&tx_a.tze_outputs[0].precondition,
|
||||
&tx_b.tze_inputs[0].witness,
|
||||
&tx_a.tze_bundle.as_ref().unwrap().vout[0].precondition,
|
||||
&tx_b.tze_bundle.as_ref().unwrap().vin[0].witness,
|
||||
&ctx
|
||||
),
|
||||
Ok(())
|
||||
|
@ -709,8 +771,8 @@ mod tests {
|
|||
let ctx = Ctx { tx: &tx_c };
|
||||
assert_eq!(
|
||||
Program.verify(
|
||||
&tx_b.tze_outputs[0].precondition,
|
||||
&tx_c.tze_inputs[0].witness,
|
||||
&tx_b.tze_bundle.as_ref().unwrap().vout[0].precondition,
|
||||
&tx_c.tze_bundle.as_ref().unwrap().vin[0].witness,
|
||||
&ctx
|
||||
),
|
||||
Ok(())
|
||||
|
@ -738,7 +800,6 @@ mod tests {
|
|||
//
|
||||
|
||||
let mut rng = OsRng;
|
||||
let mut builder_a = Builder::new(FutureNetwork, tx_height);
|
||||
|
||||
// create some inputs to spend
|
||||
let extsk = ExtendedSpendingKey::master(&[]);
|
||||
|
@ -753,61 +814,50 @@ mod tests {
|
|||
tree.append(cm1).unwrap();
|
||||
let witness1 = IncrementalWitness::from_tree(&tree);
|
||||
|
||||
let mut builder_a = demo_builder(tx_height);
|
||||
builder_a
|
||||
.add_sapling_spend(extsk, *to.diversifier(), note1, witness1.path().unwrap())
|
||||
.unwrap();
|
||||
|
||||
let mut db_a = DemoBuilder {
|
||||
txn_builder: &mut builder_a,
|
||||
extension_id: 0,
|
||||
};
|
||||
|
||||
let value = Amount::from_u64(100000).unwrap();
|
||||
let (h1, h2) = demo_hashes(&preimage_1, &preimage_2);
|
||||
db_a.demo_open(value, h1)
|
||||
builder_a
|
||||
.demo_open(value, h1)
|
||||
.map_err(|e| format!("open failure: {:?}", e))
|
||||
.unwrap();
|
||||
let (tx_a, _) = builder_a
|
||||
.txn_builder
|
||||
.build(&prover)
|
||||
.map_err(|e| format!("build failure: {:?}", e))
|
||||
.unwrap();
|
||||
let tze_a = tx_a.tze_bundle.as_ref().unwrap();
|
||||
|
||||
//
|
||||
// Transfer
|
||||
//
|
||||
|
||||
let mut builder_b = Builder::new(FutureNetwork, tx_height + 1);
|
||||
let mut db_b = DemoBuilder {
|
||||
txn_builder: &mut builder_b,
|
||||
extension_id: 0,
|
||||
};
|
||||
let prevout_a = (
|
||||
TzeOutPoint::new(tx_a.txid(), 0),
|
||||
tx_a.tze_outputs[0].clone(),
|
||||
);
|
||||
let mut builder_b = demo_builder(tx_height + 1);
|
||||
let prevout_a = (OutPoint::new(tx_a.txid(), 0), tze_a.vout[0].clone());
|
||||
let value_xfr = (value - DEFAULT_FEE).unwrap();
|
||||
db_b.demo_transfer_to_close(prevout_a, value_xfr, preimage_1, h2)
|
||||
builder_b
|
||||
.demo_transfer_to_close(prevout_a, value_xfr, preimage_1, h2)
|
||||
.map_err(|e| format!("transfer failure: {:?}", e))
|
||||
.unwrap();
|
||||
let (tx_b, _) = builder_b
|
||||
.txn_builder
|
||||
.build(&prover)
|
||||
.map_err(|e| format!("build failure: {:?}", e))
|
||||
.unwrap();
|
||||
let tze_b = tx_b.tze_bundle.as_ref().unwrap();
|
||||
|
||||
//
|
||||
// Closing transaction
|
||||
//
|
||||
|
||||
let mut builder_c = Builder::new(FutureNetwork, tx_height + 2);
|
||||
let mut db_c = DemoBuilder {
|
||||
txn_builder: &mut builder_c,
|
||||
extension_id: 0,
|
||||
};
|
||||
let prevout_b = (
|
||||
TzeOutPoint::new(tx_a.txid(), 0),
|
||||
tx_b.tze_outputs[0].clone(),
|
||||
);
|
||||
db_c.demo_close(prevout_b, preimage_2)
|
||||
let mut builder_c = demo_builder(tx_height + 2);
|
||||
let prevout_b = (OutPoint::new(tx_a.txid(), 0), tze_b.vout[0].clone());
|
||||
builder_c
|
||||
.demo_close(prevout_b, preimage_2)
|
||||
.map_err(|e| format!("close failure: {:?}", e))
|
||||
.unwrap();
|
||||
|
||||
|
@ -819,29 +869,23 @@ mod tests {
|
|||
.unwrap();
|
||||
|
||||
let (tx_c, _) = builder_c
|
||||
.txn_builder
|
||||
.build(&prover)
|
||||
.map_err(|e| format!("build failure: {:?}", e))
|
||||
.unwrap();
|
||||
let tze_c = tx_c.tze_bundle.as_ref().unwrap();
|
||||
|
||||
// Verify tx_b
|
||||
let ctx0 = Ctx { tx: &tx_b };
|
||||
assert_eq!(
|
||||
Program.verify(
|
||||
&tx_a.tze_outputs[0].precondition,
|
||||
&tx_b.tze_inputs[0].witness,
|
||||
&ctx0
|
||||
),
|
||||
Program.verify(&tze_a.vout[0].precondition, &tze_b.vin[0].witness, &ctx0),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
// Verify tx_c
|
||||
let ctx1 = Ctx { tx: &tx_c };
|
||||
assert_eq!(
|
||||
Program.verify(
|
||||
&tx_b.tze_outputs[0].precondition,
|
||||
&tx_c.tze_inputs[0].witness,
|
||||
&ctx1
|
||||
),
|
||||
Program.verify(&tze_b.vout[0].precondition, &tze_c.vin[0].witness, &ctx1),
|
||||
Ok(())
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,31 @@ and this library adheres to Rust's notion of
|
|||
- `sapling::builder` for Sapling transaction components.
|
||||
- `transparent::builder` for transparent transaction components.
|
||||
- `tze::builder` for TZE transaction components.
|
||||
- `orchard` parsing and serialization for Orchard transaction components.
|
||||
- `zcash_primitives::transaction::Authorization` a trait representing a type-level
|
||||
record of authorization types that correspond to signatures, witnesses, and
|
||||
proofs for each Zcash sub-protocol (transparent, Sprout, Sapling, TZE, and
|
||||
Orchard). This type makes it possible to encode a type-safe state machine
|
||||
for the application of authorizing data to a transaction; implementations of
|
||||
this trait represent different states of the authorization process.
|
||||
- New bundle types under the `zcash_primitives::transaction` submodules, one for
|
||||
each Zcash sub-protocol. These are now used instead of bare fields
|
||||
within the `TransactionData` type.
|
||||
- `components::sapling::Bundle` bundle of
|
||||
Sapling transaction elements. This new struct is parameterized by a
|
||||
type bounded on a newly added `sapling::Authorization` trait which
|
||||
is used to enable static reasoning about the state of Sapling proofs and
|
||||
authorizing data, as described above.
|
||||
- `components::transparent::Bundle` bundle of
|
||||
transparent transaction elements. This new struct is parameterized by a
|
||||
type bounded on a newly added `transparent::Authorization` trait which
|
||||
is used to enable static reasoning about the state of transparent witness
|
||||
data, as described above.
|
||||
- `components::tze::Bundle` bundle of TZE
|
||||
transaction elements. This new struct is parameterized by a
|
||||
type bounded on a newly added `tze::Authorization` trait which
|
||||
is used to enable static reasoning about the state of TZE witness
|
||||
data, as described above.
|
||||
|
||||
### Changed
|
||||
- MSRV is now 1.51.0.
|
||||
|
@ -48,6 +73,23 @@ and this library adheres to Rust's notion of
|
|||
- `Builder::build` no longer takes a consensus branch ID parameter. The
|
||||
builder now selects the correct consensus branch ID for the given target
|
||||
height.
|
||||
- The `zcash_primitives::transaction::TransactionData` struct has been modified
|
||||
such that it now contains common header information, and then contains
|
||||
a separate `Bundle` value for each sub-protocol (transparent, Sprout, Sapling,
|
||||
and TZE) and an Orchard bundle value has been added. `TransactionData` is now
|
||||
parameterized by a type bounded on the newly added
|
||||
`zcash_primitives::transaction::Authorization` trait. This bound has been
|
||||
propagated to the individual transaction builders, such that the authorization
|
||||
state of a transaction is clearly represented in the type and the presence
|
||||
or absence of witness and/or proof data is statically known, instead of being only
|
||||
determined at runtime via the presence or absence of `Option`al values.
|
||||
- `zcash_primitives::transaction::components::sapling` parsing and serialization
|
||||
have been adapted for use with the new `sapling::Bundle` type.
|
||||
- `zcash_primitives::transaction::Transaction` parsing and serialization
|
||||
have been adapted for use with the new `TransactionData` organization.
|
||||
- Generators for property testing have been moved out of the main transaction
|
||||
module such that they are now colocated in the modules with the types
|
||||
that they generate.
|
||||
|
||||
## [0.5.0] - 2021-03-26
|
||||
### Added
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -4,4 +4,3 @@
|
|||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 23a823e9da7e6ae62a79153ab97362dd9d81b8d9eafc396c87870dfa8aa7354c # shrinks to tx = Transaction { txid: TxId([67, 236, 122, 87, 159, 85, 97, 164, 42, 126, 150, 55, 173, 65, 86, 103, 39, 53, 166, 88, 190, 39, 82, 24, 24, 1, 247, 35, 186, 51, 22, 210]), data: TransactionData( version = Sprout(1), vin = [], vout = [], lock_time = 0, expiry_height = BlockHeight(0), value_balance = Amount(1), shielded_spends = [], shielded_outputs = [], joinsplits = [], joinsplit_pubkey = None, binding_sig = None) }
|
||||
|
|
|
@ -424,7 +424,7 @@ pub const ZIP212_GRACE_PERIOD: u32 = 32256;
|
|||
///
|
||||
/// See [ZIP 200](https://zips.z.cash/zip-0200) for more details.
|
||||
///
|
||||
/// [`signature_hash`]: crate::transaction::signature_hash
|
||||
/// [`signature_hash`]: crate::transaction::sighash::signature_hash
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum BranchId {
|
||||
/// The consensus rules at the launch of Zcash.
|
||||
|
@ -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"))]
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
//! Core traits and structs for Transparent Zcash Extensions.
|
||||
|
||||
use crate::transaction::components::{Amount, TzeOut, TzeOutPoint};
|
||||
use std::fmt;
|
||||
|
||||
use crate::transaction::components::{
|
||||
tze::{self, TzeOut},
|
||||
Amount,
|
||||
};
|
||||
|
||||
/// A typesafe wrapper for witness payloads
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AuthData(pub Vec<u8>);
|
||||
|
||||
/// Binary parsing capability for TZE preconditions & witnesses.
|
||||
///
|
||||
/// Serialization formats interpreted by implementations of this trait become consensus-critical
|
||||
|
@ -62,28 +70,28 @@ impl Precondition {
|
|||
/// treated as opaque to all but the extension corresponding to the encapsulated `extension_id`
|
||||
/// value.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Witness {
|
||||
pub struct Witness<T> {
|
||||
pub extension_id: u32,
|
||||
pub mode: u32,
|
||||
pub payload: Vec<u8>,
|
||||
pub payload: T,
|
||||
}
|
||||
|
||||
impl Witness {
|
||||
impl Witness<AuthData> {
|
||||
/// Produce the intermediate format for an extension-specific witness
|
||||
/// type.
|
||||
pub fn from<P: ToPayload>(extension_id: u32, value: &P) -> Witness {
|
||||
pub fn from<P: ToPayload>(extension_id: u32, value: &P) -> Witness<AuthData> {
|
||||
let (mode, payload) = value.to_payload();
|
||||
Witness {
|
||||
extension_id,
|
||||
mode,
|
||||
payload,
|
||||
payload: AuthData(payload),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to parse an extension-specific witness value from the
|
||||
/// intermediate representation.
|
||||
pub fn try_to<P: FromPayload>(&self) -> Result<P, P::Error> {
|
||||
P::from_payload(self.mode, &self.payload)
|
||||
P::from_payload(self.mode, &self.payload.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,7 +145,7 @@ pub trait Extension<C> {
|
|||
fn verify(
|
||||
&self,
|
||||
precondition: &Precondition,
|
||||
witness: &Witness,
|
||||
witness: &Witness<AuthData>,
|
||||
context: &C,
|
||||
) -> Result<(), Self::Error>
|
||||
where
|
||||
|
@ -146,7 +154,7 @@ pub trait Extension<C> {
|
|||
{
|
||||
self.verify_inner(
|
||||
&Self::Precondition::from_payload(precondition.mode, &precondition.payload)?,
|
||||
&Self::Witness::from_payload(witness.mode, &witness.payload)?,
|
||||
&Self::Witness::from_payload(witness.mode, &witness.payload.0)?,
|
||||
&context,
|
||||
)
|
||||
}
|
||||
|
@ -178,7 +186,7 @@ pub trait ExtensionTxBuilder<'a> {
|
|||
&mut self,
|
||||
extension_id: u32,
|
||||
mode: u32,
|
||||
prevout: (TzeOutPoint, TzeOut),
|
||||
prevout: (tze::OutPoint, TzeOut),
|
||||
witness_builder: WBuilder,
|
||||
) -> Result<(), Self::BuildError>
|
||||
where
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
//! Structs for building transactions.
|
||||
|
||||
use core::array;
|
||||
use rand::{rngs::OsRng, CryptoRng, RngCore};
|
||||
use std::array;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use rand::{rngs::OsRng, CryptoRng, RngCore};
|
||||
use orchard::bundle::{self as orchard};
|
||||
|
||||
use crate::{
|
||||
consensus::{self, BlockHeight, BranchId},
|
||||
|
@ -13,15 +15,20 @@ use crate::{
|
|||
memo::MemoBytes,
|
||||
merkle_tree::MerklePath,
|
||||
sapling::{
|
||||
keys::OutgoingViewingKey, prover::TxProver, Diversifier, Node, Note, PaymentAddress,
|
||||
keys::OutgoingViewingKey, prover::TxProver, redjubjub, Diversifier, Node, Note,
|
||||
PaymentAddress,
|
||||
},
|
||||
transaction::{
|
||||
components::{
|
||||
amount::{Amount, DEFAULT_FEE},
|
||||
sapling::builder::{self as sapling, SaplingBuilder, SaplingMetadata},
|
||||
transparent::builder::{self as transparent, TransparentBuilder},
|
||||
sapling::{
|
||||
self,
|
||||
builder::{SaplingBuilder, SaplingMetadata},
|
||||
},
|
||||
transparent::{self, builder::TransparentBuilder},
|
||||
},
|
||||
signature_hash_data, SignableInput, Transaction, TransactionData, TxVersion, SIGHASH_ALL,
|
||||
signature_hash_data, SignableInput, Transaction, TransactionData, TxVersion, Unauthorized,
|
||||
SIGHASH_ALL,
|
||||
},
|
||||
zip32::ExtendedSpendingKey,
|
||||
};
|
||||
|
@ -30,14 +37,14 @@ use crate::{
|
|||
use std::marker::PhantomData;
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use crate::transaction::components::{OutPoint, TxOut};
|
||||
use crate::{legacy::Script, transaction::components::transparent::TxOut};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use crate::{
|
||||
extensions::transparent::{ExtensionTxBuilder, ToPayload},
|
||||
extensions::transparent::{AuthData, ExtensionTxBuilder, ToPayload},
|
||||
transaction::components::{
|
||||
tze::builder::{self as tzebuilder, TzeBuilder},
|
||||
TzeOut, TzeOutPoint,
|
||||
tze::builder::TzeBuilder,
|
||||
tze::{self, TzeOut},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -51,10 +58,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 {
|
||||
|
@ -109,7 +116,7 @@ enum ChangeAddress {
|
|||
}
|
||||
|
||||
/// Generates a [`Transaction`] from its inputs and outputs.
|
||||
pub struct Builder<'a, P: consensus::Parameters, R: RngCore> {
|
||||
pub struct Builder<'a, P, R> {
|
||||
params: P,
|
||||
rng: R,
|
||||
target_height: BlockHeight,
|
||||
|
@ -119,7 +126,7 @@ pub struct Builder<'a, P: consensus::Parameters, R: RngCore> {
|
|||
sapling_builder: SaplingBuilder<P>,
|
||||
change_address: Option<ChangeAddress>,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_builder: TzeBuilder<'a, TransactionData>,
|
||||
tze_builder: TzeBuilder<'a, TransactionData<Unauthorized>>,
|
||||
#[cfg(not(feature = "zfuture"))]
|
||||
tze_builder: PhantomData<&'a ()>,
|
||||
progress_notifier: Option<Sender<Progress>>,
|
||||
|
@ -168,7 +175,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(),
|
||||
|
@ -213,7 +220,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
|
||||
|
@ -316,10 +323,10 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
}
|
||||
}
|
||||
|
||||
let (vin, vout) = self.transparent_builder.build();
|
||||
let transparent_bundle = 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,
|
||||
|
@ -331,25 +338,18 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
.map_err(Error::SaplingBuild)?;
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
let (tze_inputs, tze_outputs) = self.tze_builder.build();
|
||||
let tze_bundle = self.tze_builder.build();
|
||||
|
||||
let mut mtx = TransactionData {
|
||||
let unauthed_tx = TransactionData {
|
||||
version,
|
||||
vin,
|
||||
vout,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_inputs,
|
||||
#[cfg(feature = "zfuture")]
|
||||
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,
|
||||
transparent_bundle,
|
||||
sprout_bundle: None,
|
||||
sapling_bundle,
|
||||
orchard_bundle: None,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_bundle,
|
||||
};
|
||||
|
||||
//
|
||||
|
@ -358,68 +358,126 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
|
||||
let mut sighash = [0u8; 32];
|
||||
sighash.copy_from_slice(&signature_hash_data(
|
||||
&mtx,
|
||||
&unauthed_tx,
|
||||
consensus_branch_id,
|
||||
SIGHASH_ALL,
|
||||
SignableInput::Shielded,
|
||||
));
|
||||
|
||||
let (sapling_spend_auth_sigs, sapling_binding_sig) = self
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
let transparent_sigs = self
|
||||
.transparent_builder
|
||||
.create_signatures(&unauthed_tx, consensus_branch_id);
|
||||
|
||||
let sapling_sigs = self
|
||||
.sapling_builder
|
||||
.create_signatures(prover, &mut ctx, &mut self.rng, &sighash, &tx_metadata)
|
||||
.map_err(Error::SaplingBuild)?;
|
||||
|
||||
for (i, spend_auth_sig) in sapling_spend_auth_sigs.into_iter().enumerate() {
|
||||
mtx.shielded_spends[i].spend_auth_sig = spend_auth_sig;
|
||||
}
|
||||
mtx.binding_sig = sapling_binding_sig;
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
{
|
||||
// Create TZE input witnesses
|
||||
let tze_payloads = self
|
||||
.tze_builder
|
||||
.create_witnesses(&mtx)
|
||||
.map_err(Error::TzeBuild)?;
|
||||
for (i, payload) in tze_payloads.into_iter().enumerate() {
|
||||
mtx.tze_inputs[i].witness.payload = payload;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
{
|
||||
let script_sigs = self
|
||||
.transparent_builder
|
||||
.create_signatures(&mtx, consensus_branch_id);
|
||||
|
||||
for (i, sig) in script_sigs.into_iter().enumerate() {
|
||||
mtx.vin[i].script_sig = sig;
|
||||
}
|
||||
}
|
||||
let tze_witnesses = self
|
||||
.tze_builder
|
||||
.create_witnesses(&unauthed_tx)
|
||||
.map_err(Error::TzeBuild)?;
|
||||
|
||||
Ok((
|
||||
mtx.freeze().expect("Transaction should be complete"),
|
||||
Self::apply_signatures(
|
||||
consensus_branch_id,
|
||||
unauthed_tx,
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
transparent_sigs,
|
||||
sapling_sigs,
|
||||
None,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_witnesses,
|
||||
)
|
||||
.expect("An IO error occurred applying signatures."),
|
||||
tx_metadata,
|
||||
))
|
||||
}
|
||||
|
||||
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<(
|
||||
Vec<<orchard::Authorized as orchard::Authorization>::SpendAuth>,
|
||||
orchard::Authorized,
|
||||
)>,
|
||||
#[cfg(feature = "zfuture")] tze_witnesses: Option<Vec<AuthData>>,
|
||||
) -> io::Result<Transaction> {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
let transparent_bundle = match (unauthed_tx.transparent_bundle, transparent_sigs) {
|
||||
(Some(bundle), Some(script_sigs)) => Some(bundle.apply_signatures(script_sigs)),
|
||||
(None, None) => None,
|
||||
(b, s) => {
|
||||
panic!(
|
||||
"Mismatch between transparent bundle ({}) and signatures ({}).",
|
||||
b.is_some(),
|
||||
s.is_some()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
let transparent_bundle = unauthed_tx
|
||||
.transparent_bundle
|
||||
.map(|b| b.apply_signatures(vec![]));
|
||||
|
||||
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.");
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
let signed_tze_bundle = match (unauthed_tx.tze_bundle, tze_witnesses) {
|
||||
(Some(bundle), Some(witness_payloads)) => {
|
||||
Some(bundle.apply_signatures(witness_payloads))
|
||||
}
|
||||
(None, None) => None,
|
||||
_ => {
|
||||
panic!("Mismatch between TZE bundle and witnesses.")
|
||||
}
|
||||
};
|
||||
|
||||
let authorized_tx = TransactionData {
|
||||
version: unauthed_tx.version,
|
||||
lock_time: unauthed_tx.lock_time,
|
||||
expiry_height: unauthed_tx.expiry_height,
|
||||
transparent_bundle,
|
||||
sprout_bundle: unauthed_tx.sprout_bundle,
|
||||
sapling_bundle: signed_sapling_bundle,
|
||||
orchard_bundle: None,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_bundle: signed_tze_bundle,
|
||||
};
|
||||
|
||||
authorized_tx.freeze(consensus_branch_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> ExtensionTxBuilder<'a>
|
||||
for Builder<'a, P, R>
|
||||
{
|
||||
type BuildCtx = TransactionData;
|
||||
type BuildError = tzebuilder::Error;
|
||||
type BuildCtx = TransactionData<Unauthorized>;
|
||||
type BuildError = tze::builder::Error;
|
||||
|
||||
fn add_tze_input<WBuilder, W: ToPayload>(
|
||||
&mut self,
|
||||
extension_id: u32,
|
||||
mode: u32,
|
||||
prevout: (TzeOutPoint, TzeOut),
|
||||
prevout: (tze::OutPoint, TzeOut),
|
||||
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);
|
||||
|
@ -454,31 +512,6 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
Self::new_internal(params, height, rng)
|
||||
}
|
||||
|
||||
/// Creates a new `Builder` targeted for inclusion in the block with the given height,
|
||||
/// and randomness source, using default values for general transaction fields
|
||||
/// and the `ZFUTURE_TX_VERSION` and `ZFUTURE_VERSION_GROUP_ID` version identifiers.
|
||||
///
|
||||
/// # Default values
|
||||
///
|
||||
/// The expiry height will be set to the given height plus the default transaction
|
||||
/// expiry delta (20 blocks).
|
||||
///
|
||||
/// The fee will be set to the default fee (0.0001 ZEC).
|
||||
///
|
||||
/// The transaction will be constructed and serialized according to the
|
||||
/// NetworkUpgrade::ZFuture rules. This is intended only for use in
|
||||
/// integration testing of new features.
|
||||
///
|
||||
/// WARNING: DO NOT USE IN PRODUCTION
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub fn test_only_new_with_rng_zfuture(
|
||||
params: P,
|
||||
height: BlockHeight,
|
||||
rng: R,
|
||||
) -> Builder<'a, P, R> {
|
||||
Self::new_internal(params, height, rng)
|
||||
}
|
||||
|
||||
pub fn mock_build(self) -> Result<(Transaction, SaplingMetadata), Error> {
|
||||
self.build(&MockTxProver)
|
||||
}
|
||||
|
@ -496,7 +529,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},
|
||||
|
@ -524,7 +557,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))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -545,7 +578,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(),
|
||||
|
@ -561,7 +594,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]
|
||||
|
@ -598,7 +631,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))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -748,7 +781,7 @@ mod tests {
|
|||
.unwrap();
|
||||
assert_eq!(
|
||||
builder.build(&MockTxProver),
|
||||
Err(Error::SaplingBuild(sapling::Error::BindingSig))
|
||||
Err(Error::SaplingBuild(build_s::Error::BindingSig))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Structs representing the components within Zcash transactions.
|
||||
|
||||
pub mod amount;
|
||||
pub mod orchard;
|
||||
pub mod sapling;
|
||||
pub mod sprout;
|
||||
pub mod transparent;
|
||||
|
@ -13,7 +14,7 @@ pub use self::{
|
|||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub use self::tze::{TzeIn, TzeOut, TzeOutPoint};
|
||||
pub use self::tze::{TzeIn, TzeOut};
|
||||
|
||||
// π_A + π_B + π_C
|
||||
pub const GROTH_PROOF_SIZE: usize = 48 + 96 + 48;
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/// Functions for parsing & serialization of Orchard transaction components.
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use orchard::{
|
||||
bundle::{Action, Authorization, Authorized, Flags},
|
||||
note::{ExtractedNoteCommitment, Nullifier, TransmittedNoteCiphertext},
|
||||
primitives::redpallas::{self, SigType, Signature, SpendAuth, VerificationKey},
|
||||
value::ValueCommitment,
|
||||
Anchor,
|
||||
};
|
||||
|
||||
pub const FLAG_SPENDS_ENABLED: u8 = 0b0000_0001;
|
||||
pub const FLAG_OUTPUTS_ENABLED: u8 = 0b0000_0010;
|
||||
pub const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED);
|
||||
|
||||
pub fn read_value_commitment<R: Read>(mut reader: R) -> io::Result<ValueCommitment> {
|
||||
let mut bytes = [0u8; 32];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
let cv = ValueCommitment::from_bytes(&bytes);
|
||||
|
||||
if cv.is_none().into() {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid Pallas point for value commitment".to_owned(),
|
||||
))
|
||||
} else {
|
||||
Ok(cv.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_nullifier<R: Read>(mut reader: R) -> io::Result<Nullifier> {
|
||||
let mut bytes = [0u8; 32];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
let nullifier_ctopt = Nullifier::from_bytes(&bytes);
|
||||
if nullifier_ctopt.is_none().into() {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid Pallas point for nullifier".to_owned(),
|
||||
))
|
||||
} else {
|
||||
Ok(nullifier_ctopt.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_verification_key<R: Read>(mut reader: R) -> io::Result<VerificationKey<SpendAuth>> {
|
||||
let mut bytes = [0u8; 32];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
VerificationKey::try_from(bytes).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid verification key".to_owned(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_cmx<R: Read>(mut reader: R) -> io::Result<ExtractedNoteCommitment> {
|
||||
let mut bytes = [0u8; 32];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
let cmx = ExtractedNoteCommitment::from_bytes(&bytes);
|
||||
Option::from(cmx).ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid Pallas base for field cmx".to_owned(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_note_ciphertext<R: Read>(mut reader: R) -> io::Result<TransmittedNoteCiphertext> {
|
||||
let mut tnc = TransmittedNoteCiphertext {
|
||||
epk_bytes: [0u8; 32],
|
||||
enc_ciphertext: [0u8; 580],
|
||||
out_ciphertext: [0u8; 80],
|
||||
};
|
||||
|
||||
reader.read_exact(&mut tnc.epk_bytes)?;
|
||||
reader.read_exact(&mut tnc.enc_ciphertext)?;
|
||||
reader.read_exact(&mut tnc.out_ciphertext)?;
|
||||
|
||||
Ok(tnc)
|
||||
}
|
||||
|
||||
pub fn read_action_without_auth<R: Read>(mut reader: R) -> io::Result<Action<()>> {
|
||||
let cv_net = read_value_commitment(&mut reader)?;
|
||||
let nf_old = read_nullifier(&mut reader)?;
|
||||
let rk = read_verification_key(&mut reader)?;
|
||||
let cmx = read_cmx(&mut reader)?;
|
||||
let encrypted_note = read_note_ciphertext(&mut reader)?;
|
||||
|
||||
Ok(Action::from_parts(
|
||||
nf_old,
|
||||
rk,
|
||||
cmx,
|
||||
encrypted_note,
|
||||
cv_net,
|
||||
(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn read_flags<R: Read>(mut reader: R) -> io::Result<Flags> {
|
||||
let mut byte = [0u8; 1];
|
||||
reader.read_exact(&mut byte)?;
|
||||
if byte[0] & FLAGS_EXPECTED_UNSET == 0 {
|
||||
Ok(Flags::from_parts(
|
||||
byte[0] & FLAG_SPENDS_ENABLED != 0,
|
||||
byte[0] & FLAG_OUTPUTS_ENABLED != 0,
|
||||
))
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Unexpected bits set in Orchard flags value.",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_anchor<R: Read>(mut reader: R) -> io::Result<Anchor> {
|
||||
let mut anchor = Anchor([0u8; 32]);
|
||||
reader.read_exact(&mut anchor.0)?;
|
||||
Ok(anchor)
|
||||
}
|
||||
|
||||
pub fn read_signature<R: Read, T: SigType>(mut reader: R) -> io::Result<Signature<T>> {
|
||||
let mut bytes = [0u8; 64];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
Ok(Signature::from(bytes))
|
||||
}
|
||||
|
||||
pub fn write_value_commitment<W: Write>(mut writer: W, cv: &ValueCommitment) -> io::Result<()> {
|
||||
writer.write_all(&cv.to_bytes())
|
||||
}
|
||||
|
||||
pub fn write_nullifier<W: Write>(mut writer: W, nf: &Nullifier) -> io::Result<()> {
|
||||
writer.write_all(&nf.to_bytes())
|
||||
}
|
||||
|
||||
pub fn write_verification_key<W: Write>(
|
||||
mut writer: W,
|
||||
rk: &redpallas::VerificationKey<SpendAuth>,
|
||||
) -> io::Result<()> {
|
||||
writer.write_all(&<[u8; 32]>::from(rk))
|
||||
}
|
||||
|
||||
pub fn write_cmx<W: Write>(mut writer: W, cmx: &ExtractedNoteCommitment) -> io::Result<()> {
|
||||
writer.write_all(&cmx.to_bytes())
|
||||
}
|
||||
|
||||
pub fn write_note_ciphertext<W: Write>(
|
||||
mut writer: W,
|
||||
nc: &TransmittedNoteCiphertext,
|
||||
) -> io::Result<()> {
|
||||
writer.write_all(&nc.epk_bytes)?;
|
||||
writer.write_all(&nc.enc_ciphertext)?;
|
||||
writer.write_all(&nc.out_ciphertext)
|
||||
}
|
||||
|
||||
pub fn write_action_without_auth<W: Write>(
|
||||
mut writer: W,
|
||||
act: &Action<<Authorized as Authorization>::SpendAuth>,
|
||||
) -> io::Result<()> {
|
||||
write_value_commitment(&mut writer, &act.cv_net())?;
|
||||
write_nullifier(&mut writer, &act.nullifier())?;
|
||||
write_verification_key(&mut writer, &act.rk())?;
|
||||
write_cmx(&mut writer, &act.cmx())?;
|
||||
write_note_ciphertext(&mut writer, &act.encrypted_note())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_flags<W: Write>(mut writer: W, flags: &Flags) -> io::Result<()> {
|
||||
let mut byte = 0u8;
|
||||
if flags.spends_enabled() {
|
||||
byte |= FLAG_SPENDS_ENABLED;
|
||||
}
|
||||
if flags.outputs_enabled() {
|
||||
byte |= FLAG_OUTPUTS_ENABLED;
|
||||
}
|
||||
writer.write_all(&[byte])
|
||||
}
|
||||
|
||||
pub fn write_anchor<W: Write>(mut writer: W, anchor: &Anchor) -> io::Result<()> {
|
||||
writer.write_all(&anchor.0)
|
||||
}
|
|
@ -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 = {
|
||||
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()
|
||||
};
|
||||
/// 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 point = jubjub::ExtendedPoint::from_bytes(&bytes);
|
||||
|
||||
// Consensus rule (§7.3): Canonical encoding is enforced here
|
||||
let anchor = {
|
||||
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"))?
|
||||
};
|
||||
if point.is_none().into() {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("invalid {}", field),
|
||||
))
|
||||
} else {
|
||||
Ok(point.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// Consensus rules (§7.3) & (§7.4):
|
||||
/// - Canonical encoding is enforced here
|
||||
pub fn read_base<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,
|
||||
format!("{} not in field", field),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
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):
|
||||
/// 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.
|
||||
// - "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()
|
||||
let mut zkproof = [0u8; GROTH_PROOF_SIZE];
|
||||
reader.read_exact(&mut zkproof)?;
|
||||
|
||||
// Consensus rules (§4.4):
|
||||
// - 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_base(&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>(
|
||||
|
@ -501,8 +484,9 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
|||
rng: &mut R,
|
||||
sighash_bytes: &[u8; 32],
|
||||
tx_metadata: &SaplingMetadata,
|
||||
) -> Result<(Vec<Option<Signature>>, Option<Signature>), Error> {
|
||||
// Create Sapling spendAuth and binding signatures
|
||||
) -> Result<Option<(Vec<Signature>, Signature)>, Error> {
|
||||
// Create Sapling spendAuth signatures. These must be properly ordered with respect to the
|
||||
// shuffle that is described by tx_metadata.
|
||||
let mut spend_sigs = vec![None; self.spends.len()];
|
||||
for (i, spend) in self.spends.into_iter().enumerate() {
|
||||
spend_sigs[tx_metadata.spend_indices[i]] = Some(spend_sig_internal(
|
||||
|
@ -513,18 +497,100 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
|||
));
|
||||
}
|
||||
|
||||
// Add a binding signature if needed
|
||||
let binding_sig =
|
||||
if tx_metadata.spend_indices.is_empty() && tx_metadata.output_indices.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
prover
|
||||
.binding_sig(ctx, self.value_balance, &sighash_bytes)
|
||||
.map_err(|_| Error::BindingSig)?,
|
||||
)
|
||||
};
|
||||
if tx_metadata.spend_indices.is_empty() && tx_metadata.output_indices.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
let spend_sigs = spend_sigs
|
||||
.into_iter()
|
||||
.collect::<Option<Vec<Signature>>>()
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok((spend_sigs, binding_sig))
|
||||
let binding_sig = prover
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,13 @@ const PHGR_PROOF_SIZE: usize = 33 + 33 + 65 + 33 + 33 + 33 + 33 + 33;
|
|||
const ZC_NUM_JS_INPUTS: usize = 2;
|
||||
const ZC_NUM_JS_OUTPUTS: usize = 2;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Bundle {
|
||||
pub joinsplits: Vec<JsDescription>,
|
||||
pub joinsplit_pubkey: [u8; 32],
|
||||
pub joinsplit_sig: [u8; 64],
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum SproutProof {
|
||||
Groth([u8; GROTH_PROOF_SIZE]),
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use crate::legacy::Script;
|
||||
|
@ -10,6 +11,49 @@ use super::amount::Amount;
|
|||
|
||||
pub mod builder;
|
||||
|
||||
pub trait Authorization: Debug {
|
||||
type ScriptSig: Debug + Clone + PartialEq;
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Unauthorized;
|
||||
|
||||
impl Authorization for Unauthorized {
|
||||
type ScriptSig = ();
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Authorized;
|
||||
|
||||
impl Authorization for Authorized {
|
||||
type ScriptSig = Script;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Bundle<A: Authorization> {
|
||||
pub vin: Vec<TxIn<A>>,
|
||||
pub vout: Vec<TxOut>,
|
||||
}
|
||||
|
||||
impl Bundle<Unauthorized> {
|
||||
pub fn apply_signatures(self, script_sigs: Vec<Script>) -> Bundle<Authorized> {
|
||||
assert!(self.vin.len() == script_sigs.len());
|
||||
Bundle {
|
||||
vin: self
|
||||
.vin
|
||||
.into_iter()
|
||||
.zip(script_sigs.into_iter())
|
||||
.map(|(txin, sig)| TxIn {
|
||||
prevout: txin.prevout,
|
||||
script_sig: sig,
|
||||
sequence: txin.sequence,
|
||||
})
|
||||
.collect(),
|
||||
vout: self.vout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct OutPoint {
|
||||
hash: [u8; 32],
|
||||
|
@ -43,23 +87,25 @@ impl OutPoint {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TxIn {
|
||||
pub struct TxIn<A: Authorization> {
|
||||
pub prevout: OutPoint,
|
||||
pub script_sig: Script,
|
||||
pub script_sig: A::ScriptSig,
|
||||
pub sequence: u32,
|
||||
}
|
||||
|
||||
impl TxIn {
|
||||
impl TxIn<Unauthorized> {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "transparent-inputs")))]
|
||||
pub fn new(prevout: OutPoint) -> Self {
|
||||
TxIn {
|
||||
prevout,
|
||||
script_sig: Script::default(),
|
||||
script_sig: (),
|
||||
sequence: std::u32::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TxIn<Authorized> {
|
||||
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
|
||||
let prevout = OutPoint::read(&mut reader)?;
|
||||
let script_sig = Script::read(&mut reader)?;
|
||||
|
@ -106,3 +152,66 @@ impl TxOut {
|
|||
self.script_pubkey.write(&mut writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use proptest::collection::vec;
|
||||
use proptest::prelude::*;
|
||||
use proptest::sample::select;
|
||||
|
||||
use crate::{legacy::Script, transaction::components::amount::testing::arb_nonnegative_amount};
|
||||
|
||||
use super::{Authorized, Bundle, OutPoint, TxIn, TxOut};
|
||||
|
||||
pub const VALID_OPCODES: [u8; 8] = [
|
||||
0x00, // OP_FALSE,
|
||||
0x51, // OP_1,
|
||||
0x52, // OP_2,
|
||||
0x53, // OP_3,
|
||||
0xac, // OP_CHECKSIG,
|
||||
0x63, // OP_IF,
|
||||
0x65, // OP_VERIF,
|
||||
0x6a, // OP_RETURN,
|
||||
];
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_outpoint()(hash in prop::array::uniform32(0u8..), n in 0..100u32) -> OutPoint {
|
||||
OutPoint::new(hash, n)
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_script()(v in vec(select(&VALID_OPCODES[..]), 1..256)) -> Script {
|
||||
Script(v)
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_txin()(
|
||||
prevout in arb_outpoint(),
|
||||
script_sig in arb_script(),
|
||||
sequence in any::<u32>()
|
||||
) -> TxIn<Authorized> {
|
||||
TxIn { prevout, script_sig, sequence }
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_txout()(value in arb_nonnegative_amount(), script_pubkey in arb_script()) -> TxOut {
|
||||
TxOut { value, script_pubkey }
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_bundle()(
|
||||
vin in vec(arb_txin(), 0..10),
|
||||
vout in vec(arb_txout(), 0..10),
|
||||
) -> Option<Bundle<Authorized>> {
|
||||
if vin.is_empty() && vout.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Bundle { vin, vout })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,10 @@ use std::fmt;
|
|||
|
||||
use crate::{
|
||||
legacy::TransparentAddress,
|
||||
transaction::components::{amount::Amount, TxIn, TxOut},
|
||||
transaction::components::{
|
||||
amount::Amount,
|
||||
transparent::{self, TxIn, TxOut},
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
|
@ -13,7 +16,7 @@ use crate::{
|
|||
legacy::Script,
|
||||
transaction::{
|
||||
components::OutPoint, sighash::signature_hash_data, SignableInput, TransactionData,
|
||||
SIGHASH_ALL,
|
||||
Unauthorized, SIGHASH_ALL,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -128,48 +131,66 @@ impl TransparentBuilder {
|
|||
.sum::<Option<Amount>>()?
|
||||
}
|
||||
|
||||
pub fn build(&self) -> (Vec<TxIn>, Vec<TxOut>) {
|
||||
pub fn build(&self) -> Option<transparent::Bundle<transparent::Unauthorized>> {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
let vin = self
|
||||
let vin: Vec<TxIn<transparent::Unauthorized>> = self
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|i| TxIn::new(i.utxo.clone()))
|
||||
.collect();
|
||||
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
let vin = vec![];
|
||||
let vin: Vec<TxIn<transparent::Unauthorized>> = vec![];
|
||||
|
||||
(vin, self.vout.clone())
|
||||
if vin.is_empty() && self.vout.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(transparent::Bundle {
|
||||
vin,
|
||||
vout: self.vout.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
pub fn create_signatures(
|
||||
self,
|
||||
mtx: &TransactionData,
|
||||
mtx: &TransactionData<Unauthorized>,
|
||||
consensus_branch_id: consensus::BranchId,
|
||||
) -> Vec<Script> {
|
||||
self.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, info)| {
|
||||
let mut sighash = [0u8; 32];
|
||||
sighash.copy_from_slice(&signature_hash_data(
|
||||
mtx,
|
||||
consensus_branch_id,
|
||||
SIGHASH_ALL,
|
||||
SignableInput::transparent(i, &info.coin.script_pubkey, info.coin.value),
|
||||
));
|
||||
) -> Option<Vec<Script>> {
|
||||
if self.inputs.is_empty() && self.vout.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
self.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, info)| {
|
||||
let mut sighash = [0u8; 32];
|
||||
sighash.copy_from_slice(&signature_hash_data(
|
||||
mtx,
|
||||
consensus_branch_id,
|
||||
SIGHASH_ALL,
|
||||
SignableInput::transparent(
|
||||
i,
|
||||
&info.coin.script_pubkey,
|
||||
info.coin.value,
|
||||
),
|
||||
));
|
||||
|
||||
let msg = secp256k1::Message::from_slice(sighash.as_ref()).expect("32 bytes");
|
||||
let sig = self.secp.sign(&msg, &info.sk);
|
||||
let msg =
|
||||
secp256k1::Message::from_slice(sighash.as_ref()).expect("32 bytes");
|
||||
let sig = self.secp.sign(&msg, &info.sk);
|
||||
|
||||
// Signature has to have "SIGHASH_ALL" appended to it
|
||||
let mut sig_bytes: Vec<u8> = sig.serialize_der()[..].to_vec();
|
||||
sig_bytes.extend(&[SIGHASH_ALL as u8]);
|
||||
// Signature has to have "SIGHASH_ALL" appended to it
|
||||
let mut sig_bytes: Vec<u8> = sig.serialize_der()[..].to_vec();
|
||||
sig_bytes.extend(&[SIGHASH_ALL as u8]);
|
||||
|
||||
// P2PKH scriptSig
|
||||
Script::default() << &sig_bytes[..] << &info.pubkey[..]
|
||||
})
|
||||
.collect()
|
||||
// P2PKH scriptSig
|
||||
Script::default() << &sig_bytes[..] << &info.pubkey[..]
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Debug;
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use crate::{
|
||||
extensions::transparent as tze,
|
||||
|
@ -21,21 +21,67 @@ fn to_io_error(_: std::num::TryFromIntError) -> io::Error {
|
|||
io::Error::new(io::ErrorKind::InvalidData, "value out of range")
|
||||
}
|
||||
|
||||
pub trait Authorization: Debug {
|
||||
type Witness: Debug + Clone + PartialEq;
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Unauthorized;
|
||||
|
||||
impl Authorization for Unauthorized {
|
||||
type Witness = ();
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Authorized;
|
||||
|
||||
impl Authorization for Authorized {
|
||||
type Witness = tze::AuthData;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Bundle<A: Authorization> {
|
||||
pub vin: Vec<TzeIn<A>>,
|
||||
pub vout: Vec<TzeOut>,
|
||||
}
|
||||
|
||||
impl Bundle<Unauthorized> {
|
||||
pub fn apply_signatures(self, witnesses: Vec<tze::AuthData>) -> Bundle<Authorized> {
|
||||
assert!(self.vin.len() == witnesses.len());
|
||||
Bundle {
|
||||
vin: self
|
||||
.vin
|
||||
.into_iter()
|
||||
.zip(witnesses.into_iter())
|
||||
.map(|(tzein, payload)| TzeIn {
|
||||
prevout: tzein.prevout,
|
||||
witness: tze::Witness {
|
||||
extension_id: tzein.witness.extension_id,
|
||||
mode: tzein.witness.mode,
|
||||
payload,
|
||||
},
|
||||
})
|
||||
.collect(),
|
||||
vout: self.vout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TzeOutPoint {
|
||||
pub struct OutPoint {
|
||||
txid: TxId,
|
||||
n: u32,
|
||||
}
|
||||
|
||||
impl TzeOutPoint {
|
||||
impl OutPoint {
|
||||
pub fn new(txid: TxId, n: u32) -> Self {
|
||||
TzeOutPoint { txid, n }
|
||||
OutPoint { txid, n }
|
||||
}
|
||||
|
||||
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||
let txid = TxId::read(&mut reader)?;
|
||||
let n = reader.read_u32::<LittleEndian>()?;
|
||||
Ok(TzeOutPoint { txid, n })
|
||||
Ok(OutPoint { txid, n })
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
|
@ -53,48 +99,12 @@ impl TzeOutPoint {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TzeIn {
|
||||
pub prevout: TzeOutPoint,
|
||||
pub witness: tze::Witness,
|
||||
pub struct TzeIn<A: Authorization> {
|
||||
pub prevout: OutPoint,
|
||||
pub witness: tze::Witness<A::Witness>,
|
||||
}
|
||||
|
||||
/// Transaction encoding and decoding functions conforming to [ZIP 222].
|
||||
///
|
||||
/// [ZIP 222]: https://zips.z.cash/zip-0222#encoding-in-transactions
|
||||
impl TzeIn {
|
||||
/// Convenience constructor
|
||||
pub fn new(prevout: TzeOutPoint, extension_id: u32, mode: u32) -> Self {
|
||||
TzeIn {
|
||||
prevout,
|
||||
witness: tze::Witness {
|
||||
extension_id,
|
||||
mode,
|
||||
payload: vec![],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Read witness metadata & payload
|
||||
///
|
||||
/// Used to decode the encoded form used within a serialized
|
||||
/// transaction.
|
||||
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
|
||||
let prevout = TzeOutPoint::read(&mut reader)?;
|
||||
|
||||
let extension_id = CompactSize::read(&mut reader)?;
|
||||
let mode = CompactSize::read(&mut reader)?;
|
||||
let payload = Vector::read(&mut reader, |r| r.read_u8())?;
|
||||
|
||||
Ok(TzeIn {
|
||||
prevout,
|
||||
witness: tze::Witness {
|
||||
extension_id: u32::try_from(extension_id).map_err(to_io_error)?,
|
||||
mode: u32::try_from(mode).map_err(to_io_error)?,
|
||||
payload,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
impl<A: Authorization> TzeIn<A> {
|
||||
/// Write without witness data (for signature hashing)
|
||||
///
|
||||
/// This is also used as the prefix for the encoded form used
|
||||
|
@ -112,6 +122,46 @@ impl TzeIn {
|
|||
usize::try_from(self.witness.mode).map_err(to_io_error)?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Transaction encoding and decoding functions conforming to [ZIP 222].
|
||||
///
|
||||
/// [ZIP 222]: https://zips.z.cash/zip-0222#encoding-in-transactions
|
||||
impl TzeIn<Unauthorized> {
|
||||
/// Convenience constructor
|
||||
pub fn new(prevout: OutPoint, extension_id: u32, mode: u32) -> Self {
|
||||
TzeIn {
|
||||
prevout,
|
||||
witness: tze::Witness {
|
||||
extension_id,
|
||||
mode,
|
||||
payload: (),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TzeIn<Authorized> {
|
||||
/// Read witness metadata & payload
|
||||
///
|
||||
/// Used to decode the encoded form used within a serialized
|
||||
/// transaction.
|
||||
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
|
||||
let prevout = OutPoint::read(&mut reader)?;
|
||||
|
||||
let extension_id = CompactSize::read(&mut reader)?;
|
||||
let mode = CompactSize::read(&mut reader)?;
|
||||
let payload = Vector::read(&mut reader, |r| r.read_u8())?;
|
||||
|
||||
Ok(TzeIn {
|
||||
prevout,
|
||||
witness: tze::Witness {
|
||||
extension_id: u32::try_from(extension_id).map_err(to_io_error)?,
|
||||
mode: u32::try_from(mode).map_err(to_io_error)?,
|
||||
payload: tze::AuthData(payload),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Write prevout, extension, and mode followed by witness data.
|
||||
///
|
||||
|
@ -122,7 +172,7 @@ impl TzeIn {
|
|||
/// [`write_without_witness`]: TzeIn::write_without_witness
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
self.write_without_witness(&mut writer)?;
|
||||
Vector::write(&mut writer, &self.witness.payload, |w, b| w.write_u8(*b))
|
||||
Vector::write(&mut writer, &self.witness.payload.0, |w, b| w.write_u8(*b))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,3 +221,61 @@ impl TzeOut {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use proptest::collection::vec;
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::{
|
||||
consensus::BranchId,
|
||||
extensions::transparent::{AuthData, Precondition, Witness},
|
||||
transaction::components::amount::testing::arb_nonnegative_amount,
|
||||
transaction::testing::arb_txid,
|
||||
};
|
||||
|
||||
use super::{Authorized, Bundle, OutPoint, TzeIn, TzeOut};
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_outpoint()(txid in arb_txid(), n in 0..100u32) -> OutPoint {
|
||||
OutPoint::new(txid, n)
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_witness()(extension_id in 0..100u32, mode in 0..100u32, payload in vec(any::<u8>(), 32..256).prop_map(AuthData)) -> Witness<AuthData> {
|
||||
Witness { extension_id, mode, payload }
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_tzein()(prevout in arb_outpoint(), witness in arb_witness()) -> TzeIn<Authorized> {
|
||||
TzeIn { prevout, witness }
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_precondition()(extension_id in 0..100u32, mode in 0..100u32, payload in vec(any::<u8>(), 32..256)) -> Precondition {
|
||||
Precondition { extension_id, mode, payload }
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_tzeout()(value in arb_nonnegative_amount(), precondition in arb_precondition()) -> TzeOut {
|
||||
TzeOut { value, precondition }
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_bundle(branch_id: BranchId)(
|
||||
vin in vec(arb_tzein(), 0..10),
|
||||
vout in vec(arb_tzeout(), 0..10),
|
||||
) -> Option<Bundle<Authorized>> {
|
||||
if branch_id != BranchId::ZFuture || (vin.is_empty() && vout.is_empty()) {
|
||||
None
|
||||
} else {
|
||||
Some(Bundle { vin, vout })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,10 @@ use std::fmt;
|
|||
|
||||
use crate::{
|
||||
extensions::transparent::{self as tze, ToPayload},
|
||||
transaction::components::{amount::Amount, TzeIn, TzeOut, TzeOutPoint},
|
||||
transaction::components::{
|
||||
amount::Amount,
|
||||
tze::{Bundle, OutPoint, TzeIn, TzeOut, Unauthorized},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -32,16 +35,16 @@ struct TzeSigner<'a, BuildCtx> {
|
|||
|
||||
pub struct TzeBuilder<'a, BuildCtx> {
|
||||
signers: Vec<TzeSigner<'a, BuildCtx>>,
|
||||
tze_inputs: Vec<TzeIn>,
|
||||
tze_outputs: Vec<TzeOut>,
|
||||
vin: Vec<TzeIn<Unauthorized>>,
|
||||
vout: Vec<TzeOut>,
|
||||
}
|
||||
|
||||
impl<'a, BuildCtx> TzeBuilder<'a, BuildCtx> {
|
||||
pub fn empty() -> Self {
|
||||
TzeBuilder {
|
||||
signers: vec![],
|
||||
tze_inputs: vec![],
|
||||
tze_outputs: vec![],
|
||||
vin: vec![],
|
||||
vout: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,13 +52,12 @@ impl<'a, BuildCtx> TzeBuilder<'a, BuildCtx> {
|
|||
&mut self,
|
||||
extension_id: u32,
|
||||
mode: u32,
|
||||
(outpoint, prevout): (TzeOutPoint, TzeOut),
|
||||
(outpoint, prevout): (OutPoint, TzeOut),
|
||||
witness_builder: WBuilder,
|
||||
) where
|
||||
WBuilder: 'a + FnOnce(&BuildCtx) -> Result<W, Error>,
|
||||
{
|
||||
self.tze_inputs
|
||||
.push(TzeIn::new(outpoint, extension_id, mode));
|
||||
self.vin.push(TzeIn::new(outpoint, extension_id, mode));
|
||||
self.signers.push(TzeSigner {
|
||||
prevout,
|
||||
builder: Box::new(move |ctx| witness_builder(&ctx).map(|x| x.to_payload())),
|
||||
|
@ -73,7 +75,7 @@ impl<'a, BuildCtx> TzeBuilder<'a, BuildCtx> {
|
|||
}
|
||||
|
||||
let (mode, payload) = guarded_by.to_payload();
|
||||
self.tze_outputs.push(TzeOut {
|
||||
self.vout.push(TzeOut {
|
||||
value,
|
||||
precondition: tze::Precondition {
|
||||
extension_id,
|
||||
|
@ -91,36 +93,49 @@ impl<'a, BuildCtx> TzeBuilder<'a, BuildCtx> {
|
|||
.map(|s| s.prevout.value)
|
||||
.sum::<Option<Amount>>()?
|
||||
- self
|
||||
.tze_outputs
|
||||
.vout
|
||||
.iter()
|
||||
.map(|tzo| tzo.value)
|
||||
.sum::<Option<Amount>>()?
|
||||
}
|
||||
|
||||
pub fn build(&self) -> (Vec<TzeIn>, Vec<TzeOut>) {
|
||||
(self.tze_inputs.clone(), self.tze_outputs.clone())
|
||||
pub fn build(&self) -> Option<Bundle<Unauthorized>> {
|
||||
if self.vin.is_empty() && self.vout.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Bundle {
|
||||
vin: self.vin.clone(),
|
||||
vout: self.vout.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_witnesses(self, mtx: &BuildCtx) -> Result<Vec<Vec<u8>>, Error> {
|
||||
pub fn create_witnesses(self, mtx: &BuildCtx) -> Result<Option<Vec<tze::AuthData>>, Error> {
|
||||
// Create TZE input witnesses
|
||||
let payloads = self
|
||||
.signers
|
||||
.into_iter()
|
||||
.zip(self.tze_inputs.into_iter())
|
||||
.map(|(signer, tzein)| {
|
||||
// The witness builder function should have cached/closed over whatever data was
|
||||
// necessary for the witness to commit to at the time it was added to the
|
||||
// transaction builder; here, it then computes those commitments.
|
||||
let (mode, payload) = (signer.builder)(&mtx)?;
|
||||
let input_mode = tzein.witness.mode;
|
||||
if mode != input_mode {
|
||||
return Err(Error::WitnessModeMismatch(input_mode, mode));
|
||||
}
|
||||
if self.vin.is_empty() && self.vout.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
// Create TZE input witnesses
|
||||
let payloads = self
|
||||
.signers
|
||||
.into_iter()
|
||||
.zip(self.vin.into_iter())
|
||||
.into_iter()
|
||||
.map(|(signer, tzein)| {
|
||||
// The witness builder function should have cached/closed over whatever data was
|
||||
// necessary for the witness to commit to at the time it was added to the
|
||||
// transaction builder; here, it then computes those commitments.
|
||||
let (mode, payload) = (signer.builder)(&mtx)?;
|
||||
let input_mode = tzein.witness.mode;
|
||||
if mode != input_mode {
|
||||
return Err(Error::WitnessModeMismatch(input_mode, mode));
|
||||
}
|
||||
|
||||
Ok(payload)
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
Ok(tze::AuthData(payload))
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
|
||||
Ok(payloads)
|
||||
Ok(Some(payloads))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,32 +7,40 @@ use std::ops::Deref;
|
|||
|
||||
use crate::{
|
||||
consensus::{BlockHeight, BranchId},
|
||||
sapling::redjubjub::Signature,
|
||||
serialize::Vector,
|
||||
sapling::redjubjub,
|
||||
serialize::{CompactSize, Vector},
|
||||
};
|
||||
|
||||
use self::util::sha256d::{HashReader, HashWriter};
|
||||
use self::{
|
||||
components::{
|
||||
amount::Amount,
|
||||
sapling::{self, OutputDescription, SpendDescription},
|
||||
sprout::{self, JsDescription},
|
||||
transparent::{self, TxIn, TxOut},
|
||||
},
|
||||
sighash::{signature_hash_data, SignableInput, SIGHASH_ALL},
|
||||
util::sha256d::{HashReader, HashWriter},
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use self::components::tze;
|
||||
|
||||
pub mod builder;
|
||||
pub mod components;
|
||||
mod sighash;
|
||||
pub mod sighash;
|
||||
pub mod util;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use self::sighash::{signature_hash, signature_hash_data, SignableInput, SIGHASH_ALL};
|
||||
|
||||
use self::components::{Amount, JsDescription, OutputDescription, SpendDescription, TxIn, TxOut};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use self::components::{TzeIn, TzeOut};
|
||||
|
||||
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270;
|
||||
const OVERWINTER_TX_VERSION: u32 = 3;
|
||||
const SAPLING_VERSION_GROUP_ID: u32 = 0x892F2085;
|
||||
const SAPLING_TX_VERSION: u32 = 4;
|
||||
|
||||
const V5_TX_VERSION: u32 = 5;
|
||||
const V5_VERSION_GROUP_ID: u32 = 0x26A7270A;
|
||||
|
||||
/// These versions are used exclusively for in-development transaction
|
||||
/// serialization, and will never be active under the consensus rules.
|
||||
/// When new consensus transaction versions are added, all call sites
|
||||
|
@ -90,6 +98,7 @@ pub enum TxVersion {
|
|||
Sprout(u32),
|
||||
Overwinter,
|
||||
Sapling,
|
||||
ZcashTxV5,
|
||||
#[cfg(feature = "zfuture")]
|
||||
ZFuture,
|
||||
}
|
||||
|
@ -133,6 +142,7 @@ impl TxVersion {
|
|||
TxVersion::Sprout(v) => *v,
|
||||
TxVersion::Overwinter => OVERWINTER_TX_VERSION,
|
||||
TxVersion::Sapling => SAPLING_TX_VERSION,
|
||||
TxVersion::ZcashTxV5 => V5_TX_VERSION,
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => ZFUTURE_TX_VERSION,
|
||||
}
|
||||
|
@ -143,6 +153,7 @@ impl TxVersion {
|
|||
TxVersion::Sprout(_) => 0,
|
||||
TxVersion::Overwinter => OVERWINTER_VERSION_GROUP_ID,
|
||||
TxVersion::Sapling => SAPLING_VERSION_GROUP_ID,
|
||||
TxVersion::ZcashTxV5 => V5_VERSION_GROUP_ID,
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => ZFUTURE_VERSION_GROUP_ID,
|
||||
}
|
||||
|
@ -160,15 +171,17 @@ impl TxVersion {
|
|||
match self {
|
||||
TxVersion::Sprout(v) => *v >= 2u32,
|
||||
TxVersion::Overwinter | TxVersion::Sapling => true,
|
||||
TxVersion::ZcashTxV5 => false,
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uses_groth_proofs(&self) -> bool {
|
||||
pub fn has_sapling(&self) -> bool {
|
||||
match self {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter => false,
|
||||
TxVersion::Sapling => true,
|
||||
TxVersion::ZcashTxV5 => true,
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => true,
|
||||
}
|
||||
|
@ -188,17 +201,49 @@ impl TxVersion {
|
|||
}
|
||||
}
|
||||
|
||||
/// Authorization state for a bundle of transaction data.
|
||||
pub trait Authorization {
|
||||
type TransparentAuth: transparent::Authorization;
|
||||
type SaplingAuth: sapling::Authorization;
|
||||
type OrchardAuth: orchard::bundle::Authorization;
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
type TzeAuth: tze::Authorization;
|
||||
}
|
||||
|
||||
pub struct Authorized;
|
||||
|
||||
impl Authorization for Authorized {
|
||||
type TransparentAuth = transparent::Authorized;
|
||||
type SaplingAuth = sapling::Authorized;
|
||||
type OrchardAuth = orchard::bundle::Authorized;
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
type TzeAuth = tze::Authorized;
|
||||
}
|
||||
|
||||
pub struct Unauthorized;
|
||||
|
||||
impl Authorization for Unauthorized {
|
||||
type TransparentAuth = transparent::Unauthorized;
|
||||
type SaplingAuth = sapling::Unauthorized;
|
||||
type OrchardAuth = orchard::builder::Unauthorized;
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
type TzeAuth = tze::Unauthorized;
|
||||
}
|
||||
|
||||
/// A Zcash transaction.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct Transaction {
|
||||
txid: TxId,
|
||||
data: TransactionData,
|
||||
data: TransactionData<Authorized>,
|
||||
}
|
||||
|
||||
impl Deref for Transaction {
|
||||
type Target = TransactionData;
|
||||
type Target = TransactionData<Authorized>;
|
||||
|
||||
fn deref(&self) -> &TransactionData {
|
||||
fn deref(&self) -> &TransactionData<Authorized> {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
@ -209,95 +254,107 @@ impl PartialEq for Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TransactionData {
|
||||
pub struct TransactionData<A: Authorization> {
|
||||
pub version: TxVersion,
|
||||
pub vin: Vec<TxIn>,
|
||||
pub vout: Vec<TxOut>,
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub tze_inputs: Vec<TzeIn>,
|
||||
#[cfg(feature = "zfuture")]
|
||||
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 transparent_bundle: Option<transparent::Bundle<A::TransparentAuth>>,
|
||||
pub sprout_bundle: Option<sprout::Bundle>,
|
||||
pub sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
|
||||
pub orchard_bundle: Option<orchard::bundle::Bundle<A::OrchardAuth, Amount>>,
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub tze_bundle: Option<tze::Bundle<A::TzeAuth>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for TransactionData {
|
||||
impl<A: Authorization> std::fmt::Debug for TransactionData<A> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"TransactionData(
|
||||
version = {:?},
|
||||
vin = {:?},
|
||||
vout = {:?},{}
|
||||
lock_time = {:?},
|
||||
expiry_height = {:?},
|
||||
{}{}{}{}",
|
||||
self.version,
|
||||
self.lock_time,
|
||||
self.expiry_height,
|
||||
if let Some(b) = &self.transparent_bundle {
|
||||
format!(
|
||||
"
|
||||
vin = {:?},
|
||||
vout = {:?},",
|
||||
b.vin, b.vout
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
if let Some(b) = &self.sprout_bundle {
|
||||
format!(
|
||||
"
|
||||
joinsplits = {:?},
|
||||
joinsplit_pubkey = {:?},",
|
||||
b.joinsplits, b.joinsplit_pubkey
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
if let Some(b) = &self.sapling_bundle {
|
||||
format!(
|
||||
"
|
||||
value_balance = {:?},
|
||||
shielded_spends = {:?},
|
||||
shielded_outputs = {:?},
|
||||
joinsplits = {:?},
|
||||
joinsplit_pubkey = {:?},
|
||||
binding_sig = {:?})",
|
||||
self.version,
|
||||
self.vin,
|
||||
self.vout,
|
||||
binding_sig = {:?},",
|
||||
b.value_balance, b.shielded_spends, b.shielded_outputs, b.authorization
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
{
|
||||
#[cfg(feature = "zfuture")]
|
||||
{
|
||||
if let Some(b) = &self.tze_bundle {
|
||||
format!(
|
||||
"
|
||||
tze_inputs = {:?},
|
||||
tze_outputs = {:?},",
|
||||
self.tze_inputs, self.tze_outputs
|
||||
b.vin, b.vout
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
#[cfg(not(feature = "zfuture"))]
|
||||
""
|
||||
},
|
||||
self.lock_time,
|
||||
self.expiry_height,
|
||||
self.value_balance,
|
||||
self.shielded_spends,
|
||||
self.shielded_outputs,
|
||||
self.joinsplits,
|
||||
self.joinsplit_pubkey,
|
||||
self.binding_sig
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TransactionData {
|
||||
fn default() -> Self {
|
||||
TransactionData::new()
|
||||
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 TransactionData {
|
||||
impl Default for TransactionData<Unauthorized> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionData<Unauthorized> {
|
||||
pub fn new() -> Self {
|
||||
TransactionData {
|
||||
version: TxVersion::Sapling,
|
||||
vin: vec![],
|
||||
vout: vec![],
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_inputs: vec![],
|
||||
#[cfg(feature = "zfuture")]
|
||||
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,
|
||||
transparent_bundle: None,
|
||||
sprout_bundle: None,
|
||||
sapling_bundle: None,
|
||||
orchard_bundle: None,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_bundle: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,29 +362,28 @@ impl TransactionData {
|
|||
pub fn zfuture() -> Self {
|
||||
TransactionData {
|
||||
version: TxVersion::ZFuture,
|
||||
vin: vec![],
|
||||
vout: vec![],
|
||||
tze_inputs: vec![],
|
||||
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,
|
||||
transparent_bundle: None,
|
||||
sprout_bundle: None,
|
||||
sapling_bundle: None,
|
||||
orchard_bundle: None,
|
||||
tze_bundle: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn freeze(self) -> io::Result<Transaction> {
|
||||
Transaction::from_data(self)
|
||||
impl TransactionData<Authorized> {
|
||||
pub fn freeze(self, consensus_branch_id: BranchId) -> io::Result<Transaction> {
|
||||
Transaction::from_data(self, consensus_branch_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
fn from_data(data: TransactionData) -> io::Result<Self> {
|
||||
fn from_data(
|
||||
data: TransactionData<Authorized>,
|
||||
_consensus_branch_id: BranchId,
|
||||
) -> io::Result<Self> {
|
||||
let mut tx = Transaction {
|
||||
txid: TxId([0; 32]),
|
||||
data,
|
||||
|
@ -349,70 +405,56 @@ impl Transaction {
|
|||
let is_overwinter_v3 = version == TxVersion::Overwinter;
|
||||
let is_sapling_v4 = version == TxVersion::Sapling;
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
let has_tze = version == TxVersion::ZFuture;
|
||||
#[cfg(not(feature = "zfuture"))]
|
||||
let has_tze = false;
|
||||
|
||||
let vin = Vector::read(&mut reader, TxIn::read)?;
|
||||
let vout = Vector::read(&mut reader, TxOut::read)?;
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
let (tze_inputs, tze_outputs) = if has_tze {
|
||||
let wi = Vector::read(&mut reader, TzeIn::read)?;
|
||||
let wo = Vector::read(&mut reader, TzeOut::read)?;
|
||||
(wi, wo)
|
||||
} else {
|
||||
(vec![], vec![])
|
||||
};
|
||||
let transparent_bundle = Self::read_transparent(&mut reader)?;
|
||||
|
||||
let lock_time = reader.read_u32::<LittleEndian>()?;
|
||||
let expiry_height: BlockHeight = if is_overwinter_v3 || is_sapling_v4 || has_tze {
|
||||
let expiry_height: BlockHeight = if is_overwinter_v3 || is_sapling_v4 {
|
||||
reader.read_u32::<LittleEndian>()?.into()
|
||||
} else {
|
||||
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![])
|
||||
};
|
||||
|
||||
let (joinsplits, joinsplit_pubkey, joinsplit_sig) = if version.has_sprout() {
|
||||
let jss = Vector::read(&mut reader, |r| {
|
||||
JsDescription::read(r, version.uses_groth_proofs())
|
||||
let sprout_bundle = if version.has_sprout() {
|
||||
let joinsplits = Vector::read(&mut reader, |r| {
|
||||
JsDescription::read(r, version.has_sapling())
|
||||
})?;
|
||||
let (pubkey, sig) = if !jss.is_empty() {
|
||||
let mut joinsplit_pubkey = [0; 32];
|
||||
let mut joinsplit_sig = [0; 64];
|
||||
reader.read_exact(&mut joinsplit_pubkey)?;
|
||||
reader.read_exact(&mut joinsplit_sig)?;
|
||||
(Some(joinsplit_pubkey), Some(joinsplit_sig))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
(jss, pubkey, sig)
|
||||
} else {
|
||||
(vec![], None, None)
|
||||
};
|
||||
|
||||
let binding_sig = if (is_sapling_v4 || has_tze)
|
||||
&& !(shielded_spends.is_empty() && shielded_outputs.is_empty())
|
||||
{
|
||||
Some(Signature::read(&mut reader)?)
|
||||
if !joinsplits.is_empty() {
|
||||
let mut bundle = sprout::Bundle {
|
||||
joinsplits,
|
||||
joinsplit_pubkey: [0; 32],
|
||||
joinsplit_sig: [0; 64],
|
||||
};
|
||||
reader.read_exact(&mut bundle.joinsplit_pubkey)?;
|
||||
reader.read_exact(&mut bundle.joinsplit_sig)?;
|
||||
Some(bundle)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let binding_sig =
|
||||
if is_sapling_v4 && !(shielded_spends.is_empty() && shielded_outputs.is_empty()) {
|
||||
Some(redjubjub::Signature::read(&mut reader)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut txid = [0; 32];
|
||||
txid.copy_from_slice(&reader.into_hash());
|
||||
|
||||
|
@ -420,214 +462,150 @@ impl Transaction {
|
|||
txid: TxId(txid),
|
||||
data: TransactionData {
|
||||
version,
|
||||
vin,
|
||||
vout,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_inputs,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_outputs,
|
||||
lock_time,
|
||||
expiry_height,
|
||||
value_balance,
|
||||
shielded_spends,
|
||||
shielded_outputs,
|
||||
joinsplits,
|
||||
joinsplit_pubkey,
|
||||
joinsplit_sig,
|
||||
binding_sig,
|
||||
transparent_bundle,
|
||||
sprout_bundle,
|
||||
sapling_bundle: binding_sig.map(|binding_sig| sapling::Bundle {
|
||||
value_balance,
|
||||
shielded_spends,
|
||||
shielded_outputs,
|
||||
authorization: sapling::Authorized { binding_sig },
|
||||
}),
|
||||
orchard_bundle: None,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_bundle: None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn read_transparent<R: Read>(
|
||||
mut reader: R,
|
||||
) -> io::Result<Option<transparent::Bundle<transparent::Authorized>>> {
|
||||
let vin = Vector::read(&mut reader, TxIn::read)?;
|
||||
let vout = Vector::read(&mut reader, TxOut::read)?;
|
||||
Ok(if vin.is_empty() && vout.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(transparent::Bundle { vin, vout })
|
||||
})
|
||||
}
|
||||
|
||||
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 write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
self.version.write(&mut writer)?;
|
||||
|
||||
let is_overwinter_v3 = self.version == TxVersion::Overwinter;
|
||||
let is_sapling_v4 = self.version == TxVersion::Sapling;
|
||||
#[cfg(feature = "zfuture")]
|
||||
let has_tze = self.version == TxVersion::ZFuture;
|
||||
#[cfg(not(feature = "zfuture"))]
|
||||
let has_tze = false;
|
||||
|
||||
Vector::write(&mut writer, &self.vin, |w, e| e.write(w))?;
|
||||
Vector::write(&mut writer, &self.vout, |w, e| e.write(w))?;
|
||||
#[cfg(feature = "zfuture")]
|
||||
if has_tze {
|
||||
Vector::write(&mut writer, &self.tze_inputs, |w, e| e.write(w))?;
|
||||
Vector::write(&mut writer, &self.tze_outputs, |w, e| e.write(w))?;
|
||||
}
|
||||
self.write_transparent(&mut writer)?;
|
||||
|
||||
writer.write_u32::<LittleEndian>(self.lock_time)?;
|
||||
if is_overwinter_v3 || is_sapling_v4 || has_tze {
|
||||
if is_overwinter_v3 || is_sapling_v4 {
|
||||
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),
|
||||
)?;
|
||||
} else if self.sapling_bundle.is_some() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Sapling components may not be present if Sapling is not active.",
|
||||
));
|
||||
}
|
||||
|
||||
if self.version.has_sprout() {
|
||||
Vector::write(&mut writer, &self.joinsplits, |w, e| e.write(w))?;
|
||||
if !self.joinsplits.is_empty() {
|
||||
match self.joinsplit_pubkey {
|
||||
Some(pubkey) => writer.write_all(&pubkey)?,
|
||||
None => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Missing JoinSplit pubkey",
|
||||
));
|
||||
}
|
||||
}
|
||||
match self.joinsplit_sig {
|
||||
Some(sig) => writer.write_all(&sig)?,
|
||||
None => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Missing JoinSplit signature",
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(bundle) = self.sprout_bundle.as_ref() {
|
||||
Vector::write(&mut writer, &bundle.joinsplits, |w, e| e.write(w))?;
|
||||
writer.write_all(&bundle.joinsplit_pubkey)?;
|
||||
writer.write_all(&bundle.joinsplit_sig)?;
|
||||
} else {
|
||||
CompactSize::write(&mut writer, 0)?;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.version.has_sprout() || self.joinsplits.is_empty() {
|
||||
if self.joinsplit_pubkey.is_some() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"JoinSplit pubkey should not be present",
|
||||
));
|
||||
if self.version.has_sapling() {
|
||||
if let Some(bundle) = self.sapling_bundle.as_ref() {
|
||||
bundle.authorization.binding_sig.write(&mut writer)?;
|
||||
}
|
||||
if self.joinsplit_sig.is_some() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"JoinSplit signature should not be present",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
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 => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Missing binding signature",
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if self.binding_sig.is_some() {
|
||||
} else if self.sapling_bundle.is_some() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Binding signature should not be present",
|
||||
"Sapling components may not be present if Sapling is not active.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_transparent<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
if let Some(bundle) = &self.transparent_bundle {
|
||||
Vector::write(&mut writer, &bundle.vin, |w, e| e.write(w))?;
|
||||
Vector::write(&mut writer, &bundle.vout, |w, e| e.write(w))?;
|
||||
} else {
|
||||
CompactSize::write(&mut writer, 0)?;
|
||||
CompactSize::write(&mut writer, 0)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub fn write_tze<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
if let Some(bundle) = &self.tze_bundle {
|
||||
Vector::write(&mut writer, &bundle.vin, |w, e| e.write(w))?;
|
||||
Vector::write(&mut writer, &bundle.vout, |w, e| e.write(w))?;
|
||||
} else {
|
||||
CompactSize::write(&mut writer, 0)?;
|
||||
CompactSize::write(&mut writer, 0)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use proptest::collection::vec;
|
||||
use proptest::prelude::*;
|
||||
use proptest::sample::select;
|
||||
|
||||
use crate::{consensus::BranchId, legacy::Script};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use crate::extensions::transparent as tze;
|
||||
use crate::consensus::BranchId;
|
||||
|
||||
use super::{
|
||||
components::{amount::MAX_MONEY, Amount, OutPoint, TxIn, TxOut},
|
||||
Transaction, TransactionData, TxId, TxVersion,
|
||||
components::transparent::testing as transparent, Authorized, Transaction, TransactionData,
|
||||
TxId, TxVersion,
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use super::components::{TzeIn, TzeOut, TzeOutPoint};
|
||||
|
||||
pub const VALID_OPCODES: [u8; 8] = [
|
||||
0x00, // OP_FALSE,
|
||||
0x51, // OP_1,
|
||||
0x52, // OP_2,
|
||||
0x53, // OP_3,
|
||||
0xac, // OP_CHECKSIG,
|
||||
0x63, // OP_IF,
|
||||
0x65, // OP_VERIF,
|
||||
0x6a, // OP_RETURN,
|
||||
];
|
||||
|
||||
pub fn arb_txid() -> impl Strategy<Value = TxId> {
|
||||
prop::array::uniform32(any::<u8>()).prop_map(TxId::from_bytes)
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_outpoint()(hash in prop::array::uniform32(1u8..), n in 1..100u32) -> OutPoint {
|
||||
OutPoint::new(hash, n)
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_script()(v in vec(select(&VALID_OPCODES[..]), 1..256)) -> Script {
|
||||
Script(v)
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_txin()(prevout in arb_outpoint(), script_sig in arb_script(), sequence in any::<u32>()) -> TxIn {
|
||||
TxIn { prevout, script_sig, sequence }
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_amount()(value in 0..MAX_MONEY) -> Amount {
|
||||
Amount::from_i64(value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_txout()(value in arb_amount(), script_pubkey in arb_script()) -> TxOut {
|
||||
TxOut { value, script_pubkey }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_compose! {
|
||||
pub fn arb_tzeoutpoint()(txid in arb_txid(), n in 1..100u32) -> TzeOutPoint {
|
||||
TzeOutPoint::new(txid, n)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_compose! {
|
||||
pub fn arb_witness()(extension_id in 0..100u32, mode in 0..100u32, payload in vec(any::<u8>(), 32..256)) -> tze::Witness {
|
||||
tze::Witness { extension_id, mode, payload }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_compose! {
|
||||
pub fn arb_tzein()(prevout in arb_tzeoutpoint(), witness in arb_witness()) -> TzeIn {
|
||||
TzeIn { prevout, witness }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_compose! {
|
||||
pub fn arb_precondition()(extension_id in 0..100u32, mode in 0..100u32, payload in vec(any::<u8>(), 32..256)) -> tze::Precondition {
|
||||
tze::Precondition { extension_id, mode, payload }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_compose! {
|
||||
fn arb_tzeout()(value in arb_amount(), precondition in arb_precondition()) -> TzeOut {
|
||||
TzeOut { value, precondition }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arb_branch_id() -> impl Strategy<Value = BranchId> {
|
||||
select(vec![
|
||||
BranchId::Sprout,
|
||||
|
@ -641,81 +619,61 @@ pub mod testing {
|
|||
])
|
||||
}
|
||||
|
||||
fn tx_versions(branch_id: BranchId) -> impl Strategy<Value = TxVersion> {
|
||||
pub fn arb_tx_version(branch_id: BranchId) -> impl Strategy<Value = TxVersion> {
|
||||
match branch_id {
|
||||
BranchId::Sprout => (1..=2u32).prop_map(TxVersion::Sprout).boxed(),
|
||||
BranchId::Overwinter => Just(TxVersion::Overwinter).boxed(),
|
||||
#[cfg(feature = "zfuture")]
|
||||
BranchId::ZFuture => Just(TxVersion::ZFuture).boxed(),
|
||||
//#[cfg(feature = "zfuture")]
|
||||
//BranchId::ZFuture => Just(TxVersion::ZFuture).boxed(),
|
||||
_otherwise => Just(TxVersion::Sapling).boxed(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_compose! {
|
||||
pub fn arb_txdata(branch_id: BranchId)(
|
||||
version in tx_versions(branch_id),
|
||||
vin in vec(arb_txin(), 0..10),
|
||||
vout in vec(arb_txout(), 0..10),
|
||||
tze_inputs in vec(arb_tzein(), 0..10),
|
||||
tze_outputs in vec(arb_tzeout(), 0..10),
|
||||
lock_time in any::<u32>(),
|
||||
expiry_height in any::<u32>(),
|
||||
value_balance in arb_amount(),
|
||||
) -> TransactionData {
|
||||
TransactionData {
|
||||
version,
|
||||
vin, vout,
|
||||
tze_inputs: if branch_id == BranchId::ZFuture { tze_inputs } else { vec![] },
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "zfuture"))]
|
||||
prop_compose! {
|
||||
pub fn arb_txdata(branch_id: BranchId)(
|
||||
version in tx_versions(branch_id),
|
||||
vin in vec(arb_txin(), 0..10),
|
||||
vout in vec(arb_txout(), 0..10),
|
||||
version in arb_tx_version(branch_id),
|
||||
lock_time in any::<u32>(),
|
||||
expiry_height in any::<u32>(),
|
||||
value_balance in arb_amount(),
|
||||
) -> TransactionData {
|
||||
transparent_bundle in transparent::arb_bundle(),
|
||||
) -> 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
|
||||
transparent_bundle,
|
||||
sprout_bundle: None,
|
||||
sapling_bundle: None, //FIXME
|
||||
orchard_bundle: None, //FIXME
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_compose! {
|
||||
pub fn arb_txdata(branch_id: BranchId)(
|
||||
version in arb_tx_version(branch_id),
|
||||
lock_time in any::<u32>(),
|
||||
expiry_height in any::<u32>(),
|
||||
transparent_bundle in transparent::arb_bundle(),
|
||||
//tze_bundle in tze::arb_bundle(branch_id),
|
||||
) -> TransactionData<Authorized> {
|
||||
TransactionData {
|
||||
version,
|
||||
lock_time,
|
||||
expiry_height: expiry_height.into(),
|
||||
transparent_bundle,
|
||||
sprout_bundle: None,
|
||||
sapling_bundle: None, //FIXME
|
||||
orchard_bundle: None, //FIXME
|
||||
tze_bundle: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_tx(branch_id: BranchId)(tx_data in arb_txdata(branch_id)) -> Transaction {
|
||||
Transaction::from_data(tx_data).unwrap()
|
||||
Transaction::from_data(tx_data, branch_id).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,26 @@
|
|||
#[cfg(feature = "zfuture")]
|
||||
use std::convert::TryInto;
|
||||
|
||||
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
|
||||
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::{
|
||||
extensions::transparent::Precondition,
|
||||
serialize::{CompactSize, Vector},
|
||||
};
|
||||
use crate::extensions::transparent::Precondition;
|
||||
|
||||
use super::{
|
||||
components::{Amount, JsDescription, OutputDescription, SpendDescription, TxIn, TxOut},
|
||||
Transaction, TransactionData, TxVersion,
|
||||
components::{
|
||||
amount::Amount,
|
||||
sapling::{self, GrothProofBytes, OutputDescription, SpendDescription},
|
||||
sprout::JsDescription,
|
||||
transparent::{self, TxIn, TxOut},
|
||||
},
|
||||
Authorization, Transaction, TransactionData, TxVersion,
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use super::components::{TzeIn, TzeOut};
|
||||
|
||||
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash";
|
||||
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashPrevoutHash";
|
||||
const ZCASH_SEQUENCE_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSequencHash";
|
||||
|
@ -30,16 +29,6 @@ const ZCASH_JOINSPLITS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashJSplitsHash";
|
|||
const ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSSpendsHash";
|
||||
const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSOutputHash";
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
const ZCASH_TZE_INPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"Zcash_TzeInsHash";
|
||||
#[cfg(feature = "zfuture")]
|
||||
const ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashTzeOutsHash";
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
const ZCASH_TZE_SIGNED_INPUT_TAG: &[u8; 1] = &[0x00];
|
||||
#[cfg(feature = "zfuture")]
|
||||
const ZCASH_TRANSPARENT_SIGNED_INPUT_TAG: &[u8; 1] = &[0x01];
|
||||
|
||||
pub const SIGHASH_ALL: u32 = 1;
|
||||
const SIGHASH_NONE: u32 = 2;
|
||||
const SIGHASH_SINGLE: u32 = 3;
|
||||
|
@ -67,16 +56,7 @@ 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)
|
||||
}
|
||||
|
||||
fn prevout_hash(vin: &[TxIn]) -> Blake2bHash {
|
||||
fn prevout_hash<TA: transparent::Authorization>(vin: &[TxIn<TA>]) -> Blake2bHash {
|
||||
let mut data = Vec::with_capacity(vin.len() * 36);
|
||||
for t_in in vin {
|
||||
t_in.prevout.write(&mut data).unwrap();
|
||||
|
@ -87,7 +67,7 @@ fn prevout_hash(vin: &[TxIn]) -> Blake2bHash {
|
|||
.hash(&data)
|
||||
}
|
||||
|
||||
fn sequence_hash(vin: &[TxIn]) -> Blake2bHash {
|
||||
fn sequence_hash<TA: transparent::Authorization>(vin: &[TxIn<TA>]) -> Blake2bHash {
|
||||
let mut data = Vec::with_capacity(vin.len() * 4);
|
||||
for t_in in vin {
|
||||
(&mut data)
|
||||
|
@ -121,13 +101,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 +123,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 +140,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();
|
||||
|
@ -169,30 +153,6 @@ fn shielded_outputs_hash(shielded_outputs: &[OutputDescription]) -> Blake2bHash
|
|||
.hash(&data)
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn tze_inputs_hash(tze_inputs: &[TzeIn]) -> Blake2bHash {
|
||||
let mut data = vec![];
|
||||
for tzein in tze_inputs {
|
||||
tzein.write_without_witness(&mut data).unwrap();
|
||||
}
|
||||
Blake2bParams::new()
|
||||
.hash_length(32)
|
||||
.personal(ZCASH_TZE_INPUTS_HASH_PERSONALIZATION)
|
||||
.hash(&data)
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn tze_outputs_hash(tze_outputs: &[TzeOut]) -> Blake2bHash {
|
||||
let mut data = vec![];
|
||||
for tzeout in tze_outputs {
|
||||
tzeout.write(&mut data).unwrap();
|
||||
}
|
||||
Blake2bParams::new()
|
||||
.hash_length(32)
|
||||
.personal(ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION)
|
||||
.hash(&data)
|
||||
}
|
||||
|
||||
pub enum SignableInput<'a> {
|
||||
Shielded,
|
||||
Transparent {
|
||||
|
@ -227,8 +187,12 @@ impl<'a> SignableInput<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn signature_hash_data(
|
||||
tx: &TransactionData,
|
||||
pub fn signature_hash_data<
|
||||
TA: transparent::Authorization,
|
||||
SA: sapling::Authorization<Proof = GrothProofBytes>,
|
||||
A: Authorization<SaplingAuth = SA, TransparentAuth = TA>,
|
||||
>(
|
||||
tx: &TransactionData<A>,
|
||||
consensus_branch_id: consensus::BranchId,
|
||||
hash_type: u32,
|
||||
signable_input: SignableInput<'_>,
|
||||
|
@ -251,64 +215,91 @@ pub fn signature_hash_data(
|
|||
update_hash!(
|
||||
h,
|
||||
hash_type & SIGHASH_ANYONECANPAY == 0,
|
||||
prevout_hash(&tx.vin)
|
||||
prevout_hash(
|
||||
tx.transparent_bundle
|
||||
.as_ref()
|
||||
.map_or(&[], |b| b.vin.as_slice())
|
||||
)
|
||||
);
|
||||
update_hash!(
|
||||
h,
|
||||
hash_type & SIGHASH_ANYONECANPAY == 0
|
||||
&& (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
|
||||
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE,
|
||||
sequence_hash(&tx.vin)
|
||||
sequence_hash(
|
||||
tx.transparent_bundle
|
||||
.as_ref()
|
||||
.map_or(&[], |b| b.vin.as_slice())
|
||||
)
|
||||
);
|
||||
|
||||
if (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
|
||||
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE
|
||||
{
|
||||
h.update(outputs_hash(&tx.vout).as_ref());
|
||||
h.update(
|
||||
outputs_hash(
|
||||
tx.transparent_bundle
|
||||
.as_ref()
|
||||
.map_or(&[], |b| b.vout.as_slice()),
|
||||
)
|
||||
.as_bytes(),
|
||||
);
|
||||
} else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE {
|
||||
match signable_input {
|
||||
SignableInput::Transparent { index, .. } if index < tx.vout.len() => {
|
||||
h.update(single_output_hash(&tx.vout[index]).as_ref())
|
||||
match (tx.transparent_bundle.as_ref(), &signable_input) {
|
||||
(Some(b), SignableInput::Transparent { index, .. }) if *index < b.vout.len() => {
|
||||
h.update(single_output_hash(&b.vout[*index]).as_bytes())
|
||||
}
|
||||
_ => h.update(&[0; 32]),
|
||||
};
|
||||
} else {
|
||||
h.update(&[0; 32]);
|
||||
};
|
||||
#[cfg(feature = "zfuture")]
|
||||
if has_tze_components(&tx.version) {
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.tze_inputs.is_empty(),
|
||||
tze_inputs_hash(&tx.tze_inputs)
|
||||
);
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.tze_outputs.is_empty(),
|
||||
tze_outputs_hash(&tx.tze_outputs)
|
||||
);
|
||||
}
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.joinsplits.is_empty(),
|
||||
joinsplits_hash(tx.version, &tx.joinsplits, &tx.joinsplit_pubkey.unwrap())
|
||||
!tx.sprout_bundle
|
||||
.as_ref()
|
||||
.map_or(true, |b| b.joinsplits.is_empty()),
|
||||
{
|
||||
let bundle = tx.sprout_bundle.as_ref().unwrap();
|
||||
joinsplits_hash(
|
||||
consensus_branch_id,
|
||||
&bundle.joinsplits,
|
||||
&bundle.joinsplit_pubkey,
|
||||
)
|
||||
}
|
||||
);
|
||||
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);
|
||||
|
||||
|
@ -318,52 +309,28 @@ pub fn signature_hash_data(
|
|||
script_code,
|
||||
value,
|
||||
} => {
|
||||
#[cfg(feature = "zfuture")]
|
||||
let mut data = if has_tze_components(&tx.version) {
|
||||
// domain separation here is to avoid collision attacks
|
||||
// between transparent and TZE inputs.
|
||||
ZCASH_TRANSPARENT_SIGNED_INPUT_TAG.to_vec()
|
||||
if let Some(bundle) = tx.transparent_bundle.as_ref() {
|
||||
let mut data = vec![];
|
||||
|
||||
bundle.vin[index].prevout.write(&mut data).unwrap();
|
||||
script_code.write(&mut data).unwrap();
|
||||
data.extend_from_slice(&value.to_i64_le_bytes());
|
||||
(&mut data)
|
||||
.write_u32::<LittleEndian>(bundle.vin[index].sequence)
|
||||
.unwrap();
|
||||
h.update(&data);
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "zfuture"))]
|
||||
let mut data = vec![];
|
||||
|
||||
tx.vin[index].prevout.write(&mut data).unwrap();
|
||||
script_code.write(&mut data).unwrap();
|
||||
data.extend_from_slice(&value.to_i64_le_bytes());
|
||||
(&mut data)
|
||||
.write_u32::<LittleEndian>(tx.vin[index].sequence)
|
||||
.unwrap();
|
||||
h.update(&data);
|
||||
panic!(
|
||||
"A request has been made to sign a transparent input, but none are present."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
SignableInput::Tze {
|
||||
index,
|
||||
precondition,
|
||||
value,
|
||||
} if has_tze_components(&tx.version) => {
|
||||
// domain separation here is to avoid collision attacks
|
||||
// between transparent and TZE inputs.
|
||||
let mut data = ZCASH_TZE_SIGNED_INPUT_TAG.to_vec();
|
||||
|
||||
tx.tze_inputs[index].prevout.write(&mut data).unwrap();
|
||||
CompactSize::write(&mut data, precondition.extension_id.try_into().unwrap())
|
||||
.unwrap();
|
||||
CompactSize::write(&mut data, precondition.mode.try_into().unwrap()).unwrap();
|
||||
Vector::write(&mut data, &precondition.payload, |w, e| w.write_u8(*e)).unwrap();
|
||||
data.extend_from_slice(&value.to_i64_le_bytes());
|
||||
h.update(&data);
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
SignableInput::Tze { .. } => {
|
||||
panic!("A request has been made to sign a TZE input, but the signature hash version is not ZFuture");
|
||||
panic!("A request has been made to sign a TZE input in a V4 transaction.");
|
||||
}
|
||||
|
||||
_ => (),
|
||||
SignableInput::Shielded => (),
|
||||
}
|
||||
|
||||
h.finalize().as_ref().to_vec()
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
use ff::Field;
|
||||
use rand_core::OsRng;
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::{constants::SPENDING_KEY_GENERATOR, sapling::redjubjub::PrivateKey};
|
||||
|
||||
use super::{
|
||||
components::Amount,
|
||||
sighash::{signature_hash, SignableInput},
|
||||
Transaction, TransactionData,
|
||||
Transaction,
|
||||
};
|
||||
|
||||
use super::testing::{arb_branch_id, arb_tx};
|
||||
|
@ -27,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!(TransactionData::new().freeze().is_ok());
|
||||
|
||||
// Fails with an unexpected JoinSplit pubkey
|
||||
{
|
||||
let mut tx = TransactionData::new();
|
||||
tx.joinsplit_pubkey = Some([0; 32]);
|
||||
assert!(tx.freeze().is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_write_rejects_unexpected_joinsplit_sig() {
|
||||
// Succeeds without a JoinSplit signature
|
||||
assert!(TransactionData::new().freeze().is_ok());
|
||||
|
||||
// Fails with an unexpected JoinSplit signature
|
||||
{
|
||||
let mut tx = TransactionData::new();
|
||||
tx.joinsplit_sig = Some([0; 64]);
|
||||
assert!(tx.freeze().is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_write_rejects_unexpected_binding_sig() {
|
||||
// Succeeds without a binding signature
|
||||
assert!(TransactionData::new().freeze().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!(tx.freeze().is_err());
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn tx_serialization_roundtrip(tx in arb_branch_id().prop_flat_map(arb_tx)) {
|
||||
|
@ -78,43 +30,12 @@ proptest! {
|
|||
|
||||
let txo = Transaction::read(&txn_bytes[..]).unwrap();
|
||||
|
||||
assert_eq!(tx.version, txo.version);
|
||||
assert_eq!(tx.vin, txo.vin);
|
||||
assert_eq!(tx.vout, txo.vout);
|
||||
prop_assert_eq!(tx.version, txo.version);
|
||||
prop_assert_eq!(tx.lock_time, txo.lock_time);
|
||||
prop_assert_eq!(tx.transparent_bundle.as_ref(), txo.transparent_bundle.as_ref());
|
||||
prop_assert_eq!(tx.sapling_value_balance(), txo.sapling_value_balance());
|
||||
#[cfg(feature = "zfuture")]
|
||||
assert_eq!(tx.tze_inputs, txo.tze_inputs);
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn test_tze_tx_parse() {
|
||||
let txn_bytes = vec![
|
||||
0xFF, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x52, 0x52, 0x52, 0x52,
|
||||
0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52,
|
||||
0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x30, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x20, 0xd9, 0x81, 0x80, 0x87, 0xde, 0x72, 0x44, 0xab, 0xc1, 0xb5, 0xfc,
|
||||
0xf2, 0x8e, 0x55, 0xe4, 0x2c, 0x7f, 0xf9, 0xc6, 0x78, 0xc0, 0x60, 0x51, 0x81, 0xf3, 0x7a,
|
||||
0xc5, 0xd7, 0x41, 0x4a, 0x7b, 0x95, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let tx = Transaction::read(&txn_bytes[..]);
|
||||
|
||||
match tx {
|
||||
Ok(tx) => assert!(!tx.tze_inputs.is_empty()),
|
||||
|
||||
Err(e) => panic!(
|
||||
"An error occurred parsing a serialized TZE transaction: {}",
|
||||
e
|
||||
),
|
||||
prop_assert_eq!(tx.tze_bundle.as_ref(), txo.tze_bundle.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,7 +54,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
|
||||
);
|
||||
}
|
||||
|
@ -153,7 +74,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