@@ -13,6 +13,8 @@ pub use bitcoin::{
1313 Script ,
1414} ;
1515
16+ use bitcoin:: blockdata:: { opcodes, script:: Instruction } ;
17+
1618#[ cfg( feature = "wasm" ) ]
1719use enum_assoc:: Assoc ;
1820
@@ -160,6 +162,8 @@ pub struct Payload {
160162pub enum PayloadError {
161163 #[ error( "unrecognized pubkey script" ) ]
162164 Unrecognized ,
165+ #[ error( "{0}" ) ]
166+ InvalidOpReturn ( & ' static str ) ,
163167}
164168
165169impl Payload {
@@ -190,6 +194,46 @@ impl Payload {
190194 data : pkscript[ 2 ..] . to_vec ( ) ,
191195 output_type : pb:: BtcOutputType :: P2tr ,
192196 } )
197+ } else if matches ! ( script. as_bytes( ) . first( ) , Some ( byte) if * byte == opcodes:: all:: OP_RETURN . to_u8( ) )
198+ {
199+ let mut instructions = script. instructions_minimal ( ) ;
200+ match instructions. next ( ) {
201+ Some ( Ok ( Instruction :: Op ( op) ) ) if op == opcodes:: all:: OP_RETURN => { }
202+ _ => return Err ( PayloadError :: Unrecognized ) ,
203+ }
204+
205+ let payload = match instructions. next ( ) {
206+ None => {
207+ return Err ( PayloadError :: InvalidOpReturn (
208+ "naked OP_RETURN is not supported" ,
209+ ) )
210+ }
211+ Some ( Ok ( Instruction :: Op ( op) ) ) if op == opcodes:: all:: OP_PUSHBYTES_0 => Vec :: new ( ) ,
212+ Some ( Ok ( Instruction :: PushBytes ( push) ) ) => push. as_bytes ( ) . to_vec ( ) ,
213+ Some ( Ok ( _) ) => {
214+ return Err ( PayloadError :: InvalidOpReturn (
215+ "no data push found after OP_RETURN" ,
216+ ) )
217+ }
218+ Some ( Err ( _) ) => {
219+ return Err ( PayloadError :: InvalidOpReturn (
220+ "failed to parse OP_RETURN payload" ,
221+ ) )
222+ }
223+ } ;
224+
225+ match instructions. next ( ) {
226+ None => Ok ( Payload {
227+ data : payload,
228+ output_type : pb:: BtcOutputType :: OpReturn ,
229+ } ) ,
230+ Some ( Ok ( _) ) => Err ( PayloadError :: InvalidOpReturn (
231+ "only one data push supported after OP_RETURN" ,
232+ ) ) ,
233+ Some ( Err ( _) ) => Err ( PayloadError :: InvalidOpReturn (
234+ "failed to parse OP_RETURN payload" ,
235+ ) ) ,
236+ }
193237 } else {
194238 Err ( PayloadError :: Unrecognized )
195239 }
@@ -206,8 +250,7 @@ impl TryFrom<&bitcoin::TxOut> for TxExternalOutput {
206250 type Error = PsbtError ;
207251 fn try_from ( value : & bitcoin:: TxOut ) -> Result < Self , Self :: Error > {
208252 Ok ( TxExternalOutput {
209- payload : Payload :: from_pkscript ( value. script_pubkey . as_bytes ( ) )
210- . map_err ( |_| PsbtError :: UnknownOutputType ) ?,
253+ payload : Payload :: from_pkscript ( value. script_pubkey . as_bytes ( ) ) ?,
211254 value : value. value . to_sat ( ) ,
212255 } )
213256 }
@@ -251,6 +294,18 @@ pub enum PsbtError {
251294 #[ error( "Unrecognized/unsupported output type." ) ]
252295 #[ cfg_attr( feature = "wasm" , assoc( js_code = "unknown-output-type" ) ) ]
253296 UnknownOutputType ,
297+ #[ error( "Invalid OP_RETURN script: {0}" ) ]
298+ #[ cfg_attr( feature = "wasm" , assoc( js_code = "invalid-op-return" ) ) ]
299+ InvalidOpReturn ( & ' static str ) ,
300+ }
301+
302+ impl From < PayloadError > for PsbtError {
303+ fn from ( value : PayloadError ) -> Self {
304+ match value {
305+ PayloadError :: Unrecognized => PsbtError :: UnknownOutputType ,
306+ PayloadError :: InvalidOpReturn ( message) => PsbtError :: InvalidOpReturn ( message) ,
307+ }
308+ }
254309}
255310
256311enum OurKey {
@@ -753,6 +808,15 @@ impl<R: Runtime> PairedBitBox<R> {
753808 if transaction. script_configs . iter ( ) . any ( is_taproot_simple) {
754809 self . validate_version ( ">=9.10.0" ) ?; // taproot since 9.10.0
755810 }
811+ if transaction. outputs . iter ( ) . any ( |output| {
812+ matches ! (
813+ output,
814+ TxOutput :: External ( tx_output)
815+ if tx_output. payload. output_type == pb:: BtcOutputType :: OpReturn
816+ )
817+ } ) {
818+ self . validate_version ( ">=9.24.0" ) ?;
819+ }
756820
757821 let mut sigs: Vec < Vec < u8 > > = Vec :: new ( ) ;
758822
@@ -1181,6 +1245,62 @@ mod tests {
11811245 output_type: pb:: BtcOutputType :: P2tr ,
11821246 }
11831247 ) ;
1248+
1249+ // OP_RETURN empty (OP_0)
1250+ let pkscript = hex:: decode ( "6a00" ) . unwrap ( ) ;
1251+ assert_eq ! (
1252+ Payload :: from_pkscript( & pkscript) . unwrap( ) ,
1253+ Payload {
1254+ data: Vec :: new( ) ,
1255+ output_type: pb:: BtcOutputType :: OpReturn ,
1256+ }
1257+ ) ;
1258+
1259+ // OP_RETURN with data push
1260+ let pkscript = hex:: decode ( "6a03aabbcc" ) . unwrap ( ) ;
1261+ assert_eq ! (
1262+ Payload :: from_pkscript( & pkscript) . unwrap( ) ,
1263+ Payload {
1264+ data: vec![ 0xaa , 0xbb , 0xcc ] ,
1265+ output_type: pb:: BtcOutputType :: OpReturn ,
1266+ }
1267+ ) ;
1268+
1269+ // OP_RETURN with 80-byte payload (PUSHDATA1)
1270+ let mut pkscript = vec ! [ opcodes:: all:: OP_RETURN . to_u8( ) , 0x4c , 0x50 ] ;
1271+ pkscript. extend ( std:: iter:: repeat_n ( 0xaa , 80 ) ) ;
1272+ assert_eq ! (
1273+ Payload :: from_pkscript( & pkscript) . unwrap( ) ,
1274+ Payload {
1275+ data: vec![ 0xaa ; 80 ] ,
1276+ output_type: pb:: BtcOutputType :: OpReturn ,
1277+ }
1278+ ) ;
1279+
1280+ // Invalid OP_RETURN scripts
1281+ let pkscript = hex:: decode ( "6a" ) . unwrap ( ) ;
1282+ assert ! ( matches!(
1283+ Payload :: from_pkscript( & pkscript) ,
1284+ Err ( PayloadError :: InvalidOpReturn (
1285+ "naked OP_RETURN is not supported"
1286+ ) )
1287+ ) ) ;
1288+
1289+ let pkscript = hex:: decode ( "6a6a" ) . unwrap ( ) ;
1290+ assert ! ( matches!(
1291+ Payload :: from_pkscript( & pkscript) ,
1292+ Err ( PayloadError :: InvalidOpReturn (
1293+ "no data push found after OP_RETURN"
1294+ ) )
1295+ ) ) ;
1296+
1297+ let pkscript = hex:: decode ( "6a0000" ) . unwrap ( ) ;
1298+ assert ! ( matches!(
1299+ Payload :: from_pkscript( & pkscript) ,
1300+ Err ( PayloadError :: InvalidOpReturn (
1301+ "only one data push supported after OP_RETURN"
1302+ ) )
1303+ ) ) ;
11841304 }
11851305
11861306 // Test that a PSBT containing only p2wpkh inputs is converted correctly to a transaction to be
0 commit comments