Implement V5 transaction serialization & roundtrip property tests.
This commit is contained in:
parent
e828dbf5d0
commit
38b864c100
|
@ -179,3 +179,39 @@ pub fn write_flags<W: Write>(mut writer: W, flags: &Flags) -> io::Result<()> {
|
|||
pub fn write_anchor<W: Write>(mut writer: W, anchor: &Anchor) -> io::Result<()> {
|
||||
writer.write_all(&anchor.0)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use proptest::prelude::*;
|
||||
|
||||
use orchard::bundle::{
|
||||
testing::{self as t_orch},
|
||||
Authorized, Bundle,
|
||||
};
|
||||
|
||||
use crate::transaction::{
|
||||
components::amount::{testing::arb_amount, Amount},
|
||||
TxVersion,
|
||||
};
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_bundle()(
|
||||
orchard_value_balance in arb_amount(),
|
||||
bundle in t_orch::arb_bundle()
|
||||
) -> Bundle<Authorized, Amount> {
|
||||
// overwrite the value balance, as we can't guarantee that the
|
||||
// value doesn't exceed the MAX_MONEY bounds.
|
||||
bundle.try_map_value_balance::<_, (), _>(|_| Ok(orchard_value_balance)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arb_bundle_for_version(
|
||||
v: TxVersion,
|
||||
) -> impl Strategy<Value = Option<Bundle<Authorized, Amount>>> {
|
||||
if v.has_orchard() {
|
||||
Strategy::boxed(prop::option::of(arb_bundle()))
|
||||
} else {
|
||||
Strategy::boxed(Just(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -446,7 +446,10 @@ pub mod testing {
|
|||
redjubjub::{PrivateKey, PublicKey},
|
||||
Nullifier,
|
||||
},
|
||||
transaction::components::{amount::testing::arb_amount, GROTH_PROOF_SIZE},
|
||||
transaction::{
|
||||
components::{amount::testing::arb_amount, GROTH_PROOF_SIZE},
|
||||
TxVersion,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{Authorized, Bundle, OutputDescription, SpendDescription};
|
||||
|
@ -524,12 +527,12 @@ pub mod testing {
|
|||
rng_seed in prop::array::uniform32(prop::num::u8::ANY),
|
||||
fake_bvk_bytes in prop::array::uniform32(prop::num::u8::ANY),
|
||||
) -> Option<Bundle<Authorized>> {
|
||||
let mut rng = StdRng::from_seed(rng_seed);
|
||||
let bsk = PrivateKey(jubjub::Fr::random(&mut rng));
|
||||
|
||||
if shielded_spends.is_empty() && shielded_outputs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut rng = StdRng::from_seed(rng_seed);
|
||||
let bsk = PrivateKey(jubjub::Fr::random(&mut rng));
|
||||
|
||||
Some(
|
||||
Bundle {
|
||||
shielded_spends,
|
||||
|
@ -541,4 +544,14 @@ pub mod testing {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arb_bundle_for_version(
|
||||
v: TxVersion,
|
||||
) -> impl Strategy<Value = Option<Bundle<Authorized>>> {
|
||||
if v.has_sapling() {
|
||||
Strategy::boxed(arb_bundle())
|
||||
} else {
|
||||
Strategy::boxed(Just(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,18 @@ pub mod util;
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use blake2b_simd::Hash as Blake2bHash;
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use ff::PrimeField;
|
||||
use nonempty::NonEmpty;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::ops::Deref;
|
||||
|
||||
use orchard::{self, primitives::redpallas};
|
||||
|
||||
use crate::{
|
||||
consensus::{BlockHeight, BranchId},
|
||||
sapling::redjubjub,
|
||||
|
@ -24,15 +31,19 @@ use crate::{
|
|||
use self::{
|
||||
components::{
|
||||
amount::Amount,
|
||||
sapling::{self, OutputDescription, SpendDescription},
|
||||
orchard as orchard_serialization,
|
||||
sapling::{
|
||||
self, OutputDescription, OutputDescriptionV5, SpendDescription, SpendDescriptionV5,
|
||||
},
|
||||
sprout::{self, JsDescription},
|
||||
transparent::{self, TxIn, TxOut},
|
||||
},
|
||||
txid::{to_txid, BlockTxCommitmentDigester, TxIdDigester},
|
||||
util::sha256d::{HashReader, HashWriter},
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use self::components::tze;
|
||||
use self::components::tze::{self, TzeIn, TzeOut};
|
||||
|
||||
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270;
|
||||
const OVERWINTER_TX_VERSION: u32 = 3;
|
||||
|
@ -99,7 +110,7 @@ pub enum TxVersion {
|
|||
Sprout(u32),
|
||||
Overwinter,
|
||||
Sapling,
|
||||
ZcashTxV5,
|
||||
Zip225,
|
||||
#[cfg(feature = "zfuture")]
|
||||
ZFuture,
|
||||
}
|
||||
|
@ -114,7 +125,7 @@ impl TxVersion {
|
|||
match (version, reader.read_u32::<LittleEndian>()?) {
|
||||
(OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID) => Ok(TxVersion::Overwinter),
|
||||
(SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID) => Ok(TxVersion::Sapling),
|
||||
(V5_TX_VERSION, V5_VERSION_GROUP_ID) => Ok(TxVersion::ZcashTxV5),
|
||||
(V5_TX_VERSION, V5_VERSION_GROUP_ID) => Ok(TxVersion::Zip225),
|
||||
#[cfg(feature = "zfuture")]
|
||||
(ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID) => Ok(TxVersion::ZFuture),
|
||||
_ => Err(io::Error::new(
|
||||
|
@ -144,7 +155,7 @@ impl TxVersion {
|
|||
TxVersion::Sprout(v) => *v,
|
||||
TxVersion::Overwinter => OVERWINTER_TX_VERSION,
|
||||
TxVersion::Sapling => SAPLING_TX_VERSION,
|
||||
TxVersion::ZcashTxV5 => V5_TX_VERSION,
|
||||
TxVersion::Zip225 => V5_TX_VERSION,
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => ZFUTURE_TX_VERSION,
|
||||
}
|
||||
|
@ -155,7 +166,7 @@ impl TxVersion {
|
|||
TxVersion::Sprout(_) => 0,
|
||||
TxVersion::Overwinter => OVERWINTER_VERSION_GROUP_ID,
|
||||
TxVersion::Sapling => SAPLING_VERSION_GROUP_ID,
|
||||
TxVersion::ZcashTxV5 => V5_VERSION_GROUP_ID,
|
||||
TxVersion::Zip225 => V5_VERSION_GROUP_ID,
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => ZFUTURE_VERSION_GROUP_ID,
|
||||
}
|
||||
|
@ -173,7 +184,7 @@ impl TxVersion {
|
|||
match self {
|
||||
TxVersion::Sprout(v) => *v >= 2u32,
|
||||
TxVersion::Overwinter | TxVersion::Sapling => true,
|
||||
TxVersion::ZcashTxV5 => false,
|
||||
TxVersion::Zip225 => false,
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => true,
|
||||
}
|
||||
|
@ -187,7 +198,7 @@ impl TxVersion {
|
|||
match self {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter => false,
|
||||
TxVersion::Sapling => true,
|
||||
TxVersion::ZcashTxV5 => true,
|
||||
TxVersion::Zip225 => true,
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => true,
|
||||
}
|
||||
|
@ -196,7 +207,7 @@ impl TxVersion {
|
|||
pub fn has_orchard(&self) -> bool {
|
||||
match self {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => false,
|
||||
TxVersion::ZcashTxV5 => true,
|
||||
TxVersion::Zip225 => true,
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => true,
|
||||
}
|
||||
|
@ -214,7 +225,7 @@ impl TxVersion {
|
|||
BranchId::Sapling | BranchId::Blossom | BranchId::Heartwood | BranchId::Canopy => {
|
||||
TxVersion::Sapling
|
||||
}
|
||||
BranchId::Nu5 => TxVersion::Sapling, //TEMPORARY WORKAROUND
|
||||
BranchId::Nu5 => TxVersion::Zip225, //TEMPORARY WORKAROUND
|
||||
#[cfg(feature = "zfuture")]
|
||||
BranchId::ZFuture => TxVersion::ZFuture,
|
||||
}
|
||||
|
@ -466,6 +477,17 @@ impl TransactionData<Authorized> {
|
|||
|
||||
impl Transaction {
|
||||
fn from_data(data: TransactionData<Authorized>) -> io::Result<Self> {
|
||||
match data.version {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
|
||||
Self::from_data_v4(data)
|
||||
}
|
||||
TxVersion::Zip225 => Ok(Self::from_data_v5(data)),
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => Ok(Self::from_data_v5(data)),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_data_v4(data: TransactionData<Authorized>) -> io::Result<Self> {
|
||||
let mut tx = Transaction {
|
||||
txid: TxId([0; 32]),
|
||||
data,
|
||||
|
@ -476,22 +498,44 @@ impl Transaction {
|
|||
Ok(tx)
|
||||
}
|
||||
|
||||
fn from_data_v5(data: TransactionData<Authorized>) -> Self {
|
||||
let txid = to_txid(
|
||||
data.version,
|
||||
data.consensus_branch_id,
|
||||
&data.digest(TxIdDigester),
|
||||
);
|
||||
|
||||
Transaction { txid, data }
|
||||
}
|
||||
|
||||
pub fn txid(&self) -> TxId {
|
||||
self.txid
|
||||
}
|
||||
|
||||
#[allow(clippy::redundant_closure)]
|
||||
pub fn read<R: Read>(reader: R, consensus_branch_id: BranchId) -> io::Result<Self> {
|
||||
let mut reader = HashReader::new(reader);
|
||||
|
||||
let version = TxVersion::read(&mut reader)?;
|
||||
let is_overwinter_v3 = version == TxVersion::Overwinter;
|
||||
let is_sapling_v4 = version == TxVersion::Sapling;
|
||||
match version {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
|
||||
Self::read_v4(reader, version, consensus_branch_id)
|
||||
}
|
||||
TxVersion::Zip225 => Self::read_v5(reader.into_base_reader(), version),
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => Self::read_v5(reader.into_base_reader(), version),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::redundant_closure)]
|
||||
fn read_v4<R: Read>(
|
||||
mut reader: HashReader<R>,
|
||||
version: TxVersion,
|
||||
consensus_branch_id: BranchId,
|
||||
) -> io::Result<Self> {
|
||||
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 {
|
||||
let expiry_height: BlockHeight = if version.has_overwinter() {
|
||||
reader.read_u32::<LittleEndian>()?.into()
|
||||
} else {
|
||||
0u32.into()
|
||||
|
@ -531,15 +575,17 @@ impl Transaction {
|
|||
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 binding_sig = if version.has_sapling()
|
||||
&& !(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());
|
||||
let hash_bytes = reader.into_hash();
|
||||
txid.copy_from_slice(&hash_bytes);
|
||||
|
||||
Ok(Transaction {
|
||||
txid: TxId(txid),
|
||||
|
@ -582,16 +628,190 @@ impl Transaction {
|
|||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "valueBalance out of range"))
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
fn read_v5<R: Read>(mut reader: R, version: TxVersion) -> io::Result<Self> {
|
||||
let (consensus_branch_id, lock_time, expiry_height) =
|
||||
Self::read_v5_header_fragment(&mut reader)?;
|
||||
let transparent_bundle = Self::read_transparent(&mut reader)?;
|
||||
let sapling_bundle = Self::read_v5_sapling(&mut reader)?;
|
||||
let orchard_bundle = Self::read_v5_orchard(&mut reader)?;
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
let tze_bundle = Self::read_tze(&mut reader)?;
|
||||
|
||||
let data = TransactionData {
|
||||
version,
|
||||
consensus_branch_id,
|
||||
lock_time,
|
||||
expiry_height,
|
||||
transparent_bundle,
|
||||
sprout_bundle: None,
|
||||
sapling_bundle,
|
||||
orchard_bundle,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_bundle,
|
||||
};
|
||||
|
||||
Ok(Self::from_data_v5(data))
|
||||
}
|
||||
|
||||
fn read_v5_header_fragment<R: Read>(mut reader: R) -> io::Result<(BranchId, u32, BlockHeight)> {
|
||||
let consensus_branch_id = reader.read_u32::<LittleEndian>().and_then(|value| {
|
||||
BranchId::try_from(value).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid consensus branch id: ".to_owned() + e,
|
||||
)
|
||||
})
|
||||
})?;
|
||||
let lock_time = reader.read_u32::<LittleEndian>()?;
|
||||
let expiry_height: BlockHeight = reader.read_u32::<LittleEndian>()?.into();
|
||||
Ok((consensus_branch_id, lock_time, expiry_height))
|
||||
}
|
||||
|
||||
#[allow(clippy::redundant_closure)]
|
||||
fn read_v5_sapling<R: Read>(
|
||||
mut reader: R,
|
||||
) -> io::Result<Option<sapling::Bundle<sapling::Authorized>>> {
|
||||
let n_spends = CompactSize::read(&mut reader)?;
|
||||
let sd_v5s = Vector::read_count(&mut reader, n_spends, SpendDescriptionV5::read)?;
|
||||
let n_outputs = CompactSize::read(&mut reader)?;
|
||||
let od_v5s = Vector::read_count(&mut reader, n_outputs, OutputDescriptionV5::read)?;
|
||||
let value_balance = if n_spends > 0 || n_outputs > 0 {
|
||||
Self::read_amount(&mut reader)?
|
||||
} else {
|
||||
Amount::zero()
|
||||
};
|
||||
|
||||
let anchor = if n_spends > 0 {
|
||||
Some(sapling::read_base(&mut reader, "anchor")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let v_spend_proofs =
|
||||
Vector::read_count(&mut reader, n_spends, |r| sapling::read_zkproof(r))?;
|
||||
let v_spend_auth_sigs = Vector::read_count(&mut reader, n_spends, |r| {
|
||||
SpendDescription::read_spend_auth_sig(r)
|
||||
})?;
|
||||
let v_output_proofs =
|
||||
Vector::read_count(&mut reader, n_outputs, |r| sapling::read_zkproof(r))?;
|
||||
|
||||
let binding_sig = if n_spends > 0 || n_outputs > 0 {
|
||||
Some(redjubjub::Signature::read(&mut reader)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let shielded_spends = sd_v5s
|
||||
.into_iter()
|
||||
.zip(
|
||||
v_spend_proofs
|
||||
.into_iter()
|
||||
.zip(v_spend_auth_sigs.into_iter()),
|
||||
)
|
||||
.map(|(sd_5, (zkproof, spend_auth_sig))| {
|
||||
// the following `unwrap` is safe because we know n_spends > 0.
|
||||
sd_5.into_spend_description(anchor.unwrap(), zkproof, spend_auth_sig)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let shielded_outputs = od_v5s
|
||||
.into_iter()
|
||||
.zip(v_output_proofs.into_iter())
|
||||
.map(|(od_5, zkproof)| od_5.into_output_description(zkproof))
|
||||
.collect();
|
||||
|
||||
Ok(binding_sig.map(|binding_sig| sapling::Bundle {
|
||||
value_balance,
|
||||
shielded_spends,
|
||||
shielded_outputs,
|
||||
authorization: sapling::Authorized { binding_sig },
|
||||
}))
|
||||
}
|
||||
|
||||
fn read_v5_orchard<R: Read>(
|
||||
mut reader: R,
|
||||
) -> io::Result<Option<orchard::Bundle<orchard::bundle::Authorized, Amount>>> {
|
||||
let n_actions = CompactSize::read(&mut reader)?;
|
||||
if n_actions == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
let actions_without_auth = Vector::read_count(&mut reader, n_actions, |r| {
|
||||
orchard_serialization::read_action_without_auth(r)
|
||||
})?;
|
||||
let flags = orchard_serialization::read_flags(&mut reader)?;
|
||||
let value_balance = Self::read_amount(&mut reader)?;
|
||||
let anchor = orchard_serialization::read_anchor(&mut reader)?;
|
||||
let proof_size = CompactSize::read(&mut reader)?;
|
||||
let mut proof_bytes = vec![0u8; proof_size];
|
||||
reader.read_exact(&mut proof_bytes)?;
|
||||
let spend_sigs = Vector::read_count(&mut reader, n_actions, |r| {
|
||||
orchard_serialization::read_signature::<_, redpallas::SpendAuth>(r)
|
||||
})?;
|
||||
let binding_signature =
|
||||
orchard_serialization::read_signature::<_, redpallas::Binding>(&mut reader)
|
||||
.map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!(
|
||||
"An error occurred deserializing the Orchard binding signature: {}",
|
||||
e
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
let actions = NonEmpty::from_vec(
|
||||
actions_without_auth
|
||||
.into_iter()
|
||||
.zip(spend_sigs.into_iter())
|
||||
.map(|(act, sig)| act.map(|_| sig))
|
||||
.collect(),
|
||||
)
|
||||
.expect("A nonzero number of actions was read from the transaction data.");
|
||||
|
||||
let authorization = orchard::bundle::Authorized::from_parts(
|
||||
orchard::Proof::new(proof_bytes),
|
||||
binding_signature,
|
||||
);
|
||||
|
||||
Ok(Some(orchard::Bundle::from_parts(
|
||||
actions,
|
||||
flags,
|
||||
value_balance,
|
||||
anchor,
|
||||
authorization,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn read_tze<R: Read>(mut reader: &mut R) -> io::Result<Option<tze::Bundle<tze::Authorized>>> {
|
||||
let vin = Vector::read(&mut reader, TzeIn::read)?;
|
||||
let vout = Vector::read(&mut reader, TzeOut::read)?;
|
||||
Ok(if vin.is_empty() && vout.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(tze::Bundle { vin, vout })
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, writer: W) -> io::Result<()> {
|
||||
match self.version {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
|
||||
self.write_v4(writer)
|
||||
}
|
||||
TxVersion::Zip225 => self.write_v5(writer),
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => self.write_v5(writer),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_v4<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;
|
||||
|
||||
self.write_transparent(&mut writer)?;
|
||||
|
||||
writer.write_u32::<LittleEndian>(self.lock_time)?;
|
||||
if is_overwinter_v3 || is_sapling_v4 {
|
||||
if self.version.has_overwinter() {
|
||||
writer.write_u32::<LittleEndian>(u32::from(self.expiry_height))?;
|
||||
}
|
||||
|
||||
|
@ -638,10 +858,12 @@ impl Transaction {
|
|||
if let Some(bundle) = self.sapling_bundle.as_ref() {
|
||||
bundle.authorization.binding_sig.write(&mut writer)?;
|
||||
}
|
||||
} else if self.sapling_bundle.is_some() {
|
||||
}
|
||||
|
||||
if self.orchard_bundle.is_some() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Sapling components may not be present if Sapling is not active.",
|
||||
"Orchard components may not be present when serializing to the V4 transaction format."
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -660,6 +882,101 @@ impl Transaction {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_v5<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
self.write_v5_header(&mut writer)?;
|
||||
self.write_transparent(&mut writer)?;
|
||||
self.write_v5_sapling(&mut writer)?;
|
||||
self.write_v5_orchard(&mut writer)?;
|
||||
#[cfg(feature = "zfuture")]
|
||||
self.write_tze(&mut writer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_v5_header<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
self.version.write(&mut writer)?;
|
||||
writer.write_u32::<LittleEndian>(u32::from(self.consensus_branch_id))?;
|
||||
writer.write_u32::<LittleEndian>(self.lock_time)?;
|
||||
writer.write_u32::<LittleEndian>(u32::from(self.expiry_height))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_v5_sapling<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
if let Some(bundle) = &self.sapling_bundle {
|
||||
CompactSize::write(&mut writer, bundle.shielded_spends.len())?;
|
||||
Vector::write_items(&mut writer, &bundle.shielded_spends, |w, e| {
|
||||
e.write_v5_without_witness_data(w)
|
||||
})?;
|
||||
|
||||
CompactSize::write(&mut writer, bundle.shielded_outputs.len())?;
|
||||
Vector::write_items(&mut writer, &bundle.shielded_outputs, |w, e| {
|
||||
e.write_v5_without_proof(w)
|
||||
})?;
|
||||
|
||||
if !(bundle.shielded_spends.is_empty() && bundle.shielded_outputs.is_empty()) {
|
||||
writer.write_all(&bundle.value_balance.to_i64_le_bytes())?;
|
||||
}
|
||||
if !bundle.shielded_spends.is_empty() {
|
||||
writer.write_all(bundle.shielded_spends[0].anchor.to_repr().as_ref())?;
|
||||
}
|
||||
|
||||
Vector::write_items(
|
||||
&mut writer,
|
||||
bundle.shielded_spends.iter().map(|s| s.zkproof),
|
||||
|w, e| w.write_all(e),
|
||||
)?;
|
||||
Vector::write_items(
|
||||
&mut writer,
|
||||
bundle.shielded_spends.iter().map(|s| s.spend_auth_sig),
|
||||
|w, e| e.write(w),
|
||||
)?;
|
||||
|
||||
Vector::write_items(
|
||||
&mut writer,
|
||||
bundle.shielded_outputs.iter().map(|s| s.zkproof),
|
||||
|w, e| w.write_all(e),
|
||||
)?;
|
||||
|
||||
if !bundle.shielded_spends.is_empty() || !bundle.shielded_outputs.is_empty() {
|
||||
bundle.authorization.binding_sig.write(&mut writer)?;
|
||||
}
|
||||
} else {
|
||||
CompactSize::write(&mut writer, 0)?;
|
||||
CompactSize::write(&mut writer, 0)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_v5_orchard<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
if let Some(bundle) = &self.orchard_bundle {
|
||||
CompactSize::write(&mut writer, bundle.actions().len())?;
|
||||
Vector::write_items(&mut writer, bundle.actions().iter(), |w, a| {
|
||||
orchard_serialization::write_action_without_auth(w, a)
|
||||
})?;
|
||||
|
||||
if !bundle.actions().is_empty() {
|
||||
orchard_serialization::write_flags(&mut writer, &bundle.flags())?;
|
||||
writer.write_all(&bundle.value_balance().to_i64_le_bytes())?;
|
||||
orchard_serialization::write_anchor(&mut writer, bundle.anchor())?;
|
||||
let proof_bytes: &[u8] = bundle.authorization().proof().as_ref();
|
||||
CompactSize::write(&mut writer, proof_bytes.len())?;
|
||||
writer.write_all(&proof_bytes)?;
|
||||
Vector::write_items(
|
||||
&mut writer,
|
||||
bundle.actions().iter().map(|a| a.authorization()),
|
||||
|w, auth| w.write_all(&<[u8; 64]>::from(*auth)),
|
||||
)?;
|
||||
writer.write_all(&<[u8; 64]>::from(
|
||||
bundle.authorization().binding_signature(),
|
||||
))?;
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
|
@ -672,6 +989,11 @@ impl Transaction {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: should this be moved to `from_data` and stored?
|
||||
pub fn auth_commitment(&self) -> Result<Blake2bHash, DigestError> {
|
||||
Ok(self.data.digest(BlockTxCommitmentDigester))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -753,39 +1075,35 @@ pub enum DigestError {
|
|||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use proptest::prelude::*;
|
||||
use proptest::sample::select;
|
||||
|
||||
use crate::consensus::BranchId;
|
||||
|
||||
use super::{
|
||||
components::transparent::testing as transparent, Authorized, Transaction, TransactionData,
|
||||
TxId, TxVersion,
|
||||
components::{
|
||||
orchard::testing::{self as orchard},
|
||||
sapling::testing::{self as sapling},
|
||||
transparent::testing::{self as transparent},
|
||||
},
|
||||
Authorized, Transaction, TransactionData, TxId, TxVersion,
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use super::components::tze::testing::{self as tze};
|
||||
|
||||
pub fn arb_txid() -> impl Strategy<Value = TxId> {
|
||||
prop::array::uniform32(any::<u8>()).prop_map(TxId::from_bytes)
|
||||
}
|
||||
|
||||
pub fn arb_branch_id() -> impl Strategy<Value = BranchId> {
|
||||
select(vec![
|
||||
BranchId::Sprout,
|
||||
BranchId::Overwinter,
|
||||
BranchId::Sapling,
|
||||
BranchId::Blossom,
|
||||
BranchId::Heartwood,
|
||||
BranchId::Canopy,
|
||||
#[cfg(feature = "zfuture")]
|
||||
BranchId::ZFuture,
|
||||
])
|
||||
}
|
||||
|
||||
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(),
|
||||
_otherwise => Just(TxVersion::Sapling).boxed(),
|
||||
BranchId::Sapling | BranchId::Blossom | BranchId::Heartwood | BranchId::Canopy => {
|
||||
Just(TxVersion::Sapling).boxed()
|
||||
}
|
||||
BranchId::Nu5 => Just(TxVersion::Zip225).boxed(),
|
||||
#[cfg(feature = "zfuture")]
|
||||
BranchId::ZFuture => Just(TxVersion::ZFuture).boxed(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -793,9 +1111,13 @@ pub mod testing {
|
|||
prop_compose! {
|
||||
pub fn arb_txdata(consensus_branch_id: BranchId)(
|
||||
version in arb_tx_version(consensus_branch_id),
|
||||
)(
|
||||
lock_time in any::<u32>(),
|
||||
expiry_height in any::<u32>(),
|
||||
transparent_bundle in transparent::arb_bundle(),
|
||||
sapling_bundle in sapling::arb_bundle_for_version(version),
|
||||
orchard_bundle in orchard::arb_bundle_for_version(version),
|
||||
version in Just(version)
|
||||
) -> TransactionData<Authorized> {
|
||||
TransactionData {
|
||||
version,
|
||||
|
@ -804,8 +1126,8 @@ pub mod testing {
|
|||
expiry_height: expiry_height.into(),
|
||||
transparent_bundle,
|
||||
sprout_bundle: None,
|
||||
sapling_bundle: None, //FIXME
|
||||
orchard_bundle: None, //FIXME
|
||||
sapling_bundle,
|
||||
orchard_bundle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -814,10 +1136,14 @@ pub mod testing {
|
|||
prop_compose! {
|
||||
pub fn arb_txdata(consensus_branch_id: BranchId)(
|
||||
version in arb_tx_version(consensus_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),
|
||||
sapling_bundle in sapling::arb_bundle_for_version(version),
|
||||
orchard_bundle in orchard::arb_bundle_for_version(version),
|
||||
tze_bundle in tze::arb_bundle(consensus_branch_id),
|
||||
version in Just(version)
|
||||
) -> TransactionData<Authorized> {
|
||||
TransactionData {
|
||||
version,
|
||||
|
@ -826,9 +1152,9 @@ pub mod testing {
|
|||
expiry_height: expiry_height.into(),
|
||||
transparent_bundle,
|
||||
sprout_bundle: None,
|
||||
sapling_bundle: None, //FIXME
|
||||
orchard_bundle: None, //FIXME
|
||||
tze_bundle: None
|
||||
sapling_bundle,
|
||||
orchard_bundle,
|
||||
tze_bundle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ pub fn signature_hash<
|
|||
v4_signature_hash(tx, signable_input, hash_type)
|
||||
}
|
||||
|
||||
TxVersion::ZcashTxV5 => v5_signature_hash(tx, txid_parts, signable_input, hash_type),
|
||||
TxVersion::Zip225 => v5_signature_hash(tx, txid_parts, signable_input, hash_type),
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
TxVersion::ZFuture => v5_signature_hash(tx, txid_parts, signable_input, hash_type),
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::consensus::BranchId;
|
||||
|
||||
use super::{
|
||||
components::Amount, sighash::SignableInput, sighash_v4::v4_signature_hash, Transaction,
|
||||
components::Amount, sighash::SignableInput, sighash_v4::v4_signature_hash, testing::arb_tx,
|
||||
Transaction,
|
||||
};
|
||||
|
||||
use super::testing::{arb_branch_id, arb_tx};
|
||||
|
||||
#[test]
|
||||
fn tx_read_write() {
|
||||
let data = &self::data::tx_read_write::TX_READ_WRITE;
|
||||
|
@ -22,20 +23,89 @@ fn tx_read_write() {
|
|||
assert_eq!(&data[..], &encoded[..]);
|
||||
}
|
||||
|
||||
fn check_roundtrip(branch_id: BranchId, tx: Transaction) -> Result<(), TestCaseError> {
|
||||
let mut txn_bytes = vec![];
|
||||
tx.write(&mut txn_bytes).unwrap();
|
||||
let txo = Transaction::read(&txn_bytes[..], branch_id).unwrap();
|
||||
|
||||
prop_assert_eq!(tx.version, txo.version);
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_assert_eq!(tx.tze_bundle.as_ref(), txo.tze_bundle.as_ref());
|
||||
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());
|
||||
prop_assert_eq!(
|
||||
tx.orchard_bundle.as_ref().map(|v| *v.value_balance()),
|
||||
txo.orchard_bundle.as_ref().map(|v| *v.value_balance())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn tx_serialization_roundtrip(tx in arb_branch_id().prop_flat_map(arb_tx)) {
|
||||
let mut txn_bytes = vec![];
|
||||
tx.write(&mut txn_bytes).unwrap();
|
||||
#[ignore]
|
||||
fn tx_serialization_roundtrip_sprout(tx in arb_tx(BranchId::Sprout)) {
|
||||
check_roundtrip(BranchId::Sprout, tx)?;
|
||||
}
|
||||
}
|
||||
|
||||
let txo = Transaction::read(&txn_bytes[..], BranchId::Canopy).unwrap();
|
||||
proptest! {
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn tx_serialization_roundtrip_overwinter(tx in arb_tx(BranchId::Overwinter)) {
|
||||
check_roundtrip(BranchId::Overwinter, tx)?;
|
||||
}
|
||||
}
|
||||
|
||||
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")]
|
||||
prop_assert_eq!(tx.tze_bundle.as_ref(), txo.tze_bundle.as_ref());
|
||||
proptest! {
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn tx_serialization_roundtrip_sapling(tx in arb_tx(BranchId::Sapling)) {
|
||||
check_roundtrip(BranchId::Sapling, tx)?;
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn tx_serialization_roundtrip_blossom(tx in arb_tx(BranchId::Blossom)) {
|
||||
check_roundtrip(BranchId::Blossom, tx)?;
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn tx_serialization_roundtrip_heartwood(tx in arb_tx(BranchId::Heartwood)) {
|
||||
check_roundtrip(BranchId::Heartwood, tx)?;
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#![proptest_config(ProptestConfig::with_cases(20))]
|
||||
#[test]
|
||||
fn tx_serialization_roundtrip_canopy(tx in arb_tx(BranchId::Canopy)) {
|
||||
check_roundtrip(BranchId::Canopy, tx)?;
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#![proptest_config(ProptestConfig::with_cases(20))]
|
||||
#[test]
|
||||
fn tx_serialization_roundtrip_nu5(tx in arb_tx(BranchId::Nu5)) {
|
||||
check_roundtrip(BranchId::Nu5, tx)?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
proptest! {
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn tx_serialization_roundtrip_future(tx in arb_tx(BranchId::ZFuture)) {
|
||||
check_roundtrip(BranchId::ZFuture, tx)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,7 +124,7 @@ fn zip_0143() {
|
|||
};
|
||||
|
||||
assert_eq!(
|
||||
v4_signature_hash(&tx, signable_input, tv.hash_type).as_ref(),
|
||||
v4_signature_hash(tx.deref(), signable_input, tv.hash_type).as_ref(),
|
||||
tv.sighash
|
||||
);
|
||||
}
|
||||
|
@ -74,7 +144,7 @@ fn zip_0243() {
|
|||
};
|
||||
|
||||
assert_eq!(
|
||||
v4_signature_hash(&tx, signable_input, tv.hash_type).as_ref(),
|
||||
v4_signature_hash(tx.deref(), signable_input, tv.hash_type).as_ref(),
|
||||
tv.sighash
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,10 @@ impl<R: Read> HashReader<R> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_base_reader(self) -> R {
|
||||
self.reader
|
||||
}
|
||||
|
||||
/// Destroy this reader and return the hash of what was read.
|
||||
pub fn into_hash(self) -> Output<Sha256> {
|
||||
Sha256::digest(&self.hasher.finalize())
|
||||
|
|
Loading…
Reference in New Issue