Move transparent transaction builder to an independent module.
This commit is contained in:
parent
8267d06846
commit
ba6fc053b5
|
@ -28,7 +28,8 @@ use crate::{
|
|||
transaction::{
|
||||
components::{
|
||||
amount::{Amount, DEFAULT_FEE},
|
||||
OutputDescription, SpendDescription, TxIn, TxOut,
|
||||
transparent::builder::{self as transparent, TransparentBuilder},
|
||||
OutputDescription, SpendDescription,
|
||||
},
|
||||
signature_hash_data, SignableInput, Transaction, TransactionData, TxVersion, SIGHASH_ALL,
|
||||
},
|
||||
|
@ -36,7 +37,7 @@ use crate::{
|
|||
};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use crate::{legacy::Script, transaction::components::OutPoint};
|
||||
use crate::transaction::components::{OutPoint, TxOut};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use crate::{
|
||||
|
@ -63,6 +64,7 @@ pub enum Error {
|
|||
NoChangeAddress,
|
||||
SpendProof,
|
||||
TzeWitnessModeMismatch(u32, u32),
|
||||
TransparentBuildError(transparent::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
|
@ -81,6 +83,7 @@ impl fmt::Display for Error {
|
|||
Error::SpendProof => write!(f, "Failed to create Sapling spend proof"),
|
||||
Error::TzeWitnessModeMismatch(expected, actual) =>
|
||||
write!(f, "TZE witness builder returned a mode that did not match the mode with which the input was initially constructed: expected = {:?}, actual = {:?}", expected, actual),
|
||||
Error::TransparentBuildError(err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -198,148 +201,6 @@ impl<P: consensus::Parameters> SaplingOutput<P> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
struct TransparentInputInfo {
|
||||
sk: secp256k1::SecretKey,
|
||||
pubkey: [u8; secp256k1::constants::PUBLIC_KEY_SIZE],
|
||||
utxo: OutPoint,
|
||||
coin: TxOut,
|
||||
}
|
||||
|
||||
struct TransparentBuilder {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
secp: secp256k1::Secp256k1<secp256k1::SignOnly>,
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
inputs: Vec<TransparentInputInfo>,
|
||||
vout: Vec<TxOut>,
|
||||
}
|
||||
|
||||
impl TransparentBuilder {
|
||||
fn new() -> Self {
|
||||
TransparentBuilder {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
secp: secp256k1::Secp256k1::gen_new(),
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
inputs: vec![],
|
||||
vout: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
fn add_input(
|
||||
&mut self,
|
||||
sk: secp256k1::SecretKey,
|
||||
utxo: OutPoint,
|
||||
coin: TxOut,
|
||||
) -> Result<(), Error> {
|
||||
if coin.value.is_negative() {
|
||||
return Err(Error::InvalidAmount);
|
||||
}
|
||||
|
||||
// Ensure that the RIPEMD-160 digest of the public key associated with the
|
||||
// provided secret key matches that of the address to which the provided
|
||||
// output may be spent.
|
||||
let pubkey = secp256k1::PublicKey::from_secret_key(&self.secp, &sk).serialize();
|
||||
match coin.script_pubkey.address() {
|
||||
Some(TransparentAddress::PublicKey(hash)) => {
|
||||
use ripemd160::Ripemd160;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
if hash[..] != Ripemd160::digest(&Sha256::digest(&pubkey))[..] {
|
||||
return Err(Error::InvalidAddress);
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::InvalidAddress),
|
||||
}
|
||||
|
||||
self.inputs.push(TransparentInputInfo {
|
||||
sk,
|
||||
pubkey,
|
||||
utxo,
|
||||
coin,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_output(&mut self, to: &TransparentAddress, value: Amount) -> Result<(), Error> {
|
||||
if value.is_negative() {
|
||||
return Err(Error::InvalidAmount);
|
||||
}
|
||||
|
||||
self.vout.push(TxOut {
|
||||
value,
|
||||
script_pubkey: to.script(),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn value_balance(&self) -> Option<Amount> {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
let input_sum = self
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|input| input.coin.value)
|
||||
.sum::<Option<Amount>>()?;
|
||||
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
let input_sum = Amount::zero();
|
||||
|
||||
input_sum
|
||||
- self
|
||||
.vout
|
||||
.iter()
|
||||
.map(|vo| vo.value)
|
||||
.sum::<Option<Amount>>()?
|
||||
}
|
||||
|
||||
fn build(&self) -> (Vec<TxIn>, Vec<TxOut>) {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
let vin = self
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|i| TxIn::new(i.utxo.clone()))
|
||||
.collect();
|
||||
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
let vin = vec![];
|
||||
|
||||
(vin, self.vout.clone())
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
fn create_signatures(
|
||||
self,
|
||||
mtx: &TransactionData,
|
||||
consensus_branch_id: consensus::BranchId,
|
||||
) -> Vec<Script> {
|
||||
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(feature = "zfuture")]
|
||||
#[allow(clippy::type_complexity)]
|
||||
struct TzeSigner<'a, BuildCtx> {
|
||||
|
@ -875,7 +736,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
target_height,
|
||||
expiry_height: target_height + DEFAULT_TX_EXPIRY_DELTA,
|
||||
fee: DEFAULT_FEE,
|
||||
transparent_builder: TransparentBuilder::new(),
|
||||
transparent_builder: TransparentBuilder::empty(),
|
||||
sapling_builder: SaplingBuilder::new(),
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_builder: TzeBuilder::new(),
|
||||
|
@ -928,7 +789,9 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
utxo: OutPoint,
|
||||
coin: TxOut,
|
||||
) -> Result<(), Error> {
|
||||
self.transparent_builder.add_input(sk, utxo, coin)
|
||||
self.transparent_builder
|
||||
.add_input(sk, utxo, coin)
|
||||
.map_err(Error::TransparentBuildError)
|
||||
}
|
||||
|
||||
/// Adds a transparent address to send funds to.
|
||||
|
@ -937,7 +800,9 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
to: &TransparentAddress,
|
||||
value: Amount,
|
||||
) -> Result<(), Error> {
|
||||
self.transparent_builder.add_output(to, value)
|
||||
self.transparent_builder
|
||||
.add_output(to, value)
|
||||
.map_err(Error::TransparentBuildError)
|
||||
}
|
||||
|
||||
/// Sets the Sapling address to which any change will be sent.
|
||||
|
@ -1181,7 +1046,9 @@ mod tests {
|
|||
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||
sapling::{prover::mock::MockTxProver, Node, Rseed},
|
||||
transaction::{
|
||||
components::{amount::Amount, amount::DEFAULT_FEE},
|
||||
components::{
|
||||
amount::Amount, amount::DEFAULT_FEE, transparent::builder as transparent,
|
||||
},
|
||||
TxVersion,
|
||||
},
|
||||
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||
|
@ -1222,7 +1089,7 @@ mod tests {
|
|||
target_height: sapling_activation_height,
|
||||
expiry_height: sapling_activation_height + DEFAULT_TX_EXPIRY_DELTA,
|
||||
fee: Amount::zero(),
|
||||
transparent_builder: TransparentBuilder::new(),
|
||||
transparent_builder: TransparentBuilder::empty(),
|
||||
sapling_builder: SaplingBuilder::new(),
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_builder: TzeBuilder::new(),
|
||||
|
@ -1292,7 +1159,9 @@ mod tests {
|
|||
&TransparentAddress::PublicKey([0; 20]),
|
||||
Amount::from_i64(-1).unwrap(),
|
||||
),
|
||||
Err(Error::InvalidAmount)
|
||||
Err(Error::TransparentBuildError(
|
||||
transparent::Error::InvalidAmount
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ use crate::legacy::Script;
|
|||
|
||||
use super::amount::Amount;
|
||||
|
||||
pub mod builder;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct OutPoint {
|
||||
hash: [u8; 32],
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
//! Types and functions for building transparent transaction components.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use crate::{
|
||||
legacy::TransparentAddress,
|
||||
transaction::components::{amount::Amount, TxIn, TxOut},
|
||||
};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use crate::{
|
||||
consensus::{self},
|
||||
legacy::Script,
|
||||
transaction::{
|
||||
components::OutPoint, sighash::signature_hash_data, SignableInput, TransactionData,
|
||||
SIGHASH_ALL,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
InvalidAddress,
|
||||
InvalidAmount,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::InvalidAddress => write!(f, "Invalid address"),
|
||||
Error::InvalidAmount => write!(f, "Invalid amount"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
struct TransparentInputInfo {
|
||||
sk: secp256k1::SecretKey,
|
||||
pubkey: [u8; secp256k1::constants::PUBLIC_KEY_SIZE],
|
||||
utxo: OutPoint,
|
||||
coin: TxOut,
|
||||
}
|
||||
|
||||
pub struct TransparentBuilder {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
secp: secp256k1::Secp256k1<secp256k1::SignOnly>,
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
inputs: Vec<TransparentInputInfo>,
|
||||
vout: Vec<TxOut>,
|
||||
}
|
||||
|
||||
impl TransparentBuilder {
|
||||
pub fn empty() -> Self {
|
||||
TransparentBuilder {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
secp: secp256k1::Secp256k1::gen_new(),
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
inputs: vec![],
|
||||
vout: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
pub fn add_input(
|
||||
&mut self,
|
||||
sk: secp256k1::SecretKey,
|
||||
utxo: OutPoint,
|
||||
coin: TxOut,
|
||||
) -> Result<(), Error> {
|
||||
if coin.value.is_negative() {
|
||||
return Err(Error::InvalidAmount);
|
||||
}
|
||||
|
||||
// Ensure that the RIPEMD-160 digest of the public key associated with the
|
||||
// provided secret key matches that of the address to which the provided
|
||||
// output may be spent.
|
||||
let pubkey = secp256k1::PublicKey::from_secret_key(&self.secp, &sk).serialize();
|
||||
match coin.script_pubkey.address() {
|
||||
Some(TransparentAddress::PublicKey(hash)) => {
|
||||
use ripemd160::Ripemd160;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
if hash[..] != Ripemd160::digest(&Sha256::digest(&pubkey))[..] {
|
||||
return Err(Error::InvalidAddress);
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::InvalidAddress),
|
||||
}
|
||||
|
||||
self.inputs.push(TransparentInputInfo {
|
||||
sk,
|
||||
pubkey,
|
||||
utxo,
|
||||
coin,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_output(&mut self, to: &TransparentAddress, value: Amount) -> Result<(), Error> {
|
||||
if value.is_negative() {
|
||||
return Err(Error::InvalidAmount);
|
||||
}
|
||||
|
||||
self.vout.push(TxOut {
|
||||
value,
|
||||
script_pubkey: to.script(),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn value_balance(&self) -> Option<Amount> {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
let input_sum = self
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|input| input.coin.value)
|
||||
.sum::<Option<Amount>>()?;
|
||||
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
let input_sum = Amount::zero();
|
||||
|
||||
input_sum
|
||||
- self
|
||||
.vout
|
||||
.iter()
|
||||
.map(|vo| vo.value)
|
||||
.sum::<Option<Amount>>()?
|
||||
}
|
||||
|
||||
pub fn build(&self) -> (Vec<TxIn>, Vec<TxOut>) {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
let vin = self
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|i| TxIn::new(i.utxo.clone()))
|
||||
.collect();
|
||||
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
let vin = vec![];
|
||||
|
||||
(vin, self.vout.clone())
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
pub fn create_signatures(
|
||||
self,
|
||||
mtx: &TransactionData,
|
||||
consensus_branch_id: consensus::BranchId,
|
||||
) -> Vec<Script> {
|
||||
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()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue