Skip to main content

bsv_script/
script.rs

1/// Bitcoin Script type - a sequence of opcodes and data pushes.
2///
3/// Scripts are used in transaction inputs (unlocking) and outputs (locking)
4/// to define spending conditions. The Script wraps a `Vec<u8>` and provides
5/// methods for construction, classification, serialization, and ASM output.
6
7use std::fmt;
8
9use crate::chunk::{decode_script, push_data_prefix, ScriptChunk};
10use crate::opcodes::*;
11use crate::ScriptError;
12
13/// A Bitcoin script, represented as a byte vector newtype.
14#[derive(Clone, PartialEq, Eq)]
15pub struct Script(Vec<u8>);
16
17impl Script {
18    // -----------------------------------------------------------------------
19    // Constructors
20    // -----------------------------------------------------------------------
21
22    /// Create a new empty script.
23    ///
24    /// # Returns
25    /// An empty `Script` instance.
26    pub fn new() -> Self {
27        Script(Vec::new())
28    }
29
30    /// Create a script from a hex-encoded string.
31    ///
32    /// # Arguments
33    /// * `hex_str` - A hex string (e.g. "76a914...88ac").
34    ///
35    /// # Returns
36    /// A `Script` wrapping the decoded bytes, or an error if the hex is invalid.
37    pub fn from_hex(hex_str: &str) -> Result<Self, ScriptError> {
38        let bytes = hex::decode(hex_str)
39            ?;
40        Ok(Script(bytes))
41    }
42
43    /// Create a script from raw bytes.
44    ///
45    /// # Arguments
46    /// * `bytes` - Raw script bytes.
47    ///
48    /// # Returns
49    /// A `Script` wrapping a copy of the given bytes.
50    pub fn from_bytes(bytes: &[u8]) -> Self {
51        Script(bytes.to_vec())
52    }
53
54    /// Create a script from a Bitcoin ASM string.
55    ///
56    /// Parses space-separated tokens where known opcodes (e.g. "OP_DUP") are
57    /// emitted directly and hex strings are treated as push data.
58    ///
59    /// # Arguments
60    /// * `asm` - A space-separated ASM string.
61    ///
62    /// # Returns
63    /// A `Script`, or an error if any token is invalid.
64    pub fn from_asm(asm: &str) -> Result<Self, ScriptError> {
65        let mut script = Script::new();
66        if asm.is_empty() {
67            return Ok(script);
68        }
69        for section in asm.split(' ') {
70            if let Some(opcode) = string_to_opcode(section) {
71                script.append_opcodes(&[opcode])?;
72            } else {
73                script.append_push_data_hex(section)?;
74            }
75        }
76        Ok(script)
77    }
78
79    // -----------------------------------------------------------------------
80    // Serialization
81    // -----------------------------------------------------------------------
82
83    /// Encode the script as a hex string.
84    ///
85    /// # Returns
86    /// A lowercase hex representation of the script bytes.
87    pub fn to_hex(&self) -> String {
88        hex::encode(&self.0)
89    }
90
91    /// Convert the script to its ASM (human-readable assembly) representation.
92    ///
93    /// Each opcode or data push is represented as a space-separated token.
94    /// Data pushes appear as their hex encoding; opcodes appear by name.
95    ///
96    /// # Returns
97    /// A space-separated ASM string. Returns empty string for empty/invalid scripts.
98    pub fn to_asm(&self) -> String {
99        if self.0.is_empty() {
100            return String::new();
101        }
102        let mut parts = Vec::new();
103        let mut pos = 0;
104        while pos < self.0.len() {
105            match self.read_op(&mut pos) {
106                Ok(chunk) => {
107                    let s = chunk.to_asm_string();
108                    if !s.is_empty() {
109                        parts.push(s);
110                    }
111                }
112                Err(_) => return String::new(),
113            }
114        }
115        parts.join(" ")
116    }
117
118    /// Return a reference to the underlying bytes.
119    ///
120    /// # Returns
121    /// A byte slice of the script contents.
122    pub fn to_bytes(&self) -> &[u8] {
123        &self.0
124    }
125
126    /// Return the length of the script in bytes.
127    ///
128    /// # Returns
129    /// The number of bytes in the script.
130    pub fn len(&self) -> usize {
131        self.0.len()
132    }
133
134    /// Check if the script is empty (zero bytes).
135    ///
136    /// # Returns
137    /// `true` if the script has no bytes.
138    pub fn is_empty(&self) -> bool {
139        self.0.is_empty()
140    }
141
142    // -----------------------------------------------------------------------
143    // Script classification
144    // -----------------------------------------------------------------------
145
146    /// Check if this is a Pay-to-Public-Key-Hash (P2PKH) output script.
147    ///
148    /// Pattern: OP_DUP OP_HASH160 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG
149    ///
150    /// # Returns
151    /// `true` if the script matches the P2PKH pattern.
152    pub fn is_p2pkh(&self) -> bool {
153        let b = &self.0;
154        b.len() == 25
155            && b[0] == OP_DUP
156            && b[1] == OP_HASH160
157            && b[2] == OP_DATA_20
158            && b[23] == OP_EQUALVERIFY
159            && b[24] == OP_CHECKSIG
160    }
161
162    /// Check if this is a Pay-to-Public-Key (P2PK) output script.
163    ///
164    /// Pattern: `<pubkey> OP_CHECKSIG` (pubkey is 33 or 65 bytes with valid prefix).
165    ///
166    /// # Returns
167    /// `true` if the script matches the P2PK pattern.
168    pub fn is_p2pk(&self) -> bool {
169        let parts = match self.chunks() {
170            Ok(p) => p,
171            Err(_) => return false,
172        };
173        if parts.len() == 2 && parts[1].op == OP_CHECKSIG {
174            if let Some(ref pubkey) = parts[0].data {
175                if !pubkey.is_empty() {
176                    let version = pubkey[0];
177                    if (version == 0x04 || version == 0x06 || version == 0x07) && pubkey.len() == 65 {
178                        return true;
179                    } else if (version == 0x03 || version == 0x02) && pubkey.len() == 33 {
180                        return true;
181                    }
182                }
183            }
184        }
185        false
186    }
187
188    /// Check if this is a Pay-to-Script-Hash (P2SH) output script.
189    ///
190    /// Pattern: OP_HASH160 <20 bytes> OP_EQUAL
191    ///
192    /// # Returns
193    /// `true` if the script matches the P2SH pattern.
194    pub fn is_p2sh(&self) -> bool {
195        let b = &self.0;
196        b.len() == 23
197            && b[0] == OP_HASH160
198            && b[1] == OP_DATA_20
199            && b[22] == OP_EQUAL
200    }
201
202    /// Check if this is a data output script (OP_RETURN or OP_FALSE OP_RETURN).
203    ///
204    /// # Returns
205    /// `true` if the script begins with OP_RETURN or OP_FALSE OP_RETURN.
206    pub fn is_data(&self) -> bool {
207        let b = &self.0;
208        (!b.is_empty() && b[0] == OP_RETURN)
209            || (b.len() > 1 && b[0] == OP_FALSE && b[1] == OP_RETURN)
210    }
211
212    /// Check if this is a multisig output script.
213    ///
214    /// Pattern: `OP_N <pubkey1> <pubkey2> ... OP_M OP_CHECKMULTISIG`
215    ///
216    /// # Returns
217    /// `true` if the script matches the multisig output pattern.
218    pub fn is_multisig_out(&self) -> bool {
219        let parts = match self.chunks() {
220            Ok(p) => p,
221            Err(_) => return false,
222        };
223        if parts.len() < 3 {
224            return false;
225        }
226        if !is_small_int_op(parts[0].op) {
227            return false;
228        }
229        for chunk in &parts[1..parts.len() - 2] {
230            match &chunk.data {
231                Some(d) if !d.is_empty() => {}
232                _ => return false,
233            }
234        }
235        let second_last = &parts[parts.len() - 2];
236        let last = &parts[parts.len() - 1];
237        is_small_int_op(second_last.op) && last.op == OP_CHECKMULTISIG
238    }
239
240    // -----------------------------------------------------------------------
241    // Data extraction
242    // -----------------------------------------------------------------------
243
244    /// Extract the public key hash from a P2PKH script.
245    ///
246    /// Returns the 20-byte hash160 if the script starts with OP_DUP OP_HASH160.
247    ///
248    /// # Returns
249    /// The 20-byte public key hash, or an error if the script is not P2PKH.
250    pub fn public_key_hash(&self) -> Result<Vec<u8>, ScriptError> {
251        if self.0.is_empty() {
252            return Err(ScriptError::EmptyScript);
253        }
254        if self.0.len() <= 2 || self.0[0] != OP_DUP || self.0[1] != OP_HASH160 {
255            return Err(ScriptError::NotP2PKH);
256        }
257        let tail = &self.0[2..];
258        let parts = decode_script(tail)?;
259        match parts.first() {
260            Some(chunk) => match &chunk.data {
261                Some(data) => Ok(data.clone()),
262                None => Err(ScriptError::NotP2PKH),
263            },
264            None => Err(ScriptError::NotP2PKH),
265        }
266    }
267
268    /// Parse the script into a vector of decoded chunks.
269    ///
270    /// # Returns
271    /// A vector of `ScriptChunk` values, or an error if the script is malformed.
272    pub fn chunks(&self) -> Result<Vec<ScriptChunk>, ScriptError> {
273        decode_script(&self.0)
274    }
275
276    // -----------------------------------------------------------------------
277    // Mutation / building
278    // -----------------------------------------------------------------------
279
280    /// Append data bytes to the script with the proper PUSHDATA prefix.
281    ///
282    /// Chooses the minimal encoding: direct push for 1-75 bytes,
283    /// OP_PUSHDATA1 for 76-255, OP_PUSHDATA2 for 256-65535, etc.
284    ///
285    /// # Arguments
286    /// * `data` - The data bytes to push.
287    ///
288    /// # Returns
289    /// `Ok(())` on success, or an error if the data is too large.
290    pub fn append_push_data(&mut self, data: &[u8]) -> Result<(), ScriptError> {
291        let prefix = push_data_prefix(data.len())?;
292        self.0.extend_from_slice(&prefix);
293        self.0.extend_from_slice(data);
294        Ok(())
295    }
296
297    /// Append hex-encoded data to the script with proper PUSHDATA prefix.
298    ///
299    /// # Arguments
300    /// * `hex_str` - Hex string to decode and push.
301    ///
302    /// # Returns
303    /// `Ok(())` on success, or an error if the hex is invalid or data too large.
304    pub fn append_push_data_hex(&mut self, hex_str: &str) -> Result<(), ScriptError> {
305        let data = hex::decode(hex_str)
306            .map_err(|_| ScriptError::InvalidOpcodeData)?;
307        self.append_push_data(&data)
308    }
309
310    /// Append raw opcodes to the script.
311    ///
312    /// Rejects push data opcodes (OP_DATA_1..OP_PUSHDATA4) to prevent misuse.
313    /// Use `append_push_data` for those.
314    ///
315    /// # Arguments
316    /// * `opcodes` - Slice of opcode bytes to append.
317    ///
318    /// # Returns
319    /// `Ok(())` on success, or an error if a push data opcode is encountered.
320    pub fn append_opcodes(&mut self, opcodes: &[u8]) -> Result<(), ScriptError> {
321        for &op in opcodes {
322            if op >= OP_DATA_1 && op <= OP_PUSHDATA4 {
323                return Err(ScriptError::InvalidOpcodeType(
324                    opcode_to_string(op).to_string(),
325                ));
326            }
327        }
328        self.0.extend_from_slice(opcodes);
329        Ok(())
330    }
331
332    /// Check if this script is byte-equal to another script.
333    ///
334    /// # Arguments
335    /// * `other` - The other script to compare with.
336    ///
337    /// # Returns
338    /// `true` if both scripts have identical bytes.
339    pub fn equals(&self, other: &Script) -> bool {
340        self.0 == other.0
341    }
342
343    // -----------------------------------------------------------------------
344    // Internal helpers
345    // -----------------------------------------------------------------------
346
347    /// Read a single script operation from the given position.
348    ///
349    /// Advances `pos` past the consumed bytes. Used internally by `to_asm`.
350    ///
351    /// # Arguments
352    /// * `pos` - Mutable reference to the current read position.
353    ///
354    /// # Returns
355    /// The parsed `ScriptChunk`, or an error if the data is truncated.
356    fn read_op(&self, pos: &mut usize) -> Result<ScriptChunk, ScriptError> {
357        let b = &self.0;
358        if *pos >= b.len() {
359            return Err(ScriptError::IndexOutOfRange);
360        }
361        let op = b[*pos];
362        match op {
363            OP_PUSHDATA1 => {
364                if b.len() < *pos + 2 {
365                    return Err(ScriptError::DataTooSmall);
366                }
367                let length = b[*pos + 1] as usize;
368                *pos += 2;
369                if b.len() < *pos + length {
370                    return Err(ScriptError::DataTooSmall);
371                }
372                let data = b[*pos..*pos + length].to_vec();
373                *pos += length;
374                Ok(ScriptChunk { op: OP_PUSHDATA1, data: Some(data) })
375            }
376            OP_PUSHDATA2 => {
377                if b.len() < *pos + 3 {
378                    return Err(ScriptError::DataTooSmall);
379                }
380                let length = u16::from_le_bytes([b[*pos + 1], b[*pos + 2]]) as usize;
381                *pos += 3;
382                if b.len() < *pos + length {
383                    return Err(ScriptError::DataTooSmall);
384                }
385                let data = b[*pos..*pos + length].to_vec();
386                *pos += length;
387                Ok(ScriptChunk { op: OP_PUSHDATA2, data: Some(data) })
388            }
389            OP_PUSHDATA4 => {
390                if b.len() < *pos + 5 {
391                    return Err(ScriptError::DataTooSmall);
392                }
393                let length = u32::from_le_bytes([
394                    b[*pos + 1], b[*pos + 2], b[*pos + 3], b[*pos + 4],
395                ]) as usize;
396                *pos += 5;
397                if b.len() < *pos + length {
398                    return Err(ScriptError::DataTooSmall);
399                }
400                let data = b[*pos..*pos + length].to_vec();
401                *pos += length;
402                Ok(ScriptChunk { op: OP_PUSHDATA4, data: Some(data) })
403            }
404            _ if op >= OP_DATA_1 && op < OP_PUSHDATA1 => {
405                let length = op as usize;
406                if b.len() < *pos + 1 + length {
407                    return Err(ScriptError::DataTooSmall);
408                }
409                let data = b[*pos + 1..*pos + 1 + length].to_vec();
410                *pos += 1 + length;
411                Ok(ScriptChunk { op, data: Some(data) })
412            }
413            _ => {
414                *pos += 1;
415                Ok(ScriptChunk { op, data: None })
416            }
417        }
418    }
419}
420
421impl Default for Script {
422    fn default() -> Self {
423        Self::new()
424    }
425}
426
427impl fmt::Display for Script {
428    /// Display the script as a lowercase hex string.
429    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430        write!(f, "{}", self.to_hex())
431    }
432}
433
434impl fmt::Debug for Script {
435    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436        write!(f, "Script({})", self.to_hex())
437    }
438}
439
440impl serde::Serialize for Script {
441    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
442    where
443        S: serde::Serializer,
444    {
445        serializer.serialize_str(&self.to_hex())
446    }
447}
448
449impl<'de> serde::Deserialize<'de> for Script {
450    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
451    where
452        D: serde::Deserializer<'de>,
453    {
454        let s = String::deserialize(deserializer)?;
455        Script::from_hex(&s).map_err(serde::de::Error::custom)
456    }
457}
458
459#[cfg(test)]
460mod tests {
461    //! Tests for the Script type.
462    //!
463    //! Covers construction from hex/ASM, serialization roundtrips, script
464    //! classification (P2PKH, P2PK, P2SH, data, multisig), public key hash
465    //! extraction, push data operations, opcode appending, and equality checks.
466    //! Test vectors are derived from the Go SDK reference implementation.
467
468    use super::*;
469    use crate::opcodes::*;
470
471    // -----------------------------------------------------------------------
472    // Construction & roundtrip tests
473    // -----------------------------------------------------------------------
474
475    /// Verify that from_hex correctly decodes a P2PKH script and to_hex
476    /// produces the same lowercase hex string.
477    #[test]
478    fn test_from_hex_roundtrip() {
479        let hex_str = "76a914e2a623699e81b291c0327f408fea765d534baa2a88ac";
480        let script = Script::from_hex(hex_str).expect("valid hex should parse");
481        assert_eq!(script.to_hex(), hex_str);
482    }
483
484    /// Verify that from_hex with an empty string produces an empty script.
485    #[test]
486    fn test_from_hex_empty() {
487        let script = Script::from_hex("").expect("empty hex should parse");
488        assert!(script.is_empty());
489        assert_eq!(script.to_hex(), "");
490    }
491
492    /// Verify that from_hex rejects invalid hex characters.
493    #[test]
494    fn test_from_hex_invalid() {
495        let result = Script::from_hex("ZZZZ");
496        assert!(result.is_err());
497    }
498
499    /// Verify that to_asm produces the expected ASM string for a P2PKH script.
500    #[test]
501    fn test_to_asm_p2pkh() {
502        let hex_str = "76a914e2a623699e81b291c0327f408fea765d534baa2a88ac";
503        let script = Script::from_hex(hex_str).expect("valid hex should parse");
504        let asm = script.to_asm();
505        assert_eq!(
506            asm,
507            "OP_DUP OP_HASH160 e2a623699e81b291c0327f408fea765d534baa2a OP_EQUALVERIFY OP_CHECKSIG"
508        );
509    }
510
511    /// Verify that an empty script produces an empty ASM string.
512    #[test]
513    fn test_to_asm_empty() {
514        let script = Script::from_hex("").expect("empty hex should parse");
515        assert_eq!(script.to_asm(), "");
516    }
517
518    /// Verify that from_asm correctly parses a P2PKH ASM string and produces
519    /// the expected hex output.
520    #[test]
521    fn test_from_asm_p2pkh() {
522        let asm = "OP_DUP OP_HASH160 e2a623699e81b291c0327f408fea765d534baa2a OP_EQUALVERIFY OP_CHECKSIG";
523        let script = Script::from_asm(asm).expect("valid ASM should parse");
524        assert_eq!(
525            script.to_hex(),
526            "76a914e2a623699e81b291c0327f408fea765d534baa2a88ac"
527        );
528    }
529
530    /// Verify that from_asm with an empty string produces an empty script.
531    #[test]
532    fn test_from_asm_empty() {
533        let script = Script::from_asm("").expect("empty ASM should parse");
534        assert!(script.is_empty());
535    }
536
537    /// Verify that hex -> ASM -> hex roundtrip preserves the script.
538    #[test]
539    fn test_hex_asm_roundtrip() {
540        let hex_str = "76a914e2a623699e81b291c0327f408fea765d534baa2a88ac";
541        let script = Script::from_hex(hex_str).expect("valid hex should parse");
542        let asm = script.to_asm();
543        let script2 = Script::from_asm(&asm).expect("roundtrip ASM should parse");
544        assert_eq!(script.to_hex(), script2.to_hex());
545    }
546
547    // -----------------------------------------------------------------------
548    // Script classification tests
549    // -----------------------------------------------------------------------
550
551    /// Verify is_p2pkh returns true for a standard P2PKH script.
552    #[test]
553    fn test_is_p2pkh() {
554        let script = Script::from_hex("76a91403ececf2d12a7f614aef4c82ecf13c303bd9975d88ac")
555            .expect("valid hex");
556        assert!(script.is_p2pkh());
557    }
558
559    /// Verify is_p2pkh returns false for a non-P2PKH script.
560    #[test]
561    fn test_is_p2pkh_false_for_p2sh() {
562        let script = Script::from_hex("a9149de5aeaff9c48431ba4dd6e8af73d51f38e451cb87")
563            .expect("valid hex");
564        assert!(!script.is_p2pkh());
565    }
566
567    /// Verify is_p2pk returns true for a compressed-key P2PK script.
568    #[test]
569    fn test_is_p2pk() {
570        let script = Script::from_hex(
571            "2102f0d97c290e79bf2a8660c406aa56b6f189ff79f2245cc5aff82808b58131b4d5ac",
572        )
573        .expect("valid hex");
574        assert!(script.is_p2pk());
575    }
576
577    /// Verify is_p2pk returns false for a P2PKH script.
578    #[test]
579    fn test_is_p2pk_false_for_p2pkh() {
580        let script = Script::from_hex("76a91403ececf2d12a7f614aef4c82ecf13c303bd9975d88ac")
581            .expect("valid hex");
582        assert!(!script.is_p2pk());
583    }
584
585    /// Verify is_p2sh returns true for a standard P2SH script.
586    #[test]
587    fn test_is_p2sh() {
588        let script = Script::from_hex("a9149de5aeaff9c48431ba4dd6e8af73d51f38e451cb87")
589            .expect("valid hex");
590        assert!(script.is_p2sh());
591    }
592
593    /// Verify is_p2sh returns false for a P2PKH script.
594    #[test]
595    fn test_is_p2sh_false_for_p2pkh() {
596        let script = Script::from_hex("76a91403ececf2d12a7f614aef4c82ecf13c303bd9975d88ac")
597            .expect("valid hex");
598        assert!(!script.is_p2sh());
599    }
600
601    /// Verify is_data returns true for an OP_FALSE OP_RETURN data script.
602    #[test]
603    fn test_is_data_op_false_op_return() {
604        // OP_FALSE OP_RETURN followed by data
605        let script = Script::from_hex(
606            "006a04ac1eed884d53027b2276657273696f6e223a22302e31222c22686569676874223a3634323436302c22707265764d696e65724964223a22303365393264336535633366376264393435646662663438653761393933393362316266623366313166333830616533306432383665376666326165633561323730227d"
607        ).expect("valid hex");
608        assert!(script.is_data());
609    }
610
611    /// Verify is_data returns true for a plain OP_RETURN script.
612    #[test]
613    fn test_is_data_op_return() {
614        let script = Script::from_bytes(&[OP_RETURN, 0x04, 0x01, 0x02, 0x03, 0x04]);
615        assert!(script.is_data());
616    }
617
618    /// Verify is_data returns false for a P2PKH script.
619    #[test]
620    fn test_is_data_false_for_p2pkh() {
621        let script = Script::from_hex("76a91403ececf2d12a7f614aef4c82ecf13c303bd9975d88ac")
622            .expect("valid hex");
623        assert!(!script.is_data());
624    }
625
626    /// Verify is_multisig_out returns true for a valid multisig script.
627    #[test]
628    fn test_is_multisig_out() {
629        // OP_2 <pubkey1> <pubkey2> <pubkey3> OP_3 OP_CHECKMULTISIG
630        let script = Script::from_hex("5201110122013353ae").expect("valid hex");
631        assert!(script.is_multisig_out());
632    }
633
634    /// Verify is_multisig_out returns false for a non-multisig script.
635    #[test]
636    fn test_is_multisig_out_false_for_p2pkh() {
637        let script = Script::from_hex("76a91403ececf2d12a7f614aef4c82ecf13c303bd9975d88ac")
638            .expect("valid hex");
639        assert!(!script.is_multisig_out());
640    }
641
642    // -----------------------------------------------------------------------
643    // Public key hash extraction
644    // -----------------------------------------------------------------------
645
646    /// Verify public_key_hash extracts the correct 20-byte hash from P2PKH.
647    #[test]
648    fn test_public_key_hash() {
649        let script = Script::from_hex("76a91404d03f746652cfcb6cb55119ab473a045137d26588ac")
650            .expect("valid hex");
651        let pkh = script.public_key_hash().expect("should extract PKH");
652        assert_eq!(hex::encode(&pkh), "04d03f746652cfcb6cb55119ab473a045137d265");
653    }
654
655    /// Verify public_key_hash from bytes matches the hex-constructed version.
656    #[test]
657    fn test_public_key_hash_from_bytes() {
658        let bytes = hex::decode("76a91404d03f746652cfcb6cb55119ab473a045137d26588ac")
659            .expect("valid hex");
660        let script = Script::from_bytes(&bytes);
661        let pkh = script.public_key_hash().expect("should extract PKH");
662        assert_eq!(hex::encode(&pkh), "04d03f746652cfcb6cb55119ab473a045137d265");
663    }
664
665    /// Verify public_key_hash returns an error for an empty script.
666    #[test]
667    fn test_public_key_hash_empty() {
668        let script = Script::new();
669        let result = script.public_key_hash();
670        assert!(result.is_err());
671    }
672
673    /// Verify public_key_hash returns an error for a non-P2PKH script (OP_DUP alone).
674    #[test]
675    fn test_public_key_hash_nonstandard() {
676        let script = Script::from_hex("76").expect("valid hex");
677        let result = script.public_key_hash();
678        assert!(result.is_err());
679    }
680
681    // -----------------------------------------------------------------------
682    // Append operations
683    // -----------------------------------------------------------------------
684
685    /// Verify append_push_data correctly pushes small data (<=75 bytes).
686    #[test]
687    fn test_append_push_data_small() {
688        let mut script = Script::new();
689        let data = vec![0x01, 0x02, 0x03, 0x04, 0x05];
690        script.append_push_data(&data).expect("push should succeed");
691        // 5-byte push: prefix is 0x05 (length), then the 5 data bytes
692        assert_eq!(script.to_hex(), "050102030405");
693    }
694
695    /// Verify append_push_data uses OP_PUSHDATA1 for data in 76..=255 range.
696    #[test]
697    fn test_append_push_data_medium() {
698        let mut script = Script::new();
699        let data = vec![0xAA; 80]; // 80 bytes triggers OP_PUSHDATA1
700        script.append_push_data(&data).expect("push should succeed");
701        let hex_str = script.to_hex();
702        // OP_PUSHDATA1 = 0x4c, then 0x50 (80), then 80 bytes of 0xAA
703        assert_eq!(&hex_str[..4], "4c50");
704        assert_eq!(hex_str.len(), 4 + 80 * 2);
705    }
706
707    /// Verify append_push_data uses OP_PUSHDATA2 for data in 256..=65535 range.
708    #[test]
709    fn test_append_push_data_large() {
710        let mut script = Script::new();
711        let data = vec![0xBB; 256]; // 256 bytes triggers OP_PUSHDATA2
712        script.append_push_data(&data).expect("push should succeed");
713        let hex_str = script.to_hex();
714        // OP_PUSHDATA2 = 0x4d, then 0x0001 (256 LE), then 256 bytes of 0xBB
715        assert_eq!(&hex_str[..6], "4d0001");
716        assert_eq!(hex_str.len(), 6 + 256 * 2);
717    }
718
719    /// Verify append_opcodes appends a single valid opcode.
720    #[test]
721    fn test_append_opcodes_single() {
722        let mut script = Script::from_asm("OP_2 OP_2 OP_ADD").expect("valid ASM");
723        script
724            .append_opcodes(&[OP_EQUALVERIFY])
725            .expect("should succeed");
726        assert_eq!(script.to_asm(), "OP_2 OP_2 OP_ADD OP_EQUALVERIFY");
727    }
728
729    /// Verify append_opcodes appends multiple valid opcodes.
730    #[test]
731    fn test_append_opcodes_multiple() {
732        let mut script = Script::from_asm("OP_2 OP_2 OP_ADD").expect("valid ASM");
733        script
734            .append_opcodes(&[OP_EQUAL, OP_VERIFY])
735            .expect("should succeed");
736        assert_eq!(script.to_asm(), "OP_2 OP_2 OP_ADD OP_EQUAL OP_VERIFY");
737    }
738
739    /// Verify append_opcodes rejects push data opcodes (OP_PUSHDATA1 etc.).
740    #[test]
741    fn test_append_opcodes_rejects_pushdata() {
742        let mut script = Script::from_asm("OP_2 OP_2 OP_ADD").expect("valid ASM");
743        let result = script.append_opcodes(&[OP_EQUAL, OP_PUSHDATA1]);
744        assert!(result.is_err());
745    }
746
747    // -----------------------------------------------------------------------
748    // Equality
749    // -----------------------------------------------------------------------
750
751    /// Verify two scripts built from the same hex are equal.
752    #[test]
753    fn test_equals_same_hex() {
754        let s1 = Script::from_hex("76a91404d03f746652cfcb6cb55119ab473a045137d26588ac")
755            .expect("valid hex");
756        let s2 = Script::from_hex("76a91404d03f746652cfcb6cb55119ab473a045137d26588ac")
757            .expect("valid hex");
758        assert!(s1.equals(&s2));
759        assert_eq!(s1, s2);
760    }
761
762    /// Verify two scripts built from the same bytes are equal.
763    #[test]
764    fn test_equals_same_bytes() {
765        let bytes = hex::decode("5201110122013353ae").expect("valid hex");
766        let s1 = Script::from_bytes(&bytes);
767        let s2 = Script::from_bytes(&bytes);
768        assert!(s1.equals(&s2));
769    }
770
771    /// Verify two scripts with different bytes are not equal.
772    #[test]
773    fn test_not_equals_different_hex() {
774        let s1 = Script::from_hex("76a91404d03f746652cfcb6cb55119ab473a045137d26566ac")
775            .expect("valid hex");
776        let s2 = Script::from_hex("76a91404d03f746652cfcb6cb55119ab473a045137d26588ac")
777            .expect("valid hex");
778        assert!(!s1.equals(&s2));
779        assert_ne!(s1, s2);
780    }
781
782    // -----------------------------------------------------------------------
783    // Serialization (JSON)
784    // -----------------------------------------------------------------------
785
786    /// Verify Script serializes to a hex JSON string.
787    #[test]
788    fn test_serde_serialize() {
789        let script = Script::from_asm("OP_2 OP_2 OP_ADD OP_4 OP_EQUALVERIFY")
790            .expect("valid ASM");
791        let json_str = serde_json::to_string(&script).expect("should serialize");
792        assert_eq!(json_str, r#""5252935488""#);
793    }
794
795    /// Verify Script deserializes from a hex JSON string.
796    #[test]
797    fn test_serde_deserialize() {
798        let json_str = r#""5252935488""#;
799        let script: Script = serde_json::from_str(json_str).expect("should deserialize");
800        assert_eq!(script.to_hex(), "5252935488");
801    }
802
803    /// Verify Script deserializes from an empty hex JSON string.
804    #[test]
805    fn test_serde_deserialize_empty() {
806        let json_str = r#""""#;
807        let script: Script = serde_json::from_str(json_str).expect("should deserialize");
808        assert_eq!(script.to_hex(), "");
809    }
810
811    // -----------------------------------------------------------------------
812    // Display / Debug
813    // -----------------------------------------------------------------------
814
815    /// Verify Display trait outputs the hex string.
816    #[test]
817    fn test_display() {
818        let script = Script::from_hex("76a914e2a623699e81b291c0327f408fea765d534baa2a88ac")
819            .expect("valid hex");
820        assert_eq!(
821            format!("{}", script),
822            "76a914e2a623699e81b291c0327f408fea765d534baa2a88ac"
823        );
824    }
825
826    /// Verify Debug trait outputs the Script(...) format.
827    #[test]
828    fn test_debug() {
829        let script = Script::from_hex("76a914e2a623699e81b291c0327f408fea765d534baa2a88ac")
830            .expect("valid hex");
831        let debug_str = format!("{:?}", script);
832        assert!(debug_str.starts_with("Script("));
833        assert!(debug_str.contains("76a914"));
834    }
835
836    // -----------------------------------------------------------------------
837    // OP_RETURN / data script ASM roundtrip
838    // -----------------------------------------------------------------------
839
840    /// Verify OP_FALSE OP_RETURN data scripts produce correct ASM and roundtrip.
841    #[test]
842    fn test_op_false_op_return_asm() {
843        let hex_str = "006a223139694733575459537362796f7333754a373333794b347a45696f69314665734e55010042666166383166326364346433663239383061623162363564616166656231656631333561626339643534386461633466366134656361623230653033656365362d300274780134";
844        let script = Script::from_hex(hex_str).expect("valid hex");
845        let asm = script.to_asm();
846        // The ASM should start with OP_FALSE OP_RETURN
847        assert!(asm.starts_with("OP_FALSE OP_RETURN"));
848    }
849
850    // -----------------------------------------------------------------------
851    // Misc edge cases
852    // -----------------------------------------------------------------------
853
854    /// Verify from_bytes and len work as expected.
855    #[test]
856    fn test_from_bytes_len() {
857        let bytes = hex::decode("76a91403ececf2d12a7f614aef4c82ecf13c303bd9975d88ac")
858            .expect("valid hex");
859        let script = Script::from_bytes(&bytes);
860        assert_eq!(script.len(), 25);
861        assert!(!script.is_empty());
862    }
863
864    /// Verify Default produces an empty script.
865    #[test]
866    fn test_default() {
867        let script = Script::default();
868        assert!(script.is_empty());
869        assert_eq!(script.len(), 0);
870    }
871}