1use crate::*;
19
20#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
22pub enum WalletUnlockProofV10 {
23 Sig(usize),
25 Xhx(String),
27}
28
29impl Default for WalletUnlockProofV10 {
30 fn default() -> Self {
31 WalletUnlockProofV10::Sig(0)
32 }
33}
34
35impl ToString for WalletUnlockProofV10 {
36 fn to_string(&self) -> String {
37 match *self {
38 Self::Sig(ref index) => format!("SIG({})", index),
39 Self::Xhx(ref hash) => format!("XHX({})", hash),
40 }
41 }
42}
43
44#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
46pub enum WalletSubScriptV10 {
47 Single(WalletConditionV10),
49 Brackets(usize),
51 And(usize, usize),
53 Or(usize, usize),
55}
56
57impl WalletSubScriptV10 {
58 fn to_raw_text(self, nodes: &[WalletSubScriptV10]) -> String {
59 match self {
60 Self::Single(cond) => cond.to_string(),
61 Self::Brackets(sub_script) => format!("({})", nodes[sub_script].to_raw_text(nodes)),
62 Self::And(sub_script_1, sub_script_2) => format!(
63 "{} && {}",
64 nodes[sub_script_1].to_raw_text(nodes),
65 nodes[sub_script_2].to_raw_text(nodes),
66 ),
67 Self::Or(sub_script_1, sub_script_2) => format!(
68 "{} || {}",
69 nodes[sub_script_1].to_raw_text(nodes),
70 nodes[sub_script_2].to_raw_text(nodes),
71 ),
72 }
73 }
74}
75
76impl WalletSubScriptV10 {
77 pub fn as_single_sig(&self) -> Option<PublicKey> {
78 if let Self::Single(WalletConditionV10::Sig(pubkey)) = self {
79 Some(*pubkey)
80 } else {
81 None
82 }
83 }
84 pub fn is_single_sig(&self) -> bool {
85 matches!(self, Self::Single(WalletConditionV10::Sig(_)))
86 }
87 fn unlockable_on(
88 self,
89 nodes: &[WalletSubScriptV10],
90 signers: &HashSet<&[u8]>,
91 codes_hash: &HashSet<Hash>,
92 source_written_on: u64,
93 ) -> Result<(u64, HashSet<UsedProofV10>), ScriptNeverUnlockableError> {
94 match self {
95 Self::Single(cond) => cond
96 .unlockable_on(signers, codes_hash, source_written_on)
97 .map(|(cond_unlockable_on, used_proof_opt)| {
98 if let Some(used_proof) = used_proof_opt {
99 let mut used_proofs_set = HashSet::with_capacity(1);
100 used_proofs_set.insert(used_proof);
101 (cond_unlockable_on, used_proofs_set)
102 } else {
103 (cond_unlockable_on, HashSet::with_capacity(0))
104 }
105 }),
106 Self::Brackets(sub_script_index) => {
107 nodes[sub_script_index].unlockable_on(nodes, signers, codes_hash, source_written_on)
108 }
109 Self::And(sub_script1_index, sub_script2_index) => {
110 let (script1_unlockable_on, script1_used_proofs) = nodes[sub_script1_index]
111 .unlockable_on(nodes, signers, codes_hash, source_written_on)?;
112 let (script2_unlockable_on, script2_used_proofs) = nodes[sub_script2_index]
113 .unlockable_on(nodes, signers, codes_hash, source_written_on)?;
114 Ok((
115 std::cmp::max(script1_unlockable_on, script2_unlockable_on),
116 script1_used_proofs
117 .union(&script2_used_proofs)
118 .copied()
119 .collect(),
120 ))
121 }
122 Self::Or(sub_script1_index, sub_script2_index) => {
123 let script1_unlockable_on_res = nodes[sub_script1_index].unlockable_on(
124 nodes,
125 signers,
126 codes_hash,
127 source_written_on,
128 );
129 let script2_unlockable_on_res = nodes[sub_script2_index].unlockable_on(
130 nodes,
131 signers,
132 codes_hash,
133 source_written_on,
134 );
135 match script1_unlockable_on_res {
136 Ok((script1_unlockable_on, script1_used_proofs)) => {
137 match script2_unlockable_on_res {
138 Ok((script2_unlockable_on, script2_used_proofs)) => Ok((
139 std::cmp::min(script1_unlockable_on, script2_unlockable_on),
140 if script2_used_proofs.len() < script1_used_proofs.len() {
141 script2_used_proofs
142 } else {
143 script1_used_proofs
144 },
145 )),
146 Err(_) => Ok((script1_unlockable_on, script1_used_proofs)),
147 }
148 }
149 Err(_) => script2_unlockable_on_res,
150 }
151 }
152 }
153 }
154}
155
156pub type WalletScriptNodesV10 = SmallVec<[WalletSubScriptV10; 8]>;
157
158#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
159pub struct WalletScriptV10 {
160 pub root: WalletSubScriptV10,
161 pub nodes: WalletScriptNodesV10,
162}
163
164impl ToString for WalletScriptV10 {
165 fn to_string(&self) -> String {
166 self.root.to_raw_text(&self.nodes)
167 }
168}
169
170impl WalletScriptV10 {
171 pub fn single(condition: WalletConditionV10) -> Self {
172 WalletScriptV10 {
173 root: WalletSubScriptV10::Single(condition),
174 nodes: SmallVec::new(),
175 }
176 }
177 pub fn single_sig(pubkey: PublicKey) -> Self {
178 WalletScriptV10 {
179 root: WalletSubScriptV10::Single(WalletConditionV10::Sig(pubkey)),
180 nodes: SmallVec::new(),
181 }
182 }
183 pub fn as_single_sig(&self) -> Option<PublicKey> {
184 if self.nodes.is_empty() {
185 self.root.as_single_sig()
186 } else {
187 None
188 }
189 }
190 pub fn is_single_sig(&self) -> bool {
191 self.nodes.is_empty() && self.root.is_single_sig()
192 }
193 pub fn and(cond1: WalletConditionV10, cond2: WalletConditionV10) -> Self {
194 let mut nodes = SmallVec::new();
195 nodes.push(WalletSubScriptV10::Single(cond1));
196 nodes.push(WalletSubScriptV10::Single(cond2));
197
198 WalletScriptV10 {
199 root: WalletSubScriptV10::And(0, 1),
200 nodes,
201 }
202 }
203 pub fn and_and(
204 cond1: WalletConditionV10,
205 cond2: WalletConditionV10,
206 cond3: WalletConditionV10,
207 ) -> Self {
208 let mut nodes = SmallVec::new();
209 nodes.push(WalletSubScriptV10::Single(cond1));
210 nodes.push(WalletSubScriptV10::And(2, 3));
211 nodes.push(WalletSubScriptV10::Single(cond2));
212 nodes.push(WalletSubScriptV10::Single(cond3));
213
214 WalletScriptV10 {
215 root: WalletSubScriptV10::And(0, 1),
216 nodes,
217 }
218 }
219 pub fn or(cond1: WalletConditionV10, cond2: WalletConditionV10) -> Self {
220 let mut nodes = SmallVec::new();
221 nodes.push(WalletSubScriptV10::Single(cond1));
222 nodes.push(WalletSubScriptV10::Single(cond2));
223
224 WalletScriptV10 {
225 root: WalletSubScriptV10::Or(0, 1),
226 nodes,
227 }
228 }
229 pub fn pubkeys(&self) -> BTreeSet<PublicKey> {
230 let mut pubkeys = BTreeSet::new();
231 if let WalletSubScriptV10::Single(WalletConditionV10::Sig(pubkey)) = self.root {
232 pubkeys.insert(pubkey);
233 }
234 for node in &self.nodes {
235 if let WalletSubScriptV10::Single(WalletConditionV10::Sig(pubkey)) = node {
236 pubkeys.insert(*pubkey);
237 }
238 }
239 pubkeys
240 }
241 pub(crate) fn unlockable_on(
242 &self,
243 signers: &HashSet<&[u8]>,
244 codes_hash: &HashSet<Hash>,
245 source_written_on: u64,
246 ) -> Result<(u64, HashSet<UsedProofV10>), ScriptNeverUnlockableError> {
247 self.root
248 .unlockable_on(&self.nodes, signers, codes_hash, source_written_on)
249 }
250}
251
252#[derive(Clone, Copy, Debug, Error, PartialEq)]
253#[error("Script never unlockable")]
254pub struct ScriptNeverUnlockableError;
255
256#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
258pub enum WalletConditionV10 {
259 Sig(PublicKey),
261 Xhx(Hash),
263 Cltv(u64),
265 Csv(u64),
267}
268
269impl ToString for WalletConditionV10 {
270 fn to_string(&self) -> String {
271 match *self {
272 Self::Sig(ref pubkey) => format!("SIG({})", pubkey),
273 Self::Xhx(ref hash) => format!("XHX({})", hash),
274 Self::Cltv(timestamp) => format!("CLTV({})", timestamp),
275 Self::Csv(duration) => format!("CSV({})", duration),
276 }
277 }
278}
279
280#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
281pub(crate) enum UsedProofV10 {
282 Sig(PublicKey),
283 CodeHash(Hash),
284}
285
286impl WalletConditionV10 {
287 pub(crate) fn unlockable_on(
288 &self,
289 signers: &HashSet<&[u8]>,
290 codes_hash: &HashSet<Hash>,
291 source_written_on: u64,
292 ) -> Result<(u64, Option<UsedProofV10>), ScriptNeverUnlockableError> {
293 match self {
294 Self::Sig(pubkey) => {
295 if signers.contains(&pubkey.as_ref()[..32]) {
296 Ok((0, Some(UsedProofV10::Sig(*pubkey))))
297 } else {
298 Err(ScriptNeverUnlockableError)
299 }
300 }
301 Self::Xhx(code_hash) => {
302 if codes_hash.contains(code_hash) {
303 Ok((0, Some(UsedProofV10::CodeHash(*code_hash))))
304 } else {
305 Err(ScriptNeverUnlockableError)
306 }
307 }
308 Self::Cltv(timestamp) => Ok((*timestamp, None)),
309 Self::Csv(duration_secs) => Ok((source_written_on + *duration_secs, None)),
310 }
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use dubp_common::crypto::keys::PublicKey as _;
318 use maplit::hashset;
319 use smallvec::smallvec as svec;
320 use unwrap::unwrap;
321
322 #[test]
323 fn test_script_or_unlockable_on() {
324 let p1 = unwrap!(PublicKey::from_base58(
325 "D7CYHJXjaH4j7zRdWngUbsURPnSnjsCYtvo6f8dvW3C"
326 ));
327 let cond1 = WalletConditionV10::Sig(p1);
328 let cond2 = WalletConditionV10::Cltv(123);
329 let script = WalletScriptV10::or(cond1, cond2);
330
331 assert_eq!(
332 Ok((0, hashset![])),
333 script.unlockable_on(&hashset![&p1.as_ref()[..32]], &hashset![], 0),
334 );
335 assert_eq!(
336 Ok((123, hashset![])),
337 script.unlockable_on(&hashset![&[0u8; 32][..]], &hashset![], 0),
338 );
339 }
340
341 #[test]
342 fn test_script_and_unlockable_on() {
343 let p1 = unwrap!(PublicKey::from_base58(
344 "D7CYHJXjaH4j7zRdWngUbsURPnSnjsCYtvo6f8dvW3C"
345 ));
346 let cond1 = WalletConditionV10::Sig(p1);
347 let cond2 = WalletConditionV10::Cltv(123);
348 let script = WalletScriptV10::and(cond1, cond2);
349
350 assert_eq!(
351 Ok((123, hashset![UsedProofV10::Sig(p1)])),
352 script.unlockable_on(&hashset![&p1.as_ref()[..32]], &hashset![], 0)
353 );
354 assert_eq!(
355 Err(ScriptNeverUnlockableError),
356 script.unlockable_on(&hashset![&[0u8; 32][..]], &hashset![], 0),
357 );
358 }
359
360 #[test]
361 fn test_script_complex() {
362 let p1 = unwrap!(PublicKey::from_base58(
363 "D7CYHJXjaH4j7zRdWngUbsURPnSnjsCYtvo6f8dvW3C"
364 ));
365 let h1 = unwrap!(Hash::from_hex(
366 "3D8BF2B661155EA073D80A1E1171212261AD4D21F2E41737BDE192871C469ABE"
367 ));
368 let cond1 = WalletConditionV10::Sig(p1);
369 let cond2 = WalletConditionV10::Cltv(123);
370 let cond3 = WalletConditionV10::Xhx(h1);
371
372 let script = WalletScriptV10 {
373 root: WalletSubScriptV10::Or(0, 4),
374 nodes: svec![
375 WalletSubScriptV10::Single(cond3),
376 WalletSubScriptV10::Single(cond1),
377 WalletSubScriptV10::Single(cond2),
378 WalletSubScriptV10::And(1, 2),
379 WalletSubScriptV10::Brackets(3),
380 ],
381 };
382
383 assert_eq!(
384 "XHX(3D8BF2B661155EA073D80A1E1171212261AD4D21F2E41737BDE192871C469ABE) || (SIG(D7CYHJXjaH4j7zRdWngUbsURPnSnjsCYtvo6f8dvW3C) && CLTV(123))",
385 script.to_string()
386 );
387 assert_eq!(
388 Ok((123, hashset![UsedProofV10::Sig(p1)])),
389 script.unlockable_on(&hashset![&p1.as_ref()[..32]], &hashset![], 0),
390 );
391 }
392
393 #[test]
394 fn test_sig_cond_unlockable_on() {
395 let p1 = unwrap!(PublicKey::from_base58(
396 "D7CYHJXjaH4j7zRdWngUbsURPnSnjsCYtvo6f8dvW3C"
397 ));
398 let cond = WalletConditionV10::Sig(p1);
399
400 assert_eq!(
401 Ok((0, Some(UsedProofV10::Sig(p1)))),
402 cond.unlockable_on(&hashset![&p1.as_ref()[..32]], &hashset![], 0),
403 );
404 assert_eq!(
405 Err(ScriptNeverUnlockableError),
406 cond.unlockable_on(&hashset![&[0u8; 32][..]], &hashset![], 0),
407 );
408 }
409
410 #[test]
411 fn test_xhx_cond_unlockable_on() {
412 let h1 = Hash::compute(b"1");
413 let cond = WalletConditionV10::Xhx(h1);
414
415 assert_eq!(
416 Ok((0, Some(UsedProofV10::CodeHash(h1)))),
417 cond.unlockable_on(&hashset![], &hashset![h1], 0),
418 );
419 assert_eq!(
420 Err(ScriptNeverUnlockableError),
421 cond.unlockable_on(&hashset![&[0u8; 32][..]], &hashset![], 0),
422 );
423 }
424
425 #[test]
426 fn test_cltv_cond_unlockable_on() {
427 let cond = WalletConditionV10::Cltv(123);
428
429 assert_eq!(
430 Ok((123, None)),
431 cond.unlockable_on(&hashset![], &hashset![], 0),
432 );
433 }
434
435 #[test]
436 fn test_csv_cond_unlockable_on() {
437 let cond = WalletConditionV10::Csv(123);
438
439 assert_eq!(
440 Ok((369, None)),
441 cond.unlockable_on(&hashset![], &hashset![], 246),
442 );
443 }
444
445 #[test]
446 fn test_and_and() {
447 let cond1 = WalletConditionV10::Csv(123);
448 let cond2 = WalletConditionV10::Csv(456);
449 let cond3 = WalletConditionV10::Csv(789);
450
451 let script = WalletScriptV10::and_and(cond1, cond2, cond3);
452 assert_eq!(&script.to_string(), "CSV(123) && CSV(456) && CSV(789)");
453 }
454
455 #[test]
456 fn test_as_single_sig() {
457 assert_eq!(
458 WalletScriptV10::single_sig(PublicKey::default()).as_single_sig(),
459 Some(PublicKey::default())
460 );
461 assert!(WalletScriptV10::single(WalletConditionV10::Csv(100))
462 .as_single_sig()
463 .is_none());
464
465 let cond1 = WalletConditionV10::Csv(123);
466 let cond2 = WalletConditionV10::Csv(456);
467 assert!(WalletScriptV10::and(cond1, cond2).as_single_sig().is_none());
468 }
469
470 #[test]
471 fn test_is_single_sig() {
472 assert!(WalletScriptV10::single_sig(PublicKey::default()).is_single_sig());
473 assert!(!WalletScriptV10::single(WalletConditionV10::Csv(100)).is_single_sig());
474
475 let cond1 = WalletConditionV10::Csv(123);
476 let cond2 = WalletConditionV10::Csv(456);
477 assert!(!WalletScriptV10::and(cond1, cond2).is_single_sig());
478 }
479}