zcash_transparent: Refactor code so it compiles in its new crate

This commit is contained in:
Jack Grigg 2024-12-14 13:11:32 +00:00 committed by Kris Nuttycombe
parent 1a3eeab703
commit e4915f99c4
28 changed files with 303 additions and 273 deletions

3
Cargo.lock generated
View File

@ -1249,6 +1249,7 @@ dependencies = [
"zcash_primitives",
"zcash_proofs",
"zcash_protocol",
"zcash_transparent",
]
[[package]]
@ -6424,7 +6425,9 @@ dependencies = [
name = "zcash_protocol"
version = "0.4.2"
dependencies = [
"core2",
"document-features",
"hex",
"incrementalmerkletree",
"incrementalmerkletree-testing",
"memuse",

View File

@ -6,6 +6,9 @@ and this library adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- `zcash_protocol::TxId` (moved from `zcash_primitives::transaction`).
## [0.4.2] - 2024-12-13
### Added
- `no-std` compatibility (`alloc` is required). A default-enabled `std` feature

View File

@ -28,6 +28,10 @@ memuse = { workspace = true, optional = true }
# - Documentation
document-features = { workspace = true, optional = true }
# - Encodings
core2.workspace = true
hex.workspace = true
# - Test dependencies
proptest = { workspace = true, optional = true }
incrementalmerkletree = { workspace = true, optional = true }

View File

@ -31,6 +31,9 @@ pub mod local_consensus;
pub mod memo;
pub mod value;
mod txid;
pub use txid::TxId;
/// A Zcash shielded transfer protocol.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ShieldedProtocol {

View File

@ -0,0 +1,65 @@
use alloc::string::ToString;
use core::fmt;
use core2::io::{self, Read, Write};
#[cfg(feature = "std")]
use memuse::DynamicUsage;
/// The identifier for a Zcash transaction.
///
/// - For v1-4 transactions, this is a double-SHA-256 hash of the encoded transaction.
/// This means that it is malleable, and only a reliable identifier for transactions
/// that have been mined.
/// - For v5 transactions onwards, this identifier is derived only from "effecting" data,
/// and is non-malleable in all contexts.
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct TxId([u8; 32]);
#[cfg(feature = "std")]
memuse::impl_no_dynamic_usage!(TxId);
impl fmt::Debug for TxId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// The (byte-flipped) hex string is more useful than the raw bytes, because we can
// look that up in RPC methods and block explorers.
let txid_str = self.to_string();
f.debug_tuple("TxId").field(&txid_str).finish()
}
}
impl fmt::Display for TxId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut data = self.0;
data.reverse();
formatter.write_str(&hex::encode(data))
}
}
impl AsRef<[u8; 32]> for TxId {
fn as_ref(&self) -> &[u8; 32] {
&self.0
}
}
impl From<TxId> for [u8; 32] {
fn from(value: TxId) -> Self {
value.0
}
}
impl TxId {
pub const fn from_bytes(bytes: [u8; 32]) -> Self {
TxId(bytes)
}
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let mut hash = [0u8; 32];
reader.read_exact(&mut hash)?;
Ok(TxId::from_bytes(hash))
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.0)?;
Ok(())
}
}

View File

@ -27,6 +27,7 @@ zcash_protocol = {workspace = true, features = ["local-consensus"] }
# Transparent
secp256k1.workspace = true
transparent.workspace = true
# Sprout
ed25519-zebra = "4"

View File

@ -3,6 +3,7 @@ use std::{
convert::{TryFrom, TryInto},
};
use ::transparent::sighash::SighashType;
use bellman::groth16;
use group::GroupEncoding;
use orchard::note_encryption::OrchardDomain;
@ -283,7 +284,7 @@ pub(crate) fn inspect(
let sig = secp256k1::ecdsa::Signature::from_der(
&txin.script_sig.0[1..1 + sig_len],
);
let hash_type = txin.script_sig.0[1 + sig_len];
let hash_type = SighashType::parse(txin.script_sig.0[1 + sig_len]);
let pubkey_bytes = &txin.script_sig.0[1 + sig_len + 2..];
let pubkey = secp256k1::PublicKey::from_slice(pubkey_bytes);
@ -293,13 +294,18 @@ pub(crate) fn inspect(
i, e
);
}
if hash_type.is_none() {
eprintln!(" ⚠️ Txin {} has invalid sighash type", i);
}
if let Err(e) = pubkey {
eprintln!(
" ⚠️ Txin {} has invalid pubkey encoding: {}",
i, e
);
}
if let (Ok(sig), Ok(pubkey)) = (sig, pubkey) {
if let (Ok(sig), Some(hash_type), Ok(pubkey)) =
(sig, hash_type, pubkey)
{
#[allow(deprecated)]
if pubkey_to_address(&pubkey) != addr {
eprintln!(" ⚠️ Txin {} pubkey does not match coin's script_pubkey", i);
@ -307,14 +313,16 @@ pub(crate) fn inspect(
let sighash = signature_hash(
tx,
&SignableInput::Transparent {
hash_type,
index: i,
// For P2PKH these are the same.
script_code: &coin.script_pubkey,
script_pubkey: &coin.script_pubkey,
value: coin.value,
},
&SignableInput::Transparent(
::transparent::sighash::SignableInput::from_parts(
hash_type,
i,
// For P2PKH these are the same.
&coin.script_pubkey,
&coin.script_pubkey,
coin.value,
),
),
txid_parts,
);
let msg = secp256k1::Message::from_slice(sighash.as_ref())

View File

@ -95,7 +95,21 @@ impl Signer {
// TODO
input
.sign(index, &self.tx_data, &self.txid_parts, sk, &self.secp)
.sign(
index,
|input| {
v5_signature_hash(
&self.tx_data,
&SignableInput::Transparent(input),
&self.txid_parts,
)
.as_ref()
.try_into()
.unwrap()
},
sk,
&self.secp,
)
.map_err(Error::TransparentSign)?;
// Update transaction modifiability:

View File

@ -24,8 +24,8 @@ and this library adheres to Rust's notion of
- `zcash_primitives::transaction::components::transparent`:
- `builder::TransparentBuilder::add_input` now takes `secp256k1::PublicKey`
instead of `secp256k1::SecretKey`.
- `Bundle<Unauthorized>::apply_signatures` now takes an additional argument
`&TransparentSigningSet`.
- `Bundle<Unauthorized>::apply_signatures` has had its arguments replaced with
a function providing the sighash calculation, and `&TransparentSigningSet`.
- `builder::Error` has a new variant `MissingSigningKey`.
- `zcash_primitives::transaction::builder`:
- `Builder::add_orchard_spend` now takes `orchard::keys::FullViewingKey`
@ -38,6 +38,9 @@ and this library adheres to Rust's notion of
- `&TransparentSigningSet`
- `&[sapling::zip32::ExtendedSpendingKey]`
- `&[orchard::keys::SpendAuthorizingKey]`
- `zcash_primitives::transaction::sighash`:
- `SignableInput::Transparent` is now a wrapper around
`zcash_transparent::sighash::SignableInput`.
## [0.20.0] - 2024-11-14

View File

@ -806,10 +806,14 @@ impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder<
.clone()
.map(|b| {
b.apply_signatures(
#[cfg(feature = "transparent-inputs")]
&unauthed_tx,
#[cfg(feature = "transparent-inputs")]
&txid_parts,
|input| {
*signature_hash(
&unauthed_tx,
&SignableInput::Transparent(input),
&txid_parts,
)
.as_ref()
},
transparent_signing_set,
)
})

View File

@ -13,9 +13,7 @@ mod tests;
use blake2b_simd::Hash as Blake2bHash;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use memuse::DynamicUsage;
use std::convert::TryFrom;
use std::fmt;
use std::fmt::Debug;
use std::io::{self, Read, Write};
use std::ops::Deref;
@ -60,63 +58,7 @@ const ZFUTURE_VERSION_GROUP_ID: u32 = 0xFFFFFFFF;
#[cfg(zcash_unstable = "zfuture")]
const ZFUTURE_TX_VERSION: u32 = 0x0000FFFF;
/// The identifier for a Zcash transaction.
///
/// - For v1-4 transactions, this is a double-SHA-256 hash of the encoded transaction.
/// This means that it is malleable, and only a reliable identifier for transactions
/// that have been mined.
/// - For v5 transactions onwards, this identifier is derived only from "effecting" data,
/// and is non-malleable in all contexts.
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct TxId([u8; 32]);
memuse::impl_no_dynamic_usage!(TxId);
impl fmt::Debug for TxId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// The (byte-flipped) hex string is more useful than the raw bytes, because we can
// look that up in RPC methods and block explorers.
let txid_str = self.to_string();
f.debug_tuple("TxId").field(&txid_str).finish()
}
}
impl fmt::Display for TxId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut data = self.0;
data.reverse();
formatter.write_str(&hex::encode(data))
}
}
impl AsRef<[u8; 32]> for TxId {
fn as_ref(&self) -> &[u8; 32] {
&self.0
}
}
impl From<TxId> for [u8; 32] {
fn from(value: TxId) -> Self {
value.0
}
}
impl TxId {
pub fn from_bytes(bytes: [u8; 32]) -> Self {
TxId(bytes)
}
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let mut hash = [0u8; 32];
reader.read_exact(&mut hash)?;
Ok(TxId::from_bytes(hash))
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.0)?;
Ok(())
}
}
pub use zcash_protocol::TxId;
/// The set of defined transaction format versions.
///
@ -611,12 +553,12 @@ impl Transaction {
fn from_data_v4(data: TransactionData<Authorized>) -> io::Result<Self> {
let mut tx = Transaction {
txid: TxId([0; 32]),
txid: TxId::from_bytes([0; 32]),
data,
};
let mut writer = HashWriter::default();
tx.write(&mut writer)?;
tx.txid.0.copy_from_slice(&writer.into_hash());
tx.txid = TxId::from_bytes(writer.into_hash().into());
Ok(tx)
}
@ -706,7 +648,7 @@ impl Transaction {
txid.copy_from_slice(&hash_bytes);
Ok(Transaction {
txid: TxId(txid),
txid: TxId::from_bytes(txid),
data: TransactionData {
version,
consensus_branch_id,

View File

@ -1,13 +1,10 @@
use blake2b_simd::Hash as Blake2bHash;
use super::{
components::amount::NonNegativeAmount, sighash_v4::v4_signature_hash,
sighash_v5::v5_signature_hash, Authorization, TransactionData, TxDigests, TxVersion,
};
use crate::{
legacy::Script,
sapling::{self, bundle::GrothProofBytes},
sighash_v4::v4_signature_hash, sighash_v5::v5_signature_hash, Authorization, TransactionData,
TxDigests, TxVersion,
};
use crate::sapling::{self, bundle::GrothProofBytes};
#[cfg(zcash_unstable = "zfuture")]
use {super::components::Amount, crate::extensions::transparent::Precondition};
@ -16,13 +13,7 @@ pub use transparent::sighash::*;
pub enum SignableInput<'a> {
Shielded,
Transparent {
hash_type: u8,
index: usize,
script_code: &'a Script,
script_pubkey: &'a Script,
value: NonNegativeAmount,
},
Transparent(transparent::sighash::SignableInput<'a>),
#[cfg(zcash_unstable = "zfuture")]
Tze {
index: usize,
@ -35,7 +26,7 @@ impl<'a> SignableInput<'a> {
pub fn hash_type(&self) -> u8 {
match self {
SignableInput::Shielded => SIGHASH_ALL,
SignableInput::Transparent { hash_type, .. } => *hash_type,
SignableInput::Transparent(input) => input.hash_type().encode(),
#[cfg(zcash_unstable = "zfuture")]
SignableInput::Tze { .. } => SIGHASH_ALL,
}

View File

@ -186,8 +186,8 @@ pub fn v4_signature_hash<
);
} 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())
(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]),
};
@ -235,18 +235,13 @@ pub fn v4_signature_hash<
match signable_input {
SignableInput::Shielded => (),
SignableInput::Transparent {
index,
script_code,
value,
..
} => {
SignableInput::Transparent(input) => {
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());
data.extend_from_slice(&bundle.vin[*index].sequence.to_le_bytes());
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());
data.extend_from_slice(&bundle.vin[*input.index()].sequence.to_le_bytes());
h.update(&data);
} else {
panic!(

View File

@ -89,10 +89,10 @@ fn transparent_sig_digest<A: TransparentAuthorizingContext>(
txid_digests.sequence_digest
};
let outputs_digest = if let SignableInput::Transparent { index, .. } = input {
let outputs_digest = if let SignableInput::Transparent(input) = input {
if flag_single {
if *index < bundle.vout.len() {
transparent_outputs_hash(&[&bundle.vout[*index]])
if *input.index() < bundle.vout.len() {
transparent_outputs_hash(&[&bundle.vout[*input.index()]])
} else {
transparent_outputs_hash::<TxOut>(&[])
}
@ -110,17 +110,11 @@ fn transparent_sig_digest<A: TransparentAuthorizingContext>(
//S.2g.iii: scriptPubKey (field encoding)
//S.2g.iv: nSequence (4-byte unsigned little-endian)
let mut ch = hasher(ZCASH_TRANSPARENT_INPUT_HASH_PERSONALIZATION);
if let SignableInput::Transparent {
index,
script_pubkey,
value,
..
} = input
{
let txin = &bundle.vin[*index];
if let SignableInput::Transparent(input) = input {
let txin = &bundle.vin[*input.index()];
txin.prevout.write(&mut ch).unwrap();
ch.write_all(&value.to_i64_le_bytes()).unwrap();
script_pubkey.write(&mut ch).unwrap();
ch.write_all(&input.value().to_i64_le_bytes()).unwrap();
input.script_pubkey().write(&mut ch).unwrap();
ch.write_all(&txin.sequence.to_le_bytes()).unwrap();
}
let txin_sig_digest = ch.finalize();

View File

@ -1,3 +1,4 @@
use ::transparent::sighash::SighashType;
use blake2b_simd::Hash as Blake2bHash;
use std::ops::Deref;
@ -9,10 +10,7 @@ use crate::{
use super::{
sapling,
sighash::{
SignableInput, TransparentAuthorizingContext, SIGHASH_ALL, SIGHASH_ANYONECANPAY,
SIGHASH_NONE, SIGHASH_SINGLE,
},
sighash::{SignableInput, TransparentAuthorizingContext},
sighash_v4::v4_signature_hash,
sighash_v5::v5_signature_hash,
testing::arb_tx,
@ -136,13 +134,15 @@ fn zip_0143() {
for tv in self::data::zip_0143::make_test_vectors() {
let tx = Transaction::read(&tv.tx[..], tv.consensus_branch_id).unwrap();
let signable_input = match tv.transparent_input {
Some(n) => SignableInput::Transparent {
hash_type: tv.hash_type as u8,
index: n as usize,
script_code: &tv.script_code,
script_pubkey: &tv.script_code,
value: NonNegativeAmount::from_nonnegative_i64(tv.amount).unwrap(),
},
Some(n) => {
SignableInput::Transparent(::transparent::sighash::SignableInput::from_parts(
SighashType::parse(tv.hash_type as u8).unwrap(),
n as usize,
&tv.script_code,
&tv.script_code,
NonNegativeAmount::from_nonnegative_i64(tv.amount).unwrap(),
))
}
_ => SignableInput::Shielded,
};
@ -158,13 +158,15 @@ fn zip_0243() {
for tv in self::data::zip_0243::make_test_vectors() {
let tx = Transaction::read(&tv.tx[..], tv.consensus_branch_id).unwrap();
let signable_input = match tv.transparent_input {
Some(n) => SignableInput::Transparent {
hash_type: tv.hash_type as u8,
index: n as usize,
script_code: &tv.script_code,
script_pubkey: &tv.script_code,
value: NonNegativeAmount::from_nonnegative_i64(tv.amount).unwrap(),
},
Some(n) => {
SignableInput::Transparent(::transparent::sighash::SignableInput::from_parts(
SighashType::parse(tv.hash_type as u8).unwrap(),
n as usize,
&tv.script_code,
&tv.script_code,
NonNegativeAmount::from_nonnegative_i64(tv.amount).unwrap(),
))
}
_ => SignableInput::Shielded,
};
@ -287,27 +289,30 @@ fn zip_0244() {
let bundle = txdata.transparent_bundle().unwrap();
let value = bundle.authorization.input_amounts[index];
let script_pubkey = &bundle.authorization.input_scriptpubkeys[index];
let signable_input = |hash_type| SignableInput::Transparent {
hash_type,
index,
script_code: script_pubkey,
script_pubkey,
value,
let signable_input = |hash_type| {
SignableInput::Transparent(::transparent::sighash::SignableInput::from_parts(
hash_type,
index,
script_pubkey,
script_pubkey,
value,
))
};
assert_eq!(
v5_signature_hash(&txdata, &signable_input(SIGHASH_ALL), &txid_parts).as_ref(),
v5_signature_hash(&txdata, &signable_input(SighashType::ALL), &txid_parts).as_ref(),
&tv.sighash_all.unwrap()
);
assert_eq!(
v5_signature_hash(&txdata, &signable_input(SIGHASH_NONE), &txid_parts).as_ref(),
v5_signature_hash(&txdata, &signable_input(SighashType::NONE), &txid_parts)
.as_ref(),
&tv.sighash_none.unwrap()
);
if index < bundle.vout.len() {
assert_eq!(
v5_signature_hash(&txdata, &signable_input(SIGHASH_SINGLE), &txid_parts)
v5_signature_hash(&txdata, &signable_input(SighashType::SINGLE), &txid_parts)
.as_ref(),
&tv.sighash_single.unwrap()
);
@ -318,7 +323,7 @@ fn zip_0244() {
assert_eq!(
v5_signature_hash(
&txdata,
&signable_input(SIGHASH_ALL | SIGHASH_ANYONECANPAY),
&signable_input(SighashType::ALL_ANYONECANPAY),
&txid_parts,
)
.as_ref(),
@ -328,7 +333,7 @@ fn zip_0244() {
assert_eq!(
v5_signature_hash(
&txdata,
&signable_input(SIGHASH_NONE | SIGHASH_ANYONECANPAY),
&signable_input(SighashType::NONE_ANYONECANPAY),
&txid_parts,
)
.as_ref(),
@ -339,7 +344,7 @@ fn zip_0244() {
assert_eq!(
v5_signature_hash(
&txdata,
&signable_input(SIGHASH_SINGLE | SIGHASH_ANYONECANPAY),
&signable_input(SighashType::SINGLE_ANYONECANPAY),
&txid_parts,
)
.as_ref(),

View File

@ -420,7 +420,7 @@ pub fn to_txid(
digests.tze_digests.as_ref(),
);
TxId(<[u8; 32]>::try_from(txid_digest.as_bytes()).unwrap())
TxId::from_bytes(<[u8; 32]>::try_from(txid_digest.as_bytes()).unwrap())
}
/// Digester which constructs a digest of only the witness data.

View File

@ -9,9 +9,6 @@ use std::ops::Shl;
use zcash_encoding::Vector;
#[cfg(feature = "transparent-inputs")]
pub mod keys;
/// Defined script opcodes.
///
/// Most of the opcodes are unused by this crate, but we define them so that the alternate

View File

@ -3,26 +3,18 @@
use std::collections::BTreeMap;
use std::fmt;
use zcash_protocol::value::{BalanceError, ZatBalance as Amount, Zatoshis as NonNegativeAmount};
use crate::{
legacy::{Script, TransparentAddress},
transaction::{
components::{
amount::{Amount, BalanceError, NonNegativeAmount},
transparent::{self, Authorization, Authorized, Bundle, TxIn, TxOut},
},
sighash::TransparentAuthorizingContext,
},
address::{Script, TransparentAddress},
bundle::{Authorization, Authorized, Bundle, TxIn, TxOut},
pczt,
sighash::{SighashType, SignableInput, TransparentAuthorizingContext},
};
#[cfg(feature = "transparent-inputs")]
use {
crate::transaction::{
self as tx,
components::transparent::OutPoint,
sighash::{signature_hash, SighashType, SignableInput, SIGHASH_ALL},
TransactionData, TxDigests,
},
blake2b_simd::Hash as Blake2bHash,
crate::{bundle::OutPoint, sighash::SIGHASH_ALL},
sha2::Digest,
};
@ -206,7 +198,7 @@ impl TransparentBuilder {
(Amount::from(input_sum) - Amount::from(output_sum)).ok_or(BalanceError::Underflow)
}
pub fn build(self) -> Option<transparent::Bundle<Unauthorized>> {
pub fn build(self) -> Option<Bundle<Unauthorized>> {
#[cfg(feature = "transparent-inputs")]
let vin: Vec<TxIn<Unauthorized>> = self
.inputs
@ -220,7 +212,7 @@ impl TransparentBuilder {
if vin.is_empty() && self.vout.is_empty() {
None
} else {
Some(transparent::Bundle {
Some(Bundle {
vin,
vout: self.vout,
authorization: Unauthorized {
@ -231,12 +223,13 @@ impl TransparentBuilder {
}
}
pub(crate) fn build_for_pczt(self) -> Option<transparent::pczt::Bundle> {
/// Builds a bundle containing the given inputs and outputs, for inclusion in a PCZT.
pub fn build_for_pczt(self) -> Option<pczt::Bundle> {
#[cfg(feature = "transparent-inputs")]
let inputs = self
.inputs
.iter()
.map(|i| transparent::pczt::Input {
.map(|i| pczt::Input {
prevout_txid: i.utxo.hash,
prevout_index: i.utxo.n,
sequence: None,
@ -267,7 +260,7 @@ impl TransparentBuilder {
let outputs = self
.vout
.into_iter()
.map(|o| transparent::pczt::Output {
.map(|o| pczt::Output {
value: o.value,
script_pubkey: o.script_pubkey,
// We don't currently support spending P2SH coins, so we only ever see
@ -280,7 +273,7 @@ impl TransparentBuilder {
})
.collect();
Some(transparent::pczt::Bundle { inputs, outputs })
Some(pczt::Bundle { inputs, outputs })
}
}
}
@ -323,12 +316,15 @@ impl TransparentAuthorizingContext for Unauthorized {
}
impl Bundle<Unauthorized> {
pub fn apply_signatures(
#[cfg_attr(not(feature = "transparent-inputs"), allow(unused_variables))]
pub fn apply_signatures<F>(
self,
#[cfg(feature = "transparent-inputs")] mtx: &TransactionData<tx::Unauthorized>,
#[cfg(feature = "transparent-inputs")] txid_parts_cache: &TxDigests<Blake2bHash>,
calculate_sighash: F,
signing_set: &TransparentSigningSet,
) -> Result<Bundle<Authorized>, Error> {
) -> Result<Bundle<Authorized>, Error>
where
F: Fn(SignableInput) -> [u8; 32],
{
#[cfg(feature = "transparent-inputs")]
let script_sigs = self
.authorization
@ -343,17 +339,13 @@ impl Bundle<Unauthorized> {
.find(|(_, pubkey)| pubkey == &info.pubkey)
.ok_or(Error::MissingSigningKey)?;
let sighash = signature_hash(
mtx,
&SignableInput::Transparent {
hash_type: SIGHASH_ALL,
index,
script_code: &info.coin.script_pubkey, // for p2pkh, always the same as script_pubkey
script_pubkey: &info.coin.script_pubkey,
value: info.coin.value,
},
txid_parts_cache,
);
let sighash = calculate_sighash(SignableInput {
hash_type: SighashType::ALL,
index,
script_code: &info.coin.script_pubkey, // for p2pkh, always the same as script_pubkey
script_pubkey: &info.coin.script_pubkey,
value: info.coin.value,
});
let msg = secp256k1::Message::from_slice(sighash.as_ref()).expect("32 bytes");
let sig = signing_set.secp.sign_ecdsa(&msg, sk);
@ -369,7 +361,7 @@ impl Bundle<Unauthorized> {
#[cfg(not(feature = "transparent-inputs"))]
let script_sigs = std::iter::empty::<Result<Script, Error>>();
Ok(transparent::Bundle {
Ok(Bundle {
vin: self
.vin
.iter()

View File

@ -1,20 +1,18 @@
//! Structs representing the components within Zcash transactions.
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use zcash_protocol::TxId;
use std::fmt::Debug;
use std::io::{self, Read, Write};
use zcash_protocol::value::{BalanceError, ZatBalance as Amount, Zatoshis as NonNegativeAmount};
use crate::{
legacy::{Script, TransparentAddress},
transaction::{sighash::TransparentAuthorizingContext, TxId},
address::{Script, TransparentAddress},
sighash::TransparentAuthorizingContext,
};
use super::amount::{Amount, BalanceError, NonNegativeAmount};
pub mod builder;
pub mod pczt;
pub trait Authorization: Debug {
type ScriptSig: Debug + Clone + PartialEq;
}
@ -23,7 +21,7 @@ pub trait Authorization: Debug {
/// information for creating sighashes.
#[derive(Debug)]
pub struct EffectsOnly {
inputs: Vec<TxOut>,
pub(crate) inputs: Vec<TxOut>,
}
impl Authorization for EffectsOnly {
@ -56,11 +54,6 @@ pub trait MapAuth<A: Authorization, B: Authorization> {
}
/// The identity map.
///
/// This can be used with [`TransactionData::map_authorization`] when you want to map the
/// authorization of a subset of a transaction's bundles.
///
/// [`TransactionData::map_authorization`]: crate::transaction::TransactionData::map_authorization
impl MapAuth<Authorized, Authorized> for () {
fn map_script_sig(
&self,
@ -138,8 +131,8 @@ impl<A: Authorization> Bundle<A> {
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct OutPoint {
hash: TxId,
n: u32,
pub(crate) hash: TxId,
pub(crate) n: u32,
}
impl OutPoint {
@ -156,7 +149,7 @@ impl OutPoint {
#[cfg(any(test, feature = "test-dependencies"))]
pub const fn fake() -> Self {
OutPoint {
hash: TxId([1u8; 32]),
hash: TxId::from_bytes([1u8; 32]),
n: 1,
}
}
@ -262,8 +255,9 @@ pub mod testing {
use proptest::collection::vec;
use proptest::prelude::*;
use proptest::sample::select;
use zcash_protocol::value::testing::arb_zatoshis;
use crate::{legacy::Script, transaction::components::amount::testing::arb_nonnegative_amount};
use crate::address::Script;
use super::{Authorized, Bundle, OutPoint, TxIn, TxOut};
@ -301,7 +295,7 @@ pub mod testing {
}
prop_compose! {
pub fn arb_txout()(value in arb_nonnegative_amount(), script_pubkey in arb_script()) -> TxOut {
pub fn arb_txout()(value in arb_zatoshis(), script_pubkey in arb_script()) -> TxOut {
TxOut { value, script_pubkey }
}
}

View File

@ -11,7 +11,7 @@ use zcash_protocol::consensus::{self, NetworkConstants};
use zcash_spec::PrfExpand;
use zip32::AccountId;
use super::TransparentAddress;
use crate::address::TransparentAddress;
/// The scope of a transparent key.
///

View File

@ -4,12 +4,9 @@ use std::collections::BTreeMap;
use bip32::ChildNumber;
use getset::Getters;
use zcash_protocol::value::Zatoshis;
use zcash_protocol::{value::Zatoshis, TxId};
use crate::{
legacy::Script,
transaction::{sighash::SighashType, TxId},
};
use crate::{address::Script, sighash::SighashType};
mod parse;
pub use parse::ParseError;
@ -37,7 +34,7 @@ pub use tx_extractor::{TxExtractorError, Unbound};
/// This struct is for representing Sapling in a partially-created transaction. If you
/// have a fully-created transaction, use [the regular `Bundle` struct].
///
/// [the regular `Bundle` struct]: super::Bundle
/// [the regular `Bundle` struct]: crate::bundle::Bundle
#[derive(Debug, Getters)]
#[getset(get = "pub")]
pub struct Bundle {
@ -68,7 +65,7 @@ impl Bundle {
/// This struct is for representing transparent spends in a partially-created transaction.
/// If you have a fully-created transaction, use [the regular `TxIn` struct].
///
/// [the regular `TxIn` struct]: super::TxIn
/// [the regular `TxIn` struct]: crate::bundle::TxIn
#[derive(Debug, Getters)]
#[getset(get = "pub")]
pub struct Input {
@ -185,7 +182,7 @@ pub struct Input {
/// transaction. If you have a fully-created transaction, use
/// [the regular `TxOut` struct].
///
/// [the regular `TxOut` struct]: super::TxOut
/// [the regular `TxOut` struct]: crate::bundle::TxOut
#[derive(Debug, Getters)]
#[getset(get = "pub")]
pub struct Output {

View File

@ -1,12 +1,9 @@
use std::collections::BTreeMap;
use bip32::ChildNumber;
use zcash_protocol::value::Zatoshis;
use zcash_protocol::{value::Zatoshis, TxId};
use crate::{
legacy::Script,
transaction::{sighash::SighashType, TxId},
};
use crate::{address::Script, sighash::SighashType};
use super::{Bip32Derivation, Bundle, Input, Output};

View File

@ -1,10 +1,4 @@
use blake2b_simd::Hash as Blake2bHash;
use crate::transaction::{
sighash::{SignableInput, TransparentAuthorizingContext},
sighash_v5::v5_signature_hash,
Authorization, TransactionData, TxDigests,
};
use crate::sighash::SignableInput;
impl super::Input {
/// Signs the transparent spend with the given spend authorizing key.
@ -12,43 +6,37 @@ impl super::Input {
/// It is the caller's responsibility to perform any semantic validity checks on the
/// PCZT (for example, comfirming that the change amounts are correct) before calling
/// this method.
pub fn sign<
TA: TransparentAuthorizingContext,
A: Authorization<TransparentAuth = TA>,
C: secp256k1::Signing,
>(
pub fn sign<C: secp256k1::Signing, F>(
&mut self,
index: usize,
mtx: &TransactionData<A>,
txid_parts: &TxDigests<Blake2bHash>,
calculate_sighash: F,
sk: &secp256k1::SecretKey,
secp: &secp256k1::Secp256k1<C>,
) -> Result<(), SignerError> {
let hash_type = self.sighash_type.encode();
) -> Result<(), SignerError>
where
F: FnOnce(SignableInput) -> [u8; 32],
{
let pubkey = sk.public_key(secp).serialize();
// Check that the corresponding pubkey appears in either `script_pubkey` or
// `redeem_script`.
// TODO
let sighash = v5_signature_hash(
mtx,
&SignableInput::Transparent {
hash_type,
index,
script_code: self.redeem_script.as_ref().unwrap_or(&self.script_pubkey), // for p2pkh, always the same as script_pubkey
script_pubkey: &self.script_pubkey,
value: self.value,
},
txid_parts,
);
let sighash = calculate_sighash(SignableInput {
hash_type: self.sighash_type,
index,
// for p2pkh, always the same as script_pubkey
script_code: self.redeem_script.as_ref().unwrap_or(&self.script_pubkey),
script_pubkey: &self.script_pubkey,
value: self.value,
});
let msg = secp256k1::Message::from_slice(sighash.as_ref()).expect("32 bytes");
let msg = secp256k1::Message::from_slice(&sighash).expect("32 bytes");
let sig = secp.sign_ecdsa(&msg, sk);
// Signature has to have the SighashType appended to it.
let mut sig_bytes: Vec<u8> = sig.serialize_der()[..].to_vec();
sig_bytes.extend([hash_type]);
sig_bytes.extend([self.sighash_type.encode()]);
self.partial_signatures.insert(pubkey, sig_bytes);

View File

@ -1,7 +1,7 @@
use ripemd::Ripemd160;
use sha2::{Digest, Sha256};
use crate::legacy::{Script, TransparentAddress};
use crate::address::{Script, TransparentAddress};
impl super::Bundle {
/// Finalizes the spends for this bundle.

View File

@ -1,11 +1,9 @@
use zcash_protocol::value::Zatoshis;
use crate::{
legacy::Script,
transaction::{
components::{transparent, OutPoint},
sighash::TransparentAuthorizingContext,
},
address::Script,
bundle::{Authorization, EffectsOnly, OutPoint, TxIn, TxOut},
sighash::TransparentAuthorizingContext,
};
use super::Input;
@ -15,10 +13,10 @@ impl super::Bundle {
///
/// This is used by the Signer role to produce the transaction sighash.
///
/// [regular `Bundle`]: transparent::Bundle
/// [regular `Bundle`]: super::Bundle
pub fn extract_effects(
&self,
) -> Result<Option<transparent::Bundle<transparent::EffectsOnly>>, TxExtractorError> {
) -> Result<Option<crate::bundle::Bundle<EffectsOnly>>, TxExtractorError> {
self.to_tx_data(|_| Ok(()), |bundle| Ok(effects_only(bundle)))
}
@ -26,8 +24,8 @@ impl super::Bundle {
///
/// This is used by the Transaction Extractor role to produce the final transaction.
///
/// [regular `Bundle`]: transparent::Bundle
pub fn extract(self) -> Result<Option<transparent::Bundle<Unbound>>, TxExtractorError> {
/// [regular `Bundle`]: super::Bundle
pub fn extract(self) -> Result<Option<crate::bundle::Bundle<Unbound>>, TxExtractorError> {
self.to_tx_data(
|input| {
input
@ -43,11 +41,11 @@ impl super::Bundle {
&self,
script_sig: F,
bundle_auth: G,
) -> Result<Option<transparent::Bundle<A>>, E>
) -> Result<Option<crate::bundle::Bundle<A>>, E>
where
A: transparent::Authorization,
A: Authorization,
E: From<TxExtractorError>,
F: Fn(&Input) -> Result<<A as transparent::Authorization>::ScriptSig, E>,
F: Fn(&Input) -> Result<<A as Authorization>::ScriptSig, E>,
G: FnOnce(&Self) -> Result<A, E>,
{
let vin = self
@ -56,7 +54,7 @@ impl super::Bundle {
.map(|input| {
let prevout = OutPoint::new(input.prevout_txid.into(), input.prevout_index);
Ok(transparent::TxIn {
Ok(TxIn {
prevout,
script_sig: script_sig(input)?,
sequence: input.sequence.unwrap_or(std::u32::MAX),
@ -67,7 +65,7 @@ impl super::Bundle {
let vout = self
.outputs
.iter()
.map(|output| transparent::TxOut {
.map(|output| TxOut {
value: output.value,
script_pubkey: output.script_pubkey.clone(),
})
@ -76,7 +74,7 @@ impl super::Bundle {
Ok(if vin.is_empty() && vout.is_empty() {
None
} else {
Some(transparent::Bundle {
Some(crate::bundle::Bundle {
vin,
vout,
authorization: bundle_auth(self)?,
@ -93,25 +91,25 @@ pub enum TxExtractorError {
MissingScriptSig,
}
fn effects_only(bundle: &super::Bundle) -> transparent::EffectsOnly {
fn effects_only(bundle: &super::Bundle) -> EffectsOnly {
let inputs = bundle
.inputs
.iter()
.map(|input| transparent::TxOut {
.map(|input| TxOut {
value: input.value,
script_pubkey: input.script_pubkey.clone(),
})
.collect();
transparent::EffectsOnly { inputs }
EffectsOnly { inputs }
}
/// Authorizing data for a transparent bundle in a transaction that is just missing
/// binding signatures.
#[derive(Debug)]
pub struct Unbound(transparent::EffectsOnly);
pub struct Unbound(EffectsOnly);
impl transparent::Authorization for Unbound {
impl Authorization for Unbound {
type ScriptSig = Script;
}

View File

@ -1,7 +1,7 @@
use ripemd::Ripemd160;
use sha2::{Digest, Sha256};
use crate::legacy::{Script, TransparentAddress};
use crate::address::{Script, TransparentAddress};
use super::{Bip32Derivation, Bundle, Input, Output};

View File

@ -1,7 +1,7 @@
use ripemd::Ripemd160;
use sha2::{Digest, Sha256};
use crate::legacy::TransparentAddress;
use crate::address::TransparentAddress;
impl super::Input {
/// Verifies the consistency of this transparent input.

View File

@ -1,3 +1,4 @@
use getset::Getters;
use zcash_protocol::value::Zatoshis;
use crate::{address::Script, bundle::Authorization};
@ -55,3 +56,34 @@ pub trait TransparentAuthorizingContext: Authorization {
/// providing these inputs.
fn input_scriptpubkeys(&self) -> Vec<Script>;
}
/// A transparent input that is signable because we know its value and `script_pubkey`.
#[derive(Debug, Getters)]
#[getset(get = "pub")]
pub struct SignableInput<'a> {
pub(crate) hash_type: SighashType,
pub(crate) index: usize,
pub(crate) script_code: &'a Script,
pub(crate) script_pubkey: &'a Script,
pub(crate) value: Zatoshis,
}
#[cfg(feature = "test-dependencies")]
impl<'a> SignableInput<'a> {
/// Constructs a signable input from its parts.
pub fn from_parts(
hash_type: SighashType,
index: usize,
script_code: &'a Script,
script_pubkey: &'a Script,
value: Zatoshis,
) -> Self {
Self {
hash_type,
index,
script_code,
script_pubkey,
value,
}
}
}