pallet_bitcoin/
types.rs

1use bitcoin::consensus::Encodable;
2use bitcoin::locktime::absolute;
3use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
4use scale_info::TypeInfo;
5use sp_core::H256;
6use sp_std::vec::Vec;
7
8/// An absolute lock time value, representing either a block height or a UNIX timestamp (seconds
9/// since epoch).
10#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, DecodeWithMemTracking)]
11pub enum LockTime {
12    /// A block height lock time value.
13    Height(u32),
14    /// A UNIX timestamp lock time value.
15    ///
16    /// A UNIX timestamp, seconds since epoch, guaranteed to always contain a valid time value.
17    Time(u32),
18}
19
20impl From<absolute::LockTime> for LockTime {
21    fn from(lock_time: absolute::LockTime) -> Self {
22        match lock_time {
23            absolute::LockTime::Blocks(n) => Self::Height(n.to_consensus_u32()),
24            absolute::LockTime::Seconds(n) => Self::Time(n.to_consensus_u32()),
25        }
26    }
27}
28
29impl From<LockTime> for absolute::LockTime {
30    fn from(val: LockTime) -> Self {
31        match val {
32            LockTime::Height(n) => Self::Blocks(
33                absolute::Height::from_consensus(n).expect("Invalid height in LockTime"),
34            ),
35            LockTime::Time(n) => {
36                Self::Seconds(absolute::Time::from_consensus(n).expect("Invalid time in LockTime"))
37            }
38        }
39    }
40}
41
42/// Wrapper type for representing [`bitcoin::Txid`] in runtime, stored in reversed byte order.
43#[derive(
44    Clone, Copy, TypeInfo, Encode, Decode, MaxEncodedLen, PartialEq, DecodeWithMemTracking,
45)]
46pub struct Txid(H256);
47
48impl Txid {
49    /// Converts a [`bitcoin::Txid`]` to a [`Txid`].
50    pub fn from_bitcoin_txid(txid: bitcoin::Txid) -> Self {
51        let mut bytes = Vec::with_capacity(32);
52        txid.consensus_encode(&mut bytes)
53            .expect("txid must be encoded correctly; qed");
54
55        bytes.reverse();
56
57        let bytes: [u8; 32] = bytes
58            .try_into()
59            .expect("Bitcoin txid is sha256 hash which must fit into [u8; 32]; qed");
60
61        Self(H256::from(bytes))
62    }
63
64    /// Converts a [`Txid`] to a [`bitcoin::Txid`].
65    pub fn into_bitcoin_txid(self) -> bitcoin::Txid {
66        let mut bytes = self.encode();
67        bytes.reverse();
68        bitcoin::consensus::Decodable::consensus_decode(&mut bytes.as_slice())
69            .expect("Decode must succeed as txid was guaranteed to be encoded correctly; qed")
70    }
71}
72
73impl core::fmt::Debug for Txid {
74    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75        for byte in self.0.as_bytes().iter() {
76            write!(f, "{byte:02x}")?;
77        }
78        Ok(())
79    }
80}
81
82/// A reference to a transaction output.
83#[derive(
84    Clone, Debug, Encode, Decode, TypeInfo, PartialEq, MaxEncodedLen, DecodeWithMemTracking,
85)]
86pub struct OutPoint {
87    /// The transaction ID of the referenced output.
88    pub txid: Txid,
89    /// The index of the output within the referenced transaction.
90    pub output_index: u32,
91}
92
93impl From<bitcoin::OutPoint> for OutPoint {
94    fn from(out_point: bitcoin::OutPoint) -> Self {
95        Self {
96            txid: Txid::from_bitcoin_txid(out_point.txid),
97            output_index: out_point.vout,
98        }
99    }
100}
101
102impl From<OutPoint> for bitcoin::OutPoint {
103    fn from(val: OutPoint) -> Self {
104        bitcoin::OutPoint {
105            txid: val.txid.into_bitcoin_txid(),
106            vout: val.output_index,
107        }
108    }
109}
110
111/// The Witness is the data used to unlock bitcoin since the [segwit upgrade].
112///
113/// Can be logically seen as an array of bytestrings, i.e. `Vec<Vec<u8>>`, and it is serialized on the wire
114/// in that format.
115///
116/// [segwit upgrade]: <https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki>
117#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq)]
118pub struct Witness {
119    /// Contains the witness `Vec<Vec<u8>>` serialization without the initial varint indicating the
120    /// number of elements (which is stored in `witness_elements`).
121    content: Vec<u8>,
122    /// The number of elements in the witness.
123    ///
124    /// Stored separately (instead of as a VarInt in the initial part of content) so that methods
125    /// like [`Witness::push`] don't have to shift the entire array.
126    // usize
127    witness_elements: u64,
128    /// This is the valid index pointing to the beginning of the index area. This area is 4 *
129    /// stack_size bytes at the end of the content vector which stores the indices of each item.
130    indices_start: u64,
131}
132
133/// Regular bitcoin transaction input.
134///
135/// Structurely same with [`bitcoin::TxIn`].
136#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, DecodeWithMemTracking)]
137pub struct RegularTxIn {
138    /// The reference to the previous output that is being used as an input.
139    pub previous_output: OutPoint,
140    /// The unlocking script (scriptSig) that pushes values onto the stack,
141    /// enabling the referenced output's script to be satisfied.
142    pub unlocking_script: Vec<u8>,
143    /// The sequence number, which suggests to miners which of two
144    /// conflicting transactions should be preferred, or 0xFFFFFFFF
145    /// to ignore this feature. This is generally never used since
146    /// the miner behavior cannot be enforced.
147    pub sequence: u32,
148    /// Witness data: an array of byte-arrays.
149    /// Note that this field is *not* (de)serialized with the rest of the TxIn in
150    /// Encodable/Decodable, as it is (de)serialized at the end of the full
151    /// Transaction. It *is* (de)serialized with the rest of the TxIn in other
152    /// (de)serialization routines.
153    pub witness: Vec<Vec<u8>>,
154}
155
156/// Bitcoin transaction input.
157///
158/// This type is a wrapper around [`bitcoin::TxIn`], designed to provide a user-friendly representation
159/// of transaction inputs (`TxIn`) in polkadot.js.org. It handles both coinbase (block reward)
160/// transactions and standard transactions.
161#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, DecodeWithMemTracking)]
162pub enum TxIn {
163    /// Input from a coinbase transaction, which does not reference any previous output.
164    Coinbase {
165        /// Arbitrary data used in the coinbase transaction, such as extra nonce or miner-specific information.
166        data: Vec<u8>,
167        /// Sequence.
168        ///
169        /// Note that the value of sequence is not always Sequence::MAX, see https://www.blockchain.com/explorer/transactions/btc/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429
170        sequence: u32,
171    },
172    /// Input from a regular transaction, which references a previous output (`OutPoint`).
173    Regular(RegularTxIn),
174}
175
176impl From<bitcoin::TxIn> for TxIn {
177    fn from(txin: bitcoin::TxIn) -> Self {
178        if txin.previous_output.is_null() {
179            Self::Coinbase {
180                data: txin.script_sig.into_bytes(),
181                sequence: txin.sequence.0,
182            }
183        } else {
184            Self::Regular(RegularTxIn {
185                previous_output: txin.previous_output.into(),
186                unlocking_script: txin.script_sig.into_bytes(),
187                sequence: txin.sequence.0,
188                witness: txin.witness.to_vec(),
189            })
190        }
191    }
192}
193
194impl From<TxIn> for bitcoin::TxIn {
195    fn from(val: TxIn) -> Self {
196        match val {
197            TxIn::Coinbase { data, sequence } => bitcoin::TxIn {
198                previous_output: bitcoin::OutPoint::null(),
199                script_sig: bitcoin::ScriptBuf::from_bytes(data),
200                sequence: bitcoin::Sequence(sequence),
201                witness: bitcoin::Witness::new(),
202            },
203            TxIn::Regular(RegularTxIn {
204                previous_output,
205                unlocking_script,
206                sequence,
207                witness,
208            }) => bitcoin::TxIn {
209                previous_output: previous_output.into(),
210                script_sig: bitcoin::ScriptBuf::from_bytes(unlocking_script),
211                sequence: bitcoin::Sequence(sequence),
212                witness: witness.into(),
213            },
214        }
215    }
216}
217
218/// Bitcoin transaction output.
219#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, DecodeWithMemTracking)]
220pub struct TxOut {
221    /// The value of the output, in satoshis.
222    pub value: u64,
223    /// The script which must be satisfied for the output to be spent.
224    pub script_pubkey: Vec<u8>,
225}
226
227/// Bitcoin transaction.
228#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, DecodeWithMemTracking)]
229pub struct Transaction {
230    /// The protocol version, is currently expected to be 1 or 2 (BIP 68).
231    pub version: i32,
232    /// Block height or timestamp. Transaction cannot be included in a block until this height/time.
233    pub lock_time: LockTime,
234    /// List of transaction inputs.
235    pub input: Vec<TxIn>,
236    /// List of transaction outputs.
237    pub output: Vec<TxOut>,
238}
239
240impl From<Transaction> for bitcoin::Transaction {
241    fn from(val: Transaction) -> Self {
242        let Transaction {
243            version,
244            lock_time,
245            input,
246            output,
247        } = val;
248
249        bitcoin::Transaction {
250            version: bitcoin::transaction::Version(version),
251            lock_time: lock_time.into(),
252            input: input.into_iter().map(Into::into).collect(),
253            output: output
254                .into_iter()
255                .map(|txout| bitcoin::TxOut {
256                    value: bitcoin::Amount::from_sat(txout.value),
257                    script_pubkey: bitcoin::ScriptBuf::from_bytes(txout.script_pubkey),
258                })
259                .collect(),
260        }
261    }
262}
263
264impl From<bitcoin::Transaction> for Transaction {
265    fn from(btc_tx: bitcoin::Transaction) -> Self {
266        Self {
267            version: btc_tx.version.0,
268            lock_time: btc_tx.lock_time.into(),
269            input: btc_tx.input.into_iter().map(Into::into).collect(),
270            output: btc_tx
271                .output
272                .into_iter()
273                .map(|txout| TxOut {
274                    value: txout.value.to_sat(),
275                    script_pubkey: txout.script_pubkey.into_bytes(),
276                })
277                .collect(),
278        }
279    }
280}