Skip to content

Commit a61cbab

Browse files
committed
transaction: introduce PeginWitness type
TBH I'm not sure how best to review this commit. I thought it was going to be easy but then it wound up being a ton of boilerplate. I did -not- use the decoder_newtype macro because I made this complicated error mapping function. Maybe I should drop that here, or maybe I should integrate it into the macro somehow. I don't know. There is some discussion on rust-bitcoin. See rust-bitcoin/rust-bitcoin#6435
1 parent 0b8fa43 commit a61cbab

8 files changed

Lines changed: 608 additions & 125 deletions

File tree

rustfmt.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ ignore = [
22
"/",
33
"!/src/lib.rs",
44
"!/src/confidential/*.rs",
5+
"!/src/transaction/pegin_witness.rs"
56
]
67
hard_tabs = false
78
tab_spaces = 4

src/encode.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,23 @@ pub use bitcoin::{self, consensus::encode::MAX_VEC_SIZE};
2929

3030
use crate::taproot::TapLeafHash;
3131

32-
struct ByteCounter<W> {
32+
/// Adaptor to count bytes, used to implement Encodable/Decodable
33+
/// in terms of the new Encode/Decode traits.
34+
pub(crate) struct ByteCounter<W> {
3335
inner: W,
3436
count: usize,
3537
}
3638

39+
impl<W> ByteCounter<W> {
40+
pub(crate) fn new(inner: W) -> Self {
41+
Self { inner, count: 0 }
42+
}
43+
44+
pub(crate) fn into_count(self) -> usize {
45+
self.count
46+
}
47+
}
48+
3749
impl<W> io::Write for ByteCounter<W>
3850
where W: io::Write
3951
{
@@ -84,7 +96,9 @@ pub enum Error {
8496
BadLockTime(crate::LockTime),
8597
/// `VarInt` was encoded in a non-minimal way.
8698
NonMinimalVarInt,
87-
/// Error decoding a transaction witness.
99+
/// Error decoding a pegin witness.
100+
PeginWitness(crate::PeginWitnessDecoderError),
101+
/// Error decoding a script witness.
88102
Witness(crate::WitnessDecoderError),
89103
}
90104

@@ -113,7 +127,8 @@ impl fmt::Display for Error {
113127
Error::HexVariableError(ref e) => write!(f, "Hex variable error: {}", e),
114128
Error::BadLockTime(ref lt) => write!(f, "Invalid locktime {}", lt),
115129
Error::NonMinimalVarInt => write!(f, "non-minimal varint"),
116-
Self::Witness(..) => f.write_str("error decoding witness"),
130+
Self::PeginWitness(..) => f.write_str("error decoding pegin witness"),
131+
Self::Witness(..) => f.write_str("error decoding script witness"),
117132
}
118133
}
119134
}
@@ -122,6 +137,7 @@ impl error::Error for Error {
122137
fn cause(&self) -> Option<&dyn error::Error> {
123138
match *self {
124139
Error::Secp256k1zkp(ref e) => Some(e),
140+
Self::PeginWitness(ref e) => Some(e),
125141
Self::Witness(ref e) => Some(e),
126142
_ => None,
127143
}
@@ -267,9 +283,9 @@ impl Decodable for crate::locktime::Time {
267283

268284
impl Encodable for crate::Witness {
269285
fn consensus_encode<W: io::Write>(&self, e: W) -> Result<usize, Error> {
270-
let mut counter = ByteCounter { inner: e, count: 0 };
286+
let mut counter = ByteCounter::new(e);
271287
crate::encoding::encode_to_writer(self, &mut counter)?;
272-
Ok(counter.count)
288+
Ok(counter.into_count())
273289
}
274290
}
275291

src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,10 @@ pub use crate::schnorr::{SchnorrSig, SchnorrSigError};
9595
pub use crate::script::Script;
9696
pub use crate::sighash::SchnorrSighashType;
9797
pub use crate::transaction::{
98-
AssetIssuance, EcdsaSighashType, OutPoint, PeginData, PegoutData, Sequence, Transaction, TxIn,
99-
TxInWitness, TxOut, TxOutWitness, Witness, WitnessDecoder, WitnessDecoderError, WitnessEncoder,
98+
AssetIssuance, EcdsaSighashType, OutPoint, PeginData, PeginDataDecoder, PeginDataEncoder,
99+
PeginWitness, PeginWitnessDecoder, PeginWitnessDecoderError, PeginWitnessEncoder, PegoutData,
100+
Sequence, Transaction, TxIn, TxInWitness, TxOut, TxOutWitness, Witness, WitnessDecoder,
101+
WitnessDecoderError, WitnessEncoder,
100102
};
101103

102104
// Encode a compact size to a slice without allocating

src/pset/map/input.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use crate::pset::raw;
3232
use crate::pset::serialize;
3333
use crate::pset::{self, error, Error};
3434
use crate::{transaction::SighashTypeParseError, SchnorrSighashType};
35-
use crate::{AssetIssuance, BlockHash, EcdsaSighashType, RangeProof, Script, Transaction, TxIn, TxOut, Txid, SurjectionProof};
35+
use crate::{AssetIssuance, BlockHash, EcdsaSighashType, PeginWitness, RangeProof, Script, Transaction, TxIn, TxOut, Txid, SurjectionProof};
3636
use bitcoin::bip32::KeySource;
3737
use bitcoin::{PublicKey, key::XOnlyPublicKey};
3838
use secp256k1_zkp::{self, Tweak, ZERO_TWEAK};
@@ -252,7 +252,7 @@ pub struct Input {
252252
/// Pegin Value
253253
pub pegin_value: Option<u64>,
254254
/// Pegin Witness
255-
pub pegin_witness: Option<Vec<Vec<u8>>>,
255+
pub pegin_witness: Option<PeginWitness>,
256256
/// Issuance inflation keys
257257
pub issuance_inflation_keys: Option<u64>,
258258
/// Issuance inflation keys commitment
@@ -743,7 +743,7 @@ impl Map for Input {
743743
impl_pset_prop_insert_pair!(self.pegin_value <= <raw_key: _> | <raw_value : u64>);
744744
}
745745
PSBT_ELEMENTS_IN_PEG_IN_WITNESS => {
746-
impl_pset_prop_insert_pair!(self.pegin_witness <= <raw_key: _> | <raw_value : Vec<Vec<u8>>>);
746+
impl_pset_prop_insert_pair!(self.pegin_witness <= <raw_key: _> | <raw_value : PeginWitness>);
747747
}
748748
PSBT_ELEMENTS_IN_ISSUANCE_INFLATION_KEYS => {
749749
impl_pset_prop_insert_pair!(self.issuance_inflation_keys <= <raw_key: _> | <raw_value : u64>);

src/pset/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,7 @@ impl PartiallySignedTransaction {
310310
.unwrap_or_default(),
311311
pegin_witness: psetin
312312
.pegin_witness
313-
.as_ref()
314-
.map(Vec::to_owned)
313+
.clone()
315314
.unwrap_or_default(),
316315
},
317316
};

src/pset/serialize.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ impl_pset_de_serialize!(crate::Sequence);
6868
impl_pset_de_serialize!(crate::locktime::Height);
6969
impl_pset_de_serialize!(crate::locktime::Time);
7070
impl_pset_de_serialize!([u8; 32]);
71-
impl_pset_de_serialize!(Vec<Vec<u8>>); // peginWitness
71+
impl_pset_de_serialize!(crate::PeginWitness); // peginWitness
7272
impl_pset_de_serialize!(crate::Witness); // scriptWitness
7373
impl_pset_hash_de_serialize!(Txid);
7474
impl_pset_hash_de_serialize!(ripemd160::Hash);

src/transaction/mod.rs

Lines changed: 19 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
//! # Transactions
1616
//!
1717
18+
mod pegin_witness;
1819
mod witness;
1920

2021
use std::{io, fmt, str, cmp};
@@ -23,7 +24,6 @@ use std::convert::TryFrom;
2324

2425
use bitcoin::{self, VarInt};
2526
use bitcoin::hashes::Hash as _;
26-
use internals::slice::SliceExt;
2727
use crate::hashes::{sha256d, HashEngine as _};
2828

2929
use crate::{confidential, ContractHash};
@@ -37,6 +37,9 @@ use secp256k1_zkp::{
3737
Tweak, ZERO_TWEAK,
3838
};
3939

40+
pub use self::pegin_witness::{
41+
PeginData, PeginDataDecoder, PeginDataEncoder,
42+
PeginWitness, PeginWitnessDecoder, PeginWitnessDecoderError, PeginWitnessEncoder};
4043
pub use self::witness::{Witness, WitnessDecoder, WitnessDecoderError, WitnessEncoder};
4144

4245
/// Description of an asset issuance in a transaction input
@@ -371,7 +374,7 @@ pub struct TxInWitness {
371374
/// Traditional script witness
372375
pub script_witness: Witness,
373376
/// Pegin witness, basically the same thing
374-
pub pegin_witness: Vec<Vec<u8>>,
377+
pub pegin_witness: PeginWitness,
375378
}
376379
impl_consensus_encoding!(TxInWitness, amount_rangeproof, inflation_keys_rangeproof, script_witness, pegin_witness);
377380

@@ -382,7 +385,7 @@ impl TxInWitness {
382385
amount_rangeproof: RangeProof::EMPTY,
383386
inflation_keys_rangeproof: RangeProof::EMPTY,
384387
script_witness: Witness::new(),
385-
pegin_witness: Vec::new(),
388+
pegin_witness: PeginWitness::EMPTY,
386389
}
387390
}
388391

@@ -401,84 +404,6 @@ impl Default for TxInWitness {
401404
}
402405
}
403406

404-
405-
/// Parsed data from a transaction input's pegin witness
406-
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
407-
pub struct PeginData<'tx> {
408-
/// Reference to the pegin output on the mainchain
409-
pub outpoint: bitcoin::OutPoint,
410-
/// The value, in satoshis, of the pegin
411-
pub value: u64,
412-
/// Asset type being pegged in
413-
pub asset: AssetId,
414-
/// Hash of genesis block of originating blockchain
415-
pub genesis_hash: bitcoin::BlockHash,
416-
/// The claim script that we should hash to tweak our address. Unparsed
417-
/// to avoid unnecessary allocation and copying. Typical use is simply
418-
/// to feed it raw into a hash function.
419-
pub claim_script: &'tx [u8],
420-
/// Mainchain transaction; not parsed to save time/memory since the
421-
/// parsed transaction is typically not useful without auxiliary
422-
/// data (e.g. knowing how to compute pegin addresses for the
423-
/// sidechain).
424-
pub tx: &'tx [u8],
425-
/// Merkle proof of transaction inclusion; also not parsed
426-
pub merkle_proof: &'tx [u8],
427-
/// The Bitcoin block that the pegin output appears in; scraped
428-
/// from the transaction inclusion proof
429-
pub referenced_block: bitcoin::BlockHash,
430-
}
431-
432-
impl<'tx> PeginData<'tx> {
433-
/// Construct the pegin data from a pegin witness.
434-
/// Returns None if not a valid pegin witness.
435-
pub fn from_pegin_witness(
436-
pegin_witness: &'tx [Vec<u8>],
437-
prevout: bitcoin::OutPoint,
438-
) -> Result<PeginData<'tx>, &'static str> {
439-
let Ok(pegin_witness) = <&[Vec<u8>; 6]>::try_from(pegin_witness) else {
440-
return Err("size not 6");
441-
};
442-
let Some((block_header, _)) = SliceExt::split_first_chunk::<80>(pegin_witness[5].as_slice()) else {
443-
return Err("merkle proof too short");
444-
};
445-
446-
Ok(PeginData {
447-
outpoint: prevout,
448-
value: bitcoin::consensus::deserialize(&pegin_witness[0]).map_err(|_| "invalid value")?,
449-
asset: encode::deserialize(&pegin_witness[1]).map_err(|_| "invalid asset")?,
450-
genesis_hash: bitcoin::consensus::deserialize(&pegin_witness[2])
451-
.map_err(|_| "invalid genesis hash")?,
452-
claim_script: &pegin_witness[3],
453-
tx: &pegin_witness[4],
454-
merkle_proof: &pegin_witness[5],
455-
referenced_block: bitcoin::BlockHash::hash(block_header),
456-
})
457-
}
458-
459-
/// Construct a pegin witness from the pegin data.
460-
pub fn to_pegin_witness(&self) -> Vec<Vec<u8>> {
461-
vec![
462-
bitcoin::consensus::serialize(&self.value),
463-
encode::serialize(&self.asset),
464-
bitcoin::consensus::serialize(&self.genesis_hash),
465-
self.claim_script.to_vec(),
466-
self.tx.to_vec(),
467-
self.merkle_proof.to_vec(),
468-
]
469-
}
470-
471-
/// Parse the mainchain tx provided as pegin data.
472-
pub fn parse_tx(&self) -> Result<bitcoin::Transaction, bitcoin::consensus::encode::Error> {
473-
bitcoin::consensus::encode::deserialize(self.tx)
474-
}
475-
476-
/// Parse the merkle inclusion proof provided as pegin data.
477-
pub fn parse_merkle_proof(&self) -> Result<bitcoin::MerkleBlock, bitcoin::consensus::encode::Error> {
478-
bitcoin::consensus::encode::deserialize(self.merkle_proof)
479-
}
480-
}
481-
482407
/// A transaction input, which defines old coins to be consumed
483408
#[derive(Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
484409
pub struct TxIn {
@@ -603,10 +528,8 @@ impl TxIn {
603528
/// Extracts witness data from a pegin. Will return `None` if any data
604529
/// cannot be parsed. The combination of `is_pegin()` returning `true`
605530
/// and `pegin_data()` returning `None` indicates an invalid transaction.
606-
pub fn pegin_data(&self) -> Option<PeginData<'_>> {
607-
self.pegin_prevout().and_then(|p| {
608-
PeginData::from_pegin_witness(&self.witness.pegin_witness, p).ok()
609-
})
531+
pub fn pegin_data(&self) -> Option<&PeginData> {
532+
self.witness.pegin_witness.data()
610533
}
611534

612535
/// Helper to determine whether an input has an asset issuance attached
@@ -961,11 +884,7 @@ impl Transaction {
961884
VarInt(wit.len() as u64).size() +
962885
wit.len()
963886
).sum::<usize>() +
964-
VarInt(input.witness.pegin_witness.len() as u64).size() +
965-
input.witness.pegin_witness.iter().map(|wit|
966-
VarInt(wit.len() as u64).size() +
967-
wit.len()
968-
).sum::<usize>()
887+
input.witness.pegin_witness.encoded_size()
969888
} else {
970889
0
971890
}
@@ -1654,24 +1573,18 @@ mod tests {
16541573
assert_eq!(tx.input[0].witness.pegin_witness.len(), 6);
16551574
assert_eq!(
16561575
tx.input[0].pegin_data(),
1657-
Some(super::PeginData {
1658-
outpoint: bitcoin::OutPoint {
1659-
txid: bitcoin::Txid::from_str(
1660-
"c9d88eb5130365deed045eab11cfd3eea5ba32ad45fa2e156ae6ead5f1fce93f",
1661-
).unwrap(),
1662-
vout: 0,
1663-
},
1576+
Some(&super::PeginData {
16641577
value: 100_000_000,
1665-
asset: tx.output[0].asset.explicit().unwrap(),
1578+
asset_id: tx.output[0].asset.explicit().unwrap(),
16661579
genesis_hash: bitcoin::BlockHash::from_str(
16671580
"0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
16681581
).unwrap(),
1669-
claim_script: &[
1582+
claim_script: bitcoin::ScriptBuf::from(vec![
16701583
0x00, 0x14, 0x1a, 0xb7, 0xf5, 0x99, 0x5c, 0xf0,
16711584
0xdf, 0xcb, 0x90, 0xcb, 0xb0, 0x2b, 0x63, 0x39,
16721585
0x7e, 0x53, 0x26, 0xea, 0xe6, 0xfe,
1673-
],
1674-
tx: &[
1586+
]),
1587+
transaction: vec![
16751588
0x02, 0x00, 0x00, 0x00, 0x01, 0x13, 0x24, 0x4f,
16761589
0xa5, 0x9f, 0xcb, 0x40, 0x71, 0x24, 0x03, 0x8f,
16771590
0xf9, 0x12, 0x1e, 0xd5, 0x46, 0xf6, 0xdc, 0x21,
@@ -1697,7 +1610,7 @@ mod tests {
16971610
0xfc, 0x1f, 0xc8, 0xf1, 0xa4, 0x95, 0xaf, 0xfa,
16981611
0x88, 0xac, 0xf4, 0x01, 0x00, 0x00,
16991612
],
1700-
merkle_proof: &[
1613+
merkle_proof: vec![
17011614
0x00, 0x00, 0x00, 0x20, 0xa0, 0x60, 0x08, 0x6a,
17021615
0xf9, 0x2a, 0xc3, 0x4d, 0xbb, 0xc8, 0xbd, 0x89,
17031616
0xbb, 0xbe, 0x03, 0xef, 0x7e, 0x00, 0x16, 0x93,
@@ -1729,7 +1642,7 @@ mod tests {
17291642
);
17301643
assert_eq!(
17311644
tx.input[0].witness.pegin_witness,
1732-
tx.input[0].pegin_data().unwrap().to_pegin_witness(),
1645+
PeginWitness::new(tx.input[0].pegin_data().unwrap().clone()),
17331646
);
17341647

17351648
assert_eq!(tx.output.len(), 2);
@@ -2386,19 +2299,13 @@ mod tests {
23862299

23872300
#[test]
23882301
fn malformed_pegin() {
2389-
let mut input: TxIn = hex_deserialize!("\
2390-
0004000000000000ffffffff0000040000c0c0c0c0c0c0c0c0c0000000000000\
2391-
00805555555555555505c0c0c0c0c03fc0c0c0c0c0c0c0c0c0c0c0c00200ff01\
2392-
0000000000fd0000000000000000010000000000ffffffffffffffff00000000\
2393-
000000ff000000000000010000000000000000000001002d342d35313700\
2394-
");
2395-
input.witness = hex_deserialize!("\
2302+
let wit = hex::decode_to_vec("\
23962303
0000000608202020202020202020202020202020202020202020202020202020\
23972304
2020202020202020202020202020202020202020202020202020202020202020\
23982305
2020202020202020202020202020202020202020202020202020202020200000\
23992306
00000000000000000000000000000002000400000000\
2400-
");
2401-
assert!(input.pegin_data().is_none());
2307+
").unwrap();
2308+
assert!(TxInWitness::consensus_decode(&wit[..]).is_err());
24022309
}
24032310

24042311
#[test]

0 commit comments

Comments
 (0)