1use bitcoin::hashes::sha256;
2use bitcoin::hashes::Hash;
3use bitcoin::opcodes::all::*;
4use bitcoin::script::Instruction;
5use bitcoin::taproot::TaprootSpendInfo;
6use bitcoin::ScriptBuf;
7use bitcoin::XOnlyPublicKey;
8use std::fmt;
9
10pub fn arknote_script(preimage_hash: &sha256::Hash) -> ScriptBuf {
15 ScriptBuf::builder()
16 .push_opcode(OP_SHA256)
17 .push_slice(preimage_hash.as_byte_array())
18 .push_opcode(OP_EQUAL)
19 .into_script()
20}
21
22pub fn multisig_script(pk_0: XOnlyPublicKey, pk_1: XOnlyPublicKey) -> ScriptBuf {
24 ScriptBuf::builder()
25 .push_x_only_key(&pk_0)
26 .push_opcode(OP_CHECKSIGVERIFY)
27 .push_x_only_key(&pk_1)
28 .push_opcode(OP_CHECKSIG)
29 .into_script()
30}
31
32pub fn multisig_3_of_3_script(
37 pk_0: XOnlyPublicKey,
38 pk_1: XOnlyPublicKey,
39 pk_2: XOnlyPublicKey,
40) -> ScriptBuf {
41 ScriptBuf::builder()
42 .push_x_only_key(&pk_0)
43 .push_opcode(OP_CHECKSIGVERIFY)
44 .push_x_only_key(&pk_1)
45 .push_opcode(OP_CHECKSIGVERIFY)
46 .push_x_only_key(&pk_2)
47 .push_opcode(OP_CHECKSIG)
48 .into_script()
49}
50
51pub fn csv_sig_script(locktime: bitcoin::Sequence, pk: XOnlyPublicKey) -> ScriptBuf {
55 ScriptBuf::builder()
56 .push_int(locktime.to_consensus_u32() as i64)
57 .push_opcode(OP_CSV)
58 .push_opcode(OP_DROP)
59 .push_x_only_key(&pk)
60 .push_opcode(OP_CHECKSIG)
61 .into_script()
62}
63
64pub fn tr_script_pubkey(spend_info: &TaprootSpendInfo) -> ScriptBuf {
66 let output_key = spend_info.output_key();
67 let builder = bitcoin::blockdata::script::Builder::new();
68 builder
69 .push_opcode(OP_PUSHNUM_1)
70 .push_slice(output_key.serialize())
71 .into_script()
72}
73
74pub fn extract_checksig_pubkeys(script: &ScriptBuf) -> Vec<XOnlyPublicKey> {
81 let instructions: Vec<_> = script.instructions().filter_map(|inst| inst.ok()).collect();
82
83 let mut pubkeys = Vec::new();
84
85 for window in instructions.windows(2) {
86 let (push, checksig) = (&window[0], &window[1]);
87
88 if let Instruction::PushBytes(bytes) = push {
90 if bytes.len() != 32 {
91 continue;
92 }
93
94 let is_checksig = matches!(
95 checksig,
96 Instruction::Op(op) if *op == OP_CHECKSIG || *op == OP_CHECKSIGVERIFY
97 );
98
99 if let Ok(pk) = XOnlyPublicKey::from_slice(bytes.as_bytes()) {
100 if is_checksig {
101 pubkeys.push(pk);
102 }
103 }
104 }
105 }
106
107 pubkeys
108}
109
110pub fn extract_sequence_from_csv_sig_script(
111 script: &ScriptBuf,
112) -> Result<bitcoin::Sequence, InvalidCsvSigScriptError> {
113 let csv_index = script
114 .to_bytes()
115 .windows(2)
116 .position(|window| *window == [OP_CSV.to_u8(), OP_DROP.to_u8()])
117 .ok_or(InvalidCsvSigScriptError)?;
118
119 let before_csv = &script.to_bytes()[..csv_index];
120
121 let sequence = if before_csv.len() > 1 {
124 &before_csv[1..]
125 } else {
126 before_csv
127 };
128
129 let mut sequence = sequence.to_vec();
130 sequence.reverse();
131
132 let mut buffer = [0u8; 4];
133 let input_len = sequence.len();
134 let start_index = 4 - input_len; buffer[start_index..].copy_from_slice(&sequence);
137
138 let sequence = u32::from_be_bytes(buffer);
139
140 let sequence = bitcoin::Sequence::from_consensus(sequence);
141
142 Ok(sequence)
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct InvalidCsvSigScriptError;
147
148impl fmt::Display for InvalidCsvSigScriptError {
149 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150 f.write_str("invalid CSV-Sig script")
151 }
152}
153
154impl std::error::Error for InvalidCsvSigScriptError {}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use bitcoin::locktime;
160 use bitcoin::XOnlyPublicKey;
161 use std::str::FromStr;
162
163 #[test]
164 fn test_extract_sequence_from_csv_sig_script() {
165 let locktime_seconds = 1024;
167 let sequence = bitcoin::Sequence::from_seconds_ceil(locktime_seconds).unwrap();
168
169 let pk = XOnlyPublicKey::from_str(
170 "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
171 )
172 .unwrap();
173
174 let script = csv_sig_script(sequence, pk);
175
176 let parsed = extract_sequence_from_csv_sig_script(&script).unwrap();
177 let parsed = parsed.to_relative_lock_time();
178
179 assert_eq!(
180 parsed,
181 locktime::relative::LockTime::from_512_second_intervals(2).into()
182 );
183 }
184
185 #[test]
186 fn test_multisig_3_of_3_script() {
187 let pk_0 = XOnlyPublicKey::from_str(
188 "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
189 )
190 .unwrap();
191 let pk_1 = XOnlyPublicKey::from_str(
192 "28845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
193 )
194 .unwrap();
195 let pk_2 = XOnlyPublicKey::from_str(
196 "38845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
197 )
198 .unwrap();
199
200 let script = multisig_3_of_3_script(pk_0, pk_1, pk_2);
201 let pubkeys = extract_checksig_pubkeys(&script);
202
203 assert_eq!(pubkeys.len(), 3);
204 assert_eq!(pubkeys[0], pk_0);
205 assert_eq!(pubkeys[1], pk_1);
206 assert_eq!(pubkeys[2], pk_2);
207 }
208
209 #[test]
210 fn test_extract_checksig_pubkeys_from_multisig() {
211 let pk_0 = XOnlyPublicKey::from_str(
212 "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
213 )
214 .unwrap();
215 let pk_1 = XOnlyPublicKey::from_str(
216 "28845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
217 )
218 .unwrap();
219
220 let script = multisig_script(pk_0, pk_1);
221 let pubkeys = extract_checksig_pubkeys(&script);
222
223 assert_eq!(pubkeys.len(), 2);
224 assert_eq!(pubkeys[0], pk_0);
225 assert_eq!(pubkeys[1], pk_1);
226 }
227
228 #[test]
229 fn test_extract_checksig_pubkeys_from_csv_sig() {
230 let pk = XOnlyPublicKey::from_str(
231 "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
232 )
233 .unwrap();
234 let sequence = bitcoin::Sequence::from_seconds_ceil(1024).unwrap();
235
236 let script = csv_sig_script(sequence, pk);
237 let pubkeys = extract_checksig_pubkeys(&script);
238
239 assert_eq!(pubkeys.len(), 1);
240 assert_eq!(pubkeys[0], pk);
241 }
242
243 #[test]
244 fn test_extract_checksig_pubkeys_empty_script() {
245 let script = ScriptBuf::new();
246 let pubkeys = extract_checksig_pubkeys(&script);
247
248 assert!(pubkeys.is_empty());
249 }
250
251 #[test]
252 fn test_extract_checksig_pubkeys_no_checksig() {
253 let script = ScriptBuf::builder()
255 .push_opcode(OP_DROP)
256 .push_opcode(OP_RETURN)
257 .into_script();
258
259 let pubkeys = extract_checksig_pubkeys(&script);
260
261 assert!(pubkeys.is_empty());
262 }
263}