1use std::cell::RefCell;
5use std::collections::HashMap;
6
7use crate::hash::HashFn;
8use crate::insertable::{Format, b, ihash, ihash160, lookup, push, ttweak};
9use crate::out::Out;
10use crate::pubkey::PubKey;
11
12pub fn format_def(name: &str) -> Option<Format> {
14 let f = match name {
15 "p2pkh" => vec![
16 b(&[0x76, 0xa9]),
17 push(ihash160(lookup("pubkey:comp"))),
18 b(&[0x88, 0xac]),
19 ],
20 "p2pukh" => vec![
21 b(&[0x76, 0xa9]),
22 push(ihash160(lookup("pubkey:uncomp"))),
23 b(&[0x88, 0xac]),
24 ],
25 "p2pk" => vec![push(lookup("pubkey:comp")), b(&[0xac])],
26 "p2puk" => vec![push(lookup("pubkey:uncomp")), b(&[0xac])],
27 "p2wpkh" => vec![b(&[0x00]), push(ihash160(lookup("pubkey:comp")))],
28 "p2tr" => vec![b(&[0x51]), push(ttweak(lookup("pubkey:comp")))],
29 "p2sh:p2pkh" => vec![b(&[0xa9]), push(ihash160(lookup("p2pkh"))), b(&[0x87])],
30 "p2sh:p2pukh" => vec![b(&[0xa9]), push(ihash160(lookup("p2pukh"))), b(&[0x87])],
31 "p2sh:p2pk" => vec![b(&[0xa9]), push(ihash160(lookup("p2pk"))), b(&[0x87])],
32 "p2sh:p2puk" => vec![b(&[0xa9]), push(ihash160(lookup("p2puk"))), b(&[0x87])],
33 "p2sh:p2wpkh" => vec![b(&[0xa9]), push(ihash160(lookup("p2wpkh"))), b(&[0x87])],
34 "p2wsh:p2pkh" => vec![b(&[0x00]), push(ihash(lookup("p2pkh"), &[HashFn::Sha256]))],
35 "p2wsh:p2pukh" => vec![b(&[0x00]), push(ihash(lookup("p2pukh"), &[HashFn::Sha256]))],
36 "p2wsh:p2pk" => vec![b(&[0x00]), push(ihash(lookup("p2pk"), &[HashFn::Sha256]))],
37 "p2wsh:p2puk" => vec![b(&[0x00]), push(ihash(lookup("p2puk"), &[HashFn::Sha256]))],
38 "p2wsh:p2wpkh" => vec![b(&[0x00]), push(ihash(lookup("p2wpkh"), &[HashFn::Sha256]))],
39 "eth" => vec![ihash(lookup("pubkey:uncomp"), &[HashFn::EtherHash])],
40 "massa_pubkey" => vec![b(&[0x00]), lookup("pubkey:ed25519")],
41 "massa" => vec![
42 b(&[0x00, 0x00]),
43 ihash(lookup("massa_pubkey"), &[HashFn::Blake3]),
44 ],
45 "solana" => vec![lookup("pubkey:ed25519")],
46 _ => return None,
47 };
48 Some(f)
49}
50
51pub const ALL_FORMATS: &[&str] = &[
53 "p2pkh",
54 "p2pukh",
55 "p2pk",
56 "p2puk",
57 "p2wpkh",
58 "p2tr",
59 "p2sh:p2pkh",
60 "p2sh:p2pukh",
61 "p2sh:p2pk",
62 "p2sh:p2puk",
63 "p2sh:p2wpkh",
64 "p2wsh:p2pkh",
65 "p2wsh:p2pukh",
66 "p2wsh:p2pk",
67 "p2wsh:p2puk",
68 "p2wsh:p2wpkh",
69 "eth",
70 "massa_pubkey",
71 "massa",
72 "solana",
73];
74
75pub fn formats_per_network(network: &str) -> Option<&'static [&'static str]> {
77 Some(match network {
78 "bitcoin" => &[
79 "p2tr",
80 "p2wpkh",
81 "p2sh:p2wpkh",
82 "p2puk",
83 "p2pk",
84 "p2pukh",
85 "p2pkh",
86 ],
87 "bitcoin-cash" => &["p2puk", "p2pk", "p2pukh", "p2pkh"],
88 "litecoin" => &["p2wpkh", "p2sh:p2wpkh", "p2puk", "p2pk", "p2pukh", "p2pkh"],
89 "dogecoin" => &["p2puk", "p2pk", "p2pukh", "p2pkh"],
90 "evm" => &["eth"],
91 "massa" => &["massa"],
92 "solana" => &["solana"],
93 _ => return None,
94 })
95}
96
97pub struct Script {
99 pubkey: PubKey,
100 cache: RefCell<HashMap<String, Vec<u8>>>,
101}
102
103impl Script {
104 pub fn new(pubkey: impl Into<PubKey>) -> Script {
106 Script {
107 pubkey: pubkey.into(),
108 cache: RefCell::new(HashMap::new()),
109 }
110 }
111
112 pub fn pubkey(&self) -> &PubKey {
114 &self.pubkey
115 }
116
117 pub fn generate(&self, name: &str) -> Result<Vec<u8>, String> {
120 if let Some(v) = self.cache.borrow().get(name) {
121 return Ok(v.clone());
122 }
123
124 if matches!(
126 name,
127 "pubkey:pkix" | "pubkey:ed25519" | "pubkey:comp" | "pubkey:uncomp"
128 ) {
129 let res = self.pubkey.bytes_for(name)?;
130 self.cache
131 .borrow_mut()
132 .insert(name.to_string(), res.clone());
133 return Ok(res);
134 }
135
136 let format = format_def(name).ok_or_else(|| format!("unsupported format {name}"))?;
137 let mut out = Vec::new();
138 for piece in &format {
139 out.extend_from_slice(&piece.bytes(self)?);
140 }
141 self.cache
142 .borrow_mut()
143 .insert(name.to_string(), out.clone());
144 Ok(out)
145 }
146
147 pub fn out(&self, name: &str) -> Result<Out, String> {
149 let buf = self.generate(name)?;
150 Ok(Out::make(name, buf, &[]))
151 }
152
153 pub fn address(&self, script: &str, flags: &[&str]) -> Result<String, String> {
156 let out = self.out(script)?;
157 out.address(flags)
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use crate::crypto::secp256k1::SecpPrivateKey;
165
166 fn key() -> SecpPrivateKey {
167 let mut s = [0u8; 32];
168 s.copy_from_slice(
169 &hex::decode("eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf")
170 .unwrap(),
171 );
172 SecpPrivateKey::from_bytes(&s).unwrap()
173 }
174
175 #[test]
176 fn generates_p2pkh_script() {
177 let s = Script::new(key().public_key());
178 let script = s.generate("p2pkh").unwrap();
179 assert_eq!(script.len(), 25);
181 assert_eq!(&script[..3], &[0x76, 0xa9, 0x14]);
182 assert_eq!(&script[23..], &[0x88, 0xac]);
183 }
184
185 #[test]
186 fn generates_p2wpkh_and_p2tr() {
187 let s = Script::new(key().public_key());
188 let wpkh = s.generate("p2wpkh").unwrap();
189 assert_eq!(wpkh.len(), 22);
190 assert_eq!(&wpkh[..2], &[0x00, 0x14]);
191 let tr = s.generate("p2tr").unwrap();
192 assert_eq!(tr.len(), 34);
193 assert_eq!(&tr[..2], &[0x51, 0x20]);
194 }
195
196 #[test]
197 fn caching_is_consistent() {
198 let s = Script::new(key().public_key());
199 assert_eq!(
200 s.generate("p2sh:p2wpkh").unwrap(),
201 s.generate("p2sh:p2wpkh").unwrap()
202 );
203 }
204}