diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index 7e2cb10d4..98e62da51 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -3,6 +3,7 @@ use std::io; use std::marker::PhantomData; +use zcash_encoding::CompactSize; use zcash_protocol::value::BalanceError; pub mod amount { @@ -57,10 +58,28 @@ impl Bundles for AllBundles { type Orchard = Orchard; } +pub struct SomeBundles { + _transparent: PhantomData, + _sapling: PhantomData, + _orchard: PhantomData, +} + +impl Bundles for SomeBundles { + type Transparent = T; + type Sapling = S; + type Orchard = O; +} + pub trait ShieldedValueBalance { fn value_balance(&self) -> Amount; } +impl ShieldedValueBalance for () { + fn value_balance(&self) -> Amount { + Amount::zero() + } +} + impl ShieldedValueBalance for ::sapling::Bundle { fn value_balance(&self) -> Amount { *self.value_balance() @@ -83,6 +102,23 @@ pub trait TransparentPart { F: FnMut(&OutPoint) -> Result; } +pub enum NoTransparent {} + +impl TransparentPart for NoTransparent { + /// Values of this type are not exposed in the public API (the transaction method is + /// gated off). + type Bundle = (); + type Authorization = transparent::Authorized; + + fn value_balance(_: &Self::Bundle, _: F) -> Result + where + E: From, + F: FnMut(&OutPoint) -> Result, + { + Ok(Amount::zero()) + } +} + #[derive(Debug)] pub struct Transparent { _auth: PhantomData, @@ -108,6 +144,15 @@ pub trait SaplingPart { type Authorization: ::sapling::bundle::Authorization; } +pub enum NoSapling {} + +impl SaplingPart for NoSapling { + /// Values of this type are not exposed in the public API (the transaction method is + /// gated off). + type Bundle = (); + type Authorization = ::sapling::bundle::Authorized; +} + #[derive(Debug)] pub struct Sapling { _auth: PhantomData, @@ -123,6 +168,15 @@ pub trait OrchardPart { type Authorization: ::orchard::bundle::Authorization; } +pub enum NoOrchard {} + +impl OrchardPart for NoOrchard { + /// Values of this type are not exposed in the public API (the transaction method is + /// gated off). + type Bundle = (); + type Authorization = ::orchard::bundle::Authorized; +} + #[derive(Debug)] pub struct Orchard { _auth: PhantomData, @@ -141,6 +195,29 @@ pub(super) trait TransparentEnc: fn write(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()>; } +impl TransparentEnc for NoTransparent { + fn read(mut reader: R) -> io::Result> { + match CompactSize::read(&mut reader)? { + 0 => match CompactSize::read(&mut reader)? { + 0 => Ok(None), + n => Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("Expected no transparent outputs, found {}", n), + )), + }, + n => Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("Expected no transparent inputs, found {}", n), + )), + } + } + + fn write(_: Option<&Self::Bundle>, mut writer: W) -> io::Result<()> { + CompactSize::write(&mut writer, 0)?; + CompactSize::write(&mut writer, 0) + } +} + pub(super) trait SaplingEnc: SaplingPart { @@ -174,6 +251,95 @@ pub(super) trait SaplingEnc: fn write_v5_bundle(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()>; } +impl SaplingEnc for NoSapling { + type V4Components = (); + + fn read_v4_components( + mut reader: R, + tx_has_sapling: bool, + ) -> io::Result { + if tx_has_sapling { + const ZERO: Amount = Amount::zero(); + + match super::Transaction::read_amount(&mut reader)? { + ZERO => match CompactSize::read(&mut reader)? { + 0 => match CompactSize::read(&mut reader)? { + 0 => Ok(()), + n => Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("Expected no Sapling Outputs, found {}", n), + )), + }, + n => Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("Expected no Sapling Spends, found {}", n), + )), + }, + vb => Err(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "Expected no Sapling valueBalance, found {} zatoshis", + i64::from(vb) + ), + )), + } + } else { + Ok(()) + } + } + + fn read_v4_binding_sig( + _: R, + _: bool, + _: Self::V4Components, + ) -> io::Result> { + Ok(None) + } + + fn write_v4_components( + _: Option<&Self::Bundle>, + mut writer: W, + tx_has_sapling: bool, + ) -> io::Result<()> { + if tx_has_sapling { + writer.write_all(&Amount::zero().to_i64_le_bytes())?; + CompactSize::write(&mut writer, 0)?; + CompactSize::write(&mut writer, 0)?; + } + + Ok(()) + } + + fn write_v4_binding_sig( + _: Option<&Self::Bundle>, + _: W, + _: bool, + ) -> io::Result<()> { + Ok(()) + } + + fn read_v5_bundle(mut reader: R) -> io::Result> { + match CompactSize::read(&mut reader)? { + 0 => match CompactSize::read(&mut reader)? { + 0 => Ok(None), + n => Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("Expected no Sapling Outputs, found {}", n), + )), + }, + n => Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("Expected no Sapling Spends, found {}", n), + )), + } + } + + fn write_v5_bundle(_: Option<&Self::Bundle>, mut writer: W) -> io::Result<()> { + CompactSize::write(&mut writer, 0)?; + CompactSize::write(&mut writer, 0) + } +} + pub(super) trait OrchardEnc: OrchardPart { @@ -181,3 +347,19 @@ pub(super) trait OrchardEnc: fn write_v5_bundle(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()>; } + +impl OrchardEnc for NoOrchard { + fn read_v5_bundle(reader: R) -> io::Result> { + match CompactSize::read(reader)? { + 0 => Ok(None), + n => Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("Expected no Orchard Actions, found {}", n), + )), + } + } + + fn write_v5_bundle(_: Option<&Self::Bundle>, writer: W) -> io::Result<()> { + CompactSize::write(writer, 0) + } +} diff --git a/zcash_primitives/src/transaction/sighash_v4.rs b/zcash_primitives/src/transaction/sighash_v4.rs index 462d1bd29..8a01e0631 100644 --- a/zcash_primitives/src/transaction/sighash_v4.rs +++ b/zcash_primitives/src/transaction/sighash_v4.rs @@ -15,7 +15,8 @@ use super::{ sapling as sapling_serialization, sprout::JsDescription, transparent::{self, TxIn, TxOut}, - Bundles, Sapling, SaplingPart, ShieldedValueBalance, Transparent, TransparentPart, + Bundles, NoSapling, NoTransparent, Sapling, SaplingPart, ShieldedValueBalance, Transparent, + TransparentPart, }, sighash::{SignableInput, SIGHASH_ANYONECANPAY, SIGHASH_MASK, SIGHASH_NONE, SIGHASH_SINGLE}, TransactionData, @@ -57,6 +58,37 @@ pub trait TransparentSigDigester: TransparentPart { ) -> Vec; } +impl TransparentSigDigester for NoTransparent { + fn digest_prevout(_: Option<&Self::Bundle>) -> Blake2bHash { + Blake2bParams::new() + .hash_length(32) + .personal(ZCASH_PREVOUTS_HASH_PERSONALIZATION) + .hash(&[]) + } + + fn digest_sequence(_: Option<&Self::Bundle>) -> Blake2bHash { + Blake2bParams::new() + .hash_length(32) + .personal(ZCASH_SEQUENCE_HASH_PERSONALIZATION) + .hash(&[]) + } + + fn digest_outputs(_: Option<&Self::Bundle>) -> Blake2bHash { + Blake2bParams::new() + .hash_length(32) + .personal(ZCASH_OUTPUTS_HASH_PERSONALIZATION) + .hash(&[]) + } + + fn digest_single_output(_: Option<&Self::Bundle>, _: &SignableInput<'_>) -> [u8; 32] { + [0; 32] + } + + fn digest_signable_input(_: Option<&Self::Bundle>, _: &SignableInput<'_>) -> Vec { + panic!("A request has been made to sign a transparent input, but none are present."); + } +} + impl TransparentSigDigester for Transparent { fn digest_prevout(transparent_bundle: Option<&Self::Bundle>) -> Blake2bHash { prevout_hash(transparent_bundle.map_or(&[], |b| b.vin.as_slice())) @@ -190,6 +222,16 @@ pub trait SaplingSigDigester: SaplingPart { fn digest_outputs(sapling_bundle: Option<&Self::Bundle>) -> [u8; 32]; } +impl SaplingSigDigester for NoSapling { + fn digest_spends(_: Option<&Self::Bundle>) -> [u8; 32] { + [0; 32] + } + + fn digest_outputs(_: Option<&Self::Bundle>) -> [u8; 32] { + [0; 32] + } +} + impl SaplingSigDigester for Sapling where A: sapling::bundle::Authorization, diff --git a/zcash_primitives/src/transaction/sighash_v5.rs b/zcash_primitives/src/transaction/sighash_v5.rs index 5ca599929..e74f7aa81 100644 --- a/zcash_primitives/src/transaction/sighash_v5.rs +++ b/zcash_primitives/src/transaction/sighash_v5.rs @@ -6,7 +6,7 @@ use zcash_encoding::Array; use crate::transaction::{ components::{ transparent::{self, TxOut}, - Bundles, Transparent, TransparentPart, + Bundles, NoTransparent, Transparent, TransparentPart, }, sighash::{ SignableInput, TransparentAuthorizingContext, SIGHASH_ANYONECANPAY, SIGHASH_MASK, @@ -47,6 +47,15 @@ pub trait TransparentSigDigester: TransparentPart { ) -> Blake2bHash; } +impl TransparentSigDigester for NoTransparent { + fn digest( + _: Option<(&Self::Bundle, &TransparentDigests)>, + _: &SignableInput<'_>, + ) -> Blake2bHash { + hash_transparent_txid_data(None) + } +} + impl TransparentSigDigester for Transparent { fn digest( tx_data: Option<(&Self::Bundle, &TransparentDigests)>, diff --git a/zcash_primitives/src/transaction/txid.rs b/zcash_primitives/src/transaction/txid.rs index 630a7a36f..d43f280a8 100644 --- a/zcash_primitives/src/transaction/txid.rs +++ b/zcash_primitives/src/transaction/txid.rs @@ -19,8 +19,8 @@ use super::{ components::{ amount::Amount, transparent::{self, TxIn, TxOut}, - AllBundles, Bundles, Orchard, OrchardPart, Sapling, SaplingPart, Transparent, - TransparentPart, + AllBundles, Bundles, NoOrchard, NoSapling, NoTransparent, Orchard, OrchardPart, Sapling, + SaplingPart, Transparent, TransparentPart, }, Authorized, TransactionDigest, TransparentDigests, TxDigests, TxId, TxVersion, }; @@ -205,6 +205,12 @@ trait TransparentDigester: TransparentPart { -> Option>; } +impl TransparentDigester for NoTransparent { + fn digest(_: Option<&Self::Bundle>) -> Option> { + None + } +} + impl TransparentDigester for Transparent { fn digest( transparent_bundle: Option<&Self::Bundle>, @@ -273,6 +279,12 @@ trait SaplingDigester: SaplingPart { fn digest(sapling_bundle: Option<&Self::Bundle>) -> Option; } +impl SaplingDigester for NoSapling { + fn digest(_: Option<&Self::Bundle>) -> Option { + None + } +} + impl SaplingDigester for Sapling { fn digest(sapling_bundle: Option<&Self::Bundle>) -> Option { sapling_bundle.map(hash_sapling_txid_data) @@ -304,6 +316,12 @@ trait OrchardDigester: OrchardPart { fn digest(orchard_bundle: Option<&Self::Bundle>) -> Option; } +impl OrchardDigester for NoOrchard { + fn digest(_: Option<&Self::Bundle>) -> Option { + None + } +} + impl OrchardDigester for Orchard { fn digest(orchard_bundle: Option<&Self::Bundle>) -> Option { orchard_bundle.map(|b| b.commitment().0)