Merge pull request #478 from sanket1729/psbt_again
Psbt hash preimages (again)
This commit is contained in:
commit
35d729d9f0
|
@ -4,7 +4,3 @@ Cargo.lock
|
|||
#fuzz
|
||||
fuzz/hfuzz_target
|
||||
fuzz/hfuzz_workspace
|
||||
|
||||
#IntelliJ project files
|
||||
.idea
|
||||
*.iml
|
||||
|
|
|
@ -18,6 +18,16 @@ use std::fmt;
|
|||
use blockdata::transaction::Transaction;
|
||||
use util::psbt::raw;
|
||||
|
||||
use hashes;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Enum for marking psbt hash error
|
||||
pub enum PsbtHash {
|
||||
Ripemd,
|
||||
Sha256,
|
||||
Hash160,
|
||||
Hash256,
|
||||
}
|
||||
/// Ways that a Partially Signed Transaction might fail.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
|
@ -48,6 +58,17 @@ pub enum Error {
|
|||
},
|
||||
/// Unable to parse as a standard SigHash type.
|
||||
NonStandardSigHashType(u32),
|
||||
/// Parsing errors from bitcoin_hashes
|
||||
HashParseError(hashes::Error),
|
||||
/// The pre-image must hash to the correponding psbt hash
|
||||
InvalidPreimageHashPair {
|
||||
/// Hash-type
|
||||
hash_type: PsbtHash,
|
||||
/// Pre-image
|
||||
preimage: Vec<u8>,
|
||||
/// Hash value
|
||||
hash: Vec<u8>,
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
|
@ -65,8 +86,20 @@ impl fmt::Display for Error {
|
|||
f.write_str("partially signed transactions must have an unsigned transaction")
|
||||
}
|
||||
Error::NoMorePairs => f.write_str("no more key-value pairs for this psbt map"),
|
||||
Error::HashParseError(e) => write!(f, "Hash Parse Error: {}", e),
|
||||
Error::InvalidPreimageHashPair{ref preimage, ref hash, ref hash_type} => {
|
||||
// directly using debug forms of psbthash enums
|
||||
write!(f, "Preimage {:?} does not match {:?} hash {:?}", preimage, hash_type, hash )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl From<hashes::Error> for Error {
|
||||
fn from(e: hashes::Error) -> Error {
|
||||
Error::HashParseError(e)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,6 +130,7 @@ macro_rules! impl_psbt_insert_pair {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
macro_rules! impl_psbt_get_pair {
|
||||
($rv:ident.push($slf:ident.$unkeyed_name:ident as <$unkeyed_typeval:expr, _>|<$unkeyed_value_type:ty>)) => {
|
||||
|
@ -155,3 +156,33 @@ macro_rules! impl_psbt_get_pair {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
// macros for serde of hashes
|
||||
macro_rules! impl_psbt_hash_de_serialize {
|
||||
($hash_type:ty) => {
|
||||
impl_psbt_hash_serialize!($hash_type);
|
||||
impl_psbt_hash_deserialize!($hash_type);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_psbt_hash_deserialize {
|
||||
($hash_type:ty) => {
|
||||
impl $crate::util::psbt::serialize::Deserialize for $hash_type {
|
||||
fn deserialize(bytes: &[u8]) -> Result<Self, $crate::consensus::encode::Error> {
|
||||
<$hash_type>::from_slice(&bytes[..]).map_err(|e| {
|
||||
$crate::util::psbt::Error::from(e).into()
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_psbt_hash_serialize {
|
||||
($hash_type:ty) => {
|
||||
impl $crate::util::psbt::serialize::Serialize for $hash_type {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
self.into_inner().to_vec()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,18 +12,19 @@
|
|||
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
//
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::btree_map::{Entry, BTreeMap};
|
||||
|
||||
use blockdata::script::Script;
|
||||
use blockdata::transaction::{SigHashType, Transaction, TxOut};
|
||||
use consensus::encode;
|
||||
use util::bip32::KeySource;
|
||||
use hashes::{self, hash160, ripemd160, sha256, sha256d};
|
||||
use util::key::PublicKey;
|
||||
use util::psbt;
|
||||
use util::psbt::map::Map;
|
||||
use util::psbt::raw;
|
||||
use util::psbt::Error;
|
||||
|
||||
use util::psbt::serialize::Deserialize;
|
||||
use util::psbt::{Error, error};
|
||||
/// A key-value map for an input of the corresponding index in the unsigned
|
||||
/// transaction.
|
||||
#[derive(Clone, Default, Debug, PartialEq)]
|
||||
|
@ -55,6 +56,15 @@ pub struct Input {
|
|||
/// The finalized, fully-constructed scriptWitness with signatures and any
|
||||
/// other scripts necessary for this input to pass validation.
|
||||
pub final_script_witness: Option<Vec<Vec<u8>>>,
|
||||
/// TODO: Proof of reserves commitment
|
||||
/// RIPEMD hash to preimage map
|
||||
pub ripemd_preimages: BTreeMap<ripemd160::Hash, Vec<u8>>,
|
||||
/// SHA256 hash to preimage map
|
||||
pub sha256_preimages: BTreeMap<sha256::Hash, Vec<u8>>,
|
||||
/// HSAH160 hash to preimage map
|
||||
pub hash160_preimages: BTreeMap<hash160::Hash, Vec<u8>>,
|
||||
/// HAS256 hash to preimage map
|
||||
pub hash256_preimages: BTreeMap<sha256d::Hash, Vec<u8>>,
|
||||
/// Unknown key-value pairs for this input.
|
||||
pub unknown: BTreeMap<raw::Key, Vec<u8>>,
|
||||
}
|
||||
|
@ -117,10 +127,26 @@ impl Map for Input {
|
|||
self.hd_keypaths <= <raw_key: PublicKey>|<raw_value: KeySource>
|
||||
}
|
||||
}
|
||||
_ => match self.unknown.entry(raw_key) {
|
||||
::std::collections::btree_map::Entry::Vacant(empty_key) => {empty_key.insert(raw_value);},
|
||||
::std::collections::btree_map::Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).into()),
|
||||
10u8 => {
|
||||
psbt_insert_hash_pair(&mut self.ripemd_preimages, raw_key, raw_value, error::PsbtHash::Ripemd)?;
|
||||
}
|
||||
11u8 => {
|
||||
psbt_insert_hash_pair(&mut self.sha256_preimages, raw_key, raw_value, error::PsbtHash::Sha256)?;
|
||||
}
|
||||
12u8 => {
|
||||
psbt_insert_hash_pair(&mut self.hash160_preimages, raw_key, raw_value, error::PsbtHash::Hash160)?;
|
||||
}
|
||||
13u8 => {
|
||||
psbt_insert_hash_pair(&mut self.hash256_preimages, raw_key, raw_value, error::PsbtHash::Hash256)?;
|
||||
}
|
||||
_ => match self.unknown.entry(raw_key) {
|
||||
::std::collections::btree_map::Entry::Vacant(empty_key) => {
|
||||
empty_key.insert(raw_value);
|
||||
}
|
||||
::std::collections::btree_map::Entry::Occupied(k) => {
|
||||
return Err(Error::DuplicateKey(k.key().clone()).into())
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -165,6 +191,22 @@ impl Map for Input {
|
|||
rv.push(self.final_script_witness as <8u8, _>|<Script>)
|
||||
}
|
||||
|
||||
impl_psbt_get_pair! {
|
||||
rv.push(self.ripemd_preimages as <10u8, ripemd160::Hash>|<Vec<u8>>)
|
||||
}
|
||||
|
||||
impl_psbt_get_pair! {
|
||||
rv.push(self.sha256_preimages as <11u8, sha256::Hash>|<Vec<u8>>)
|
||||
}
|
||||
|
||||
impl_psbt_get_pair! {
|
||||
rv.push(self.hash160_preimages as <12u8, hash160::Hash>|<Vec<u8>>)
|
||||
}
|
||||
|
||||
impl_psbt_get_pair! {
|
||||
rv.push(self.hash256_preimages as <13u8, sha256d::Hash>|<Vec<u8>>)
|
||||
}
|
||||
|
||||
for (key, value) in self.unknown.iter() {
|
||||
rv.push(raw::Pair {
|
||||
key: key.clone(),
|
||||
|
@ -185,6 +227,10 @@ impl Map for Input {
|
|||
|
||||
self.partial_sigs.extend(other.partial_sigs);
|
||||
self.hd_keypaths.extend(other.hd_keypaths);
|
||||
self.ripemd_preimages.extend(other.ripemd_preimages);
|
||||
self.sha256_preimages.extend(other.sha256_preimages);
|
||||
self.hash160_preimages.extend(other.hash160_preimages);
|
||||
self.hash256_preimages.extend(other.hash256_preimages);
|
||||
self.unknown.extend(other.unknown);
|
||||
|
||||
merge!(redeem_script, self, other);
|
||||
|
@ -197,3 +243,34 @@ impl Map for Input {
|
|||
}
|
||||
|
||||
impl_psbtmap_consensus_enc_dec_oding!(Input);
|
||||
|
||||
fn psbt_insert_hash_pair<H>(
|
||||
map: &mut BTreeMap<H, Vec<u8>>,
|
||||
raw_key: raw::Key,
|
||||
raw_value: Vec<u8>,
|
||||
hash_type: error::PsbtHash,
|
||||
) -> Result<(), encode::Error>
|
||||
where
|
||||
H: hashes::Hash + Deserialize,
|
||||
{
|
||||
if raw_key.key.is_empty() {
|
||||
return Err(psbt::Error::InvalidKey(raw_key).into());
|
||||
}
|
||||
let key_val: H = Deserialize::deserialize(&raw_key.key)?;
|
||||
match map.entry(key_val) {
|
||||
Entry::Vacant(empty_key) => {
|
||||
let val: Vec<u8> = Deserialize::deserialize(&raw_value)?;
|
||||
if <H as hashes::Hash>::hash(&val) != key_val {
|
||||
return Err(psbt::Error::InvalidPreimageHashPair {
|
||||
preimage: val,
|
||||
hash: Vec::from(key_val.borrow()),
|
||||
hash_type: hash_type,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
empty_key.insert(val);
|
||||
Ok(())
|
||||
}
|
||||
Entry::Occupied(_) => return Err(psbt::Error::DuplicateKey(raw_key).into()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,6 +164,7 @@ impl Decodable for PartiallySignedTransaction {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use hashes::hex::FromHex;
|
||||
use hashes::{sha256, hash160, Hash, ripemd160};
|
||||
use hash_types::Txid;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
@ -176,7 +177,7 @@ mod tests {
|
|||
use consensus::encode::{deserialize, serialize, serialize_hex};
|
||||
use util::bip32::{ChildNumber, ExtendedPrivKey, ExtendedPubKey, Fingerprint, KeySource};
|
||||
use util::key::PublicKey;
|
||||
use util::psbt::map::{Global, Output};
|
||||
use util::psbt::map::{Global, Output, Input};
|
||||
use util::psbt::raw;
|
||||
|
||||
use super::PartiallySignedTransaction;
|
||||
|
@ -562,4 +563,116 @@ mod tests {
|
|||
assert_eq!(psbt.inputs[0].unknown, unknown)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_and_deserialize_preimage_psbt(){
|
||||
// create a sha preimage map
|
||||
let mut sha256_preimages = BTreeMap::new();
|
||||
sha256_preimages.insert(sha256::Hash::hash(&[1u8, 2u8]), vec![1u8, 2u8]);
|
||||
sha256_preimages.insert(sha256::Hash::hash(&[1u8]), vec![1u8]);
|
||||
|
||||
// same for hash160
|
||||
let mut hash160_preimages = BTreeMap::new();
|
||||
hash160_preimages.insert(hash160::Hash::hash(&[1u8, 2u8]), vec![1u8, 2u8]);
|
||||
hash160_preimages.insert(hash160::Hash::hash(&[1u8]), vec![1u8]);
|
||||
|
||||
// same vector as valid_vector_1 from BIPs with added
|
||||
let mut unserialized = PartiallySignedTransaction {
|
||||
global: Global {
|
||||
unsigned_tx: Transaction {
|
||||
version: 2,
|
||||
lock_time: 1257139,
|
||||
input: vec![TxIn {
|
||||
previous_output: OutPoint {
|
||||
txid: Txid::from_hex(
|
||||
"f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126",
|
||||
).unwrap(),
|
||||
vout: 0,
|
||||
},
|
||||
script_sig: Script::new(),
|
||||
sequence: 4294967294,
|
||||
witness: vec![],
|
||||
}],
|
||||
output: vec![
|
||||
TxOut {
|
||||
value: 99999699,
|
||||
script_pubkey: hex_script!("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"),
|
||||
},
|
||||
TxOut {
|
||||
value: 100000000,
|
||||
script_pubkey: hex_script!("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"),
|
||||
},
|
||||
],
|
||||
},
|
||||
unknown: BTreeMap::new(),
|
||||
},
|
||||
inputs: vec![Input {
|
||||
non_witness_utxo: Some(Transaction {
|
||||
version: 1,
|
||||
lock_time: 0,
|
||||
input: vec![TxIn {
|
||||
previous_output: OutPoint {
|
||||
txid: Txid::from_hex(
|
||||
"e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389",
|
||||
).unwrap(),
|
||||
vout: 1,
|
||||
},
|
||||
script_sig: hex_script!("160014be18d152a9b012039daf3da7de4f53349eecb985"),
|
||||
sequence: 4294967295,
|
||||
witness: vec![
|
||||
Vec::from_hex("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01").unwrap(),
|
||||
Vec::from_hex("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105").unwrap(),
|
||||
],
|
||||
},
|
||||
TxIn {
|
||||
previous_output: OutPoint {
|
||||
txid: Txid::from_hex(
|
||||
"b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886",
|
||||
).unwrap(),
|
||||
vout: 1,
|
||||
},
|
||||
script_sig: hex_script!("160014fe3e9ef1a745e974d902c4355943abcb34bd5353"),
|
||||
sequence: 4294967295,
|
||||
witness: vec![
|
||||
Vec::from_hex("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01").unwrap(),
|
||||
Vec::from_hex("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3").unwrap(),
|
||||
],
|
||||
}],
|
||||
output: vec![
|
||||
TxOut {
|
||||
value: 200000000,
|
||||
script_pubkey: hex_script!("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac"),
|
||||
},
|
||||
TxOut {
|
||||
value: 190303501938,
|
||||
script_pubkey: hex_script!("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587"),
|
||||
},
|
||||
],
|
||||
}),
|
||||
..Default::default()
|
||||
},],
|
||||
outputs: vec![
|
||||
Output {
|
||||
..Default::default()
|
||||
},
|
||||
Output {
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
};
|
||||
unserialized.inputs[0].hash160_preimages = hash160_preimages;
|
||||
unserialized.inputs[0].sha256_preimages = sha256_preimages;
|
||||
|
||||
let rtt : PartiallySignedTransaction = hex_psbt!(&serialize_hex(&unserialized)).unwrap();
|
||||
assert_eq!(rtt, unserialized);
|
||||
|
||||
// Now add an ripemd160 with incorrect preimage
|
||||
let mut ripemd160_preimages = BTreeMap::new();
|
||||
ripemd160_preimages.insert(ripemd160::Hash::hash(&[17u8]), vec![18u8]);
|
||||
unserialized.inputs[0].ripemd_preimages = ripemd160_preimages;
|
||||
|
||||
// Now the roundtrip should fail as the preimage is incorrect.
|
||||
let rtt : Result<PartiallySignedTransaction, _> = hex_psbt!(&serialize_hex(&unserialized));
|
||||
assert!(rtt.is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ use blockdata::script::Script;
|
|||
use blockdata::transaction::{SigHashType, Transaction, TxOut};
|
||||
use consensus::encode::{self, serialize, Decodable};
|
||||
use util::bip32::{ChildNumber, Fingerprint, KeySource};
|
||||
use hashes::{hash160, ripemd160, sha256, sha256d, Hash};
|
||||
use util::key::PublicKey;
|
||||
use util::psbt;
|
||||
|
||||
|
@ -42,6 +43,10 @@ pub trait Deserialize: Sized {
|
|||
impl_psbt_de_serialize!(Transaction);
|
||||
impl_psbt_de_serialize!(TxOut);
|
||||
impl_psbt_de_serialize!(Vec<Vec<u8>>); // scriptWitness
|
||||
impl_psbt_hash_de_serialize!(ripemd160::Hash);
|
||||
impl_psbt_hash_de_serialize!(sha256::Hash);
|
||||
impl_psbt_hash_de_serialize!(hash160::Hash);
|
||||
impl_psbt_hash_de_serialize!(sha256d::Hash);
|
||||
|
||||
impl Serialize for Script {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
|
|
Loading…
Reference in New Issue