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