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 csv_sig_script(locktime: bitcoin::Sequence, pk: XOnlyPublicKey) -> ScriptBuf {
36 ScriptBuf::builder()
37 .push_int(locktime.to_consensus_u32() as i64)
38 .push_opcode(OP_CSV)
39 .push_opcode(OP_DROP)
40 .push_x_only_key(&pk)
41 .push_opcode(OP_CHECKSIG)
42 .into_script()
43}
44
45pub fn tr_script_pubkey(spend_info: &TaprootSpendInfo) -> ScriptBuf {
47 let output_key = spend_info.output_key();
48 let builder = bitcoin::blockdata::script::Builder::new();
49 builder
50 .push_opcode(OP_PUSHNUM_1)
51 .push_slice(output_key.serialize())
52 .into_script()
53}
54
55pub fn extract_checksig_pubkeys(script: &ScriptBuf) -> Vec<XOnlyPublicKey> {
62 let instructions: Vec<_> = script.instructions().filter_map(|inst| inst.ok()).collect();
63
64 let mut pubkeys = Vec::new();
65
66 for window in instructions.windows(2) {
67 let (push, checksig) = (&window[0], &window[1]);
68
69 if let Instruction::PushBytes(bytes) = push {
71 if bytes.len() != 32 {
72 continue;
73 }
74
75 let is_checksig = matches!(
76 checksig,
77 Instruction::Op(op) if *op == OP_CHECKSIG || *op == OP_CHECKSIGVERIFY
78 );
79
80 if let Ok(pk) = XOnlyPublicKey::from_slice(bytes.as_bytes()) {
81 if is_checksig {
82 pubkeys.push(pk);
83 }
84 }
85 }
86 }
87
88 pubkeys
89}
90
91pub fn extract_sequence_from_csv_sig_script(
92 script: &ScriptBuf,
93) -> Result<bitcoin::Sequence, InvalidCsvSigScriptError> {
94 let csv_index = script
95 .to_bytes()
96 .windows(2)
97 .position(|window| *window == [OP_CSV.to_u8(), OP_DROP.to_u8()])
98 .ok_or(InvalidCsvSigScriptError)?;
99
100 let before_csv = &script.to_bytes()[..csv_index];
101
102 let sequence = if before_csv.len() > 1 {
105 &before_csv[1..]
106 } else {
107 before_csv
108 };
109
110 let mut sequence = sequence.to_vec();
111 sequence.reverse();
112
113 let mut buffer = [0u8; 4];
114 let input_len = sequence.len();
115 let start_index = 4 - input_len; buffer[start_index..].copy_from_slice(&sequence);
118
119 let sequence = u32::from_be_bytes(buffer);
120
121 let sequence = bitcoin::Sequence::from_consensus(sequence);
122
123 Ok(sequence)
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct InvalidCsvSigScriptError;
128
129impl fmt::Display for InvalidCsvSigScriptError {
130 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131 f.write_str("invalid CSV-Sig script")
132 }
133}
134
135impl std::error::Error for InvalidCsvSigScriptError {}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use bitcoin::locktime;
141 use bitcoin::XOnlyPublicKey;
142 use std::str::FromStr;
143
144 #[test]
145 fn test_extract_sequence_from_csv_sig_script() {
146 let locktime_seconds = 1024;
148 let sequence = bitcoin::Sequence::from_seconds_ceil(locktime_seconds).unwrap();
149
150 let pk = XOnlyPublicKey::from_str(
151 "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
152 )
153 .unwrap();
154
155 let script = csv_sig_script(sequence, pk);
156
157 let parsed = extract_sequence_from_csv_sig_script(&script).unwrap();
158 let parsed = parsed.to_relative_lock_time();
159
160 assert_eq!(
161 parsed,
162 locktime::relative::LockTime::from_512_second_intervals(2).into()
163 );
164 }
165
166 #[test]
167 fn test_extract_checksig_pubkeys_from_multisig() {
168 let pk_0 = XOnlyPublicKey::from_str(
169 "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
170 )
171 .unwrap();
172 let pk_1 = XOnlyPublicKey::from_str(
173 "28845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
174 )
175 .unwrap();
176
177 let script = multisig_script(pk_0, pk_1);
178 let pubkeys = extract_checksig_pubkeys(&script);
179
180 assert_eq!(pubkeys.len(), 2);
181 assert_eq!(pubkeys[0], pk_0);
182 assert_eq!(pubkeys[1], pk_1);
183 }
184
185 #[test]
186 fn test_extract_checksig_pubkeys_from_csv_sig() {
187 let pk = XOnlyPublicKey::from_str(
188 "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
189 )
190 .unwrap();
191 let sequence = bitcoin::Sequence::from_seconds_ceil(1024).unwrap();
192
193 let script = csv_sig_script(sequence, pk);
194 let pubkeys = extract_checksig_pubkeys(&script);
195
196 assert_eq!(pubkeys.len(), 1);
197 assert_eq!(pubkeys[0], pk);
198 }
199
200 #[test]
201 fn test_extract_checksig_pubkeys_empty_script() {
202 let script = ScriptBuf::new();
203 let pubkeys = extract_checksig_pubkeys(&script);
204
205 assert!(pubkeys.is_empty());
206 }
207
208 #[test]
209 fn test_extract_checksig_pubkeys_no_checksig() {
210 let script = ScriptBuf::builder()
212 .push_opcode(OP_DROP)
213 .push_opcode(OP_RETURN)
214 .into_script();
215
216 let pubkeys = extract_checksig_pubkeys(&script);
217
218 assert!(pubkeys.is_empty());
219 }
220}