Merge pull request #375 from nuttycom/feature/zip-225

ZIP 225 & ZIP 244
This commit is contained in:
str4d 2021-06-08 17:28:25 +01:00 committed by GitHub
commit 0bfd1f7544
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 4262 additions and 965 deletions

View File

@ -8,6 +8,7 @@ use crate::encoding::{
};
/// An address that funds can be sent to.
// TODO: rename to ParsedAddress
#[derive(Debug, PartialEq, Clone)]
pub enum RecipientAddress {
Shielded(PaymentAddress),

View File

@ -238,8 +238,7 @@ where
},
RecipientAddress::Transparent(addr) => {
let script = addr.script();
tx.transparent_bundle
.as_ref()
tx.transparent_bundle()
.and_then(|b| {
b.vout
.iter()

View File

@ -44,7 +44,7 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
) -> Vec<DecryptedOutput> {
let mut decrypted = vec![];
if let Some(bundle) = tx.sapling_bundle.as_ref() {
if let Some(bundle) = tx.sapling_bundle() {
for (account, extfvk) in extfvks.iter() {
let ivk = extfvk.fvk.vk.ivk();
let ovk = extfvk.fvk.ovk;

View File

@ -489,7 +489,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
//
// Assumes that create_spend_to_address() will never be called in parallel, which is a
// reasonable assumption for a light client such as a mobile phone.
if let Some(bundle) = sent_tx.tx.sapling_bundle.as_ref() {
if let Some(bundle) = sent_tx.tx.sapling_bundle() {
for spend in &bundle.shielded_spends {
wallet::mark_spent(up, tx_ref, &spend.nullifier)?;
}

View File

@ -650,14 +650,14 @@ pub fn put_tx_data<'a, P>(
if stmts
.stmt_update_tx_data
.execute(params![u32::from(tx.expiry_height), raw_tx, txid,])?
.execute(params![u32::from(tx.expiry_height()), raw_tx, txid,])?
== 0
{
// It isn't there, so insert our transaction into the database.
stmts.stmt_insert_tx_data.execute(params![
txid,
created_at,
u32::from(tx.expiry_height),
u32::from(tx.expiry_height()),
raw_tx
])?;

View File

@ -68,9 +68,9 @@ pub fn get_spendable_notes<P>(
"SELECT diversifier, value, rcm, witness
FROM received_notes
INNER JOIN transactions ON transactions.id_tx = received_notes.tx
INNER JOIN sapling_witnesses ON sapling_witnesses.note = received_notes.id_note
WHERE account = :account
AND spent IS NULL
INNER JOIN sapling_witnesses ON sapling_witnesses.note = received_notes.id_note
WHERE account = :account
AND spent IS NULL
AND transactions.block <= :anchor_height
AND sapling_witnesses.block = :anchor_height",
)?;
@ -153,7 +153,7 @@ mod tests {
use zcash_primitives::{
block::BlockHash,
consensus::BlockHeight,
consensus::{BlockHeight, BranchId},
legacy::TransparentAddress,
sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver},
transaction::{components::Amount, Transaction},
@ -617,7 +617,7 @@ mod tests {
|row| row.get(0),
)
.unwrap();
let tx = Transaction::read(&raw_tx[..]).unwrap();
let tx = Transaction::read(&raw_tx[..], BranchId::Canopy).unwrap();
// Fetch the output index from the database
let output_index: i64 = db_write
@ -631,8 +631,7 @@ mod tests {
)
.unwrap();
let output =
&tx.sapling_bundle.as_ref().unwrap().shielded_outputs[output_index as usize];
let output = &tx.sapling_bundle().unwrap().shielded_outputs[output_index as usize];
try_sapling_output_recovery(
&network,

View File

@ -10,7 +10,7 @@ edition = "2018"
[dependencies]
blake2b_simd = "0.5"
zcash_primitives = { version = "0.5", path = "../zcash_primitives", features = ["zfuture"] }
zcash_primitives = { version = "0.5", path = "../zcash_primitives", features = ["zfuture" ] }
[dev-dependencies]
ff = "0.10"

View File

@ -78,14 +78,14 @@ pub trait Epoch {
/// by the context.
impl<'a> demo::Context for Context<'a> {
fn is_tze_only(&self) -> bool {
self.tx.transparent_bundle.is_none()
&& self.tx.sapling_bundle.is_none()
&& self.tx.sprout_bundle.is_none()
&& self.tx.orchard_bundle.is_none()
self.tx.transparent_bundle().is_none()
&& self.tx.sapling_bundle().is_none()
&& self.tx.sprout_bundle().is_none()
&& self.tx.orchard_bundle().is_none()
}
fn tx_tze_outputs(&self) -> &[TzeOut] {
if let Some(bundle) = &self.tx.tze_bundle {
if let Some(bundle) = self.tx.tze_bundle() {
&bundle.vout
} else {
&[]

View File

@ -495,7 +495,7 @@ mod tests {
builder::Builder,
components::{
amount::{Amount, DEFAULT_FEE},
tze::{Bundle, OutPoint, TzeIn, TzeOut},
tze::{Authorized, Bundle, OutPoint, TzeIn, TzeOut},
},
Transaction, TransactionData, TxVersion,
},
@ -621,14 +621,14 @@ mod tests {
/// by the context.
impl<'a> Context for Ctx<'a> {
fn is_tze_only(&self) -> bool {
self.tx.transparent_bundle.is_none()
&& self.tx.sprout_bundle.is_none()
&& self.tx.sapling_bundle.is_none()
&& self.tx.orchard_bundle.is_none()
self.tx.transparent_bundle().is_none()
&& self.tx.sapling_bundle().is_none()
&& self.tx.sprout_bundle().is_none()
&& self.tx.orchard_bundle().is_none()
}
fn tx_tze_outputs(&self) -> &[TzeOut] {
match &self.tx.tze_bundle {
match self.tx.tze_bundle() {
Some(b) => &b.vout,
None => &[],
}
@ -683,20 +683,22 @@ mod tests {
precondition: tze::Precondition::from(0, &Precondition::open(hash_1)),
};
let tx_a = TransactionData {
version: TxVersion::ZFuture,
lock_time: 0,
expiry_height: 0u32.into(),
transparent_bundle: None,
sprout_bundle: None,
sapling_bundle: None,
orchard_bundle: None,
tze_bundle: Some(Bundle {
let tx_a = TransactionData::from_parts(
TxVersion::ZFuture,
BranchId::ZFuture,
0,
0u32.into(),
None,
None,
None,
None,
Some(Bundle {
vin: vec![],
vout: vec![out_a],
authorization: Authorized,
}),
}
.freeze(BranchId::ZFuture)
)
.freeze()
.unwrap();
//
@ -712,20 +714,22 @@ mod tests {
precondition: tze::Precondition::from(0, &Precondition::close(hash_2)),
};
let tx_b = TransactionData {
version: TxVersion::ZFuture,
lock_time: 0,
expiry_height: 0u32.into(),
transparent_bundle: None,
sprout_bundle: None,
sapling_bundle: None,
orchard_bundle: None,
tze_bundle: Some(Bundle {
let tx_b = TransactionData::from_parts(
TxVersion::ZFuture,
BranchId::ZFuture,
0,
0u32.into(),
None,
None,
None,
None,
Some(Bundle {
vin: vec![in_b],
vout: vec![out_b],
authorization: Authorized,
}),
}
.freeze(BranchId::ZFuture)
)
.freeze()
.unwrap();
//
@ -737,20 +741,22 @@ mod tests {
witness: tze::Witness::from(0, &Witness::close(preimage_2)),
};
let tx_c = TransactionData {
version: TxVersion::ZFuture,
lock_time: 0,
expiry_height: 0u32.into(),
transparent_bundle: None,
sprout_bundle: None,
sapling_bundle: None,
orchard_bundle: None,
tze_bundle: Some(Bundle {
let tx_c = TransactionData::from_parts(
TxVersion::ZFuture,
BranchId::ZFuture,
0,
0u32.into(),
None,
None,
None,
None,
Some(Bundle {
vin: vec![in_c],
vout: vec![],
authorization: Authorized,
}),
}
.freeze(BranchId::ZFuture)
)
.freeze()
.unwrap();
// Verify tx_b
@ -758,8 +764,8 @@ mod tests {
let ctx = Ctx { tx: &tx_b };
assert_eq!(
Program.verify(
&tx_a.tze_bundle.as_ref().unwrap().vout[0].precondition,
&tx_b.tze_bundle.as_ref().unwrap().vin[0].witness,
&tx_a.tze_bundle().unwrap().vout[0].precondition,
&tx_b.tze_bundle().unwrap().vin[0].witness,
&ctx
),
Ok(())
@ -771,8 +777,8 @@ mod tests {
let ctx = Ctx { tx: &tx_c };
assert_eq!(
Program.verify(
&tx_b.tze_bundle.as_ref().unwrap().vout[0].precondition,
&tx_c.tze_bundle.as_ref().unwrap().vin[0].witness,
&tx_b.tze_bundle().unwrap().vout[0].precondition,
&tx_c.tze_bundle().unwrap().vin[0].witness,
&ctx
),
Ok(())
@ -830,7 +836,7 @@ mod tests {
.build(&prover)
.map_err(|e| format!("build failure: {:?}", e))
.unwrap();
let tze_a = tx_a.tze_bundle.as_ref().unwrap();
let tze_a = tx_a.tze_bundle().unwrap();
//
// Transfer
@ -848,7 +854,7 @@ mod tests {
.build(&prover)
.map_err(|e| format!("build failure: {:?}", e))
.unwrap();
let tze_b = tx_b.tze_bundle.as_ref().unwrap();
let tze_b = tx_b.tze_bundle().unwrap();
//
// Closing transaction
@ -873,7 +879,7 @@ mod tests {
.build(&prover)
.map_err(|e| format!("build failure: {:?}", e))
.unwrap();
let tze_c = tx_c.tze_bundle.as_ref().unwrap();
let tze_c = tx_c.tze_bundle().unwrap();
// Verify tx_b
let ctx0 = Ctx { tx: &tx_b };

View File

@ -10,7 +10,7 @@ use zcash_primitives::{
Diversifier, PaymentAddress, SaplingIvk, ValueCommitment,
},
transaction::components::{
sapling::{Authorized, OutputDescription},
sapling::{GrothProofBytes, OutputDescription},
GROTH_PROOF_SIZE,
},
};
@ -23,7 +23,7 @@ fn bench_note_decryption(c: &mut Criterion) {
let invalid_ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
// Construct a fake Sapling output as if we had just deserialized a transaction.
let output: OutputDescription<Authorized> = {
let output: OutputDescription<GrothProofBytes> = {
let diversifier = Diversifier([0; 11]);
let pk_d = diversifier.g_d().unwrap() * valid_ivk.0;
let pa = PaymentAddress::from_parts(diversifier, pk_d).unwrap();

View File

@ -386,7 +386,7 @@ pub fn try_sapling_output_recovery_with_ock<P: consensus::Parameters>(
params: &P,
height: BlockHeight,
ock: &OutgoingCipherKey,
output: &OutputDescription<sapling::Authorized>,
output: &OutputDescription<sapling::GrothProofBytes>,
) -> Option<(Note, PaymentAddress, MemoBytes)> {
let domain = SaplingDomain {
params: params.clone(),
@ -408,7 +408,7 @@ pub fn try_sapling_output_recovery<P: consensus::Parameters>(
params: &P,
height: BlockHeight,
ovk: &OutgoingViewingKey,
output: &OutputDescription<sapling::Authorized>,
output: &OutputDescription<sapling::GrothProofBytes>,
) -> Option<(Note, PaymentAddress, MemoBytes)> {
let domain = SaplingDomain {
params: params.clone(),
@ -465,7 +465,7 @@ mod tests {
OutgoingViewingKey,
OutgoingCipherKey,
SaplingIvk,
OutputDescription<sapling::Authorized>,
OutputDescription<sapling::GrothProofBytes>,
) {
let ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
@ -498,7 +498,7 @@ mod tests {
) -> (
OutgoingViewingKey,
OutgoingCipherKey,
OutputDescription<sapling::Authorized>,
OutputDescription<sapling::GrothProofBytes>,
) {
let diversifier = Diversifier([0; 11]);
let pk_d = diversifier.g_d().unwrap() * ivk.0;

View File

@ -1,4 +1,5 @@
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use nonempty::NonEmpty;
use std::io::{self, Read, Write};
const MAX_SIZE: usize = 0x02000000;
@ -70,7 +71,7 @@ impl Vector {
F: Fn(&mut R) -> io::Result<E>,
{
let count = CompactSize::read(&mut reader)?;
(0..count).map(|_| func(&mut reader)).collect()
Array::read(reader, count, func)
}
pub fn write<W: Write, E, F>(mut writer: W, vec: &[E], func: F) -> io::Result<()>
@ -80,6 +81,40 @@ impl Vector {
CompactSize::write(&mut writer, vec.len())?;
vec.iter().try_for_each(|e| func(&mut writer, e))
}
pub fn write_nonempty<W: Write, E, F>(
mut writer: W,
vec: &NonEmpty<E>,
func: F,
) -> io::Result<()>
where
F: Fn(&mut W, &E) -> io::Result<()>,
{
CompactSize::write(&mut writer, vec.len())?;
vec.iter().try_for_each(|e| func(&mut writer, e))
}
}
pub struct Array;
impl Array {
pub fn read<R: Read, E, F>(mut reader: R, count: usize, func: F) -> io::Result<Vec<E>>
where
F: Fn(&mut R) -> io::Result<E>,
{
(0..count).map(|_| func(&mut reader)).collect()
}
pub fn write<W: Write, E, I: IntoIterator<Item = E>, F>(
mut writer: W,
vec: I,
func: F,
) -> io::Result<()>
where
F: Fn(&mut W, &E) -> io::Result<()>,
{
vec.into_iter().try_for_each(|e| func(&mut writer, &e))
}
}
pub struct Optional;

View File

@ -1,13 +1,14 @@
//! Structs for building transactions.
use rand::{rngs::OsRng, CryptoRng, RngCore};
use std::array;
use std::error;
use std::fmt;
use std::io;
use std::sync::mpsc::Sender;
use orchard::bundle::{self as orchard};
#[cfg(not(feature = "zfuture"))]
use std::marker::PhantomData;
use rand::{rngs::OsRng, CryptoRng, RngCore};
use crate::{
consensus::{self, BlockHeight, BranchId},
@ -15,8 +16,7 @@ use crate::{
memo::MemoBytes,
merkle_tree::MerklePath,
sapling::{
keys::OutgoingViewingKey, prover::TxProver, redjubjub, Diversifier, Node, Note,
PaymentAddress,
keys::OutgoingViewingKey, prover::TxProver, Diversifier, Node, Note, PaymentAddress,
},
transaction::{
components::{
@ -27,21 +27,19 @@ use crate::{
},
transparent::{self, builder::TransparentBuilder},
},
signature_hash_data, SignableInput, Transaction, TransactionData, TxVersion, Unauthorized,
SIGHASH_ALL,
sighash::{signature_hash, SignableInput, SIGHASH_ALL},
txid::TxIdDigester,
Transaction, TransactionData, TxVersion, Unauthorized,
},
zip32::ExtendedSpendingKey,
};
#[cfg(not(feature = "zfuture"))]
use std::marker::PhantomData;
#[cfg(feature = "transparent-inputs")]
use crate::{legacy::Script, transaction::components::transparent::TxOut};
use crate::transaction::components::transparent::TxOut;
#[cfg(feature = "zfuture")]
use crate::{
extensions::transparent::{AuthData, ExtensionTxBuilder, ToPayload},
extensions::transparent::{ExtensionTxBuilder, ToPayload},
transaction::components::{
tze::builder::TzeBuilder,
tze::{self, TzeOut},
@ -279,11 +277,6 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
///
/// Upon success, returns a tuple containing the final transaction, and the
/// [`SaplingMetadata`] generated during the build process.
///
/// `consensus_branch_id` must be valid for the block height that this transaction is
/// targeting. An invalid `consensus_branch_id` will *not* result in an error from
/// this function, and instead will generate a transaction that will be rejected by
/// the network.
pub fn build(
mut self,
prover: &impl TxProver,
@ -325,23 +318,25 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
let transparent_bundle = self.transparent_builder.build();
let mut rng = self.rng;
let mut ctx = prover.new_sapling_proving_context();
let (sapling_bundle, tx_metadata) = self
let sapling_bundle = self
.sapling_builder
.build(
prover,
&mut ctx,
&mut self.rng,
&mut rng,
self.target_height,
self.progress_notifier.as_ref(),
)
.map_err(Error::SaplingBuild)?;
#[cfg(feature = "zfuture")]
let tze_bundle = self.tze_builder.build();
let (tze_bundle, tze_signers) = self.tze_builder.build();
let unauthed_tx = TransactionData {
let unauthed_tx: TransactionData<Unauthorized> = TransactionData {
version,
consensus_branch_id: BranchId::for_height(&self.params, self.target_height),
lock_time: 0,
expiry_height: self.expiry_height,
transparent_bundle,
@ -355,110 +350,63 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
//
// Signatures -- everything but the signatures must already have been added.
//
let txid_parts = unauthed_tx.digest(TxIdDigester);
let mut sighash = [0u8; 32];
sighash.copy_from_slice(&signature_hash_data(
&unauthed_tx,
consensus_branch_id,
SIGHASH_ALL,
SignableInput::Shielded,
));
#[cfg(feature = "transparent-inputs")]
let transparent_sigs = self
.transparent_builder
.create_signatures(&unauthed_tx, consensus_branch_id);
let sapling_sigs = self
.sapling_builder
.create_signatures(prover, &mut ctx, &mut self.rng, &sighash, &tx_metadata)
.map_err(Error::SaplingBuild)?;
let transparent_bundle = unauthed_tx.transparent_bundle.clone().map(|b| {
b.apply_signatures(
#[cfg(feature = "transparent-inputs")]
&unauthed_tx,
#[cfg(feature = "transparent-inputs")]
&txid_parts,
)
});
#[cfg(feature = "zfuture")]
let tze_witnesses = self
.tze_builder
.create_witnesses(&unauthed_tx)
let tze_bundle = unauthed_tx
.tze_bundle
.clone()
.map(|b| b.into_authorized(&unauthed_tx, tze_signers))
.transpose()
.map_err(Error::TzeBuild)?;
Ok((
Self::apply_signatures(
consensus_branch_id,
unauthed_tx,
#[cfg(feature = "transparent-inputs")]
transparent_sigs,
sapling_sigs,
None,
#[cfg(feature = "zfuture")]
tze_witnesses,
)
.expect("An IO error occurred applying signatures."),
tx_metadata,
))
}
// the commitment being signed is shared across all Sapling inputs; once
// V4 transactions are deprecated this should just be the txid, but
// for now we need to continue to compute it here.
let shielded_sig_commitment = signature_hash(
&unauthed_tx,
SIGHASH_ALL,
&SignableInput::Shielded,
&txid_parts,
);
fn apply_signatures(
consensus_branch_id: consensus::BranchId,
unauthed_tx: TransactionData<Unauthorized>,
#[cfg(feature = "transparent-inputs")] transparent_sigs: Option<Vec<Script>>,
sapling_sigs: Option<(Vec<redjubjub::Signature>, redjubjub::Signature)>,
_orchard_auth: Option<(
Vec<<orchard::Authorized as orchard::Authorization>::SpendAuth>,
orchard::Authorized,
)>,
#[cfg(feature = "zfuture")] tze_witnesses: Option<Vec<AuthData>>,
) -> 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))
}
(None, None) => None,
_ => {
panic!("Mismatch between sapling bundle and signatures.");
}
};
#[cfg(feature = "zfuture")]
let signed_tze_bundle = match (unauthed_tx.tze_bundle, tze_witnesses) {
(Some(bundle), Some(witness_payloads)) => {
Some(bundle.apply_signatures(witness_payloads))
}
(None, None) => None,
_ => {
panic!("Mismatch between TZE bundle and witnesses.")
}
let (sapling_bundle, tx_metadata) = match unauthed_tx
.sapling_bundle
.map(|b| {
b.apply_signatures(prover, &mut ctx, &mut rng, shielded_sig_commitment.as_ref())
})
.transpose()
.map_err(Error::SaplingBuild)?
{
Some((bundle, meta)) => (Some(bundle), meta),
None => (None, SaplingMetadata::empty()),
};
let authorized_tx = TransactionData {
version: unauthed_tx.version,
consensus_branch_id: unauthed_tx.consensus_branch_id,
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,
sapling_bundle,
orchard_bundle: None,
#[cfg(feature = "zfuture")]
tze_bundle: signed_tze_bundle,
tze_bundle,
};
authorized_tx.freeze(consensus_branch_id)
// The unwrap() here is safe because the txid hashing
// of freeze() should be infalliable.
Ok((authorized_tx.freeze().unwrap(), tx_metadata))
}
}
@ -530,19 +478,19 @@ mod tests {
transaction::components::{
amount::{Amount, DEFAULT_FEE},
sapling::builder::{self as build_s},
transparent::builder::{self as transparent},
transparent::builder::{self as build_t},
},
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
};
use super::{Builder, Error, SaplingBuilder, DEFAULT_TX_EXPIRY_DELTA};
#[cfg(not(feature = "zfuture"))]
use std::marker::PhantomData;
#[cfg(feature = "zfuture")]
use super::TzeBuilder;
#[cfg(not(feature = "zfuture"))]
use std::marker::PhantomData;
#[test]
fn fails_on_negative_output() {
let extsk = ExtendedSpendingKey::master(&[]);
@ -646,7 +594,7 @@ mod tests {
&TransparentAddress::PublicKey([0; 20]),
Amount::from_i64(-1).unwrap(),
),
Err(Error::TransparentBuild(transparent::Error::InvalidAmount))
Err(Error::TransparentBuild(build_t::Error::InvalidAmount))
);
}

View File

@ -2,6 +2,8 @@ use std::convert::TryFrom;
use std::iter::Sum;
use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
use orchard::value as orchard;
pub const COIN: i64 = 1_0000_0000;
pub const MAX_MONEY: i64 = 21_000_000 * COIN;
@ -180,6 +182,14 @@ impl Neg for Amount {
}
}
impl TryFrom<orchard::ValueSum> for Amount {
type Error = ();
fn try_from(v: orchard::ValueSum) -> Result<Amount, Self::Error> {
i64::try_from(v).map_err(|_| ()).and_then(Amount::try_from)
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::prelude::prop_compose;

View File

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

View File

@ -33,14 +33,6 @@ impl Authorization for Unproven {
type AuthSig = ();
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Unauthorized;
impl Authorization for Unauthorized {
type Proof = GrothProofBytes;
type AuthSig = ();
}
#[derive(Debug, Copy, Clone)]
pub struct Authorized {
pub binding_sig: redjubjub::Signature,
@ -54,36 +46,11 @@ impl Authorization for Authorized {
#[derive(Debug, Clone)]
pub struct Bundle<A: Authorization> {
pub shielded_spends: Vec<SpendDescription<A>>,
pub shielded_outputs: Vec<OutputDescription<A>>,
pub shielded_outputs: Vec<OutputDescription<A::Proof>>,
pub value_balance: Amount,
pub authorization: A,
}
impl Bundle<Unauthorized> {
pub fn apply_signatures(
self,
spend_auth_sigs: Vec<Signature>,
binding_sig: Signature,
) -> Bundle<Authorized> {
assert!(self.shielded_spends.len() == spend_auth_sigs.len());
Bundle {
shielded_spends: self
.shielded_spends
.iter()
.zip(spend_auth_sigs.iter())
.map(|(spend, sig)| spend.apply_signature(*sig))
.collect(),
shielded_outputs: self
.shielded_outputs
.into_iter()
.map(|o| o.into_authorized())
.collect(), //TODO, is there a zero-cost way to do this?
value_balance: self.value_balance,
authorization: Authorized { binding_sig },
}
}
}
#[derive(Clone)]
pub struct SpendDescription<A: Authorization> {
pub cv: jubjub::ExtendedPoint,
@ -94,19 +61,6 @@ pub struct SpendDescription<A: Authorization> {
pub spend_auth_sig: A::AuthSig,
}
impl SpendDescription<Unauthorized> {
pub fn apply_signature(&self, spend_auth_sig: Signature) -> SpendDescription<Authorized> {
SpendDescription {
cv: self.cv,
anchor: self.anchor,
nullifier: self.nullifier,
rk: self.rk.clone(),
zkproof: self.zkproof,
spend_auth_sig,
}
}
}
impl<A: Authorization> std::fmt::Debug for SpendDescription<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
@ -206,7 +160,7 @@ impl SpendDescription<Authorized> {
})
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
pub fn write_v4<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.cv.to_bytes())?;
writer.write_all(self.anchor.to_repr().as_ref())?;
writer.write_all(&self.nullifier.0)?;
@ -214,21 +168,58 @@ impl SpendDescription<Authorized> {
writer.write_all(&self.zkproof)?;
self.spend_auth_sig.write(&mut writer)
}
pub fn write_v5_without_witness_data<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.cv.to_bytes())?;
writer.write_all(&self.nullifier.0)?;
self.rk.write(&mut writer)
}
}
#[derive(Clone)]
pub struct OutputDescription<A: Authorization> {
pub struct SpendDescriptionV5 {
pub cv: jubjub::ExtendedPoint,
pub nullifier: Nullifier,
pub rk: PublicKey,
}
impl SpendDescriptionV5 {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
let cv = read_point(&mut reader, "cv")?;
let nullifier = SpendDescription::read_nullifier(&mut reader)?;
let rk = SpendDescription::read_rk(&mut reader)?;
Ok(SpendDescriptionV5 { cv, nullifier, rk })
}
pub fn into_spend_description(
self,
anchor: bls12_381::Scalar,
zkproof: GrothProofBytes,
spend_auth_sig: Signature,
) -> SpendDescription<Authorized> {
SpendDescription {
cv: self.cv,
anchor,
nullifier: self.nullifier,
rk: self.rk,
zkproof,
spend_auth_sig,
}
}
}
#[derive(Clone)]
pub struct OutputDescription<Proof> {
pub cv: jubjub::ExtendedPoint,
pub cmu: bls12_381::Scalar,
pub ephemeral_key: jubjub::ExtendedPoint,
pub enc_ciphertext: [u8; 580],
pub out_ciphertext: [u8; 80],
pub zkproof: A::Proof,
pub zkproof: Proof,
}
impl<P: consensus::Parameters, A: Authorization> ShieldedOutput<SaplingDomain<P>>
for OutputDescription<A>
{
impl<P: consensus::Parameters, A> ShieldedOutput<SaplingDomain<P>> for OutputDescription<A> {
fn epk(&self) -> &jubjub::ExtendedPoint {
&self.ephemeral_key
}
@ -242,7 +233,7 @@ impl<P: consensus::Parameters, A: Authorization> ShieldedOutput<SaplingDomain<P>
}
}
impl<A: Authorization> std::fmt::Debug for OutputDescription<A> {
impl<A> std::fmt::Debug for OutputDescription<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
@ -252,57 +243,28 @@ impl<A: Authorization> std::fmt::Debug for OutputDescription<A> {
}
}
impl OutputDescription<Authorized> {
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
impl OutputDescription<GrothProofBytes> {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
// Consensus rules (§4.5):
// - Canonical encoding is enforced here.
// - "Not small order" is enforced in SaplingVerificationContext::check_output()
// (located in zcash_proofs::sapling::verifier).
let cv = {
let mut bytes = [0u8; 32];
reader.read_exact(&mut bytes)?;
let cv = jubjub::ExtendedPoint::from_bytes(&bytes);
if cv.is_none().into() {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid cv"));
}
cv.unwrap()
};
let cv = read_point(&mut reader, "cv")?;
// Consensus rule (§7.4): Canonical encoding is enforced here
let cmu = {
let mut f = [0u8; 32];
reader.read_exact(&mut f)?;
bls12_381::Scalar::from_repr(f)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "cmu not in field"))?
};
let cmu = read_base(&mut reader, "cmu")?;
// Consensus rules (§4.5):
// - Canonical encoding is enforced here.
// - "Not small order" is enforced in SaplingVerificationContext::check_output()
let ephemeral_key = {
let mut bytes = [0u8; 32];
reader.read_exact(&mut bytes)?;
let ephemeral_key = jubjub::ExtendedPoint::from_bytes(&bytes);
if ephemeral_key.is_none().into() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid ephemeral_key",
));
}
ephemeral_key.unwrap()
};
let ephemeral_key = read_point(&mut reader, "ephemeral key")?;
let mut enc_ciphertext = [0u8; 580];
let mut out_ciphertext = [0u8; 80];
reader.read_exact(&mut enc_ciphertext)?;
reader.read_exact(&mut out_ciphertext)?;
// Consensus rules (§4.5):
// - Canonical encoding is enforced by the API of SaplingVerificationContext::check_output()
// due to the need to parse this into a bellman::groth16::Proof.
// - Proof validity is enforced in SaplingVerificationContext::check_output()
let mut zkproof = [0u8; GROTH_PROOF_SIZE];
reader.read_exact(&mut zkproof)?;
let zkproof = read_zkproof(&mut reader)?;
Ok(OutputDescription {
cv,
@ -313,10 +275,8 @@ impl OutputDescription<Authorized> {
zkproof,
})
}
}
impl<A: Authorization<Proof = GrothProofBytes>> OutputDescription<A> {
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
pub fn write_v4<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.cv.to_bytes())?;
writer.write_all(self.cmu.to_repr().as_ref())?;
writer.write_all(&self.ephemeral_key.to_bytes())?;
@ -324,17 +284,56 @@ impl<A: Authorization<Proof = GrothProofBytes>> OutputDescription<A> {
writer.write_all(&self.out_ciphertext)?;
writer.write_all(&self.zkproof)
}
pub fn write_v5_without_proof<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.cv.to_bytes())?;
writer.write_all(self.cmu.to_repr().as_ref())?;
writer.write_all(&self.ephemeral_key.to_bytes())?;
writer.write_all(&self.enc_ciphertext)?;
writer.write_all(&self.out_ciphertext)
}
}
impl OutputDescription<Unauthorized> {
pub fn into_authorized(self) -> OutputDescription<Authorized> {
#[derive(Clone)]
pub struct OutputDescriptionV5 {
pub cv: jubjub::ExtendedPoint,
pub cmu: bls12_381::Scalar,
pub ephemeral_key: jubjub::ExtendedPoint,
pub enc_ciphertext: [u8; 580],
pub out_ciphertext: [u8; 80],
}
impl OutputDescriptionV5 {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
let cv = read_point(&mut reader, "cv")?;
let cmu = read_base(&mut reader, "cmu")?;
let ephemeral_key = read_point(&mut reader, "ephemeral key")?;
let mut enc_ciphertext = [0u8; 580];
let mut out_ciphertext = [0u8; 80];
reader.read_exact(&mut enc_ciphertext)?;
reader.read_exact(&mut out_ciphertext)?;
Ok(OutputDescriptionV5 {
cv,
cmu,
ephemeral_key,
enc_ciphertext,
out_ciphertext,
})
}
pub fn into_output_description(
self,
zkproof: GrothProofBytes,
) -> OutputDescription<GrothProofBytes> {
OutputDescription {
cv: self.cv,
cmu: self.cmu,
ephemeral_key: self.ephemeral_key,
enc_ciphertext: self.enc_ciphertext,
out_ciphertext: self.out_ciphertext,
zkproof: self.zkproof,
zkproof,
}
}
}
@ -345,7 +344,7 @@ pub struct CompactOutputDescription {
pub enc_ciphertext: Vec<u8>,
}
impl<A: Authorization> From<OutputDescription<A>> for CompactOutputDescription {
impl<A> From<OutputDescription<A>> for CompactOutputDescription {
fn from(out: OutputDescription<A>) -> CompactOutputDescription {
CompactOutputDescription {
epk: out.ephemeral_key,
@ -368,3 +367,127 @@ impl<P: consensus::Parameters> ShieldedOutput<SaplingDomain<P>> for CompactOutpu
&self.enc_ciphertext
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use ff::Field;
use group::Group;
use proptest::collection::vec;
use proptest::prelude::*;
use rand::{rngs::StdRng, SeedableRng};
use std::convert::TryFrom;
use crate::{
constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR},
sapling::{
redjubjub::{PrivateKey, PublicKey},
Nullifier,
},
transaction::{
components::{amount::testing::arb_amount, GROTH_PROOF_SIZE},
TxVersion,
},
};
use super::{Authorized, Bundle, GrothProofBytes, OutputDescription, SpendDescription};
prop_compose! {
fn arb_extended_point()(rng_seed in prop::array::uniform32(any::<u8>())) -> jubjub::ExtendedPoint {
let mut rng = StdRng::from_seed(rng_seed);
let scalar = jubjub::Scalar::random(&mut rng);
jubjub::ExtendedPoint::generator() * scalar
}
}
prop_compose! {
/// produce a spend description with invalid data (useful only for serialization
/// roundtrip testing).
fn arb_spend_description()(
cv in arb_extended_point(),
anchor in vec(any::<u8>(), 64)
.prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap())
.prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)),
nullifier in prop::array::uniform32(any::<u8>())
.prop_map(|v| Nullifier::from_slice(&v).unwrap()),
zkproof in vec(any::<u8>(), GROTH_PROOF_SIZE)
.prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()),
rng_seed in prop::array::uniform32(prop::num::u8::ANY),
fake_sighash_bytes in prop::array::uniform32(prop::num::u8::ANY),
) -> SpendDescription<Authorized> {
let mut rng = StdRng::from_seed(rng_seed);
let sk1 = PrivateKey(jubjub::Fr::random(&mut rng));
let rk = PublicKey::from_private(&sk1, SPENDING_KEY_GENERATOR);
SpendDescription {
cv,
anchor,
nullifier,
rk,
zkproof,
spend_auth_sig: sk1.sign(&fake_sighash_bytes, &mut rng, SPENDING_KEY_GENERATOR),
}
}
}
prop_compose! {
/// produce an output description with invalid data (useful only for serialization
/// roundtrip testing).
pub fn arb_output_description()(
cv in arb_extended_point(),
cmu in vec(any::<u8>(), 64)
.prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap())
.prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)),
enc_ciphertext in vec(any::<u8>(), 580)
.prop_map(|v| <[u8;580]>::try_from(v.as_slice()).unwrap()),
ephemeral_key in arb_extended_point(),
out_ciphertext in vec(any::<u8>(), 80)
.prop_map(|v| <[u8;80]>::try_from(v.as_slice()).unwrap()),
zkproof in vec(any::<u8>(), GROTH_PROOF_SIZE)
.prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()),
) -> OutputDescription<GrothProofBytes> {
OutputDescription {
cv,
cmu,
ephemeral_key,
enc_ciphertext,
out_ciphertext,
zkproof,
}
}
}
prop_compose! {
pub fn arb_bundle()(
shielded_spends in vec(arb_spend_description(), 0..30),
shielded_outputs in vec(arb_output_description(), 0..30),
value_balance in arb_amount(),
rng_seed in prop::array::uniform32(prop::num::u8::ANY),
fake_bvk_bytes in prop::array::uniform32(prop::num::u8::ANY),
) -> Option<Bundle<Authorized>> {
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,
shielded_outputs,
value_balance,
authorization: Authorized { binding_sig: bsk.sign(&fake_bvk_bytes, &mut rng, VALUE_COMMITMENT_RANDOMNESS_GENERATOR) },
}
)
}
}
}
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))
}
}
}

View File

@ -23,7 +23,10 @@ use crate::{
builder::Progress,
components::{
amount::Amount,
sapling::{Bundle, OutputDescription, SpendDescription, Unauthorized},
sapling::{
Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription,
SpendDescription,
},
},
},
zip32::ExtendedSpendingKey,
@ -56,7 +59,8 @@ impl fmt::Display for Error {
}
}
struct SpendDescriptionInfo {
#[derive(Debug, Clone)]
pub struct SpendDescriptionInfo {
extsk: ExtendedSpendingKey,
diversifier: Diversifier,
note: Note,
@ -110,7 +114,7 @@ impl SaplingOutput {
prover: &Pr,
ctx: &mut Pr::SaplingProvingContext,
rng: &mut R,
) -> OutputDescription<Unauthorized> {
) -> OutputDescription<GrothProofBytes> {
let encryptor = sapling_note_encryption::<R, P>(
self.ovk,
self.note.clone(),
@ -146,7 +150,7 @@ impl SaplingOutput {
}
/// Metadata about a transaction created by a [`SaplingBuilder`].
#[derive(Debug, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct SaplingMetadata {
spend_indices: Vec<usize>,
output_indices: Vec<usize>,
@ -192,6 +196,22 @@ pub struct SaplingBuilder<P> {
outputs: Vec<SaplingOutput>,
}
#[derive(Clone)]
pub struct Unauthorized {
tx_metadata: SaplingMetadata,
}
impl std::fmt::Debug for Unauthorized {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "Unauthorized")
}
}
impl Authorization for Unauthorized {
type Proof = GrothProofBytes;
type AuthSig = SpendDescriptionInfo;
}
impl<P: consensus::Parameters> SaplingBuilder<P> {
pub fn new(params: P, target_height: BlockHeight) -> Self {
SaplingBuilder {
@ -284,15 +304,16 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
}
pub fn build<Pr: TxProver, R: RngCore>(
&self,
self,
prover: &Pr,
ctx: &mut Pr::SaplingProvingContext,
mut rng: R,
target_height: BlockHeight,
progress_notifier: Option<&Sender<Progress>>,
) -> Result<(Option<Bundle<Unauthorized>>, SaplingMetadata), Error> {
) -> Result<Option<Bundle<Unauthorized>>, Error> {
// Record initial positions of spends and outputs
let mut indexed_spends: Vec<_> = self.spends.iter().enumerate().collect();
let params = self.params;
let mut indexed_spends: Vec<_> = self.spends.into_iter().enumerate().collect();
let mut indexed_outputs: Vec<_> = self
.outputs
.iter()
@ -328,7 +349,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
.expect("Sapling anchor must be set if Sapling spends are present.");
indexed_spends
.iter()
.into_iter()
.enumerate()
.map(|(i, (pos, spend))| {
let proof_generation_key = spend.extsk.expsk.proof_generation_key();
@ -352,7 +373,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
.map_err(|_| Error::SpendProof)?;
// Record the post-randomized spend location
tx_metadata.spend_indices[*pos] = i;
tx_metadata.spend_indices[pos] = i;
// Update progress and send a notification on the channel
progress += 1;
@ -369,7 +390,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
nullifier,
rk,
zkproof,
spend_auth_sig: (),
spend_auth_sig: spend,
})
})
.collect::<Result<Vec<_>, Error>>()?
@ -378,7 +399,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
};
// Create Sapling OutputDescriptions
let shielded_outputs: Vec<OutputDescription<Unauthorized>> = indexed_outputs
let shielded_outputs: Vec<OutputDescription<GrothProofBytes>> = indexed_outputs
.into_iter()
.enumerate()
.map(|(i, output)| {
@ -404,7 +425,6 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
}
(diversifier, g_d)
};
let (pk_d, payment_address) = loop {
let dummy_ivk = jubjub::Fr::random(&mut rng);
let pk_d = g_d * dummy_ivk;
@ -414,7 +434,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
};
let rseed =
generate_random_rseed_internal(&self.params, target_height, &mut rng);
generate_random_rseed_internal(&params, target_height, &mut rng);
(
payment_address,
@ -470,47 +490,59 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
shielded_spends,
shielded_outputs,
value_balance: self.value_balance,
authorization: Unauthorized,
authorization: Unauthorized { tx_metadata },
})
};
Ok((bundle, tx_metadata))
Ok(bundle)
}
}
pub fn create_signatures<Pr: TxProver, R: RngCore>(
impl SpendDescription<Unauthorized> {
pub fn apply_signature(&self, spend_auth_sig: Signature) -> SpendDescription<Authorized> {
SpendDescription {
cv: self.cv,
anchor: self.anchor,
nullifier: self.nullifier,
rk: self.rk.clone(),
zkproof: self.zkproof,
spend_auth_sig,
}
}
}
impl Bundle<Unauthorized> {
pub fn apply_signatures<Pr: TxProver, R: RngCore>(
self,
prover: &Pr,
ctx: &mut Pr::SaplingProvingContext,
rng: &mut R,
sighash_bytes: &[u8; 32],
tx_metadata: &SaplingMetadata,
) -> Result<Option<(Vec<Signature>, Signature)>, Error> {
// Create Sapling spendAuth signatures. These must be properly ordered with respect to the
// shuffle that is described by tx_metadata.
let mut spend_sigs = vec![None; self.spends.len()];
for (i, spend) in self.spends.into_iter().enumerate() {
spend_sigs[tx_metadata.spend_indices[i]] = Some(spend_sig_internal(
PrivateKey(spend.extsk.expsk.ask),
spend.alpha,
sighash_bytes,
rng,
));
}
) -> Result<(Bundle<Authorized>, SaplingMetadata), Error> {
let binding_sig = prover
.binding_sig(ctx, self.value_balance, sighash_bytes)
.map_err(|_| Error::BindingSig)?;
if tx_metadata.spend_indices.is_empty() && tx_metadata.output_indices.is_empty() {
Ok(None)
} else {
let spend_sigs = spend_sigs
.into_iter()
.collect::<Option<Vec<Signature>>>()
.unwrap_or_default();
let binding_sig = prover
.binding_sig(ctx, self.value_balance, sighash_bytes)
.map_err(|_| Error::BindingSig)?;
Ok(Some((spend_sigs, binding_sig)))
}
Ok((
Bundle {
shielded_spends: self
.shielded_spends
.iter()
.map(|spend| {
spend.apply_signature(spend_sig_internal(
PrivateKey(spend.spend_auth_sig.extsk.expsk.ask),
spend.spend_auth_sig.alpha,
sighash_bytes,
rng,
))
})
.collect(),
shielded_outputs: self.shielded_outputs,
value_balance: self.value_balance,
authorization: Authorized { binding_sig },
},
self.authorization.tx_metadata,
))
}
}
@ -574,23 +606,22 @@ pub mod testing {
let prover = MockTxProver;
let mut ctx = prover.new_sapling_proving_context();
let (bundle, meta) = builder.build(
let bundle = builder.build(
&prover,
&mut ctx,
&mut rng,
target_height.unwrap(),
None
).unwrap();
).unwrap().unwrap();
let (spend_auth_sigs, binding_sig) = builder.create_signatures(
let (bundle, _) = bundle.apply_signatures(
&prover,
&mut ctx,
&mut rng,
&fake_sighash_bytes,
&meta
).unwrap().unwrap();
).unwrap();
bundle.unwrap().apply_signatures(spend_auth_sigs, binding_sig)
bundle
}
}
}

View File

@ -15,13 +15,6 @@ 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;
@ -33,25 +26,7 @@ impl Authorization for Authorized {
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,
}
}
pub authorization: A,
}
#[derive(Clone, Debug, PartialEq)]
@ -93,18 +68,6 @@ pub struct TxIn<A: Authorization> {
pub sequence: u32,
}
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: (),
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)?;
@ -210,7 +173,7 @@ pub mod testing {
if vin.is_empty() && vout.is_empty() {
None
} else {
Some(Bundle { vin, vout })
Some(Bundle { vin, vout, authorization: Authorized })
}
}
}

View File

@ -2,21 +2,25 @@
use std::fmt;
#[cfg(feature = "transparent-inputs")]
use blake2b_simd::Hash as Blake2bHash;
use crate::{
legacy::TransparentAddress,
transaction::components::{
amount::Amount,
transparent::{self, TxIn, TxOut},
transparent::{self, Authorization, Authorized, Bundle, TxIn, TxOut},
},
};
#[cfg(feature = "transparent-inputs")]
use crate::{
consensus::{self},
legacy::Script,
transaction::{
components::OutPoint, sighash::signature_hash_data, SignableInput, TransactionData,
Unauthorized, SIGHASH_ALL,
self as tx,
components::OutPoint,
sighash::{signature_hash, SignableInput, SIGHASH_ALL},
TransactionData, TxDigests,
},
};
@ -36,6 +40,7 @@ impl fmt::Display for Error {
}
#[cfg(feature = "transparent-inputs")]
#[derive(Debug, Clone)]
struct TransparentInputInfo {
sk: secp256k1::SecretKey,
pubkey: [u8; secp256k1::constants::PUBLIC_KEY_SIZE],
@ -51,6 +56,18 @@ pub struct TransparentBuilder {
vout: Vec<TxOut>,
}
#[derive(Debug, Clone)]
pub struct Unauthorized {
#[cfg(feature = "transparent-inputs")]
secp: secp256k1::Secp256k1<secp256k1::SignOnly>,
#[cfg(feature = "transparent-inputs")]
inputs: Vec<TransparentInputInfo>,
}
impl Authorization for Unauthorized {
type ScriptSig = ();
}
impl TransparentBuilder {
pub fn empty() -> Self {
TransparentBuilder {
@ -131,66 +148,94 @@ impl TransparentBuilder {
.sum::<Option<Amount>>()?
}
pub fn build(&self) -> Option<transparent::Bundle<transparent::Unauthorized>> {
pub fn build(self) -> Option<transparent::Bundle<Unauthorized>> {
#[cfg(feature = "transparent-inputs")]
let vin: Vec<TxIn<transparent::Unauthorized>> = self
let vin: Vec<TxIn<Unauthorized>> = self
.inputs
.iter()
.map(|i| TxIn::new(i.utxo.clone()))
.collect();
#[cfg(not(feature = "transparent-inputs"))]
let vin: Vec<TxIn<transparent::Unauthorized>> = vec![];
let vin: Vec<TxIn<Unauthorized>> = vec![];
if vin.is_empty() && self.vout.is_empty() {
None
} else {
Some(transparent::Bundle {
vin,
vout: self.vout.clone(),
vout: self.vout,
authorization: Unauthorized {
#[cfg(feature = "transparent-inputs")]
secp: self.secp,
#[cfg(feature = "transparent-inputs")]
inputs: self.inputs,
},
})
}
}
}
impl TxIn<Unauthorized> {
#[cfg(feature = "transparent-inputs")]
pub fn create_signatures(
self,
mtx: &TransactionData<Unauthorized>,
consensus_branch_id: consensus::BranchId,
) -> 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);
// 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(),
)
#[cfg_attr(docsrs, doc(cfg(feature = "transparent-inputs")))]
pub fn new(prevout: OutPoint) -> Self {
TxIn {
prevout,
script_sig: (),
sequence: std::u32::MAX,
}
}
}
impl Bundle<Unauthorized> {
pub fn apply_signatures(
self,
#[cfg(feature = "transparent-inputs")] mtx: &TransactionData<tx::Unauthorized>,
#[cfg(feature = "transparent-inputs")] txid_parts_cache: &TxDigests<Blake2bHash>,
) -> Bundle<Authorized> {
#[cfg(feature = "transparent-inputs")]
let script_sigs: Vec<Script> = self
.authorization
.inputs
.iter()
.enumerate()
.map(|(i, info)| {
let sighash = signature_hash(
mtx,
SIGHASH_ALL,
&SignableInput::transparent(i, &info.coin.script_pubkey, info.coin.value),
txid_parts_cache,
);
let msg = secp256k1::Message::from_slice(sighash.as_ref()).expect("32 bytes");
let sig = self.authorization.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]);
// P2PKH scriptSig
Script::default() << &sig_bytes[..] << &info.pubkey[..]
})
.collect();
#[cfg(not(feature = "transparent-inputs"))]
let script_sigs = vec![];
transparent::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,
authorization: Authorized,
}
}
}

View File

@ -25,13 +25,6 @@ pub trait Authorization: Debug {
type Witness: Debug + Clone + PartialEq;
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Unauthorized;
impl Authorization for Unauthorized {
type Witness = ();
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Authorized;
@ -41,30 +34,9 @@ impl Authorization for Authorized {
#[derive(Debug, Clone, PartialEq)]
pub struct Bundle<A: Authorization> {
pub vin: Vec<TzeIn<A>>,
pub vin: Vec<TzeIn<A::Witness>>,
pub vout: Vec<TzeOut>,
}
impl Bundle<Unauthorized> {
pub fn apply_signatures(self, witnesses: Vec<tze::AuthData>) -> Bundle<Authorized> {
assert!(self.vin.len() == witnesses.len());
Bundle {
vin: self
.vin
.into_iter()
.zip(witnesses.into_iter())
.map(|(tzein, payload)| TzeIn {
prevout: tzein.prevout,
witness: tze::Witness {
extension_id: tzein.witness.extension_id,
mode: tzein.witness.mode,
payload,
},
})
.collect(),
vout: self.vout,
}
}
pub authorization: A,
}
#[derive(Clone, Debug, PartialEq)]
@ -99,12 +71,12 @@ impl OutPoint {
}
#[derive(Clone, Debug, PartialEq)]
pub struct TzeIn<A: Authorization> {
pub struct TzeIn<Payload> {
pub prevout: OutPoint,
pub witness: tze::Witness<A::Witness>,
pub witness: tze::Witness<Payload>,
}
impl<A: Authorization> TzeIn<A> {
impl<Payload> TzeIn<Payload> {
/// Write without witness data (for signature hashing)
///
/// This is also used as the prefix for the encoded form used
@ -127,7 +99,7 @@ impl<A: Authorization> TzeIn<A> {
/// Transaction encoding and decoding functions conforming to [ZIP 222].
///
/// [ZIP 222]: https://zips.z.cash/zip-0222#encoding-in-transactions
impl TzeIn<Unauthorized> {
impl TzeIn<()> {
/// Convenience constructor
pub fn new(prevout: OutPoint, extension_id: u32, mode: u32) -> Self {
TzeIn {
@ -141,7 +113,7 @@ impl TzeIn<Unauthorized> {
}
}
impl TzeIn<Authorized> {
impl TzeIn<<Authorized as Authorization>::Witness> {
/// Read witness metadata & payload
///
/// Used to decode the encoded form used within a serialized
@ -249,7 +221,7 @@ pub mod testing {
}
prop_compose! {
pub fn arb_tzein()(prevout in arb_outpoint(), witness in arb_witness()) -> TzeIn<Authorized> {
pub fn arb_tzein()(prevout in arb_outpoint(), witness in arb_witness()) -> TzeIn<AuthData> {
TzeIn { prevout, witness }
}
}
@ -274,7 +246,7 @@ pub mod testing {
if branch_id != BranchId::ZFuture || (vin.is_empty() && vout.is_empty()) {
None
} else {
Some(Bundle { vin, vout })
Some(Bundle { vin, vout, authorization: Authorized })
}
}
}

View File

@ -5,9 +5,12 @@ use std::fmt;
use crate::{
extensions::transparent::{self as tze, ToPayload},
transaction::components::{
amount::Amount,
tze::{Bundle, OutPoint, TzeIn, TzeOut, Unauthorized},
transaction::{
self as tx,
components::{
amount::Amount,
tze::{Authorization, Authorized, Bundle, OutPoint, TzeIn, TzeOut},
},
},
};
@ -28,17 +31,24 @@ impl fmt::Display for Error {
}
#[allow(clippy::type_complexity)]
struct TzeSigner<'a, BuildCtx> {
pub struct TzeSigner<'a, BuildCtx> {
prevout: TzeOut,
builder: Box<dyn FnOnce(&BuildCtx) -> Result<(u32, Vec<u8>), Error> + 'a>,
}
pub struct TzeBuilder<'a, BuildCtx> {
signers: Vec<TzeSigner<'a, BuildCtx>>,
vin: Vec<TzeIn<Unauthorized>>,
vin: Vec<TzeIn<()>>,
vout: Vec<TzeOut>,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Unauthorized;
impl Authorization for Unauthorized {
type Witness = ();
}
impl<'a, BuildCtx> TzeBuilder<'a, BuildCtx> {
pub fn empty() -> Self {
TzeBuilder {
@ -99,43 +109,63 @@ impl<'a, BuildCtx> TzeBuilder<'a, BuildCtx> {
.sum::<Option<Amount>>()?
}
pub fn build(&self) -> Option<Bundle<Unauthorized>> {
pub fn build(self) -> (Option<Bundle<Unauthorized>>, Vec<TzeSigner<'a, BuildCtx>>) {
if self.vin.is_empty() && self.vout.is_empty() {
None
(None, vec![])
} else {
Some(Bundle {
vin: self.vin.clone(),
vout: self.vout.clone(),
})
}
}
pub fn create_witnesses(self, mtx: &BuildCtx) -> Result<Option<Vec<tze::AuthData>>, Error> {
// Create TZE input witnesses
if self.vin.is_empty() && self.vout.is_empty() {
Ok(None)
} else {
// Create TZE input witnesses
let payloads = self
.signers
.into_iter()
.zip(self.vin.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(tze::AuthData(payload))
})
.collect::<Result<Vec<_>, Error>>()?;
Ok(Some(payloads))
(
Some(Bundle {
vin: self.vin.clone(),
vout: self.vout.clone(),
authorization: Unauthorized,
}),
self.signers,
)
}
}
}
impl Bundle<Unauthorized> {
pub fn into_authorized(
self,
unauthed_tx: &tx::TransactionData<tx::Unauthorized>,
signers: Vec<TzeSigner<'_, tx::TransactionData<tx::Unauthorized>>>,
) -> Result<Bundle<Authorized>, Error> {
// Create TZE input witnesses
let payloads = signers
.into_iter()
.zip(self.vin.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)(unauthed_tx)?;
let input_mode = tzein.witness.mode;
if mode != input_mode {
return Err(Error::WitnessModeMismatch(input_mode, mode));
}
Ok(tze::AuthData(payload))
})
.collect::<Result<Vec<_>, Error>>()?;
Ok(Bundle {
vin: self
.vin
.into_iter()
.zip(payloads.into_iter())
.map(|(tzein, payload)| TzeIn {
prevout: tzein.prevout,
witness: tze::Witness {
extension_id: tzein.witness.extension_id,
mode: tzein.witness.mode,
payload,
},
})
.collect(),
vout: self.vout,
authorization: Authorized,
})
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,349 +1,136 @@
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
use byteorder::{LittleEndian, WriteBytesExt};
use ff::PrimeField;
use group::GroupEncoding;
use crate::legacy::Script;
use blake2b_simd::Hash as Blake2bHash;
use std::convert::TryInto;
use crate::{
consensus::{self, BranchId},
legacy::Script,
use super::{
components::{
sapling::{self, GrothProofBytes},
Amount,
},
sighash_v4::v4_signature_hash,
sighash_v5::v5_signature_hash,
Authorization, TransactionData, TxDigests, TxVersion,
};
#[cfg(feature = "zfuture")]
use crate::extensions::transparent::Precondition;
use super::{
components::{
amount::Amount,
sapling::{self, GrothProofBytes, OutputDescription, SpendDescription},
sprout::JsDescription,
transparent::{self, TxIn, TxOut},
},
Authorization, Transaction, TransactionData, TxVersion,
};
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash";
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashPrevoutHash";
const ZCASH_SEQUENCE_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSequencHash";
const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashOutputsHash";
const ZCASH_JOINSPLITS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashJSplitsHash";
const ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSSpendsHash";
const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSOutputHash";
pub const SIGHASH_ALL: u32 = 1;
const SIGHASH_NONE: u32 = 2;
const SIGHASH_SINGLE: u32 = 3;
const SIGHASH_MASK: u32 = 0x1f;
const SIGHASH_ANYONECANPAY: u32 = 0x80;
pub const SIGHASH_NONE: u32 = 2;
pub const SIGHASH_SINGLE: u32 = 3;
pub const SIGHASH_MASK: u32 = 0x1f;
pub const SIGHASH_ANYONECANPAY: u32 = 0x80;
macro_rules! update_u32 {
($h:expr, $value:expr, $tmp:expr) => {
(&mut $tmp[..4]).write_u32::<LittleEndian>($value).unwrap();
$h.update(&$tmp[..4]);
};
pub struct TransparentInput<'a> {
index: usize,
script_code: &'a Script,
value: Amount,
}
macro_rules! update_hash {
($h:expr, $cond:expr, $value:expr) => {
if $cond {
$h.update(&$value.as_ref());
} else {
$h.update(&[0; 32]);
}
};
}
fn has_overwinter_components(version: &TxVersion) -> bool {
!matches!(version, TxVersion::Sprout(_))
}
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();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_PREVOUTS_HASH_PERSONALIZATION)
.hash(&data)
}
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)
.write_u32::<LittleEndian>(t_in.sequence)
.unwrap();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_SEQUENCE_HASH_PERSONALIZATION)
.hash(&data)
}
fn outputs_hash(vout: &[TxOut]) -> Blake2bHash {
let mut data = Vec::with_capacity(vout.len() * (4 + 1));
for t_out in vout {
t_out.write(&mut data).unwrap();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_OUTPUTS_HASH_PERSONALIZATION)
.hash(&data)
}
fn single_output_hash(tx_out: &TxOut) -> Blake2bHash {
let mut data = vec![];
tx_out.write(&mut data).unwrap();
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_OUTPUTS_HASH_PERSONALIZATION)
.hash(&data)
}
fn joinsplits_hash(
consensus_branch_id: BranchId,
joinsplits: &[JsDescription],
joinsplit_pubkey: &[u8; 32],
) -> Blake2bHash {
let mut data = Vec::with_capacity(
joinsplits.len()
* if consensus_branch_id.sprout_uses_groth_proofs() {
1698 // JSDescription with Groth16 proof
} else {
1802 // JSDescription with PHGR13 proof
},
);
for js in joinsplits {
js.write(&mut data).unwrap();
}
data.extend_from_slice(joinsplit_pubkey);
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_JOINSPLITS_HASH_PERSONALIZATION)
.hash(&data)
}
fn shielded_spends_hash<A: sapling::Authorization<Proof = GrothProofBytes>>(
shielded_spends: &[SpendDescription<A>],
) -> Blake2bHash {
let mut data = Vec::with_capacity(shielded_spends.len() * 384);
for s_spend in shielded_spends {
data.extend_from_slice(&s_spend.cv.to_bytes());
data.extend_from_slice(s_spend.anchor.to_repr().as_ref());
data.extend_from_slice(&s_spend.nullifier.0);
s_spend.rk.write(&mut data).unwrap();
data.extend_from_slice(&s_spend.zkproof);
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION)
.hash(&data)
}
fn shielded_outputs_hash<A: sapling::Authorization<Proof = GrothProofBytes>>(
shielded_outputs: &[OutputDescription<A>],
) -> Blake2bHash {
let mut data = Vec::with_capacity(shielded_outputs.len() * 948);
for s_out in shielded_outputs {
s_out.write(&mut data).unwrap();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION)
.hash(&data)
}
pub enum SignableInput<'a> {
Shielded,
Transparent {
index: usize,
script_code: &'a Script,
value: Amount,
},
#[cfg(feature = "zfuture")]
Tze {
index: usize,
precondition: &'a Precondition,
value: Amount,
},
}
impl<'a> SignableInput<'a> {
pub fn transparent(index: usize, script_code: &'a Script, value: Amount) -> Self {
SignableInput::Transparent {
impl<'a> TransparentInput<'a> {
pub fn new(index: usize, script_code: &'a Script, value: Amount) -> Self {
TransparentInput {
index,
script_code,
value,
}
}
#[cfg(feature = "zfuture")]
pub fn tze(index: usize, precondition: &'a Precondition, value: Amount) -> Self {
SignableInput::Tze {
pub fn index(&self) -> usize {
self.index
}
pub fn script_code(&self) -> &'a Script {
self.script_code
}
pub fn value(&self) -> Amount {
self.value
}
}
#[cfg(feature = "zfuture")]
pub struct TzeInput<'a> {
index: usize,
precondition: &'a Precondition,
value: Amount,
}
#[cfg(feature = "zfuture")]
impl<'a> TzeInput<'a> {
pub fn new(index: usize, precondition: &'a Precondition, value: Amount) -> Self {
TzeInput {
index,
precondition,
value,
}
}
}
pub fn signature_hash_data<
TA: transparent::Authorization,
SA: sapling::Authorization<Proof = GrothProofBytes>,
A: Authorization<SaplingAuth = SA, TransparentAuth = TA>,
>(
tx: &TransactionData<A>,
consensus_branch_id: consensus::BranchId,
hash_type: u32,
signable_input: SignableInput<'_>,
) -> Vec<u8> {
if has_overwinter_components(&tx.version) {
let mut personal = [0; 16];
(&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
(&mut personal[12..])
.write_u32::<LittleEndian>(consensus_branch_id.into())
.unwrap();
pub fn index(&self) -> usize {
self.index
}
let mut h = Blake2bParams::new()
.hash_length(32)
.personal(&personal)
.to_state();
let mut tmp = [0; 8];
pub fn precondition(&self) -> &'a Precondition {
self.precondition
}
update_u32!(h, tx.version.header(), tmp);
update_u32!(h, tx.version.version_group_id(), tmp);
update_hash!(
h,
hash_type & SIGHASH_ANYONECANPAY == 0,
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.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.transparent_bundle
.as_ref()
.map_or(&[], |b| b.vout.as_slice()),
)
.as_bytes(),
);
} else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE {
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]),
};
} else {
h.update(&[0; 32]);
};
update_hash!(
h,
!tx.sprout_bundle
.as_ref()
.map_or(true, |b| b.joinsplits.is_empty()),
{
let bundle = tx.sprout_bundle.as_ref().unwrap();
joinsplits_hash(
consensus_branch_id,
&bundle.joinsplits,
&bundle.joinsplit_pubkey,
)
}
);
if tx.version.has_sapling() {
update_hash!(
h,
!tx.sapling_bundle
.as_ref()
.map_or(true, |b| b.shielded_spends.is_empty()),
shielded_spends_hash(
tx.sapling_bundle
.as_ref()
.unwrap()
.shielded_spends
.as_slice()
)
);
update_hash!(
h,
!tx.sapling_bundle
.as_ref()
.map_or(true, |b| b.shielded_outputs.is_empty()),
shielded_outputs_hash(
tx.sapling_bundle
.as_ref()
.unwrap()
.shielded_outputs
.as_slice()
)
);
}
update_u32!(h, tx.lock_time, tmp);
update_u32!(h, tx.expiry_height.into(), tmp);
if tx.version.has_sapling() {
h.update(&tx.sapling_value_balance().to_i64_le_bytes());
}
update_u32!(h, hash_type, tmp);
match signable_input {
SignableInput::Transparent {
index,
script_code,
value,
} => {
if let Some(bundle) = tx.transparent_bundle.as_ref() {
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 {
panic!(
"A request has been made to sign a transparent input, but none are present."
);
}
}
#[cfg(feature = "zfuture")]
SignableInput::Tze { .. } => {
panic!("A request has been made to sign a TZE input in a V4 transaction.");
}
SignableInput::Shielded => (),
}
h.finalize().as_ref().to_vec()
} else {
unimplemented!()
pub fn value(&self) -> Amount {
self.value
}
}
pub fn signature_hash(
tx: &Transaction,
consensus_branch_id: consensus::BranchId,
hash_type: u32,
signable_input: SignableInput<'_>,
) -> Vec<u8> {
signature_hash_data(tx, consensus_branch_id, hash_type, signable_input)
pub enum SignableInput<'a> {
Shielded,
Transparent(TransparentInput<'a>),
#[cfg(feature = "zfuture")]
Tze(TzeInput<'a>),
}
impl<'a> SignableInput<'a> {
pub fn transparent(index: usize, script_code: &'a Script, value: Amount) -> Self {
SignableInput::Transparent(TransparentInput {
index,
script_code,
value,
})
}
#[cfg(feature = "zfuture")]
pub fn tze(index: usize, precondition: &'a Precondition, value: Amount) -> Self {
SignableInput::Tze(TzeInput {
index,
precondition,
value,
})
}
}
pub struct SignatureHash(Blake2bHash);
impl AsRef<[u8; 32]> for SignatureHash {
fn as_ref(&self) -> &[u8; 32] {
self.0.as_ref().try_into().unwrap()
}
}
pub fn signature_hash<
'a,
SA: sapling::Authorization<Proof = GrothProofBytes>,
A: Authorization<SaplingAuth = SA>,
>(
tx: &TransactionData<A>,
hash_type: u32,
signable_input: &SignableInput<'a>,
txid_parts: &TxDigests<Blake2bHash>,
) -> SignatureHash {
SignatureHash(match tx.version {
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
v4_signature_hash(tx, hash_type, signable_input)
}
TxVersion::Zip225 => v5_signature_hash(tx, hash_type, signable_input, txid_parts),
#[cfg(feature = "zfuture")]
TxVersion::ZFuture => v5_signature_hash(tx, hash_type, signable_input, txid_parts),
})
}

View File

@ -0,0 +1,271 @@
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
use byteorder::{LittleEndian, WriteBytesExt};
use ff::PrimeField;
use group::GroupEncoding;
use crate::consensus::BranchId;
use super::{
components::{
sapling::{self, GrothProofBytes, OutputDescription, SpendDescription},
sprout::JsDescription,
transparent::{self, TxIn, TxOut},
},
sighash::{SignableInput, SIGHASH_ANYONECANPAY, SIGHASH_MASK, SIGHASH_NONE, SIGHASH_SINGLE},
Authorization, TransactionData,
};
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash";
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashPrevoutHash";
const ZCASH_SEQUENCE_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSequencHash";
const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashOutputsHash";
const ZCASH_JOINSPLITS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashJSplitsHash";
const ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSSpendsHash";
const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSOutputHash";
macro_rules! update_u32 {
($h:expr, $value:expr, $tmp:expr) => {
(&mut $tmp[..4]).write_u32::<LittleEndian>($value).unwrap();
$h.update(&$tmp[..4]);
};
}
macro_rules! update_hash {
($h:expr, $cond:expr, $value:expr) => {
if $cond {
$h.update(&$value.as_ref());
} else {
$h.update(&[0; 32]);
}
};
}
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();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_PREVOUTS_HASH_PERSONALIZATION)
.hash(&data)
}
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)
.write_u32::<LittleEndian>(t_in.sequence)
.unwrap();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_SEQUENCE_HASH_PERSONALIZATION)
.hash(&data)
}
fn outputs_hash(vout: &[TxOut]) -> Blake2bHash {
let mut data = Vec::with_capacity(vout.len() * (4 + 1));
for t_out in vout {
t_out.write(&mut data).unwrap();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_OUTPUTS_HASH_PERSONALIZATION)
.hash(&data)
}
fn single_output_hash(tx_out: &TxOut) -> Blake2bHash {
let mut data = vec![];
tx_out.write(&mut data).unwrap();
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_OUTPUTS_HASH_PERSONALIZATION)
.hash(&data)
}
fn joinsplits_hash(
consensus_branch_id: BranchId,
joinsplits: &[JsDescription],
joinsplit_pubkey: &[u8; 32],
) -> Blake2bHash {
let mut data = Vec::with_capacity(
joinsplits.len()
* if consensus_branch_id.sprout_uses_groth_proofs() {
1698 // JSDescription with Groth16 proof
} else {
1802 // JsDescription with PHGR13 proof
},
);
for js in joinsplits {
js.write(&mut data).unwrap();
}
data.extend_from_slice(joinsplit_pubkey);
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_JOINSPLITS_HASH_PERSONALIZATION)
.hash(&data)
}
fn shielded_spends_hash<A: sapling::Authorization<Proof = GrothProofBytes>>(
shielded_spends: &[SpendDescription<A>],
) -> Blake2bHash {
let mut data = Vec::with_capacity(shielded_spends.len() * 384);
for s_spend in shielded_spends {
data.extend_from_slice(&s_spend.cv.to_bytes());
data.extend_from_slice(s_spend.anchor.to_repr().as_ref());
data.extend_from_slice(&s_spend.nullifier.as_ref());
s_spend.rk.write(&mut data).unwrap();
data.extend_from_slice(&s_spend.zkproof);
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION)
.hash(&data)
}
fn shielded_outputs_hash(shielded_outputs: &[OutputDescription<GrothProofBytes>]) -> Blake2bHash {
let mut data = Vec::with_capacity(shielded_outputs.len() * 948);
for s_out in shielded_outputs {
s_out.write_v4(&mut data).unwrap();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION)
.hash(&data)
}
pub fn v4_signature_hash<
SA: sapling::Authorization<Proof = GrothProofBytes>,
A: Authorization<SaplingAuth = SA>,
>(
tx: &TransactionData<A>,
hash_type: u32,
signable_input: &SignableInput<'_>,
) -> Blake2bHash {
if tx.version.has_overwinter() {
let mut personal = [0; 16];
(&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
(&mut personal[12..])
.write_u32::<LittleEndian>(tx.consensus_branch_id.into())
.unwrap();
let mut h = Blake2bParams::new()
.hash_length(32)
.personal(&personal)
.to_state();
let mut tmp = [0; 8];
update_u32!(h, tx.version.header(), tmp);
update_u32!(h, tx.version.version_group_id(), tmp);
update_hash!(
h,
hash_type & SIGHASH_ANYONECANPAY == 0,
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.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.transparent_bundle
.as_ref()
.map_or(&[], |b| b.vout.as_slice()),
)
.as_bytes(),
);
} else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE {
match (tx.transparent_bundle.as_ref(), signable_input) {
(Some(b), SignableInput::Transparent(input)) if input.index() < b.vout.len() => {
h.update(single_output_hash(&b.vout[input.index()]).as_bytes())
}
_ => h.update(&[0; 32]),
};
} else {
h.update(&[0; 32]);
};
update_hash!(
h,
!tx.sprout_bundle
.as_ref()
.map_or(true, |b| b.joinsplits.is_empty()),
{
let bundle = tx.sprout_bundle.as_ref().unwrap();
joinsplits_hash(
tx.consensus_branch_id,
&bundle.joinsplits,
&bundle.joinsplit_pubkey,
)
}
);
if tx.version.has_sapling() {
update_hash!(
h,
!tx.sapling_bundle
.as_ref()
.map_or(true, |b| b.shielded_spends.is_empty()),
shielded_spends_hash(&tx.sapling_bundle.as_ref().unwrap().shielded_spends)
);
update_hash!(
h,
!tx.sapling_bundle
.as_ref()
.map_or(true, |b| b.shielded_outputs.is_empty()),
shielded_outputs_hash(&tx.sapling_bundle.as_ref().unwrap().shielded_outputs)
);
}
update_u32!(h, tx.lock_time, tmp);
update_u32!(h, tx.expiry_height.into(), tmp);
if tx.version.has_sapling() {
h.update(&tx.sapling_value_balance().to_i64_le_bytes());
}
update_u32!(h, hash_type, tmp);
match signable_input {
SignableInput::Shielded => (),
SignableInput::Transparent(input) => {
if let Some(bundle) = tx.transparent_bundle.as_ref() {
let mut data = vec![];
bundle.vin[input.index()].prevout.write(&mut data).unwrap();
input.script_code().write(&mut data).unwrap();
data.extend_from_slice(&input.value().to_i64_le_bytes());
(&mut data)
.write_u32::<LittleEndian>(bundle.vin[input.index()].sequence)
.unwrap();
h.update(&data);
} else {
panic!(
"A request has been made to sign a transparent input, but none are present."
);
}
}
#[cfg(feature = "zfuture")]
SignableInput::Tze(_) => {
panic!("A request has been made to sign a TZE input, but the transaction version is not ZFuture");
}
}
h.finalize()
} else {
panic!("Signature hashing for pre-overwinter transactions is not supported.")
}
}

View File

@ -0,0 +1,182 @@
use std::io::Write;
use blake2b_simd::{Hash as Blake2bHash, Params, State};
use byteorder::{LittleEndian, WriteBytesExt};
use crate::transaction::{
components::transparent::{self, TxOut},
sighash::{
SignableInput, TransparentInput, SIGHASH_ANYONECANPAY, SIGHASH_MASK, SIGHASH_NONE,
SIGHASH_SINGLE,
},
txid::{
to_hash, transparent_outputs_hash, transparent_prevout_hash, transparent_sequence_hash,
},
Authorization, TransactionData, TransparentDigests, TxDigests,
};
#[cfg(feature = "zfuture")]
use std::convert::TryInto;
#[cfg(feature = "zfuture")]
use crate::{
serialize::{CompactSize, Vector},
transaction::{components::tze, sighash::TzeInput, TzeDigests},
};
const ZCASH_TRANSPARENT_INPUT_HASH_PERSONALIZATION: &[u8; 16] = b"Zcash___TxInHash";
#[cfg(feature = "zfuture")]
const ZCASH_TZE_INPUT_HASH_PERSONALIZATION: &[u8; 16] = b"Zcash__TzeInHash";
fn hasher(personal: &[u8; 16]) -> State {
Params::new().hash_length(32).personal(personal).to_state()
}
fn transparent_input_sigdigests<A: transparent::Authorization>(
bundle: &transparent::Bundle<A>,
input: &TransparentInput<'_>,
txid_digests: &TransparentDigests<Blake2bHash>,
hash_type: u32,
) -> TransparentDigests<Blake2bHash> {
let flag_anyonecanpay = hash_type & SIGHASH_ANYONECANPAY != 0;
let flag_single = hash_type & SIGHASH_MASK == SIGHASH_SINGLE;
let flag_none = hash_type & SIGHASH_MASK == SIGHASH_NONE;
let prevout_digest = if flag_anyonecanpay {
transparent_prevout_hash::<A>(&[])
} else {
txid_digests.prevout_digest
};
let sequence_digest = if flag_anyonecanpay || flag_single || flag_none {
transparent_sequence_hash::<A>(&[])
} else {
txid_digests.sequence_digest
};
let outputs_digest = if flag_single {
if input.index() < bundle.vout.len() {
transparent_outputs_hash(&[&bundle.vout[input.index()]])
} else {
transparent_outputs_hash::<TxOut>(&[])
}
} else if flag_none {
transparent_outputs_hash::<TxOut>(&[])
} else {
txid_digests.outputs_digest
};
// If we are serializing an input (i.e. this is not a JoinSplit signature hash):
// a. outpoint (32-byte hash + 4-byte little endian)
// b. scriptCode of the input (serialized as scripts inside CTxOuts)
// c. value of the output spent by this input (8-byte little endian)
// d. nSequence of the input (4-byte little endian)
let mut ch = hasher(ZCASH_TRANSPARENT_INPUT_HASH_PERSONALIZATION);
let txin = &bundle.vin[input.index()];
txin.prevout.write(&mut ch).unwrap();
input.script_code().write(&mut ch).unwrap();
ch.write_all(&input.value().to_i64_le_bytes()).unwrap();
ch.write_u32::<LittleEndian>(txin.sequence).unwrap();
let per_input_digest = ch.finalize();
TransparentDigests {
prevout_digest,
sequence_digest,
outputs_digest,
per_input_digest: Some(per_input_digest),
}
}
#[cfg(feature = "zfuture")]
fn tze_input_sigdigests<A: tze::Authorization>(
bundle: &tze::Bundle<A>,
input: &TzeInput<'_>,
txid_digests: &TzeDigests<Blake2bHash>,
) -> TzeDigests<Blake2bHash> {
let mut ch = hasher(ZCASH_TZE_INPUT_HASH_PERSONALIZATION);
let tzein = &bundle.vin[input.index()];
tzein.prevout.write(&mut ch).unwrap();
CompactSize::write(
&mut ch,
input.precondition().extension_id.try_into().unwrap(),
)
.unwrap();
CompactSize::write(&mut ch, input.precondition().mode.try_into().unwrap()).unwrap();
Vector::write(&mut ch, &input.precondition().payload, |w, e| {
w.write_u8(*e)
})
.unwrap();
ch.write_all(&input.value().to_i64_le_bytes()).unwrap();
let per_input_digest = ch.finalize();
TzeDigests {
inputs_digest: txid_digests.inputs_digest,
outputs_digest: txid_digests.outputs_digest,
per_input_digest: Some(per_input_digest),
}
}
pub fn v5_signature_hash<A: Authorization>(
tx: &TransactionData<A>,
hash_type: u32,
signable_input: &SignableInput<'_>,
txid_parts: &TxDigests<Blake2bHash>,
) -> Blake2bHash {
match signable_input {
SignableInput::Shielded => to_hash(
tx.version,
tx.consensus_branch_id,
txid_parts.header_digest,
txid_parts.transparent_digests.as_ref(),
txid_parts.sapling_digest,
txid_parts.orchard_digest,
#[cfg(feature = "zfuture")]
txid_parts.tze_digests.as_ref(),
),
SignableInput::Transparent(input) => {
if let Some((bundle, txid_digests)) = tx
.transparent_bundle
.as_ref()
.zip(txid_parts.transparent_digests.as_ref())
{
to_hash(
tx.version,
tx.consensus_branch_id,
txid_parts.header_digest,
Some(&transparent_input_sigdigests(
bundle,
input,
txid_digests,
hash_type,
)),
txid_parts.sapling_digest,
txid_parts.orchard_digest,
#[cfg(feature = "zfuture")]
txid_parts.tze_digests.as_ref(),
)
} else {
panic!("Transaction has no transparent inputs to sign.")
}
}
#[cfg(feature = "zfuture")]
SignableInput::Tze(input) => {
if let Some((bundle, txid_digests)) =
tx.tze_bundle.as_ref().zip(txid_parts.tze_digests.as_ref())
{
to_hash(
tx.version,
tx.consensus_branch_id,
txid_parts.header_digest,
txid_parts.transparent_digests.as_ref(),
txid_parts.sapling_digest,
txid_parts.orchard_digest,
#[cfg(feature = "zfuture")]
Some(&tze_input_sigdigests(bundle, input, txid_digests)),
)
} else {
panic!("Transaction has no TZE inputs to sign.")
}
}
}
}

View File

@ -1,17 +1,23 @@
use std::ops::Deref;
use proptest::prelude::*;
use crate::{consensus::BranchId, legacy::Script};
use super::{
components::Amount,
sighash::{signature_hash, SignableInput},
sighash::{SignableInput, SIGHASH_ALL, SIGHASH_ANYONECANPAY, SIGHASH_NONE, SIGHASH_SINGLE},
sighash_v4::v4_signature_hash,
sighash_v5::v5_signature_hash,
testing::arb_tx,
txid::TxIdDigester,
Transaction,
};
use super::testing::{arb_branch_id, arb_tx};
#[test]
fn tx_read_write() {
let data = &self::data::tx_read_write::TX_READ_WRITE;
let tx = Transaction::read(&data[..]).unwrap();
let tx = Transaction::read(&data[..], BranchId::Canopy).unwrap();
assert_eq!(
format!("{}", tx.txid()),
"64f0bd7fe30ce23753358fe3a2dc835b8fba9c0274c4e2c54a6f73114cb55639"
@ -22,20 +28,89 @@ fn tx_read_write() {
assert_eq!(&data[..], &encoded[..]);
}
fn check_roundtrip(tx: Transaction) -> Result<(), TestCaseError> {
let mut txn_bytes = vec![];
tx.write(&mut txn_bytes).unwrap();
let txo = Transaction::read(&txn_bytes[..], tx.consensus_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(tx)?;
}
}
let txo = Transaction::read(&txn_bytes[..]).unwrap();
proptest! {
#[test]
#[ignore]
fn tx_serialization_roundtrip_overwinter(tx in arb_tx(BranchId::Overwinter)) {
check_roundtrip(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(tx)?;
}
}
proptest! {
#[test]
#[ignore]
fn tx_serialization_roundtrip_blossom(tx in arb_tx(BranchId::Blossom)) {
check_roundtrip(tx)?;
}
}
proptest! {
#[test]
#[ignore]
fn tx_serialization_roundtrip_heartwood(tx in arb_tx(BranchId::Heartwood)) {
check_roundtrip(tx)?;
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10))]
#[test]
fn tx_serialization_roundtrip_canopy(tx in arb_tx(BranchId::Canopy)) {
check_roundtrip(tx)?;
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10))]
#[test]
fn tx_serialization_roundtrip_nu5(tx in arb_tx(BranchId::Nu5)) {
check_roundtrip(tx)?;
}
}
#[cfg(feature = "zfuture")]
proptest! {
#[test]
#[ignore]
fn tx_serialization_roundtrip_future(tx in arb_tx(BranchId::ZFuture)) {
check_roundtrip(tx)?;
}
}
@ -43,7 +118,7 @@ mod data;
#[test]
fn zip_0143() {
for tv in self::data::zip_0143::make_test_vectors() {
let tx = Transaction::read(&tv.tx[..]).unwrap();
let tx = Transaction::read(&tv.tx[..], tv.consensus_branch_id).unwrap();
let signable_input = match tv.transparent_input {
Some(n) => SignableInput::transparent(
n as usize,
@ -54,7 +129,7 @@ fn zip_0143() {
};
assert_eq!(
signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, signable_input).as_ref(),
v4_signature_hash(tx.deref(), tv.hash_type, &signable_input).as_ref(),
tv.sighash
);
}
@ -63,7 +138,7 @@ fn zip_0143() {
#[test]
fn zip_0243() {
for tv in self::data::zip_0243::make_test_vectors() {
let tx = Transaction::read(&tv.tx[..]).unwrap();
let tx = Transaction::read(&tv.tx[..], tv.consensus_branch_id).unwrap();
let signable_input = match tv.transparent_input {
Some(n) => SignableInput::transparent(
n as usize,
@ -74,8 +149,88 @@ fn zip_0243() {
};
assert_eq!(
signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, signable_input).as_ref(),
v4_signature_hash(tx.deref(), tv.hash_type, &signable_input).as_ref(),
tv.sighash
);
}
}
#[test]
fn zip_0244() {
for tv in self::data::zip_0244::make_test_vectors() {
let tx = Transaction::read(&tv.tx[..], BranchId::Nu5).unwrap();
assert_eq!(tx.txid.as_ref(), &tv.txid);
let txid_parts = tx.deref().digest(TxIdDigester);
match tv.transparent_input {
Some(n) => {
let script = Script(tv.script_code.unwrap());
let signable_input = SignableInput::transparent(
n as usize,
&script,
Amount::from_nonnegative_i64(tv.amount.unwrap()).unwrap(),
);
assert_eq!(
v5_signature_hash(tx.deref(), SIGHASH_ALL, &signable_input, &txid_parts)
.as_ref(),
&tv.sighash_all
);
assert_eq!(
v5_signature_hash(tx.deref(), SIGHASH_NONE, &signable_input, &txid_parts)
.as_ref(),
&tv.sighash_none.unwrap()
);
assert_eq!(
v5_signature_hash(tx.deref(), SIGHASH_SINGLE, &signable_input, &txid_parts)
.as_ref(),
&tv.sighash_single.unwrap()
);
assert_eq!(
v5_signature_hash(
tx.deref(),
SIGHASH_ALL | SIGHASH_ANYONECANPAY,
&signable_input,
&txid_parts,
)
.as_ref(),
&tv.sighash_all_anyone.unwrap()
);
assert_eq!(
v5_signature_hash(
tx.deref(),
SIGHASH_NONE | SIGHASH_ANYONECANPAY,
&signable_input,
&txid_parts,
)
.as_ref(),
&tv.sighash_none_anyone.unwrap()
);
assert_eq!(
v5_signature_hash(
tx.deref(),
SIGHASH_SINGLE | SIGHASH_ANYONECANPAY,
&signable_input,
&txid_parts,
)
.as_ref(),
&tv.sighash_single_anyone.unwrap()
);
}
_ => {
let signable_input = SignableInput::Shielded;
assert_eq!(
v5_signature_hash(tx.deref(), SIGHASH_ALL, &signable_input, &txid_parts)
.as_ref(),
tv.sighash_all
);
}
};
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,596 @@
use std::borrow::Borrow;
use std::convert::TryFrom;
use std::io::Write;
use blake2b_simd::{Hash as Blake2bHash, Params, State};
use byteorder::{LittleEndian, WriteBytesExt};
use ff::PrimeField;
use group::GroupEncoding;
use orchard::bundle::{self as orchard};
use crate::consensus::{BlockHeight, BranchId};
use super::{
components::{
amount::Amount,
orchard as ser_orch,
sapling::{self, OutputDescription, SpendDescription},
transparent::{self, TxIn, TxOut},
},
Authorization, Authorized, TransactionDigest, TransparentDigests, TxDigests, TxId, TxVersion,
};
#[cfg(feature = "zfuture")]
use super::{
components::tze::{self, TzeIn, TzeOut},
TzeDigests,
};
/// TxId tree root personalization
const ZCASH_TX_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashTxHash_";
// TxId level 1 node personalization
const ZCASH_HEADERS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdHeadersHash";
const ZCASH_TRANSPARENT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTranspaHash";
const ZCASH_SAPLING_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSaplingHash";
const ZCASH_ORCHARD_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrchardHash";
#[cfg(feature = "zfuture")]
const ZCASH_TZE_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTZE____Hash";
// TxId transparent level 2 node personalization
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdPrevoutHash";
const ZCASH_SEQUENCE_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSequencHash";
const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOutputsHash";
// TxId tze level 2 node personalization
#[cfg(feature = "zfuture")]
const ZCASH_TZE_INPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTZEIns_Hash";
#[cfg(feature = "zfuture")]
const ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTZEOutsHash";
// TxId sapling level 2 node personalization
const ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendsHash";
const ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendCHash";
const ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendNHash";
const ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutputHash";
const ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutC__Hash";
const ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutM__Hash";
const ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutN__Hash";
const ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActCHash";
const ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActMHash";
const ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActNHash";
const ZCASH_AUTH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZTxAuthHash_";
const ZCASH_TRANSPARENT_SCRIPTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthTransHash";
const ZCASH_SAPLING_SIGS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthSapliHash";
const ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthOrchaHash";
#[cfg(feature = "zfuture")]
const ZCASH_TZE_WITNESSES_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthTZE__Hash";
fn hasher(personal: &[u8; 16]) -> State {
Params::new().hash_length(32).personal(personal).to_state()
}
/// Sequentially append the serialized value of each transparent input
/// to a hash personalized by ZCASH_PREVOUTS_HASH_PERSONALIZATION.
/// In the case that no inputs are provided, this produces a default
/// hash from just the personalization string.
pub(crate) fn transparent_prevout_hash<TransparentAuth: transparent::Authorization>(
vin: &[TxIn<TransparentAuth>],
) -> Blake2bHash {
let mut h = hasher(ZCASH_PREVOUTS_HASH_PERSONALIZATION);
for t_in in vin {
t_in.prevout.write(&mut h).unwrap();
}
h.finalize()
}
/// Hash of the little-endian u32 interpretation of the
/// `sequence` values for each TxIn record passed in vin.
pub(crate) fn transparent_sequence_hash<TransparentAuth: transparent::Authorization>(
vin: &[TxIn<TransparentAuth>],
) -> Blake2bHash {
let mut h = hasher(ZCASH_SEQUENCE_HASH_PERSONALIZATION);
for t_in in vin {
(&mut h).write_u32::<LittleEndian>(t_in.sequence).unwrap();
}
h.finalize()
}
/// Sequentially append the full serialized value of each transparent output
/// to a hash personalized by ZCASH_OUTPUTS_HASH_PERSONALIZATION.
/// In the case that no outputs are provided, this produces a default
/// hash from just the personalization string.
pub(crate) fn transparent_outputs_hash<T: Borrow<TxOut>>(vout: &[T]) -> Blake2bHash {
let mut h = hasher(ZCASH_OUTPUTS_HASH_PERSONALIZATION);
for t_out in vout {
t_out.borrow().write(&mut h).unwrap();
}
h.finalize()
}
/// Sequentially append the serialized value of each TZE input, excluding
/// witness data, to a hash personalized by ZCASH_TZE_INPUTS_HASH_PERSONALIZATION.
/// In the case that no inputs are provided, this produces a default
/// hash from just the personalization string.
#[cfg(feature = "zfuture")]
pub(crate) fn hash_tze_inputs<A>(tze_inputs: &[TzeIn<A>]) -> Blake2bHash {
let mut h = hasher(ZCASH_TZE_INPUTS_HASH_PERSONALIZATION);
for tzein in tze_inputs {
tzein.write_without_witness(&mut h).unwrap();
}
h.finalize()
}
/// Sequentially append the full serialized value of each TZE output
/// to a hash personalized by ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION.
/// In the case that no outputs are provided, this produces a default
/// hash from just the personalization string.
#[cfg(feature = "zfuture")]
pub(crate) fn hash_tze_outputs(tze_outputs: &[TzeOut]) -> Blake2bHash {
let mut h = hasher(ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION);
for tzeout in tze_outputs {
tzeout.write(&mut h).unwrap();
}
h.finalize()
}
/// Write disjoint parts of each Sapling shielded spend to a pair of hashes:
/// * \[nullifier*\] - personalized with ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION
/// * \[(cv, anchor, rk, zkproof)*\] - personalized with ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION
///
/// Then, hash these together personalized by ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION
pub(crate) fn hash_sapling_spends<A: sapling::Authorization>(
shielded_spends: &[SpendDescription<A>],
) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION);
if !shielded_spends.is_empty() {
let mut ch = hasher(ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION);
let mut nh = hasher(ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION);
for s_spend in shielded_spends {
// we build the hash of nullifiers separately for compact blocks.
ch.write_all(&s_spend.nullifier.as_ref()).unwrap();
nh.write_all(&s_spend.cv.to_bytes()).unwrap();
nh.write_all(&s_spend.anchor.to_repr()).unwrap();
s_spend.rk.write(&mut nh).unwrap();
}
let compact_digest = ch.finalize();
h.write_all(&compact_digest.as_bytes()).unwrap();
let noncompact_digest = nh.finalize();
h.write_all(&noncompact_digest.as_bytes()).unwrap();
}
h.finalize()
}
/// Write disjoint parts of each Sapling shielded output as 3 separate hashes:
/// * \[(cmu, epk, enc_ciphertext\[..52\])*\] personalized with ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION
/// * \[enc_ciphertext\[52..564\]*\] (memo ciphertexts) personalized with ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION
/// * \[(cv, enc_ciphertext\[564..\], out_ciphertext, zkproof)*\] personalized with ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION
///
/// Then, hash these together personalized with ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION
pub(crate) fn hash_sapling_outputs<A>(shielded_outputs: &[OutputDescription<A>]) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION);
if !shielded_outputs.is_empty() {
let mut ch = hasher(ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION);
let mut mh = hasher(ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION);
let mut nh = hasher(ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION);
for s_out in shielded_outputs {
ch.write_all(&s_out.cmu.to_repr().as_ref()).unwrap();
ch.write_all(&s_out.ephemeral_key.to_bytes()).unwrap();
ch.write_all(&s_out.enc_ciphertext[..52]).unwrap();
mh.write_all(&s_out.enc_ciphertext[52..564]).unwrap();
nh.write_all(&s_out.cv.to_bytes()).unwrap();
nh.write_all(&s_out.enc_ciphertext[564..]).unwrap();
nh.write_all(&s_out.out_ciphertext).unwrap();
}
h.write_all(&ch.finalize().as_bytes()).unwrap();
h.write_all(&mh.finalize().as_bytes()).unwrap();
h.write_all(&nh.finalize().as_bytes()).unwrap();
}
h.finalize()
}
/// The txid commits to the hash of all transparent outputs. The
/// prevout and sequence_hash components of txid
fn transparent_digests<A: transparent::Authorization>(
bundle: &transparent::Bundle<A>,
) -> TransparentDigests<Blake2bHash> {
TransparentDigests {
prevout_digest: transparent_prevout_hash(&bundle.vin),
sequence_digest: transparent_sequence_hash(&bundle.vin),
outputs_digest: transparent_outputs_hash(&bundle.vout),
per_input_digest: None,
}
}
#[cfg(feature = "zfuture")]
fn tze_digests<A: tze::Authorization>(bundle: &tze::Bundle<A>) -> TzeDigests<Blake2bHash> {
// The txid commits to the hash for all outputs.
TzeDigests {
inputs_digest: hash_tze_inputs(&bundle.vin),
outputs_digest: hash_tze_outputs(&bundle.vout),
per_input_digest: None,
}
}
fn hash_header_txid_data(
version: TxVersion,
// we commit to the consensus branch ID with the header
consensus_branch_id: BranchId,
lock_time: u32,
expiry_height: BlockHeight,
) -> Blake2bHash {
let mut h = hasher(ZCASH_HEADERS_HASH_PERSONALIZATION);
h.write_u32::<LittleEndian>(version.header()).unwrap();
h.write_u32::<LittleEndian>(version.version_group_id())
.unwrap();
h.write_u32::<LittleEndian>(consensus_branch_id.into())
.unwrap();
h.write_u32::<LittleEndian>(lock_time).unwrap();
h.write_u32::<LittleEndian>(expiry_height.into()).unwrap();
h.finalize()
}
fn hash_transparent_txid_data(t_digests: Option<&TransparentDigests<Blake2bHash>>) -> Blake2bHash {
let mut h = hasher(ZCASH_TRANSPARENT_HASH_PERSONALIZATION);
if let Some(d) = t_digests {
h.write_all(d.prevout_digest.as_bytes()).unwrap();
h.write_all(d.sequence_digest.as_bytes()).unwrap();
h.write_all(d.outputs_digest.as_bytes()).unwrap();
if let Some(s) = d.per_input_digest {
h.write_all(s.as_bytes()).unwrap();
};
}
h.finalize()
}
fn hash_sapling_txid_data<A: sapling::Authorization>(bundle: &sapling::Bundle<A>) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_HASH_PERSONALIZATION);
if !(bundle.shielded_spends.is_empty() && bundle.shielded_outputs.is_empty()) {
h.write_all(hash_sapling_spends(&bundle.shielded_spends).as_bytes())
.unwrap();
h.write_all(hash_sapling_outputs(&bundle.shielded_outputs).as_bytes())
.unwrap();
h.write_all(&bundle.value_balance.to_i64_le_bytes())
.unwrap();
}
h.finalize()
}
fn hash_sapling_txid_empty() -> Blake2bHash {
hasher(ZCASH_SAPLING_HASH_PERSONALIZATION).finalize()
}
/// Write disjoint parts of each Orchard shielded action as 3 separate hashes:
/// * \[(nullifier, cmx, ephemeral_key, enc_ciphertext\[..52\])*\] personalized
/// with ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION
/// * \[enc_ciphertext\[52..564\]*\] (memo ciphertexts) personalized
/// with ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION
/// * \[(cv, rk, enc_ciphertext\[564..\], out_ciphertext)*\] personalized
/// with ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION
///
/// Then, hash these together along with (flags, value_balance_orchard, anchor_orchard),
/// personalized with ZCASH_ORCHARD_ACTIONS_HASH_PERSONALIZATION
fn hash_orchard_txid_data<A: orchard::Authorization>(
bundle: &orchard::Bundle<A, Amount>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION);
let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION);
let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION);
let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION);
for action in bundle.actions().iter() {
ch.write_all(&action.nullifier().to_bytes()).unwrap();
ch.write_all(&action.cmx().to_bytes()).unwrap();
ch.write_all(&action.encrypted_note().epk_bytes).unwrap();
ch.write_all(&action.encrypted_note().enc_ciphertext[..52])
.unwrap();
mh.write_all(&action.encrypted_note().enc_ciphertext[52..564])
.unwrap();
nh.write_all(&action.cv_net().to_bytes()).unwrap();
nh.write_all(&<[u8; 32]>::from(action.rk())).unwrap();
nh.write_all(&action.encrypted_note().enc_ciphertext[564..])
.unwrap();
nh.write_all(&action.encrypted_note().out_ciphertext)
.unwrap();
}
h.write_all(&ch.finalize().as_bytes()).unwrap();
h.write_all(&mh.finalize().as_bytes()).unwrap();
h.write_all(&nh.finalize().as_bytes()).unwrap();
ser_orch::write_flags(&mut h, bundle.flags()).unwrap();
h.write_all(&bundle.value_balance().to_i64_le_bytes())
.unwrap();
ser_orch::write_anchor(&mut h, bundle.anchor()).unwrap();
h.finalize()
}
fn hash_orchard_txid_empty() -> Blake2bHash {
hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION).finalize()
}
#[cfg(feature = "zfuture")]
fn hash_tze_txid_data(tze_digests: Option<&TzeDigests<Blake2bHash>>) -> Blake2bHash {
let mut h = hasher(ZCASH_TZE_HASH_PERSONALIZATION);
if let Some(d) = tze_digests {
h.write_all(d.inputs_digest.as_bytes()).unwrap();
h.write_all(d.outputs_digest.as_bytes()).unwrap();
if let Some(s) = d.per_input_digest {
h.write_all(s.as_bytes()).unwrap();
}
}
h.finalize()
}
pub struct TxIdDigester;
// A TransactionDigest implementation that commits to all of the effecting
// data of a transaction to produce a nonmalleable transaction identifier.
//
// This expects and relies upon the existence of canonical encodings for
// each effecting component of a transaction.
impl<A: Authorization> TransactionDigest<A> for TxIdDigester {
type HeaderDigest = Blake2bHash;
type TransparentDigest = Option<TransparentDigests<Blake2bHash>>;
type SaplingDigest = Option<Blake2bHash>;
type OrchardDigest = Option<Blake2bHash>;
#[cfg(feature = "zfuture")]
type TzeDigest = Option<TzeDigests<Blake2bHash>>;
type Digest = TxDigests<Blake2bHash>;
fn digest_header(
&self,
version: TxVersion,
consensus_branch_id: BranchId,
lock_time: u32,
expiry_height: BlockHeight,
) -> Self::HeaderDigest {
hash_header_txid_data(version, consensus_branch_id, lock_time, expiry_height)
}
fn digest_transparent(
&self,
transparent_bundle: Option<&transparent::Bundle<A::TransparentAuth>>,
) -> Self::TransparentDigest {
transparent_bundle.map(transparent_digests)
}
fn digest_sapling(
&self,
sapling_bundle: Option<&sapling::Bundle<A::SaplingAuth>>,
) -> Self::SaplingDigest {
sapling_bundle.map(hash_sapling_txid_data)
}
fn digest_orchard(
&self,
orchard_bundle: Option<&orchard::Bundle<A::OrchardAuth, Amount>>,
) -> Self::OrchardDigest {
orchard_bundle.map(hash_orchard_txid_data)
}
#[cfg(feature = "zfuture")]
fn digest_tze(&self, tze_bundle: Option<&tze::Bundle<A::TzeAuth>>) -> Self::TzeDigest {
tze_bundle.map(tze_digests)
}
fn combine(
&self,
header_digest: Self::HeaderDigest,
transparent_digests: Self::TransparentDigest,
sapling_digest: Self::SaplingDigest,
orchard_digest: Self::OrchardDigest,
#[cfg(feature = "zfuture")] tze_digests: Self::TzeDigest,
) -> Self::Digest {
TxDigests {
header_digest,
transparent_digests,
sapling_digest,
orchard_digest,
#[cfg(feature = "zfuture")]
tze_digests,
}
}
}
pub(crate) fn to_hash(
_txversion: TxVersion,
consensus_branch_id: BranchId,
header_digest: Blake2bHash,
transparent_digests: Option<&TransparentDigests<Blake2bHash>>,
sapling_digest: Option<Blake2bHash>,
orchard_digest: Option<Blake2bHash>,
#[cfg(feature = "zfuture")] tze_digests: Option<&TzeDigests<Blake2bHash>>,
) -> Blake2bHash {
let mut personal = [0; 16];
(&mut personal[..12]).copy_from_slice(ZCASH_TX_PERSONALIZATION_PREFIX);
(&mut personal[12..])
.write_u32::<LittleEndian>(consensus_branch_id.into())
.unwrap();
let mut h = hasher(&personal);
h.write_all(header_digest.as_bytes()).unwrap();
h.write_all(hash_transparent_txid_data(transparent_digests).as_bytes())
.unwrap();
h.write_all(
sapling_digest
.unwrap_or_else(hash_sapling_txid_empty)
.as_bytes(),
)
.unwrap();
h.write_all(
orchard_digest
.unwrap_or_else(hash_orchard_txid_empty)
.as_bytes(),
)
.unwrap();
#[cfg(feature = "zfuture")]
if _txversion.has_tze() {
h.write_all(hash_tze_txid_data(tze_digests).as_bytes())
.unwrap();
}
h.finalize()
}
pub fn to_txid(
txversion: TxVersion,
consensus_branch_id: BranchId,
digests: &TxDigests<Blake2bHash>,
) -> TxId {
let txid_digest = to_hash(
txversion,
consensus_branch_id,
digests.header_digest,
digests.transparent_digests.as_ref(),
digests.sapling_digest,
digests.orchard_digest,
#[cfg(feature = "zfuture")]
digests.tze_digests.as_ref(),
);
TxId(<[u8; 32]>::try_from(txid_digest.as_bytes()).unwrap())
}
/// Digester which constructs a digest of only the witness data.
/// This does not internally commit to the txid, so if that is
/// desired it should be done using the result of this digest
/// function.
pub struct BlockTxCommitmentDigester;
impl TransactionDigest<Authorized> for BlockTxCommitmentDigester {
/// We use the header digest to pass the transaction ID into
/// where it needs to be used for personalization string construction.
type HeaderDigest = BranchId;
type TransparentDigest = Blake2bHash;
type SaplingDigest = Blake2bHash;
type OrchardDigest = Blake2bHash;
#[cfg(feature = "zfuture")]
type TzeDigest = Blake2bHash;
type Digest = Blake2bHash;
fn digest_header(
&self,
_version: TxVersion,
consensus_branch_id: BranchId,
_lock_time: u32,
_expiry_height: BlockHeight,
) -> Self::HeaderDigest {
consensus_branch_id
}
fn digest_transparent(
&self,
transparent_bundle: Option<&transparent::Bundle<transparent::Authorized>>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_TRANSPARENT_SCRIPTS_HASH_PERSONALIZATION);
if let Some(bundle) = transparent_bundle {
for txin in &bundle.vin {
h.write_all(&txin.script_sig.0).unwrap();
}
}
h.finalize()
}
fn digest_sapling(
&self,
sapling_bundle: Option<&sapling::Bundle<sapling::Authorized>>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_SAPLING_SIGS_HASH_PERSONALIZATION);
if let Some(bundle) = sapling_bundle {
for spend in &bundle.shielded_spends {
h.write_all(&spend.zkproof).unwrap();
}
for spend in &bundle.shielded_spends {
spend.spend_auth_sig.write(&mut h).unwrap();
}
for output in &bundle.shielded_outputs {
h.write_all(&output.zkproof).unwrap();
}
bundle.authorization.binding_sig.write(&mut h).unwrap();
}
h.finalize()
}
fn digest_orchard(
&self,
orchard_bundle: Option<&orchard::Bundle<orchard::Authorized, Amount>>,
) -> Self::OrchardDigest {
let mut h = hasher(ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION);
if let Some(bundle) = orchard_bundle {
h.write_all(bundle.authorization().proof().as_ref())
.unwrap();
for action in bundle.actions().iter() {
h.write_all(&<[u8; 64]>::from(action.authorization()))
.unwrap();
}
h.write_all(&<[u8; 64]>::from(
bundle.authorization().binding_signature(),
))
.unwrap();
}
h.finalize()
}
#[cfg(feature = "zfuture")]
fn digest_tze(&self, tze_bundle: Option<&tze::Bundle<tze::Authorized>>) -> Blake2bHash {
let mut h = hasher(ZCASH_TZE_WITNESSES_HASH_PERSONALIZATION);
if let Some(bundle) = tze_bundle {
for tzein in &bundle.vin {
h.write_all(&tzein.witness.payload.0).unwrap();
}
}
h.finalize()
}
fn combine(
&self,
consensus_branch_id: Self::HeaderDigest,
transparent_digest: Self::TransparentDigest,
sapling_digest: Self::SaplingDigest,
orchard_digest: Self::OrchardDigest,
#[cfg(feature = "zfuture")] tze_digest: Self::TzeDigest,
) -> Self::Digest {
let digests = [
transparent_digest,
sapling_digest,
orchard_digest,
#[cfg(feature = "zfuture")]
tze_digest,
];
let mut personal = [0; 16];
(&mut personal[..12]).copy_from_slice(ZCASH_AUTH_PERSONALIZATION_PREFIX);
(&mut personal[12..])
.write_u32::<LittleEndian>(consensus_branch_id.into())
.unwrap();
let mut h = hasher(&personal);
for digest in &digests {
h.write_all(digest.as_bytes()).unwrap();
}
h.finalize()
}
}

View File

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