rustywallet_descriptor/
script.rs

1//! Script generation from descriptors
2//!
3//! Generates script pubkeys for different descriptor types.
4
5use crate::descriptor::Descriptor;
6use crate::error::DescriptorError;
7use rustywallet_keys::public_key::{PublicKey, PublicKeyFormat};
8use sha2::{Sha256, Digest};
9
10/// Script pubkey generated from a descriptor
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct ScriptPubkey {
13    /// The raw script bytes
14    pub script: Vec<u8>,
15    /// Script type
16    pub script_type: ScriptType,
17}
18
19/// Type of script
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum ScriptType {
22    /// P2PK - Pay to pubkey
23    P2pk,
24    /// P2PKH - Pay to pubkey hash
25    P2pkh,
26    /// P2SH - Pay to script hash
27    P2sh,
28    /// P2WPKH - Pay to witness pubkey hash
29    P2wpkh,
30    /// P2WSH - Pay to witness script hash
31    P2wsh,
32    /// P2TR - Pay to Taproot
33    P2tr,
34}
35
36impl ScriptPubkey {
37    /// Create a P2PK script
38    pub fn p2pk(pubkey: &PublicKey) -> Self {
39        let pk_bytes = hex::decode(pubkey.to_hex(PublicKeyFormat::Compressed)).unwrap();
40        let mut script = Vec::with_capacity(pk_bytes.len() + 2);
41        script.push(pk_bytes.len() as u8); // Push pubkey length
42        script.extend_from_slice(&pk_bytes);
43        script.push(0xac); // OP_CHECKSIG
44        
45        Self {
46            script,
47            script_type: ScriptType::P2pk,
48        }
49    }
50
51    /// Create a P2PKH script
52    pub fn p2pkh(pubkey: &PublicKey) -> Self {
53        let pk_bytes = hex::decode(pubkey.to_hex(PublicKeyFormat::Compressed)).unwrap();
54        let pubkey_hash = hash160(&pk_bytes);
55        
56        let mut script = Vec::with_capacity(25);
57        script.push(0x76); // OP_DUP
58        script.push(0xa9); // OP_HASH160
59        script.push(0x14); // Push 20 bytes
60        script.extend_from_slice(&pubkey_hash);
61        script.push(0x88); // OP_EQUALVERIFY
62        script.push(0xac); // OP_CHECKSIG
63        
64        Self {
65            script,
66            script_type: ScriptType::P2pkh,
67        }
68    }
69
70    /// Create a P2SH script
71    pub fn p2sh(redeem_script: &[u8]) -> Self {
72        let script_hash = hash160(redeem_script);
73        
74        let mut script = Vec::with_capacity(23);
75        script.push(0xa9); // OP_HASH160
76        script.push(0x14); // Push 20 bytes
77        script.extend_from_slice(&script_hash);
78        script.push(0x87); // OP_EQUAL
79        
80        Self {
81            script,
82            script_type: ScriptType::P2sh,
83        }
84    }
85
86    /// Create a P2WPKH script
87    pub fn p2wpkh(pubkey: &PublicKey) -> Self {
88        let pk_bytes = hex::decode(pubkey.to_hex(PublicKeyFormat::Compressed)).unwrap();
89        let pubkey_hash = hash160(&pk_bytes);
90        
91        let mut script = Vec::with_capacity(22);
92        script.push(0x00); // OP_0 (witness version 0)
93        script.push(0x14); // Push 20 bytes
94        script.extend_from_slice(&pubkey_hash);
95        
96        Self {
97            script,
98            script_type: ScriptType::P2wpkh,
99        }
100    }
101
102    /// Create a P2WSH script
103    pub fn p2wsh(witness_script: &[u8]) -> Self {
104        let script_hash = sha256(witness_script);
105        
106        let mut script = Vec::with_capacity(34);
107        script.push(0x00); // OP_0 (witness version 0)
108        script.push(0x20); // Push 32 bytes
109        script.extend_from_slice(&script_hash);
110        
111        Self {
112            script,
113            script_type: ScriptType::P2wsh,
114        }
115    }
116
117    /// Create a P2TR script
118    pub fn p2tr(output_key: &[u8; 32]) -> Self {
119        let mut script = Vec::with_capacity(34);
120        script.push(0x51); // OP_1 (witness version 1)
121        script.push(0x20); // Push 32 bytes
122        script.extend_from_slice(output_key);
123        
124        Self {
125            script,
126            script_type: ScriptType::P2tr,
127        }
128    }
129
130    /// Create a multisig script
131    pub fn multisig(threshold: usize, pubkeys: &[PublicKey]) -> Result<Vec<u8>, DescriptorError> {
132        if threshold == 0 || threshold > pubkeys.len() || pubkeys.len() > 16 {
133            return Err(DescriptorError::InvalidThreshold {
134                k: threshold,
135                n: pubkeys.len(),
136            });
137        }
138        
139        let mut script = Vec::new();
140        
141        // OP_k (threshold)
142        script.push(0x50 + threshold as u8); // OP_1 = 0x51, etc.
143        
144        // Push each pubkey
145        for pk in pubkeys {
146            let pk_bytes = hex::decode(pk.to_hex(PublicKeyFormat::Compressed)).unwrap();
147            script.push(pk_bytes.len() as u8);
148            script.extend_from_slice(&pk_bytes);
149        }
150        
151        // OP_n (total keys)
152        script.push(0x50 + pubkeys.len() as u8);
153        
154        // OP_CHECKMULTISIG
155        script.push(0xae);
156        
157        Ok(script)
158    }
159
160    /// Get the script bytes
161    pub fn as_bytes(&self) -> &[u8] {
162        &self.script
163    }
164
165    /// Get the script type
166    pub fn script_type(&self) -> ScriptType {
167        self.script_type
168    }
169
170    /// Check if this is a witness script
171    pub fn is_witness(&self) -> bool {
172        matches!(
173            self.script_type,
174            ScriptType::P2wpkh | ScriptType::P2wsh | ScriptType::P2tr
175        )
176    }
177}
178
179/// Generate script pubkey from descriptor at a specific index
180pub fn generate_script_pubkey(
181    descriptor: &Descriptor,
182    index: u32,
183) -> Result<ScriptPubkey, DescriptorError> {
184    match descriptor {
185        Descriptor::Pk(key) => {
186            let pubkey = key.derive_public_key(index)?;
187            Ok(ScriptPubkey::p2pk(&pubkey))
188        }
189        Descriptor::Pkh(key) => {
190            let pubkey = key.derive_public_key(index)?;
191            Ok(ScriptPubkey::p2pkh(&pubkey))
192        }
193        Descriptor::Wpkh(key) => {
194            let pubkey = key.derive_public_key(index)?;
195            Ok(ScriptPubkey::p2wpkh(&pubkey))
196        }
197        Descriptor::Sh(inner) => {
198            // Generate the inner script as redeem script
199            let inner_script = generate_redeem_script(inner, index)?;
200            Ok(ScriptPubkey::p2sh(&inner_script))
201        }
202        Descriptor::Wsh(inner) => {
203            // Generate the inner script as witness script
204            let witness_script = generate_witness_script(inner, index)?;
205            Ok(ScriptPubkey::p2wsh(&witness_script))
206        }
207        Descriptor::Tr(key) => {
208            let pubkey = key.derive_public_key(index)?;
209            // For Taproot, we need the x-only pubkey (32 bytes)
210            let pk_hex = pubkey.to_hex(PublicKeyFormat::Compressed);
211            let pk_bytes = hex::decode(&pk_hex).unwrap();
212            let mut xonly = [0u8; 32];
213            xonly.copy_from_slice(&pk_bytes[1..33]); // Skip the prefix byte
214            Ok(ScriptPubkey::p2tr(&xonly))
215        }
216        Descriptor::Multi { threshold, keys } => {
217            let pubkeys: Result<Vec<_>, _> = keys
218                .iter()
219                .map(|k| k.derive_public_key(index))
220                .collect();
221            let pubkeys = pubkeys?;
222            let script = ScriptPubkey::multisig(*threshold, &pubkeys)?;
223            // Bare multisig - wrap in P2SH
224            Ok(ScriptPubkey::p2sh(&script))
225        }
226        Descriptor::SortedMulti { threshold, keys } => {
227            let mut pubkeys: Vec<_> = keys
228                .iter()
229                .map(|k| k.derive_public_key(index))
230                .collect::<Result<Vec<_>, _>>()?;
231            // Sort pubkeys lexicographically
232            pubkeys.sort_by(|a, b| {
233                let a_hex = a.to_hex(PublicKeyFormat::Compressed);
234                let b_hex = b.to_hex(PublicKeyFormat::Compressed);
235                a_hex.cmp(&b_hex)
236            });
237            let script = ScriptPubkey::multisig(*threshold, &pubkeys)?;
238            // Bare multisig - wrap in P2SH
239            Ok(ScriptPubkey::p2sh(&script))
240        }
241    }
242}
243
244/// Generate redeem script for P2SH
245fn generate_redeem_script(descriptor: &Descriptor, index: u32) -> Result<Vec<u8>, DescriptorError> {
246    match descriptor {
247        Descriptor::Wpkh(key) => {
248            // P2SH-P2WPKH: redeem script is the P2WPKH script
249            let pubkey = key.derive_public_key(index)?;
250            let script = ScriptPubkey::p2wpkh(&pubkey);
251            Ok(script.script)
252        }
253        Descriptor::Wsh(inner) => {
254            // P2SH-P2WSH: redeem script is the P2WSH script
255            let witness_script = generate_witness_script(inner, index)?;
256            let script = ScriptPubkey::p2wsh(&witness_script);
257            Ok(script.script)
258        }
259        Descriptor::Multi { threshold, keys } => {
260            let pubkeys: Result<Vec<_>, _> = keys
261                .iter()
262                .map(|k| k.derive_public_key(index))
263                .collect();
264            ScriptPubkey::multisig(*threshold, &pubkeys?)
265        }
266        Descriptor::SortedMulti { threshold, keys } => {
267            let mut pubkeys: Vec<_> = keys
268                .iter()
269                .map(|k| k.derive_public_key(index))
270                .collect::<Result<Vec<_>, _>>()?;
271            pubkeys.sort_by(|a, b| {
272                let a_hex = a.to_hex(PublicKeyFormat::Compressed);
273                let b_hex = b.to_hex(PublicKeyFormat::Compressed);
274                a_hex.cmp(&b_hex)
275            });
276            ScriptPubkey::multisig(*threshold, &pubkeys)
277        }
278        _ => Err(DescriptorError::ScriptError(format!(
279            "Cannot create redeem script for {}",
280            descriptor.descriptor_type()
281        ))),
282    }
283}
284
285/// Generate witness script for P2WSH
286fn generate_witness_script(descriptor: &Descriptor, index: u32) -> Result<Vec<u8>, DescriptorError> {
287    match descriptor {
288        Descriptor::Multi { threshold, keys } => {
289            let pubkeys: Result<Vec<_>, _> = keys
290                .iter()
291                .map(|k| k.derive_public_key(index))
292                .collect();
293            ScriptPubkey::multisig(*threshold, &pubkeys?)
294        }
295        Descriptor::SortedMulti { threshold, keys } => {
296            let mut pubkeys: Vec<_> = keys
297                .iter()
298                .map(|k| k.derive_public_key(index))
299                .collect::<Result<Vec<_>, _>>()?;
300            pubkeys.sort_by(|a, b| {
301                let a_hex = a.to_hex(PublicKeyFormat::Compressed);
302                let b_hex = b.to_hex(PublicKeyFormat::Compressed);
303                a_hex.cmp(&b_hex)
304            });
305            ScriptPubkey::multisig(*threshold, &pubkeys)
306        }
307        _ => Err(DescriptorError::ScriptError(format!(
308            "Cannot create witness script for {}",
309            descriptor.descriptor_type()
310        ))),
311    }
312}
313
314/// HASH160 = RIPEMD160(SHA256(data))
315fn hash160(data: &[u8]) -> [u8; 20] {
316    use ripemd::Ripemd160;
317    
318    let sha = Sha256::digest(data);
319    let ripemd = Ripemd160::digest(sha);
320    
321    let mut result = [0u8; 20];
322    result.copy_from_slice(&ripemd);
323    result
324}
325
326/// SHA256
327fn sha256(data: &[u8]) -> [u8; 32] {
328    let hash = Sha256::digest(data);
329    let mut result = [0u8; 32];
330    result.copy_from_slice(&hash);
331    result
332}
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337
338    #[test]
339    fn test_p2pkh_script() {
340        let pubkey_hex = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5";
341        let pubkey = PublicKey::from_hex(pubkey_hex).unwrap();
342        
343        let script = ScriptPubkey::p2pkh(&pubkey);
344        
345        assert_eq!(script.script_type, ScriptType::P2pkh);
346        assert_eq!(script.script.len(), 25);
347        assert_eq!(script.script[0], 0x76); // OP_DUP
348        assert_eq!(script.script[1], 0xa9); // OP_HASH160
349    }
350
351    #[test]
352    fn test_p2wpkh_script() {
353        let pubkey_hex = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5";
354        let pubkey = PublicKey::from_hex(pubkey_hex).unwrap();
355        
356        let script = ScriptPubkey::p2wpkh(&pubkey);
357        
358        assert_eq!(script.script_type, ScriptType::P2wpkh);
359        assert_eq!(script.script.len(), 22);
360        assert_eq!(script.script[0], 0x00); // OP_0
361        assert_eq!(script.script[1], 0x14); // Push 20 bytes
362    }
363
364    #[test]
365    fn test_p2tr_script() {
366        let output_key = [0x42u8; 32];
367        let script = ScriptPubkey::p2tr(&output_key);
368        
369        assert_eq!(script.script_type, ScriptType::P2tr);
370        assert_eq!(script.script.len(), 34);
371        assert_eq!(script.script[0], 0x51); // OP_1
372        assert_eq!(script.script[1], 0x20); // Push 32 bytes
373    }
374
375    #[test]
376    fn test_multisig_script() {
377        let pk1_hex = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5";
378        let pk2_hex = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
379        
380        let pk1 = PublicKey::from_hex(pk1_hex).unwrap();
381        let pk2 = PublicKey::from_hex(pk2_hex).unwrap();
382        
383        let script = ScriptPubkey::multisig(2, &[pk1, pk2]).unwrap();
384        
385        assert_eq!(script[0], 0x52); // OP_2
386        assert_eq!(*script.last().unwrap(), 0xae); // OP_CHECKMULTISIG
387    }
388}