Skip to main content

bsv_rs/script/
locking_script.rs

1//! Locking Script (scriptPubKey).
2//!
3//! The LockingScript class represents a locking script in a Bitcoin SV transaction.
4//! It extends the Script class and is used specifically for output scripts that lock funds.
5
6use super::address::Address;
7use super::chunk::ScriptChunk;
8use super::script::Script;
9use crate::Result;
10
11/// Represents a locking script (output script / scriptPubKey).
12///
13/// A locking script defines the conditions that must be satisfied to spend
14/// the output it locks.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct LockingScript(Script);
17
18impl LockingScript {
19    /// Creates a new empty locking script.
20    pub fn new() -> Self {
21        Self(Script::new())
22    }
23
24    /// Creates a locking script from a vector of chunks.
25    pub fn from_chunks(chunks: Vec<ScriptChunk>) -> Self {
26        Self(Script::from_chunks(chunks))
27    }
28
29    /// Constructs a LockingScript from an ASM formatted string.
30    pub fn from_asm(asm: &str) -> Result<Self> {
31        Ok(Self(Script::from_asm(asm)?))
32    }
33
34    /// Constructs a LockingScript from a hexadecimal string.
35    pub fn from_hex(hex: &str) -> Result<Self> {
36        Ok(Self(Script::from_hex(hex)?))
37    }
38
39    /// Constructs a LockingScript from binary data.
40    pub fn from_binary(bin: &[u8]) -> Result<Self> {
41        Ok(Self(Script::from_binary(bin)?))
42    }
43
44    /// Constructs a LockingScript from a Script.
45    pub fn from_script(script: Script) -> Self {
46        Self(script)
47    }
48
49    /// Returns a reference to the underlying Script.
50    pub fn as_script(&self) -> &Script {
51        &self.0
52    }
53
54    /// Converts this LockingScript into its underlying Script.
55    pub fn into_script(self) -> Script {
56        self.0
57    }
58
59    /// Serializes to ASM string.
60    pub fn to_asm(&self) -> String {
61        self.0.to_asm()
62    }
63
64    /// Serializes to hex string.
65    pub fn to_hex(&self) -> String {
66        self.0.to_hex()
67    }
68
69    /// Serializes to binary.
70    pub fn to_binary(&self) -> Vec<u8> {
71        self.0.to_binary()
72    }
73
74    /// Returns the chunks.
75    pub fn chunks(&self) -> Vec<ScriptChunk> {
76        self.0.chunks()
77    }
78
79    /// Returns the number of chunks.
80    pub fn len(&self) -> usize {
81        self.0.len()
82    }
83
84    /// Returns true if empty.
85    pub fn is_empty(&self) -> bool {
86        self.0.is_empty()
87    }
88
89    /// Checks if push-only.
90    pub fn is_push_only(&self) -> bool {
91        self.0.is_push_only()
92    }
93
94    /// Returns true (this is a locking script).
95    pub fn is_locking_script(&self) -> bool {
96        true
97    }
98
99    /// Returns false (this is not an unlocking script).
100    pub fn is_unlocking_script(&self) -> bool {
101        false
102    }
103
104    /// Extracts a P2PKH address from this locking script, if it matches the
105    /// P2PKH pattern: `OP_DUP OP_HASH160 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG`.
106    ///
107    /// Returns `None` for non-P2PKH scripts.
108    ///
109    /// # Example
110    ///
111    /// ```rust,ignore
112    /// use bsv_rs::script::LockingScript;
113    ///
114    /// let script = LockingScript::from_hex("76a914...88ac")?;
115    /// if let Some(address) = script.to_address() {
116    ///     println!("Address: {}", address);
117    /// }
118    /// ```
119    pub fn to_address(&self) -> Option<Address> {
120        let hash = self.0.extract_pubkey_hash()?;
121        Address::new_from_public_key_hash(&hash, true).ok()
122    }
123}
124
125impl Default for LockingScript {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131impl From<Script> for LockingScript {
132    fn from(script: Script) -> Self {
133        Self(script)
134    }
135}
136
137impl From<LockingScript> for Script {
138    fn from(locking: LockingScript) -> Self {
139        locking.0
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_is_locking_script() {
149        let script = LockingScript::new();
150        assert!(script.is_locking_script());
151        assert!(!script.is_unlocking_script());
152    }
153
154    #[test]
155    fn test_from_hex() {
156        let hex = "76a914000000000000000000000000000000000000000088ac";
157        let script = LockingScript::from_hex(hex).unwrap();
158        assert_eq!(script.to_hex(), hex);
159    }
160
161    #[test]
162    fn test_from_asm() {
163        let asm = "OP_DUP OP_HASH160";
164        let script = LockingScript::from_asm(asm).unwrap();
165        assert_eq!(script.to_hex(), "76a9");
166    }
167
168    #[test]
169    fn test_conversion() {
170        let script = Script::from_hex("76a9").unwrap();
171        let locking = LockingScript::from_script(script.clone());
172        let back: Script = locking.into();
173        assert_eq!(back.to_hex(), script.to_hex());
174    }
175}