Merge pull request #375 from nuttycom/feature/zip-225
ZIP 225 & ZIP 244
This commit is contained in:
commit
0bfd1f7544
|
@ -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),
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
])?;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
&[]
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(¶ms, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
}
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -16,6 +16,10 @@ impl<R: Read> HashReader<R> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_base_reader(self) -> R {
|
||||
self.reader
|
||||
}
|
||||
|
||||
/// Destroy this reader and return the hash of what was read.
|
||||
pub fn into_hash(self) -> Output<Sha256> {
|
||||
Sha256::digest(&self.hasher.finalize())
|
||||
|
|
Loading…
Reference in New Issue