Use generalized signature_hash for transaction builder.

This commit is contained in:
Kris Nuttycombe 2021-05-13 00:16:40 -06:00
parent 55d1090f70
commit 1a5aad723b
8 changed files with 63 additions and 61 deletions

View File

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

View File

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

View File

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

View File

@ -631,8 +631,7 @@ mod tests {
)
.unwrap();
let output =
&tx.sapling_bundle().as_ref().unwrap().shielded_outputs[output_index as usize];
let output = &tx.sapling_bundle().unwrap().shielded_outputs[output_index as usize];
try_sapling_output_recovery(
&network,

View File

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

View File

@ -85,7 +85,7 @@ impl<'a> demo::Context for Context<'a> {
}
fn tx_tze_outputs(&self) -> &[TzeOut] {
if let Some(bundle) = &self.tx.tze_bundle() {
if let Some(bundle) = self.tx.tze_bundle() {
&bundle.vout
} else {
&[]

View File

@ -1,13 +1,15 @@
//! 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},
@ -27,16 +29,13 @@ use crate::{
},
transparent::{self, builder::TransparentBuilder},
},
sighash::{SignableInput, SIGHASH_ALL},
sighash_v4::v4_signature_hash,
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};
@ -280,11 +279,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,
@ -343,7 +337,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
let unauthed_tx = TransactionData {
version,
consensus_branch_id,
consensus_branch_id: BranchId::for_height(&self.params, self.target_height),
lock_time: 0,
expiry_height: self.expiry_height,
transparent_bundle,
@ -357,18 +351,32 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
//
// Signatures -- everything but the signatures must already have been added.
//
let mut sighash = [0u8; 32];
sighash.copy_from_slice(
&v4_signature_hash(&unauthed_tx, SignableInput::Shielded, SIGHASH_ALL).as_ref(),
);
let txid_parts = unauthed_tx.digest(TxIdDigester);
#[cfg(feature = "transparent-inputs")]
let transparent_sigs = self.transparent_builder.create_signatures(&unauthed_tx);
let transparent_sigs = self
.transparent_builder
.create_signatures(&unauthed_tx, &txid_parts);
// 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,
SignableInput::Shielded,
&txid_parts,
SIGHASH_ALL,
);
let sapling_sigs = self
.sapling_builder
.create_signatures(prover, &mut ctx, &mut self.rng, &sighash, &tx_metadata)
.create_signatures(
prover,
&mut ctx,
&mut self.rng,
shielded_sig_commitment.as_ref(),
&tx_metadata,
)
.map_err(Error::SaplingBuild)?;
#[cfg(feature = "zfuture")]
@ -380,10 +388,9 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
Ok((
Self::apply_signatures(
unauthed_tx,
sapling_sigs,
#[cfg(feature = "transparent-inputs")]
transparent_sigs,
sapling_sigs,
None,
#[cfg(feature = "zfuture")]
tze_witnesses,
)
@ -392,26 +399,18 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
))
}
fn apply_signatures(
pub fn apply_signatures(
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 = "transparent-inputs")] transparent_sigs: Option<Vec<Script>>,
#[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()
);
_ => {
panic!("Mismatch between transparent bundle and signatures.");
}
};
@ -526,19 +525,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(&[]);
@ -642,7 +641,7 @@ mod tests {
&TransparentAddress::PublicKey([0; 20]),
Amount::from_i64(-1).unwrap(),
),
Err(Error::TransparentBuild(transparent::Error::InvalidAmount))
Err(Error::TransparentBuild(build_t::Error::InvalidAmount))
);
}

View File

@ -2,6 +2,9 @@
use std::fmt;
#[cfg(feature = "transparent-inputs")]
use blake2b_simd::Hash as Blake2bHash;
use crate::{
legacy::TransparentAddress,
transaction::components::{
@ -15,9 +18,8 @@ use crate::{
legacy::Script,
transaction::{
components::OutPoint,
sighash::{SignableInput, SIGHASH_ALL},
sighash_v4::v4_signature_hash,
TransactionData, Unauthorized,
sighash::{signature_hash, SignableInput, SIGHASH_ALL},
TransactionData, TxDigests, Unauthorized,
},
};
@ -154,7 +156,11 @@ impl TransparentBuilder {
}
#[cfg(feature = "transparent-inputs")]
pub fn create_signatures(self, mtx: &TransactionData<Unauthorized>) -> Option<Vec<Script>> {
pub fn create_signatures(
self,
mtx: &TransactionData<Unauthorized>,
txid_parts_cache: &TxDigests<Blake2bHash>,
) -> Option<Vec<Script>> {
if self.inputs.is_empty() && self.vout.is_empty() {
None
} else {
@ -163,18 +169,15 @@ impl TransparentBuilder {
.iter()
.enumerate()
.map(|(i, info)| {
let mut sighash = [0u8; 32];
sighash.copy_from_slice(
&v4_signature_hash(
mtx,
SignableInput::transparent(
i,
&info.coin.script_pubkey,
info.coin.value,
),
SIGHASH_ALL,
)
.as_ref(),
let sighash = signature_hash(
mtx,
SignableInput::transparent(
i,
&info.coin.script_pubkey,
info.coin.value,
),
txid_parts_cache,
SIGHASH_ALL,
);
let msg =