1use crate::ark_address::ArkAddress;
2use crate::script::csv_sig_script;
3use crate::script::multisig_3_of_3_script;
4use crate::script::multisig_script;
5use crate::script::tr_script_pubkey;
6use crate::Error;
7use crate::ErrorContext;
8use crate::ExitDelayKind;
9use crate::UNSPENDABLE_KEY;
10use bitcoin::key::PublicKey;
11use bitcoin::key::Secp256k1;
12use bitcoin::key::Verification;
13use bitcoin::taproot;
14use bitcoin::taproot::LeafVersion;
15use bitcoin::taproot::TaprootBuilder;
16use bitcoin::taproot::TaprootSpendInfo;
17use bitcoin::Address;
18use bitcoin::Network;
19use bitcoin::ScriptBuf;
20use bitcoin::XOnlyPublicKey;
21use std::time::Duration;
22
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25pub struct Vtxo {
26 server_forfeit: XOnlyPublicKey,
27 owner: XOnlyPublicKey,
28 owner_unilateral_exit: XOnlyPublicKey,
29 delegator: Option<XOnlyPublicKey>,
31 spend_info: TaprootSpendInfo,
32 tapscripts: Vec<ScriptBuf>,
34 address: Address,
35 exit_delay: bitcoin::Sequence,
36 exit_delay_kind: ExitDelayKind,
37 network: Network,
38}
39
40impl Vtxo {
41 pub fn new_with_custom_scripts<C>(
48 secp: &Secp256k1<C>,
49 server_forfeit: XOnlyPublicKey,
50 owner: XOnlyPublicKey,
51 scripts: Vec<ScriptBuf>,
53 exit_delay: bitcoin::Sequence,
54 network: Network,
55 ) -> Result<Self, Error>
56 where
57 C: Verification,
58 {
59 let vtxo = Self::new_with_custom_scripts_and_split_owner_keys(
60 secp,
61 server_forfeit,
62 owner,
63 owner,
64 scripts,
65 exit_delay,
66 network,
67 )?;
68
69 Ok(vtxo)
70 }
71
72 pub fn new_with_custom_scripts_and_split_owner_keys<C>(
73 secp: &Secp256k1<C>,
74 server_forfeit: XOnlyPublicKey,
75 owner: XOnlyPublicKey,
76 owner_unilateral_exit: XOnlyPublicKey,
77 scripts: Vec<ScriptBuf>,
78 exit_delay: bitcoin::Sequence,
79 network: Network,
80 ) -> Result<Self, Error>
81 where
82 C: Verification,
83 {
84 let unspendable_key: PublicKey = UNSPENDABLE_KEY
85 .parse()
86 .map_err(|e| Error::ad_hoc(format!("invalid unspendable key: {e}")))?;
87 let (unspendable_key, _) = unspendable_key.inner.x_only_public_key();
88
89 let leaf_distribution = calculate_leaf_depths(scripts.len());
90
91 let mut builder = TaprootBuilder::new();
92 for (script, depth) in scripts.iter().zip(leaf_distribution.iter()) {
93 builder = builder
94 .add_leaf(*depth as u8, script.clone())
95 .map_err(Error::ad_hoc)?;
96 }
97
98 let spend_info = builder
99 .finalize(secp, unspendable_key)
100 .map_err(|_| Error::ad_hoc("failed to finalize Taproot tree"))?;
101
102 let exit_delay_kind = ExitDelayKind::from_sequence(exit_delay)?;
103
104 let script_pubkey = tr_script_pubkey(&spend_info);
105 let address = Address::from_script(&script_pubkey, network)
106 .map_err(|e| Error::ad_hoc(format!("invalid script: {e}")))?;
107
108 Ok(Self {
109 server_forfeit,
110 owner,
111 owner_unilateral_exit,
112 delegator: None,
113 spend_info,
114 tapscripts: scripts,
115 address,
116 exit_delay,
117 exit_delay_kind,
118 network,
119 })
120 }
121
122 pub fn new_default<C>(
124 secp: &Secp256k1<C>,
125 server_signer: XOnlyPublicKey,
126 owner: XOnlyPublicKey,
127 exit_delay: bitcoin::Sequence,
128 network: Network,
129 ) -> Result<Self, Error>
130 where
131 C: Verification,
132 {
133 let forfeit_script = multisig_script(server_signer, owner);
134 let redeem_script = csv_sig_script(exit_delay, owner);
135
136 Self::new_with_custom_scripts(
137 secp,
138 server_signer,
139 owner,
140 vec![forfeit_script, redeem_script],
141 exit_delay,
142 network,
143 )
144 }
145
146 pub fn new_with_delegator<C>(
156 secp: &Secp256k1<C>,
157 server_signer: XOnlyPublicKey,
158 owner: XOnlyPublicKey,
159 delegator: XOnlyPublicKey,
160 exit_delay: bitcoin::Sequence,
161 network: Network,
162 ) -> Result<Self, Error>
163 where
164 C: Verification,
165 {
166 let forfeit_script = multisig_script(server_signer, owner);
167 let redeem_script = csv_sig_script(exit_delay, owner);
168 let delegate_script = multisig_3_of_3_script(owner, delegator, server_signer);
169
170 let mut vtxo = Self::new_with_custom_scripts(
171 secp,
172 server_signer,
173 owner,
174 vec![forfeit_script, redeem_script, delegate_script],
175 exit_delay,
176 network,
177 )?;
178
179 vtxo.delegator = Some(delegator);
180
181 Ok(vtxo)
182 }
183
184 pub fn script_pubkey(&self) -> ScriptBuf {
185 self.address.script_pubkey()
186 }
187
188 pub fn address(&self) -> &Address {
189 &self.address
190 }
191
192 pub fn owner_pk(&self) -> XOnlyPublicKey {
193 self.owner
194 }
195
196 pub fn server_pk(&self) -> XOnlyPublicKey {
197 self.server_forfeit
198 }
199
200 pub fn delegator_pk(&self) -> Option<XOnlyPublicKey> {
201 self.delegator
202 }
203
204 pub fn exit_delay(&self) -> bitcoin::Sequence {
205 self.exit_delay
206 }
207
208 pub fn to_ark_address(&self) -> ArkAddress {
209 let vtxo_tap_key = self.spend_info.output_key();
210 ArkAddress::new(self.network, self.server_forfeit, vtxo_tap_key)
211 }
212
213 pub fn get_spend_info(&self, script: ScriptBuf) -> Result<taproot::ControlBlock, Error> {
215 let control_block = self
216 .spend_info
217 .control_block(&(script, LeafVersion::TapScript))
218 .ok_or(Error::ad_hoc("could not build control block for script"))?;
219
220 Ok(control_block)
221 }
222
223 pub fn forfeit_spend_info(&self) -> Result<(ScriptBuf, taproot::ControlBlock), Error> {
228 let forfeit_script = multisig_script(self.server_forfeit, self.owner);
229
230 let control_block = self
231 .get_spend_info(forfeit_script.clone())
232 .context("missing default forfeit script")?;
233
234 Ok((forfeit_script, control_block))
235 }
236
237 pub fn exit_spend_info(&self) -> Result<(ScriptBuf, taproot::ControlBlock), Error> {
242 let exit_script = csv_sig_script(self.exit_delay, self.owner_unilateral_exit);
243
244 let control_block = self
245 .get_spend_info(exit_script.clone())
246 .context("missing default exit script")?;
247
248 Ok((exit_script, control_block))
249 }
250
251 pub fn delegate_spend_info(&self) -> Result<(ScriptBuf, taproot::ControlBlock), Error> {
256 let delegator = self
257 .delegator
258 .ok_or(Error::ad_hoc("VTXO has no delegate path"))?;
259
260 let delegate_script = multisig_3_of_3_script(self.owner, delegator, self.server_forfeit);
261
262 let control_block = self
263 .get_spend_info(delegate_script.clone())
264 .context("missing delegate script")?;
265
266 Ok((delegate_script, control_block))
267 }
268
269 pub fn tapscripts(&self) -> Vec<ScriptBuf> {
270 self.tapscripts.clone()
271 }
272
273 pub fn can_be_claimed_unilaterally_by_owner(
276 &self,
277 now: Duration,
278 confirmation_blocktime: Duration,
279 confirmations: u64,
280 ) -> bool {
281 match self.exit_delay_kind {
282 ExitDelayKind::Time(seconds) => {
283 let exit_path_time = confirmation_blocktime + seconds;
284
285 now > exit_path_time
286 }
287 ExitDelayKind::Blocks(confirmations_required) => {
288 confirmations >= confirmations_required
289 }
290 }
291 }
292}
293
294fn calculate_leaf_depths(n: usize) -> Vec<usize> {
295 if n == 0 {
297 return vec![];
298 }
299 if n == 1 {
300 return vec![0]; }
302 if n == 2 {
303 return vec![1, 1];
304 }
305
306 let min_depth = (n as f64).log2().ceil() as usize;
308
309 let nodes_at_max_depth = n - (1 << (min_depth - 1)) + 1;
311 let nodes_at_min_depth = (1 << min_depth) - nodes_at_max_depth;
312
313 let mut result = Vec::with_capacity(n);
315
316 for _ in 0..nodes_at_max_depth {
318 result.push(min_depth);
319 }
320
321 for _ in 0..nodes_at_min_depth {
323 result.push(min_depth - 1);
324 }
325
326 result
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use bitcoin::secp256k1::Secp256k1;
333 use std::str::FromStr;
334
335 fn test_keys() -> (XOnlyPublicKey, XOnlyPublicKey, XOnlyPublicKey) {
336 let server = XOnlyPublicKey::from_str(
337 "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
338 )
339 .unwrap();
340 let owner = XOnlyPublicKey::from_str(
341 "28845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
342 )
343 .unwrap();
344 let delegator = XOnlyPublicKey::from_str(
345 "38845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
346 )
347 .unwrap();
348 (server, owner, delegator)
349 }
350
351 #[test]
352 fn new_with_delegator_has_three_tapscripts() {
353 let secp = Secp256k1::new();
354 let (server, owner, delegator) = test_keys();
355 let exit_delay = bitcoin::Sequence::from_seconds_ceil(86400).unwrap();
356
357 let vtxo = Vtxo::new_with_delegator(
358 &secp,
359 server,
360 owner,
361 delegator,
362 exit_delay,
363 Network::Regtest,
364 )
365 .unwrap();
366
367 assert_eq!(vtxo.tapscripts().len(), 3);
368 assert_eq!(vtxo.delegator_pk(), Some(delegator));
369 }
370
371 #[test]
372 fn delegator_vtxo_all_spend_paths_resolve() {
373 let secp = Secp256k1::new();
374 let (server, owner, delegator) = test_keys();
375 let exit_delay = bitcoin::Sequence::from_seconds_ceil(86400).unwrap();
376
377 let vtxo = Vtxo::new_with_delegator(
378 &secp,
379 server,
380 owner,
381 delegator,
382 exit_delay,
383 Network::Regtest,
384 )
385 .unwrap();
386
387 let (forfeit_script, _cb) = vtxo.forfeit_spend_info().unwrap();
389 let (exit_script, _cb) = vtxo.exit_spend_info().unwrap();
390 let (delegate_script, _cb) = vtxo.delegate_spend_info().unwrap();
391
392 assert_ne!(forfeit_script, exit_script);
394 assert_ne!(forfeit_script, delegate_script);
395 assert_ne!(exit_script, delegate_script);
396 }
397
398 #[test]
399 fn default_vtxo_has_no_delegate_path() {
400 let secp = Secp256k1::new();
401 let (server, owner, _) = test_keys();
402 let exit_delay = bitcoin::Sequence::from_seconds_ceil(86400).unwrap();
403
404 let vtxo = Vtxo::new_default(&secp, server, owner, exit_delay, Network::Regtest).unwrap();
405
406 assert!(vtxo.delegator_pk().is_none());
407 assert!(vtxo.delegate_spend_info().is_err());
408 }
409
410 #[test]
411 fn delegator_vtxo_address_differs_from_default() {
412 let secp = Secp256k1::new();
413 let (server, owner, delegator) = test_keys();
414 let exit_delay = bitcoin::Sequence::from_seconds_ceil(86400).unwrap();
415
416 let default =
417 Vtxo::new_default(&secp, server, owner, exit_delay, Network::Regtest).unwrap();
418 let with_delegator = Vtxo::new_with_delegator(
419 &secp,
420 server,
421 owner,
422 delegator,
423 exit_delay,
424 Network::Regtest,
425 )
426 .unwrap();
427
428 assert_ne!(default.address(), with_delegator.address());
430 }
431}