rustywallet_tx/
script.rs

1//! Script building utilities.
2
3use sha2::{Sha256, Digest};
4use ripemd::Ripemd160;
5
6/// Script opcodes.
7pub mod opcodes {
8    pub const OP_0: u8 = 0x00;
9    pub const OP_PUSHBYTES_20: u8 = 0x14;
10    pub const OP_PUSHBYTES_32: u8 = 0x20;
11    pub const OP_DUP: u8 = 0x76;
12    pub const OP_HASH160: u8 = 0xa9;
13    pub const OP_EQUALVERIFY: u8 = 0x88;
14    pub const OP_CHECKSIG: u8 = 0xac;
15    pub const OP_EQUAL: u8 = 0x87;
16}
17
18/// Build a P2PKH (Pay-to-Public-Key-Hash) script.
19///
20/// Format: OP_DUP OP_HASH160 <20-byte hash> OP_EQUALVERIFY OP_CHECKSIG
21pub fn build_p2pkh_script(pubkey_hash: &[u8; 20]) -> Vec<u8> {
22    let mut script = Vec::with_capacity(25);
23    script.push(opcodes::OP_DUP);
24    script.push(opcodes::OP_HASH160);
25    script.push(opcodes::OP_PUSHBYTES_20);
26    script.extend_from_slice(pubkey_hash);
27    script.push(opcodes::OP_EQUALVERIFY);
28    script.push(opcodes::OP_CHECKSIG);
29    script
30}
31
32/// Build a P2WPKH (Pay-to-Witness-Public-Key-Hash) script.
33///
34/// Format: OP_0 <20-byte hash>
35pub fn build_p2wpkh_script(pubkey_hash: &[u8; 20]) -> Vec<u8> {
36    let mut script = Vec::with_capacity(22);
37    script.push(opcodes::OP_0);
38    script.push(opcodes::OP_PUSHBYTES_20);
39    script.extend_from_slice(pubkey_hash);
40    script
41}
42
43/// Build a P2TR (Pay-to-Taproot) script.
44///
45/// Format: OP_1 <32-byte x-only pubkey>
46pub fn build_p2tr_script(x_only_pubkey: &[u8; 32]) -> Vec<u8> {
47    let mut script = Vec::with_capacity(34);
48    script.push(0x51); // OP_1
49    script.push(opcodes::OP_PUSHBYTES_32);
50    script.extend_from_slice(x_only_pubkey);
51    script
52}
53
54/// Hash160 (SHA256 + RIPEMD160).
55pub fn hash160(data: &[u8]) -> [u8; 20] {
56    let sha = Sha256::digest(data);
57    let ripemd = Ripemd160::digest(sha);
58    ripemd.into()
59}
60
61/// Build P2PKH scriptPubKey from compressed public key.
62pub fn p2pkh_script_from_pubkey(pubkey: &[u8; 33]) -> Vec<u8> {
63    let pubkey_hash = hash160(pubkey);
64    build_p2pkh_script(&pubkey_hash)
65}
66
67/// Build P2WPKH scriptPubKey from compressed public key.
68pub fn p2wpkh_script_from_pubkey(pubkey: &[u8; 33]) -> Vec<u8> {
69    let pubkey_hash = hash160(pubkey);
70    build_p2wpkh_script(&pubkey_hash)
71}
72
73/// Extract pubkey hash from P2PKH script.
74pub fn extract_p2pkh_hash(script: &[u8]) -> Option<[u8; 20]> {
75    if script.len() == 25
76        && script[0] == opcodes::OP_DUP
77        && script[1] == opcodes::OP_HASH160
78        && script[2] == opcodes::OP_PUSHBYTES_20
79        && script[23] == opcodes::OP_EQUALVERIFY
80        && script[24] == opcodes::OP_CHECKSIG
81    {
82        let mut hash = [0u8; 20];
83        hash.copy_from_slice(&script[3..23]);
84        Some(hash)
85    } else {
86        None
87    }
88}
89
90/// Extract pubkey hash from P2WPKH script.
91pub fn extract_p2wpkh_hash(script: &[u8]) -> Option<[u8; 20]> {
92    if script.len() == 22
93        && script[0] == opcodes::OP_0
94        && script[1] == opcodes::OP_PUSHBYTES_20
95    {
96        let mut hash = [0u8; 20];
97        hash.copy_from_slice(&script[2..22]);
98        Some(hash)
99    } else {
100        None
101    }
102}
103
104/// Check if script is P2PKH.
105pub fn is_p2pkh(script: &[u8]) -> bool {
106    extract_p2pkh_hash(script).is_some()
107}
108
109/// Check if script is P2WPKH.
110pub fn is_p2wpkh(script: &[u8]) -> bool {
111    extract_p2wpkh_hash(script).is_some()
112}
113
114/// Check if script is P2TR.
115pub fn is_p2tr(script: &[u8]) -> bool {
116    script.len() == 34 && script[0] == 0x51 && script[1] == opcodes::OP_PUSHBYTES_32
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_build_p2pkh_script() {
125        let hash = [0u8; 20];
126        let script = build_p2pkh_script(&hash);
127        assert_eq!(script.len(), 25);
128        assert!(is_p2pkh(&script));
129    }
130
131    #[test]
132    fn test_build_p2wpkh_script() {
133        let hash = [0u8; 20];
134        let script = build_p2wpkh_script(&hash);
135        assert_eq!(script.len(), 22);
136        assert!(is_p2wpkh(&script));
137    }
138
139    #[test]
140    fn test_build_p2tr_script() {
141        let pubkey = [0u8; 32];
142        let script = build_p2tr_script(&pubkey);
143        assert_eq!(script.len(), 34);
144        assert!(is_p2tr(&script));
145    }
146
147    #[test]
148    fn test_extract_p2pkh_hash() {
149        let hash = [1u8; 20];
150        let script = build_p2pkh_script(&hash);
151        let extracted = extract_p2pkh_hash(&script).unwrap();
152        assert_eq!(extracted, hash);
153    }
154}