1use crate::extension;
2use bitcoin::consensus::encode::Decodable;
3use bitcoin::consensus::encode::Encodable;
4use bitcoin::consensus::encode::{self};
5use bitcoin::io;
6use bitcoin::ScriptBuf;
7use bitcoin::Transaction;
8use bitcoin::TxOut;
9use bitcoin::VarInt;
10use bitcoin::Witness;
11use std::collections::BTreeSet;
12use std::io::Cursor;
13use std::io::Read;
14
15const PACKET_TYPE: u8 = 0x01;
16const MAX_ENTRY_COUNT: usize = 1_000;
17const MAX_SCRIPT_LENGTH: usize = 10_000;
18const MAX_WITNESS_LENGTH: usize = 1_000_000;
19
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct IntrospectorEntry {
22 pub vin: u16,
23 pub script: ScriptBuf,
24 pub witness: Witness,
25}
26
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct Packet {
29 pub entries: Vec<IntrospectorEntry>,
30}
31
32#[derive(Debug, thiserror::Error)]
33pub enum PacketError {
34 #[error("empty packet")]
35 EmptyPacket,
36 #[error("max introspector entry count exceeded, max={max} got={got}")]
37 EntryCountExceeded { max: usize, got: usize },
38 #[error("empty script at entry {0}")]
39 EmptyScript(usize),
40 #[error("duplicate vin {vin} at entry {entry}")]
41 DuplicateVin { vin: u16, entry: usize },
42 #[error("max introspector script length exceeded, max={max} got={got}")]
43 ScriptLengthExceeded { max: usize, got: usize },
44 #[error("max introspector witness length exceeded, max={max} got={got}")]
45 WitnessLengthExceeded { max: usize, got: usize },
46 #[error("failed to encode packet: {0}")]
47 Encode(io::Error),
48 #[error("failed to decode witness: {0}")]
49 WitnessDecode(encode::Error),
50 #[error("failed to decode packet: {0}")]
51 Decode(encode::Error),
52 #[error("failed to read packet: {0}")]
53 Read(std::io::Error),
54 #[error("introspector payload length overflows")]
55 PayloadLengthOverflow,
56 #[error("truncated introspector payload, expected {expected} bytes got {got}")]
57 TruncatedPayload { expected: usize, got: usize },
58 #[error("unexpected {0} trailing bytes")]
59 TrailingBytes(usize),
60 #[error("failed to process extension packet: {0}")]
61 Extension(#[from] extension::ExtensionError),
62}
63
64impl Packet {
65 pub fn new(entries: Vec<IntrospectorEntry>) -> Result<Self, PacketError> {
66 let packet = Self { entries };
67 packet.validate()?;
68 Ok(packet)
69 }
70
71 pub fn validate(&self) -> Result<(), PacketError> {
72 if self.entries.is_empty() {
73 return Err(PacketError::EmptyPacket);
74 }
75
76 if self.entries.len() > MAX_ENTRY_COUNT {
77 return Err(PacketError::EntryCountExceeded {
78 max: MAX_ENTRY_COUNT,
79 got: self.entries.len(),
80 });
81 }
82
83 let mut seen = BTreeSet::new();
84 for (index, entry) in self.entries.iter().enumerate() {
85 if entry.script.is_empty() {
86 return Err(PacketError::EmptyScript(index));
87 }
88
89 let script_len = entry.script.as_bytes().len();
90 if script_len > MAX_SCRIPT_LENGTH {
91 return Err(PacketError::ScriptLengthExceeded {
92 max: MAX_SCRIPT_LENGTH,
93 got: script_len,
94 });
95 }
96
97 if !seen.insert(entry.vin) {
98 return Err(PacketError::DuplicateVin {
99 vin: entry.vin,
100 entry: index,
101 });
102 }
103 }
104
105 Ok(())
106 }
107
108 pub fn encode(&self) -> Result<Vec<u8>, PacketError> {
109 self.validate()?;
110
111 let mut bytes = Vec::new();
112 VarInt(self.entries.len() as u64)
113 .consensus_encode(&mut bytes)
114 .map_err(PacketError::Encode)?;
115
116 for entry in &self.entries {
117 bytes.extend_from_slice(&entry.vin.to_le_bytes());
118
119 let script = entry.script.as_bytes();
120 VarInt(script.len() as u64)
121 .consensus_encode(&mut bytes)
122 .map_err(PacketError::Encode)?;
123 bytes.extend_from_slice(script);
124
125 let witness = encode::serialize(&entry.witness);
126 if witness.len() > MAX_WITNESS_LENGTH {
127 return Err(PacketError::WitnessLengthExceeded {
128 max: MAX_WITNESS_LENGTH,
129 got: witness.len(),
130 });
131 }
132 VarInt(witness.len() as u64)
133 .consensus_encode(&mut bytes)
134 .map_err(PacketError::Encode)?;
135 bytes.extend_from_slice(&witness);
136 }
137
138 Ok(bytes)
139 }
140
141 pub fn decode(data: &[u8]) -> Result<Self, PacketError> {
142 let mut reader = Cursor::new(data);
143 let entry_count = VarInt::consensus_decode(&mut reader)
144 .map_err(PacketError::Decode)?
145 .0 as usize;
146
147 if entry_count > MAX_ENTRY_COUNT {
148 return Err(PacketError::EntryCountExceeded {
149 max: MAX_ENTRY_COUNT,
150 got: entry_count,
151 });
152 }
153
154 let mut entries = Vec::with_capacity(entry_count);
155 for _ in 0..entry_count {
156 let mut vin = [0_u8; 2];
157 reader.read_exact(&mut vin).map_err(PacketError::Read)?;
158 let vin = u16::from_le_bytes(vin);
159
160 let script_len = VarInt::consensus_decode(&mut reader)
161 .map_err(PacketError::Decode)?
162 .0 as usize;
163 if script_len > MAX_SCRIPT_LENGTH {
164 return Err(PacketError::ScriptLengthExceeded {
165 max: MAX_SCRIPT_LENGTH,
166 got: script_len,
167 });
168 }
169 let mut script = vec![0_u8; script_len];
170 reader.read_exact(&mut script).map_err(PacketError::Read)?;
171
172 let witness_len = VarInt::consensus_decode(&mut reader)
173 .map_err(PacketError::Decode)?
174 .0 as usize;
175 if witness_len > MAX_WITNESS_LENGTH {
176 return Err(PacketError::WitnessLengthExceeded {
177 max: MAX_WITNESS_LENGTH,
178 got: witness_len,
179 });
180 }
181 let mut witness_bytes = vec![0_u8; witness_len];
182 reader
183 .read_exact(&mut witness_bytes)
184 .map_err(PacketError::Read)?;
185 let mut witness_reader = witness_bytes.as_slice();
186 let witness = Witness::consensus_decode(&mut witness_reader)
187 .map_err(PacketError::WitnessDecode)?;
188 if !witness_reader.is_empty() {
189 return Err(PacketError::TrailingBytes(witness_reader.len()));
190 }
191
192 entries.push(IntrospectorEntry {
193 vin,
194 script: ScriptBuf::from_bytes(script),
195 witness,
196 });
197 }
198
199 let remaining = data.len() - reader.position() as usize;
200 if remaining != 0 {
201 return Err(PacketError::TrailingBytes(remaining));
202 }
203
204 Self::new(entries)
205 }
206
207 pub fn to_txout(&self) -> Result<TxOut, PacketError> {
208 let packet = self.encode()?;
209
210 Ok(extension::packet_txout(PACKET_TYPE, &packet))
211 }
212}
213
214pub fn add_packet_to_psbt(psbt: &mut bitcoin::Psbt, packet: &Packet) -> Result<(), PacketError> {
221 let packet = packet.encode()?;
222
223 extension::add_packet_to_psbt(psbt, PACKET_TYPE, &packet)?;
224
225 Ok(())
226}
227
228pub fn find_packet(tx: &Transaction) -> Result<Option<Packet>, PacketError> {
229 let Some(payload) = extension::find_packet_payload(tx, PACKET_TYPE)? else {
230 return Ok(None);
231 };
232
233 Packet::decode(payload).map(Some)
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use bitcoin::absolute;
240 use bitcoin::hex::DisplayHex;
241 use bitcoin::hex::FromHex;
242 use bitcoin::script::PushBytesBuf;
243 use bitcoin::transaction;
244 use bitcoin::Amount;
245 use bitcoin::TxIn;
246
247 fn witness(items: &[&str]) -> Witness {
248 Witness::from_slice(
249 &items
250 .iter()
251 .map(|item| Vec::from_hex(item).unwrap())
252 .collect::<Vec<_>>(),
253 )
254 }
255
256 fn tx_with_op_return_payload(payload: Vec<u8>) -> Transaction {
257 let push_bytes = PushBytesBuf::try_from(payload).unwrap();
258 Transaction {
259 version: transaction::Version::TWO,
260 lock_time: absolute::LockTime::ZERO,
261 input: vec![],
262 output: vec![TxOut {
263 value: Amount::ZERO,
264 script_pubkey: ScriptBuf::builder()
265 .push_opcode(bitcoin::opcodes::all::OP_RETURN)
266 .push_slice(push_bytes)
267 .into_script(),
268 }],
269 }
270 }
271
272 #[test]
273 fn matches_go_vectors() {
274 let packet = Packet::new(vec![IntrospectorEntry {
275 vin: 0,
276 script: ScriptBuf::from_bytes(Vec::from_hex("010203").unwrap()),
277 witness: witness(&["0405"]),
278 }])
279 .unwrap();
280
281 assert_eq!(
282 packet.encode().unwrap().to_lower_hex_string(),
283 "010000030102030401020405"
284 );
285
286 let packet = Packet::new(vec![
287 IntrospectorEntry {
288 vin: 0,
289 script: ScriptBuf::from_bytes(Vec::from_hex("01").unwrap()),
290 witness: witness(&["02"]),
291 },
292 IntrospectorEntry {
293 vin: 1,
294 script: ScriptBuf::from_bytes(Vec::from_hex("0304").unwrap()),
295 witness: witness(&["05", "06"]),
296 },
297 IntrospectorEntry {
298 vin: 5,
299 script: ScriptBuf::from_bytes(Vec::from_hex("07").unwrap()),
300 witness: Witness::default(),
301 },
302 ])
303 .unwrap();
304
305 assert_eq!(
306 packet.encode().unwrap().to_lower_hex_string(),
307 "0300000101030101020100020304050201050106050001070100"
308 );
309 }
310
311 #[test]
312 fn decode_rejects_invalid_packets() {
313 assert!(matches!(Packet::new(vec![]), Err(PacketError::EmptyPacket)));
314 assert!(matches!(
315 Packet::decode(&Vec::from_hex("0000000101ff").unwrap()),
316 Err(PacketError::TrailingBytes(5))
317 ));
318 assert!(matches!(
319 Packet::decode(&Vec::from_hex("010000fd1127").unwrap()),
320 Err(PacketError::ScriptLengthExceeded { .. })
321 ));
322 assert!(matches!(
323 Packet::decode(&Vec::from_hex("01000001510200ff").unwrap()),
324 Err(PacketError::TrailingBytes(1))
325 ));
326 }
327
328 #[test]
329 fn find_packet_rejects_invalid_extension_payload_lengths() {
330 let tx = tx_with_op_return_payload(Vec::from_hex("41524b010200").unwrap());
331 assert!(matches!(
332 find_packet(&tx),
333 Err(PacketError::Extension(
334 extension::ExtensionError::TruncatedPacketPayload {
335 expected: 7,
336 got: 6,
337 }
338 ))
339 ));
340
341 let tx = tx_with_op_return_payload(Vec::from_hex("41524b010100ff").unwrap());
342 assert!(matches!(
343 find_packet(&tx),
344 Err(PacketError::Extension(
345 extension::ExtensionError::TruncatedPacketLength
346 ))
347 ));
348
349 let tx = tx_with_op_return_payload(Vec::from_hex("41524b01ffffffffffffffffff").unwrap());
350 assert!(matches!(
351 find_packet(&tx),
352 Err(PacketError::Extension(
353 extension::ExtensionError::TruncatedPacketLength
354 ))
355 ));
356 }
357
358 #[test]
359 fn add_and_find_packet() {
360 let packet = Packet::new(vec![IntrospectorEntry {
361 vin: 0,
362 script: ScriptBuf::from_bytes(Vec::from_hex("51").unwrap()),
363 witness: Witness::default(),
364 }])
365 .unwrap();
366
367 let mut psbt = bitcoin::Psbt::from_unsigned_tx(Transaction {
368 version: transaction::Version::TWO,
369 lock_time: absolute::LockTime::ZERO,
370 input: vec![TxIn::default()],
371 output: vec![
372 TxOut {
373 value: Amount::from_sat(1_000),
374 script_pubkey: ScriptBuf::new(),
375 },
376 TxOut {
377 value: Amount::ZERO,
378 script_pubkey: ScriptBuf::new(),
379 },
380 ],
381 })
382 .unwrap();
383
384 add_packet_to_psbt(&mut psbt, &packet).unwrap();
385 assert_eq!(psbt.unsigned_tx.output.len(), 3);
386
387 let found = find_packet(&psbt.unsigned_tx).unwrap().unwrap();
388 assert_eq!(found, packet);
389 }
390
391 #[test]
392 fn duplicate_vins_are_rejected() {
393 let err = Packet::new(vec![
394 IntrospectorEntry {
395 vin: 1,
396 script: ScriptBuf::from_bytes(vec![0x51]),
397 witness: Witness::default(),
398 },
399 IntrospectorEntry {
400 vin: 1,
401 script: ScriptBuf::from_bytes(vec![0x52]),
402 witness: Witness::default(),
403 },
404 ])
405 .unwrap_err();
406
407 assert!(matches!(
408 err,
409 PacketError::DuplicateVin { vin: 1, entry: 1 }
410 ));
411 }
412}