Move Transparent components to a bundle within TransactionData

This commit is contained in:
Kris Nuttycombe 2021-04-12 18:55:16 -06:00
parent 670d03e74f
commit 523424e50b
11 changed files with 367 additions and 223 deletions

View File

@ -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")
}

View File

@ -76,8 +76,7 @@ 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.transparent_bundle.is_none()
&& self.tx.sapling_bundle.is_none()
&& self.tx.sprout_bundle.is_none()
&& self.tx.orchard_bundle.is_none()

View File

@ -603,8 +603,7 @@ 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.transparent_bundle.is_none()
&& self.tx.sprout_bundle.is_none()
&& self.tx.sapling_bundle.is_none()
&& self.tx.orchard_bundle.is_none()

View File

@ -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) }

View File

@ -26,7 +26,7 @@ use crate::{
self,
builder::{SaplingBuilder, SaplingMetadata},
},
transparent::{self, builder::TransparentBuilder, TxIn},
transparent::{self, builder::TransparentBuilder},
},
signature_hash_data, SignableInput, Transaction, TransactionData, TxVersion, Unauthorized,
SIGHASH_ALL,
@ -117,7 +117,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,
@ -324,7 +324,7 @@ 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 (sapling_bundle, tx_metadata) = self
@ -343,14 +343,13 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
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,
transparent_bundle,
sprout_bundle: None,
sapling_bundle,
orchard_bundle: None,
@ -369,10 +368,9 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
));
#[cfg(feature = "transparent-inputs")]
let transparent_sigs = Some(
self.transparent_builder
.create_signatures(&unauthed_tx, consensus_branch_id),
);
let transparent_sigs = self
.transparent_builder
.create_signatures(&unauthed_tx, consensus_branch_id);
let sapling_sigs = self
.sapling_builder
@ -380,11 +378,10 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
.map_err(Error::SaplingBuild)?;
#[cfg(feature = "zfuture")]
let tze_witnesses = Some(
self.tze_builder
.create_witnesses(&unauthed_tx)
.map_err(Error::TzeBuild)?,
);
let tze_witnesses = self
.tze_builder
.create_witnesses(&unauthed_tx)
.map_err(Error::TzeBuild)?;
Ok((
Self::apply_signatures(
@ -413,6 +410,24 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
)>,
#[cfg(feature = "zfuture")] tze_witnesses: Option<Vec<WitnessData>>,
) -> 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))
@ -425,37 +440,24 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
let mut authorized_tx = TransactionData {
version: unauthed_tx.version,
vin: unauthed_tx.vin,
vout: unauthed_tx.vout,
#[cfg(feature = "zfuture")]
tze_inputs: unauthed_tx.tze_inputs,
#[cfg(feature = "zfuture")]
tze_outputs: unauthed_tx.tze_outputs,
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 = "transparent-inputs")]
Self::apply_transparent_sigs(&mut authorized_tx.vin, transparent_sigs);
#[cfg(feature = "zfuture")]
Self::apply_tze_sigs(&mut authorized_tx.tze_inputs, tze_witnesses);
authorized_tx.freeze(consensus_branch_id)
}
#[cfg(feature = "transparent-inputs")]
pub fn apply_transparent_sigs(vin: &mut [TxIn], transparent_sigs: Option<Vec<Script>>) {
if let Some(script_sigs) = transparent_sigs {
assert!(vin.len() == script_sigs.len());
for (i, sig) in script_sigs.into_iter().enumerate() {
vin[i].script_sig = sig;
}
}
}
#[cfg(feature = "zfuture")]
pub fn apply_tze_sigs(vtzein: &mut [TzeIn], tze_witnesses: Option<Vec<WitnessData>>) {
if let Some(tze_witnesses) = tze_witnesses {

View File

@ -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(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<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 })
}
}
}
}

View File

@ -4,7 +4,7 @@ use std::fmt;
use crate::{
legacy::TransparentAddress,
transaction::components::{amount::Amount, TxIn, TxOut},
transaction::components::{amount::Amount, transparent, TxIn, TxOut},
};
#[cfg(feature = "transparent-inputs")]
@ -128,18 +128,25 @@ 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")]
@ -147,29 +154,40 @@ impl TransparentBuilder {
self,
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(),
)
}
}
}

View File

@ -103,26 +103,31 @@ impl<'a, BuildCtx> TzeBuilder<'a, BuildCtx> {
(self.tze_inputs.clone(), self.tze_outputs.clone())
}
pub fn create_witnesses(self, mtx: &BuildCtx) -> Result<Vec<WitnessData>, 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));
}
pub fn create_witnesses(self, mtx: &BuildCtx) -> Result<Option<Vec<WitnessData>>, Error> {
if self.tze_inputs.is_empty() {
Ok(None)
} else {
// Create TZE input witnesses
let payloads = self
.signers
.into_iter()
.zip(self.tze_inputs.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(WitnessData(payload))
})
.collect::<Result<Vec<_>, Error>>()?;
Ok(WitnessData(payload))
})
.collect::<Result<Vec<_>, Error>>()?;
Ok(payloads)
Ok(Some(payloads))
}
}
}

View File

@ -10,7 +10,7 @@ use orchard;
use crate::{
consensus::{BlockHeight, BranchId},
sapling::redjubjub,
serialize::Vector,
serialize::{CompactSize, Vector},
};
use self::{
@ -18,7 +18,7 @@ use self::{
amount::Amount,
sapling::{self, OutputDescription, SpendDescription},
sprout::{self, JsDescription},
transparent::{TxIn, TxOut},
transparent::{self, TxIn, TxOut},
},
sighash::{signature_hash_data, SignableInput, SIGHASH_ALL},
util::sha256d::{HashReader, HashWriter},
@ -197,6 +197,7 @@ 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;
}
@ -204,6 +205,7 @@ pub trait Authorization {
pub struct Authorized;
impl Authorization for Authorized {
type TransparentAuth = transparent::Authorized;
type SaplingAuth = sapling::Authorized;
type OrchardAuth = orchard::bundle::Authorized;
}
@ -211,6 +213,7 @@ impl Authorization for Authorized {
pub struct Unauthorized;
impl Authorization for Unauthorized {
type TransparentAuth = transparent::Unauthorized;
type SaplingAuth = sapling::Unauthorized;
type OrchardAuth = orchard::builder::Unauthorized;
}
@ -236,16 +239,24 @@ impl PartialEq for Transaction {
}
}
impl Transaction {
pub fn sapling_value_balance(&self) -> Amount {
self.data
.sapling_bundle
.as_ref()
.map_or(Amount::zero(), |b| b.value_balance)
}
}
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 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<A::OrchardAuth, Amount>>,
@ -268,8 +279,10 @@ impl<A: Authorization> std::fmt::Debug for TransactionData<A> {
joinsplit_pubkey = {:?},
binding_sig = {:?})",
self.version,
self.vin,
self.vout,
self.transparent_bundle.as_ref().map_or(&vec![], |b| &b.vin),
self.transparent_bundle
.as_ref()
.map_or(&vec![], |b| &b.vout),
{
#[cfg(feature = "zfuture")]
{
@ -321,14 +334,13 @@ 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(),
transparent_bundle: None,
sprout_bundle: None,
sapling_bundle: None,
orchard_bundle: None,
@ -339,12 +351,11 @@ impl TransactionData<Unauthorized> {
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(),
transparent_bundle: None,
sprout_bundle: None,
sapling_bundle: None,
orchard_bundle: None,
@ -374,13 +385,6 @@ impl Transaction {
self.txid
}
fn read_amount<R: Read>(mut reader: R) -> io::Result<Amount> {
let mut tmp = [0; 8];
reader.read_exact(&mut tmp)?;
Amount::from_i64_le_bytes(tmp)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "valueBalance out of range"))
}
pub fn read<R: Read>(reader: R) -> io::Result<Self> {
let mut reader = HashReader::new(reader);
@ -393,8 +397,7 @@ impl Transaction {
#[cfg(not(feature = "zfuture"))]
let has_tze = false;
let vin = Vector::read(&mut reader, TxIn::read)?;
let vout = Vector::read(&mut reader, TxOut::read)?;
let transparent_bundle = Self::read_transparent(&mut reader)?;
#[cfg(feature = "zfuture")]
let (tze_inputs, tze_outputs) = if has_tze {
@ -461,14 +464,13 @@ impl Transaction {
txid: TxId(txid),
data: TransactionData {
version,
vin,
vout,
#[cfg(feature = "zfuture")]
tze_inputs,
#[cfg(feature = "zfuture")]
tze_outputs,
lock_time,
expiry_height,
transparent_bundle,
sprout_bundle,
sapling_bundle: binding_sig.map(|binding_sig| sapling::Bundle {
value_balance,
@ -481,6 +483,25 @@ impl Transaction {
})
}
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)?;
@ -492,8 +513,8 @@ impl Transaction {
#[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))?;
self.write_transparent(&mut writer)?;
#[cfg(feature = "zfuture")]
if has_tze {
Vector::write(&mut writer, &self.tze_inputs, |w, e| e.write(w))?;
@ -526,14 +547,17 @@ impl Transaction {
.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.sprout_bundle
.as_ref()
.map_or(&vec![], |b| &b.joinsplits),
self.sprout_bundle.as_ref().map_or(&[], |b| &b.joinsplits),
|w, e| e.write(w),
)?;
for bundle in &self.sprout_bundle {
@ -545,23 +569,30 @@ impl Transaction {
if self.version.has_sapling() {
if let Some(bundle) = self.sapling_bundle.as_ref() {
bundle.authorization.binding_sig.write(&mut writer)?;
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Missing binding signature",
));
}
} else {
if self.sapling_bundle.is_some() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Binding signature should not be present",
));
}
} else if self.sapling_bundle.is_some() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Binding signature should not be present",
));
}
Ok(())
}
pub fn write_transparent<W: Write>(&self, mut writer: W) -> io::Result<()> {
match &self.transparent_bundle {
Some(bundle) => {
Vector::write(&mut writer, &bundle.vin, |w, e| e.write(w))?;
Vector::write(&mut writer, &bundle.vout, |w, e| e.write(w))?;
}
None => {
CompactSize::write(&mut writer, 0)?;
CompactSize::write(&mut writer, 0)?;
}
}
Ok(())
}
}
#[cfg(any(test, feature = "test-dependencies"))]
@ -570,64 +601,25 @@ pub mod testing {
use proptest::prelude::*;
use proptest::sample::select;
use crate::{consensus::BranchId, legacy::Script};
use crate::consensus::BranchId;
#[cfg(feature = "zfuture")]
use crate::extensions::transparent as tze;
use super::{
components::{amount::MAX_MONEY, Amount, OutPoint, TxIn, TxOut},
components::{
amount::testing::arb_nonnegative_amount, 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 {
@ -658,7 +650,7 @@ pub mod testing {
#[cfg(feature = "zfuture")]
prop_compose! {
fn arb_tzeout()(value in arb_amount(), precondition in arb_precondition()) -> TzeOut {
fn arb_tzeout()(value in arb_nonnegative_amount(), precondition in arb_precondition()) -> TzeOut {
TzeOut { value, precondition }
}
}
@ -690,8 +682,7 @@ pub mod testing {
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),
transparent_bundle in transparent::arb_bundle(),
tze_inputs in vec(arb_tzein(), 0..10),
tze_outputs in vec(arb_tzeout(), 0..10),
lock_time in any::<u32>(),
@ -699,11 +690,11 @@ pub mod testing {
) -> TransactionData<Authorized> {
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(),
transparent_bundle,
sprout_bundle: None,
sapling_bundle: None, //FIXME
orchard_bundle: None, //FIXME
@ -715,16 +706,15 @@ pub mod testing {
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),
transparent_bundle in transparent::arb_bundle(),
lock_time in any::<u32>(),
expiry_height in any::<u32>(),
) -> TransactionData<Authorized> {
TransactionData {
version,
vin, vout,
lock_time,
expiry_height: expiry_height.into(),
transparent_bundle,
sprout_bundle: None,
sapling_bundle: None, //FIXME
orchard_bundle: None, //FIXME

View File

@ -19,8 +19,10 @@ use crate::{
use super::{
components::{
sapling::{self, GrothProofBytes},
Amount, JsDescription, OutputDescription, SpendDescription, TxIn, TxOut,
amount::Amount,
sapling::{self, GrothProofBytes, OutputDescription, SpendDescription},
sprout::JsDescription,
transparent::{self, TxIn, TxOut},
},
Authorization, Transaction, TransactionData, TxVersion,
};
@ -78,7 +80,7 @@ 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();
@ -89,7 +91,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)
@ -234,8 +236,9 @@ impl<'a> SignableInput<'a> {
}
pub fn signature_hash_data<
TA: transparent::Authorization,
SA: sapling::Authorization<Proof = GrothProofBytes>,
A: Authorization<SaplingAuth = SA>,
A: Authorization<SaplingAuth = SA, TransparentAuth = TA>,
>(
tx: &TransactionData<A>,
consensus_branch_id: consensus::BranchId,
@ -260,24 +263,39 @@ 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]),
};
@ -352,52 +370,54 @@ 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() {
#[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()
} else {
vec![]
};
#[cfg(not(feature = "zfuture"))]
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();
} => {
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);
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");
}
_ => (),
SignableInput::Shielded => (),
}
h.finalize().as_ref().to_vec()

View File

@ -31,13 +31,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);
#[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.transparent_bundle, txo.transparent_bundle);
assert_eq!(tx.sapling_value_balance(), txo.sapling_value_balance());
}
}