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 13, 2024
1 parent 8dde110 commit 6da0ba6
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 52 deletions.
4 changes: 2 additions & 2 deletions zcash_client_sqlite/src/wallet/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ mod tests {
consensus::{
self, BlockHeight, BranchId, Network, NetworkConstants, NetworkUpgrade, Parameters,
},
transaction::{TransactionData, TxVersion},
transaction::{Transaction, TransactionData, TxVersion},
zip32::AccountId,
};

Expand Down Expand Up @@ -1266,7 +1266,7 @@ mod tests {
[],
)?;

let tx = TransactionData::from_parts(
let tx: Transaction = TransactionData::from_parts(
TxVersion::Sapling,
BranchId::Canopy,
0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ mod tests {
transparent::{self, Authorized, OutPoint},
Amount, TxIn, TxOut,
},
TransactionData, TxVersion,
Transaction, TransactionData, TxVersion,
},
},
};
Expand Down Expand Up @@ -413,7 +413,7 @@ mod tests {
.unwrap();

// create a UTXO to spend
let tx = TransactionData::from_parts(
let tx: Transaction = TransactionData::from_parts(
TxVersion::Sapling,
BranchId::Canopy,
0,
Expand Down
14 changes: 4 additions & 10 deletions zcash_primitives/src/transaction/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,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 @@ -256,7 +254,7 @@ impl TransparentEnc for NoTransparent {
}
}

pub(super) trait SproutEnc: SproutPart {
pub trait SproutEnc: SproutPart {
fn read_v4_bundle<R: io::Read>(
reader: R,
tx_has_sprout: bool,
Expand Down Expand Up @@ -302,9 +300,7 @@ impl SproutEnc for NoSprout {
}
}

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 @@ -424,9 +420,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
133 changes: 101 additions & 32 deletions zcash_primitives/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,22 +299,69 @@ impl Authorization for Unauthorized {
}

/// A Zcash transaction.
#[derive(Debug)]
pub struct Transaction {
pub type Transaction = TransactionWith<AllBundles<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<B>
where
B: Bundles,
B::Transparent: TransparentPart<Authorization = transparent::Authorized>,
B::Sapling: SaplingPart<Authorization = sapling::bundle::Authorized>,
B::Orchard: OrchardPart<Authorization = orchard::bundle::Authorized>,
{
txid: TxId,
data: TransactionData<AllBundles<Authorized>>,
data: TransactionData<B>,
}

impl Deref for Transaction {
type Target = TransactionData<AllBundles<Authorized>>;
impl<B> fmt::Debug for TransactionWith<B>
where
B: Bundles + fmt::Debug,
B::Transparent: TransparentPart<Authorization = transparent::Authorized> + fmt::Debug,
B::Sprout: SproutPart + fmt::Debug,
B::Sapling: SaplingPart<Authorization = sapling::bundle::Authorized> + fmt::Debug,
B::Orchard: OrchardPart<Authorization = orchard::bundle::Authorized> + fmt::Debug,
<B::Transparent as TransparentPart>::Bundle: fmt::Debug,
<B::Sprout as SproutPart>::Bundle: fmt::Debug,
<B::Sapling as SaplingPart>::Bundle: fmt::Debug,
<B::Orchard as OrchardPart>::Bundle: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TransactionWith")
.field("txid", &self.txid)
.field("data", &self.data)

Check warning on line 338 in zcash_primitives/src/transaction/mod.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/mod.rs#L335-L338

Added lines #L335 - L338 were not covered by tests
.finish()
}
}

impl<B> Deref for TransactionWith<B>
where
B: Bundles,
B::Transparent: TransparentPart<Authorization = transparent::Authorized>,
B::Sapling: SaplingPart<Authorization = sapling::bundle::Authorized>,
B::Orchard: OrchardPart<Authorization = orchard::bundle::Authorized>,
{
type Target = TransactionData<B>;

fn deref(&self) -> &TransactionData<AllBundles<Authorized>> {
fn deref(&self) -> &TransactionData<B> {
&self.data
}
}

impl PartialEq for Transaction {
fn eq(&self, other: &Transaction) -> bool {
impl<B> PartialEq for TransactionWith<B>
where
B: Bundles,
B::Transparent: TransparentPart<Authorization = transparent::Authorized>,
B::Sapling: SaplingPart<Authorization = sapling::bundle::Authorized>,
B::Orchard: OrchardPart<Authorization = orchard::bundle::Authorized>,
{
fn eq(&self, other: &Self) -> bool {

Check warning on line 364 in zcash_primitives/src/transaction/mod.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/mod.rs#L364

Added line #L364 was not covered by tests
self.txid == other.txid
}
}
Expand Down Expand Up @@ -596,14 +643,28 @@ impl<SA: sapling::bundle::Authorization, B: Bundles<Sapling = Sapling<SA>>> Tran
}
}

impl TransactionData<AllBundles<Authorized>> {
pub fn freeze(self) -> io::Result<Transaction> {
Transaction::from_data(self)
impl<B> TransactionData<B>
where
B: Bundles,
B::Transparent: TransparentEnc + txid::TransparentDigester,
B::Sprout: SproutEnc,
B::Sapling: SaplingEnc + txid::SaplingDigester,
B::Orchard: OrchardEnc + txid::OrchardDigester,
{
pub fn freeze(self) -> io::Result<TransactionWith<B>> {
TransactionWith::from_data(self)
}
}

impl Transaction {
fn from_data(data: TransactionData<AllBundles<Authorized>>) -> io::Result<Self> {
impl<B> TransactionWith<B>
where
B: Bundles,
B::Transparent: TransparentEnc + txid::TransparentDigester,
B::Sprout: SproutEnc,
B::Sapling: SaplingEnc + txid::SaplingDigester,
B::Orchard: OrchardEnc + txid::OrchardDigester,
{
fn from_data(data: TransactionData<B>) -> io::Result<Self> {
match data.version {
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
Self::from_data_v4(data)
Expand All @@ -614,8 +675,8 @@ impl Transaction {
}
}

fn from_data_v4(data: TransactionData<AllBundles<Authorized>>) -> io::Result<Self> {
let mut tx = Transaction {
fn from_data_v4(data: TransactionData<B>) -> io::Result<Self> {
let mut tx = TransactionWith {
txid: TxId([0; 32]),
data,
};
Expand All @@ -625,17 +686,17 @@ impl Transaction {
Ok(tx)
}

fn from_data_v5(data: TransactionData<AllBundles<Authorized>>) -> Self {
fn from_data_v5(data: TransactionData<B>) -> 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) -> TransactionData<AllBundles<Authorized>> {
pub fn into_data(self) -> TransactionData<B> {

Check warning on line 699 in zcash_primitives/src/transaction/mod.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/mod.rs#L699

Added line #L699 was not covered by tests
self.data
}

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

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

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

let sprout_bundle =
Sprout::read_v4_bundle(&mut reader, version.has_sprout(), version.has_sapling())?;
B::Sprout::read_v4_bundle(&mut reader, version.has_sprout(), version.has_sapling())?;

let sapling_bundle =
Sapling::read_v4_binding_sig(&mut reader, version.has_sapling(), components)?;
B::Sapling::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 @@ -711,9 +772,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 = B::Transparent::read(&mut reader)?;
let sapling_bundle = B::Sapling::read_v5_bundle(&mut reader)?;
let orchard_bundle = B::Orchard::read_v5_bundle(&mut reader)?;

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

Sapling::write_v4_components(
B::Sapling::write_v4_components(
self.sapling_bundle.as_ref(),
&mut writer,
self.version.has_sapling(),
)?;

Sprout::write_v4_bundle(
B::Sprout::write_v4_bundle(
self.sprout_bundle.as_ref(),
&mut writer,
self.version.has_sprout(),
)?;

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

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

pub fn write_v5<W: Write>(&self, mut writer: W) -> io::Result<()> {
Expand All @@ -836,7 +897,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)?;
B::Orchard::write_v5_bundle(self.orchard_bundle.as_ref(), &mut writer)?;
#[cfg(zcash_unstable = "zfuture")]
self.write_tze(&mut writer)?;
Ok(())
Expand All @@ -859,7 +920,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)
B::Sapling::write_v5_bundle(self.sapling_bundle.as_ref(), writer)
}

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

Ok(())
}
}

impl<B> TransactionWith<B>
where
B: Bundles,
B::Transparent: txid::TransparentWitnessDigester,
B::Sapling: txid::SaplingWitnessDigester,
B::Orchard: txid::OrchardWitnessDigester,
{
// TODO: should this be moved to `from_data` and stored?
pub fn auth_commitment(&self) -> Blake2bHash {
self.data.digest(BlockTxCommitmentDigester)
Expand Down
14 changes: 8 additions & 6 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 Expand Up @@ -481,7 +481,9 @@ pub fn to_txid(
TxId(<[u8; 32]>::try_from(txid_digest.as_bytes()).unwrap())
}

trait TransparentWitnessDigester: TransparentPart<Authorization = transparent::Authorized> {
pub trait TransparentWitnessDigester:
TransparentPart<Authorization = transparent::Authorized>
{
fn witness_digest(transparent_bundle: Option<&Self::Bundle>) -> Blake2bHash;
}

Expand All @@ -497,7 +499,7 @@ impl TransparentWitnessDigester for Transparent<transparent::Authorized> {
}
}

trait SaplingWitnessDigester: SaplingPart<Authorization = sapling::bundle::Authorized> {
pub trait SaplingWitnessDigester: SaplingPart<Authorization = sapling::bundle::Authorized> {
fn witness_digest(sapling_bundle: Option<&Self::Bundle>) -> Blake2bHash;
}

Expand Down Expand Up @@ -525,7 +527,7 @@ impl SaplingWitnessDigester for Sapling<sapling::bundle::Authorized> {
}
}

trait OrchardWitnessDigester: OrchardPart<Authorization = orchard::Authorized> {
pub trait OrchardWitnessDigester: OrchardPart<Authorization = orchard::Authorized> {
fn witness_digest(orchard_bundle: Option<&Self::Bundle>) -> Blake2bHash;
}

Expand Down

0 comments on commit 6da0ba6

Please sign in to comment.