@@ -46,23 +46,52 @@ use crate::vm::{LocalContext, eval};
4646/// Maximum supported merkle proof depth for `(verify-merkle-proof ...)`.
4747const VERIFY_MERKLE_PROOF_MAX_DEPTH : u32 = 24 ;
4848
49+ /// Maximum supported `scriptPubKey` size for `(get-bitcoin-tx-output? ...)`.
50+ const GET_BITCOIN_TX_OUTPUT_MAX_SCRIPT_LEN : usize = 1024 ;
51+
52+ /// Failure modes of `(get-bitcoin-tx-output? ...)`. Mapped to Clarity `(err
53+ /// uN)` codes so callers can distinguish "the tx didn't parse" from "the tx
54+ /// parsed but the output you asked for doesn't exist" without re-parsing.
55+ #[ derive( Debug , PartialEq , Eq ) ]
56+ enum ParseTxError {
57+ /// Tx bytes failed to deserialize as a Bitcoin transaction, or had
58+ /// trailing bytes after a successful parse.
59+ InvalidTx ,
60+ /// `vout` is `>=` the number of outputs in the tx.
61+ VoutOutOfRange ,
62+ /// The output's `scriptPubKey` is larger than
63+ /// `GET_BITCOIN_TX_OUTPUT_MAX_SCRIPT_LEN`.
64+ ScriptTooLarge ,
65+ }
66+
67+ impl ParseTxError {
68+ /// Clarity `(err uN)` code that this failure is reported as.
69+ fn as_error_code ( & self ) -> u128 {
70+ match self {
71+ ParseTxError :: InvalidTx => 1 ,
72+ ParseTxError :: VoutOutOfRange => 2 ,
73+ ParseTxError :: ScriptTooLarge => 3 ,
74+ }
75+ }
76+ }
77+
4978/// Parse a Bitcoin transaction (SegWit or non-SegWit) and pluck the output at
5079/// `vout`, along with the canonical (non-witness) txid in internal byte order.
51- ///
52- /// Returns `None` if the bytes don't form a valid Bitcoin tx, if `vout` is
53- /// out of range, or if there are trailing bytes after the tx.
54- fn parse_tx_output ( raw : & [ u8 ] , vout : u64 ) -> Option < ( Vec < u8 > , u64 , [ u8 ; 32 ] ) > {
55- let tx : Transaction = btc_deserialize ( raw ) . ok ( ) ? ;
56- let vout_idx = usize :: try_from ( vout ) . ok ( ) ? ;
57- let txout = tx . output . get ( vout_idx ) ?;
80+ fn parse_tx_output ( raw : & [ u8 ] , vout : u64 ) -> Result < ( Vec < u8 > , u64 , [ u8 ; 32 ] ) , ParseTxError > {
81+ let tx : Transaction = btc_deserialize ( raw ) . map_err ( |_| ParseTxError :: InvalidTx ) ? ;
82+ let vout_idx = usize :: try_from ( vout ) . map_err ( |_| ParseTxError :: VoutOutOfRange ) ? ;
83+ let txout = tx
84+ . output
85+ . get ( vout_idx )
86+ . ok_or ( ParseTxError :: VoutOutOfRange ) ?;
5887 let script_bytes = txout. script_pubkey . as_bytes ( ) ;
59- if script_bytes. len ( ) > 1024 {
60- return None ;
88+ if script_bytes. len ( ) > GET_BITCOIN_TX_OUTPUT_MAX_SCRIPT_LEN {
89+ return Err ( ParseTxError :: ScriptTooLarge ) ;
6190 }
6291 let script = script_bytes. to_vec ( ) ;
6392 let amount = txout. value ;
6493 let txid = tx. txid ( ) . 0 ;
65- Some ( ( script, amount, txid) )
94+ Ok ( ( script, amount, txid) )
6695}
6796
6897/// Canonical Bitcoin merkle-tree depth for a block containing `tx_count`
@@ -244,7 +273,10 @@ pub fn special_verify_merkle_proof(
244273/// `(get-bitcoin-tx-output? tx-bytes vout)` returns
245274/// `(response { script: (buff 1024), amount: uint, txid: (buff 32) } uint)`,
246275/// where the txid is in internal byte order (ready for `verify-merkle-proof`).
247- /// On any parse failure or out-of-range vout, returns `(err u1)`.
276+ /// On failure, returns one of:
277+ /// - `(err u1)` — `tx-bytes` did not deserialize as a Bitcoin transaction.
278+ /// - `(err u2)` — `vout` is out of range for this tx.
279+ /// - `(err u3)` — the output's `scriptPubKey` exceeds the 1024-byte cap.
248280pub fn special_get_bitcoin_tx_output (
249281 args : & [ SymbolicExpression ] ,
250282 exec_state : & mut ExecutionState ,
@@ -285,11 +317,15 @@ pub fn special_get_bitcoin_tx_output(
285317
286318 let vout_u64 = match u64:: try_from ( vout) {
287319 Ok ( v) => v,
288- Err ( _) => return Ok ( Value :: error ( Value :: UInt ( 1 ) ) ?) ,
320+ Err ( _) => {
321+ return Ok ( Value :: error ( Value :: UInt (
322+ ParseTxError :: VoutOutOfRange . as_error_code ( ) ,
323+ ) ) ?) ;
324+ }
289325 } ;
290326
291327 let result = match parse_tx_output ( & tx_bytes, vout_u64) {
292- Some ( ( script, amount, txid) ) => {
328+ Ok ( ( script, amount, txid) ) => {
293329 let tuple = TupleData :: from_data ( vec ! [
294330 (
295331 ClarityName :: from_literal( "script" ) ,
@@ -311,7 +347,7 @@ pub fn special_get_bitcoin_tx_output(
311347 } ) ?;
312348 Value :: okay ( Value :: Tuple ( tuple) ) ?
313349 }
314- None => Value :: error ( Value :: UInt ( 1 ) ) ? ,
350+ Err ( e ) => Value :: err_uint ( e . as_error_code ( ) ) ,
315351 } ;
316352
317353 Ok ( result)
@@ -368,13 +404,38 @@ mod tests {
368404 #[ test]
369405 fn parse_rejects_out_of_range_vout ( ) {
370406 let raw = hex ( SAMPLE_TX_HEX ) ;
371- assert ! ( parse_tx_output( & raw, 1 ) . is_none ( ) ) ;
407+ assert_eq ! ( parse_tx_output( & raw, 1 ) , Err ( ParseTxError :: VoutOutOfRange ) ) ;
372408 }
373409
374410 #[ test]
375411 fn parse_rejects_truncated_tx ( ) {
376412 let raw = hex ( SAMPLE_TX_HEX ) ;
377- assert ! ( parse_tx_output( & raw[ ..raw. len( ) - 1 ] , 0 ) . is_none( ) ) ;
413+ assert_eq ! (
414+ parse_tx_output( & raw[ ..raw. len( ) - 1 ] , 0 ) ,
415+ Err ( ParseTxError :: InvalidTx ) ,
416+ ) ;
417+ }
418+
419+ #[ test]
420+ fn parse_rejects_oversized_script ( ) {
421+ // Build a tx with a single output whose scriptPubKey is 1025 bytes
422+ // (one byte over the cap). The serialized length prefix uses a
423+ // CompactSize: 0xfd 0x01 0x04 = 0x401 = 1025.
424+ let mut raw = hex ( concat ! (
425+ "01000000" , // version
426+ "01" , // n_in
427+ "0000000000000000000000000000000000000000000000000000000000000000" , // prev txid
428+ "00000000" , // prev vout
429+ "00" , // scriptSig len
430+ "ffffffff" , // sequence
431+ "01" , // n_out
432+ "e803000000000000" , // amount
433+ "fd0104" , // script len = 1025
434+ ) ) ;
435+ raw. extend ( std:: iter:: repeat_n ( 0x51u8 , 1025 ) ) ; // 1025 bytes of OP_1
436+ raw. extend_from_slice ( & hex ( "00000000" ) ) ; // locktime
437+
438+ assert_eq ! ( parse_tx_output( & raw, 0 ) , Err ( ParseTxError :: ScriptTooLarge ) , ) ;
378439 }
379440
380441 #[ test]
0 commit comments