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#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, DecodeWithMemTracking)]
11pub enum LockTime {
12 Height(u32),
14 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#[derive(
44 Clone, Copy, TypeInfo, Encode, Decode, MaxEncodedLen, PartialEq, DecodeWithMemTracking,
45)]
46pub struct Txid(H256);
47
48impl Txid {
49 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 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#[derive(
84 Clone, Debug, Encode, Decode, TypeInfo, PartialEq, MaxEncodedLen, DecodeWithMemTracking,
85)]
86pub struct OutPoint {
87 pub txid: Txid,
89 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#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq)]
118pub struct Witness {
119 content: Vec<u8>,
122 witness_elements: u64,
128 indices_start: u64,
131}
132
133#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, DecodeWithMemTracking)]
137pub struct RegularTxIn {
138 pub previous_output: OutPoint,
140 pub unlocking_script: Vec<u8>,
143 pub sequence: u32,
148 pub witness: Vec<Vec<u8>>,
154}
155
156#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, DecodeWithMemTracking)]
162pub enum TxIn {
163 Coinbase {
165 data: Vec<u8>,
167 sequence: u32,
171 },
172 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#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, DecodeWithMemTracking)]
220pub struct TxOut {
221 pub value: u64,
223 pub script_pubkey: Vec<u8>,
225}
226
227#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, DecodeWithMemTracking)]
229pub struct Transaction {
230 pub version: i32,
232 pub lock_time: LockTime,
234 pub input: Vec<TxIn>,
236 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}