Skip to content

Commit

Permalink
zcash_primitives: Allow tx serialization with omitted bundles
Browse files Browse the repository at this point in the history
  • Loading branch information
str4d committed May 11, 2024
1 parent eeeacc8 commit 9383425
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 42 deletions.
12 changes: 3 additions & 9 deletions zcash_primitives/src/transaction/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,7 @@ impl<A: ::orchard::bundle::Authorization> OrchardPart for Orchard<A> {
type Authorization = A;
}

pub(super) trait TransparentEnc:
TransparentPart<Authorization = transparent::Authorized>
{
pub trait TransparentEnc: TransparentPart<Authorization = transparent::Authorized> {
fn read<R: io::Read>(reader: R) -> io::Result<Option<Self::Bundle>>;

fn write<W: io::Write>(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()>;
Expand Down Expand Up @@ -187,9 +185,7 @@ impl TransparentEnc for NoTransparent {
}
}

pub(super) trait SaplingEnc:
SaplingPart<Authorization = ::sapling::bundle::Authorized>
{
pub trait SaplingEnc: SaplingPart<Authorization = ::sapling::bundle::Authorized> {
type V4Components;

fn read_v4_components<R: io::Read>(
Expand Down Expand Up @@ -309,9 +305,7 @@ impl SaplingEnc for NoSapling {
}
}

pub(super) trait OrchardEnc:
OrchardPart<Authorization = ::orchard::bundle::Authorized>
{
pub trait OrchardEnc: OrchardPart<Authorization = ::orchard::bundle::Authorized> {
fn read_v5_bundle<R: io::Read>(reader: R) -> io::Result<Option<Self::Bundle>>;

fn write_v5_bundle<W: io::Write>(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()>;
Expand Down
123 changes: 93 additions & 30 deletions zcash_primitives/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,22 +299,67 @@ impl Authorization for Unauthorized {
}

/// A Zcash transaction.
#[derive(Debug)]
pub struct Transaction {
pub type Transaction = TransactionWith<
Transparent<transparent::Authorized>,
Sapling<sapling::bundle::Authorized>,
Orchard<orchard::bundle::Authorized>,
>;

/// An authorized Zcash transaction with configurable support in the type system for the
/// various transparent and shielded pools.
///
/// If a particular pool is disabled (by using e.g. [`components::NoTransparent`]):
/// - The pool-specific APIs will be inaccessible.
/// - The serialization APIs will encode an empty bundle for that pool.
/// - The parsing APIs will return an error when parsing a transaction with spends or
/// outputs in that pool.
pub struct TransactionWith<T, S, O>
where
T: TransparentPart<Authorization = transparent::Authorized>,
S: SaplingPart<Authorization = sapling::bundle::Authorized>,
O: OrchardPart<Authorization = orchard::bundle::Authorized>,
{
txid: TxId,
data: AuthorizedTransactionData,
data: TransactionData<T, S, O>,
}

impl Deref for Transaction {
type Target = AuthorizedTransactionData;
impl<T, S, O> fmt::Debug for TransactionWith<T, S, O>
where
T: TransparentPart<Authorization = transparent::Authorized> + fmt::Debug,
S: SaplingPart<Authorization = sapling::bundle::Authorized> + fmt::Debug,
O: OrchardPart<Authorization = orchard::bundle::Authorized> + fmt::Debug,
T::Bundle: fmt::Debug,
S::Bundle: fmt::Debug,
O::Bundle: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TransactionWith")
.field("txid", &self.txid)
.field("data", &self.data)
.finish()
}
}

impl<T, S, O> Deref for TransactionWith<T, S, O>
where
T: TransparentPart<Authorization = transparent::Authorized>,
S: SaplingPart<Authorization = sapling::bundle::Authorized>,
O: OrchardPart<Authorization = orchard::bundle::Authorized>,
{
type Target = TransactionData<T, S, O>;

fn deref(&self) -> &AuthorizedTransactionData {
fn deref(&self) -> &TransactionData<T, S, O> {
&self.data
}
}

impl PartialEq for Transaction {
fn eq(&self, other: &Transaction) -> bool {
impl<T, S, O> PartialEq for TransactionWith<T, S, O>
where
T: TransparentPart<Authorization = transparent::Authorized>,
S: SaplingPart<Authorization = sapling::bundle::Authorized>,
O: OrchardPart<Authorization = orchard::bundle::Authorized>,
{
fn eq(&self, other: &Self) -> bool {
self.txid == other.txid
}
}
Expand Down Expand Up @@ -592,14 +637,24 @@ impl<T: TransparentPart, SA: sapling::bundle::Authorization, O: OrchardPart>
}
}

impl AuthorizedTransactionData {
pub fn freeze(self) -> io::Result<Transaction> {
Transaction::from_data(self)
impl<T, S, O> TransactionData<T, S, O>
where
T: TransparentEnc + txid::TransparentDigester,
S: SaplingEnc + txid::SaplingDigester,
O: OrchardEnc + txid::OrchardDigester,
{
pub fn freeze(self) -> io::Result<TransactionWith<T, S, O>> {
TransactionWith::from_data(self)
}
}

impl Transaction {
fn from_data(data: AuthorizedTransactionData) -> io::Result<Self> {
impl<T, S, O> TransactionWith<T, S, O>
where
T: TransparentEnc + txid::TransparentDigester,
S: SaplingEnc + txid::SaplingDigester,
O: OrchardEnc + txid::OrchardDigester,
{
fn from_data(data: TransactionData<T, S, O>) -> io::Result<Self> {
match data.version {
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
Self::from_data_v4(data)
Expand All @@ -610,8 +665,8 @@ impl Transaction {
}
}

fn from_data_v4(data: AuthorizedTransactionData) -> io::Result<Self> {
let mut tx = Transaction {
fn from_data_v4(data: TransactionData<T, S, O>) -> io::Result<Self> {
let mut tx = TransactionWith {
txid: TxId([0; 32]),
data,
};
Expand All @@ -621,17 +676,17 @@ impl Transaction {
Ok(tx)
}

fn from_data_v5(data: AuthorizedTransactionData) -> Self {
fn from_data_v5(data: TransactionData<T, S, O>) -> Self {
let txid = to_txid(
data.version,
data.consensus_branch_id,
&data.digest(TxIdDigester),
);

Transaction { txid, data }
TransactionWith { txid, data }
}

pub fn into_data(self) -> AuthorizedTransactionData {
pub fn into_data(self) -> TransactionData<T, S, O> {
self.data
}

Expand Down Expand Up @@ -659,7 +714,7 @@ impl Transaction {
version: TxVersion,
consensus_branch_id: BranchId,
) -> io::Result<Self> {
let transparent_bundle = Transparent::read(&mut reader)?;
let transparent_bundle = T::read(&mut reader)?;

let lock_time = reader.read_u32::<LittleEndian>()?;
let expiry_height: BlockHeight = if version.has_overwinter() {
Expand All @@ -668,7 +723,7 @@ impl Transaction {
0u32.into()
};

let components = Sapling::read_v4_components(&mut reader, version.has_sapling())?;
let components = S::read_v4_components(&mut reader, version.has_sapling())?;

let sprout_bundle = if version.has_sprout() {
let joinsplits = Vector::read(&mut reader, |r| {
Expand All @@ -692,13 +747,13 @@ impl Transaction {
};

let sapling_bundle =
Sapling::read_v4_binding_sig(&mut reader, version.has_sapling(), components)?;
S::read_v4_binding_sig(&mut reader, version.has_sapling(), components)?;

let mut txid = [0; 32];
let hash_bytes = reader.into_hash();
txid.copy_from_slice(&hash_bytes);

Ok(Transaction {
Ok(TransactionWith {
txid: TxId(txid),
data: TransactionData {
version,
Expand All @@ -725,9 +780,9 @@ impl Transaction {
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 = Transparent::read(&mut reader)?;
let sapling_bundle = Sapling::read_v5_bundle(&mut reader)?;
let orchard_bundle = Orchard::read_v5_bundle(&mut reader)?;
let transparent_bundle = T::read(&mut reader)?;
let sapling_bundle = S::read_v5_bundle(&mut reader)?;
let orchard_bundle = O::read_v5_bundle(&mut reader)?;

#[cfg(zcash_unstable = "zfuture")]
let tze_bundle = if version.has_tze() {
Expand Down Expand Up @@ -808,7 +863,7 @@ impl Transaction {
writer.write_u32::<LittleEndian>(u32::from(self.expiry_height))?;
}

Sapling::write_v4_components(
S::write_v4_components(
self.sapling_bundle.as_ref(),
&mut writer,
self.version.has_sapling(),
Expand All @@ -824,7 +879,7 @@ impl Transaction {
}
}

Sapling::write_v4_binding_sig(
S::write_v4_binding_sig(
self.sapling_bundle.as_ref(),
&mut writer,
self.version.has_sapling(),
Expand All @@ -841,7 +896,7 @@ impl Transaction {
}

pub fn write_transparent<W: Write>(&self, writer: W) -> io::Result<()> {
Transparent::write(self.transparent_bundle.as_ref(), writer)
T::write(self.transparent_bundle.as_ref(), writer)
}

pub fn write_v5<W: Write>(&self, mut writer: W) -> io::Result<()> {
Expand All @@ -854,7 +909,7 @@ impl Transaction {
self.write_v5_header(&mut writer)?;
self.write_transparent(&mut writer)?;
self.write_v5_sapling(&mut writer)?;
Orchard::write_v5_bundle(self.orchard_bundle.as_ref(), &mut writer)?;
O::write_v5_bundle(self.orchard_bundle.as_ref(), &mut writer)?;
#[cfg(zcash_unstable = "zfuture")]
self.write_tze(&mut writer)?;
Ok(())
Expand All @@ -877,7 +932,7 @@ impl Transaction {
}

pub fn write_v5_sapling<W: Write>(&self, writer: W) -> io::Result<()> {
Sapling::write_v5_bundle(self.sapling_bundle.as_ref(), writer)
S::write_v5_bundle(self.sapling_bundle.as_ref(), writer)
}

#[cfg(zcash_unstable = "zfuture")]
Expand All @@ -892,7 +947,15 @@ impl Transaction {

Ok(())
}
}

impl
TransactionWith<
Transparent<transparent::Authorized>,
Sapling<sapling::bundle::Authorized>,
Orchard<orchard::bundle::Authorized>,
>
{
// TODO: should this be moved to `from_data` and stored?
pub fn auth_commitment(&self) -> Blake2bHash {
self.data.digest(BlockTxCommitmentDigester)
Expand Down
6 changes: 3 additions & 3 deletions zcash_primitives/src/transaction/txid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ pub(crate) fn hash_sapling_outputs<A>(shielded_outputs: &[OutputDescription<A>])
h.finalize()
}

trait TransparentDigester: TransparentPart {
pub trait TransparentDigester: TransparentPart {
fn digest(transparent_bundle: Option<&Self::Bundle>)
-> Option<TransparentDigests<Blake2bHash>>;
}
Expand Down Expand Up @@ -275,7 +275,7 @@ pub(crate) fn hash_transparent_txid_data(
h.finalize()
}

trait SaplingDigester: SaplingPart {
pub trait SaplingDigester: SaplingPart {
fn digest(sapling_bundle: Option<&Self::Bundle>) -> Option<Blake2bHash>;
}

Expand Down Expand Up @@ -312,7 +312,7 @@ fn hash_sapling_txid_empty() -> Blake2bHash {
hasher(ZCASH_SAPLING_HASH_PERSONALIZATION).finalize()
}

trait OrchardDigester: OrchardPart {
pub trait OrchardDigester: OrchardPart {
fn digest(orchard_bundle: Option<&Self::Bundle>) -> Option<Blake2bHash>;
}

Expand Down

0 comments on commit 9383425

Please sign in to comment.