• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Neptune-Crypto / neptune-core / 15023035928

14 May 2025 02:13PM UTC coverage: 71.708% (+0.05%) from 71.658%
15023035928

push

github

dan-da
fix: validate TransactionDetails

This change ensures that TransactionDetails are validated
when GlobalStateLock::record_transaction() is called and can catch validation
errors for mock-proof using networks that otherwise might slip through.

It is not strictly necessary to validate TransactionDetails when the network
uses real proofs because a valid proof indicates the Tx is valid and also the
TransactionDetails, so long as it matches the Tx.

However:
 1. A network that uses mock proofs should validate the TransactionDetails
because a "valid" mock-proof can be obtained with invalid TransactionDetails.

 2. The enum error returned from TransactionDetails provides more insight into
what is wrong than the boolean value from Transaction::verify_proof()

 3. It is preferable to use the same logic whether the network uses mock proofs
or not.

For these reasons, TxCreationArtifacts::verify() now validates the
TransactionDetails.

4 of 4 new or added lines in 2 files covered. (100.0%)

191 existing lines in 6 files now uncovered.

19967 of 27845 relevant lines covered (71.71%)

383435.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.1
/src/models/blockchain/type_scripts/time_lock.rs
1
use std::collections::HashMap;
2
use std::sync::OnceLock;
3

4
use get_size2::GetSize;
5
use itertools::Itertools;
6
use num_traits::Zero;
7
use serde::Deserialize;
8
use serde::Serialize;
9
use tasm_lib::memory::encode_to_memory;
10
use tasm_lib::memory::FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS;
11
use tasm_lib::prelude::Digest;
12
use tasm_lib::prelude::Library;
13
use tasm_lib::structure::verify_nd_si_integrity::VerifyNdSiIntegrity;
14
use tasm_lib::triton_vm::prelude::*;
15
use tasm_lib::twenty_first::math::b_field_element::BFieldElement;
16
use tasm_lib::twenty_first::math::bfield_codec::BFieldCodec;
17

18
use super::TypeScript;
19
use super::TypeScriptWitness;
20
use crate::models::blockchain::transaction::primitive_witness::PrimitiveWitness;
21
use crate::models::blockchain::transaction::primitive_witness::SaltedUtxos;
22
use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel;
23
use crate::models::blockchain::transaction::transaction_kernel::TransactionKernelField;
24
use crate::models::blockchain::transaction::utxo::Coin;
25
use crate::models::blockchain::transaction::utxo::Utxo;
26
use crate::models::blockchain::type_scripts::TypeScriptAndWitness;
27
use crate::models::proof_abstractions::mast_hash::MastHash;
28
use crate::models::proof_abstractions::tasm::program::ConsensusProgram;
29
use crate::models::proof_abstractions::timestamp::Timestamp;
30
use crate::models::proof_abstractions::SecretWitness;
31

32
#[derive(Debug, Copy, Clone, Deserialize, Serialize, BFieldCodec, GetSize, PartialEq, Eq)]
33
pub struct TimeLock;
34

35
impl TimeLock {
36
    /// Create a `TimeLock` type-script-and-state-pair that releases the coins at the
37
    /// given release date, which corresponds to the number of milliseconds that passed
38
    /// since the unix epoch started (00:00 am UTC on Jan 1 1970).
39
    pub fn until(date: Timestamp) -> Coin {
311,743✔
40
        Coin {
311,743✔
41
            type_script_hash: TimeLock.hash(),
311,743✔
42
            state: vec![date.0],
311,743✔
43
        }
311,743✔
44
    }
311,743✔
45

46
    /// Get the release date from a `Utxo`, if any. If there aren't any, return
47
    /// the null release date.
48
    pub fn extract_release_date(utxo: &Utxo) -> Timestamp {
1,755✔
49
        utxo.coins()
1,755✔
50
            .iter()
1,755✔
51
            .find_map(Coin::release_date)
1,755✔
52
            .unwrap_or_else(Timestamp::zero)
1,755✔
53
    }
1,755✔
54
}
55

56
impl ConsensusProgram for TimeLock {
57
    fn library_and_code(&self) -> (Library, Vec<LabelledInstruction>) {
4,000✔
58
        let (library, audit_preloaded_data, audit_subroutine) = {
4,000✔
59
            let mut library = Library::new();
4,000✔
60
            let audit_preloaded_data = library.import(Box::new(VerifyNdSiIntegrity::<
4,000✔
61
                TimeLockWitnessMemory,
4,000✔
62
            >::default()));
4,000✔
63
            let audit_subroutine = library.all_imports();
4,000✔
64
            (library, audit_preloaded_data, audit_subroutine)
4,000✔
65
        };
4,000✔
66

67
        // Generated by tasm-lang compiler
68
        // `tasm-lang typescript-timelock.rs type-script-time-lock.tasm`
69
        // 2024-09-09
70
        let code = triton_asm! {
4,000✔
71
        call main
72
        halt
73
        {&audit_subroutine}
4,000✔
74

75
        main:
76
        call tasmlib_verifier_own_program_digest
77
        dup 4
78
        dup 4
79
        dup 4
80
        dup 4
81
        dup 4
82
        push -6
83
        write_mem 5
84
        pop 1
85
        hint self_digest = stack[0..5]
86
        call tasmlib_io_read_stdin___digest
87
        dup 4
88
        dup 4
89
        dup 4
90
        dup 4
91
        dup 4
92
        push -11
93
        write_mem 5
94
        pop 1
95
        hint tx_kernel_digest = stack[0..5]
96
        call tasmlib_io_read_stdin___digest
97
        dup 4
98
        dup 4
99
        dup 4
100
        dup 4
101
        dup 4
102
        push -16
103
        write_mem 5
104
        pop 1
105
        hint input_utxos_digest = stack[0..5]
106
        call tasmlib_io_read_stdin___digest
107
        hint _output_utxos_digest = stack[0..5]
108
        push 5
109
        hint leaf_index = stack[0]
110
        call tasmlib_io_read_secin___bfe
111
        dup 0
112
        push -17
113
        write_mem 1
114
        pop 1
115
        hint timestamp = stack[0]
116
        push -17
117
        read_mem 1
118
        pop 1
119
        call encode_BField
120
        call tasm_langs_hash_varlen
121
        hint leaf = stack[0..5]
122
        push 3
123
        hint tree_height = stack[0]
124
        push -7
125
        read_mem 5
126
        pop 1
127
        dup 5
128
        dup 13
129
        dup 12
130
        dup 12
131
        dup 12
132
        dup 12
133
        dup 12
134
        call tasmlib_hashing_merkle_verify
135
        call tasmlib_io_read_secin___bfe
136
        hint input_utxos_pointer = stack[0]
137

138
        /* Audit preloaded data */
139
        // ... *input_utxos_pointer
140
        dup 0
141
        call {audit_preloaded_data}
142
        // ... *input_utxos_pointer witness_size
143

144
        pop 1
145
        // ... *input_utxos_pointer
146

147
        call tasmlib_io_read_secin___bfe
148
        split
149
        hint _output_utxos_pointer = stack[0..2]
150
        dup 2
151
        hint input_salted_utxos = stack[0]
152
        dup 0
153
        call tasm_langs_hash_varlen_boxed_value___SaltedUtxos
154
        hint input_salted_utxos_digest = stack[0..5]
155
        dup 4
156
        dup 4
157
        dup 4
158
        dup 4
159
        dup 4
160
        push -12
161
        read_mem 5
162
        pop 1
163
        call tasmlib_hashing_eq_digest
164
        assert
165
        dup 5
166
        addi 4
167
        hint input_utxos = stack[0]
168
        push 0
169
        hint i = stack[0]
170
        call _binop_Lt__LboolR_bool_32_while_loop
171
        pop 5
172
        pop 5
173
        pop 5
174
        pop 5
175
        pop 5
176
        pop 5
177
        pop 5
178
        pop 4
179
        return
180
        _binop_Eq__LboolR_bool_49_then:
181
        pop 1
182
        dup 0
183
        addi 1
184
        hint state = stack[0]
185
        dup 0
186
        read_mem 1
187
        pop 1
188
        push 1
189
        eq
190
        assert
191
        dup 0
192
        push 0
193
        push 1
194
        mul
195
        push 1
196
        add
197
        push 00000000001073741824
198
        dup 1
199
        lt
200
        assert
201
        add
202
        read_mem 1
203
        pop 1
204
        hint release_date = stack[0]
205
        dup 0
206
        split
207
        push -17
208
        read_mem 1
209
        pop 1
210
        split
211
        swap 3
212
        swap 1
213
        swap 3
214
        swap 2
215
        call tasmlib_arithmetic_u64_lt
216
        assert
217
        pop 1
218
        pop 1
219
        push 0
220
        return
221
        _binop_Eq__LboolR_bool_49_else:
222
        return
223
        _binop_Lt__LboolR_bool_42_while_loop:
224
        dup 0
225
        dup 2
226
        read_mem 1
227
        pop 1
228
        swap 1
229
        lt
230
        push 0
231
        eq
232
        skiz
233
        return
234
        dup 1
235
        push 1
236
        add
237
        dup 1
238
        call tasm_langs_dynamic_list_element_finder
239
        pop 1
240
        addi 1
241
        hint coin = stack[0]
242
        dup 0
243
        read_mem 1
244
        push 00000000001073741824
245
        dup 2
246
        lt
247
        assert
248
        addi 2
249
        add
250
        push 4
251
        add
252
        read_mem 5
253
        pop 1
254
        push -2
255
        read_mem 5
256
        pop 1
257
        call tasmlib_hashing_eq_digest
258
        push 1
259
        swap 1
260
        skiz
261
        call _binop_Eq__LboolR_bool_49_then
262
        skiz
263
        call _binop_Eq__LboolR_bool_49_else
264
        dup 1
265
        push 1
266
        call tasmlib_arithmetic_u32_safeadd
267
        swap 2
268
        pop 1
269
        pop 1
270
        recurse
271
        _binop_Lt__LboolR_bool_32_while_loop:
272
        dup 0
273
        dup 2
274
        read_mem 1
275
        pop 1
276
        swap 1
277
        lt
278
        push 0
279
        eq
280
        skiz
281
        return
282
        dup 1
283
        push 1
284
        add
285
        dup 1
286
        call tasm_langs_dynamic_list_element_finder
287
        pop 1
288
        addi 1
289
        addi 1
290
        hint coins = stack[0]
291
        push 0
292
        hint j = stack[0]
293
        call _binop_Lt__LboolR_bool_42_while_loop
294
        dup 2
295
        push 1
296
        call tasmlib_arithmetic_u32_safeadd
297
        swap 3
298
        pop 1
299
        pop 1
300
        pop 1
301
        recurse
302
        encode_BField:
303
        call tasmlib_memory_dyn_malloc
304
        push 1
305
        swap 1
306
        write_mem 1
307
        write_mem 1
308
        push -2
309
        add
310
        return
311
        tasm_langs_dynamic_list_element_finder:
312
        dup 0
313
        push 0
314
        eq
315
        skiz
316
        return
317
        swap 1
318
        read_mem 1
319
        push 00000000001073741824
320
        dup 2
321
        lt
322
        assert
323
        addi 2
324
        add
325
        swap 1
326
        addi -1
327
        recurse
328
        tasm_langs_hash_varlen:
329
        read_mem 1
330
        push 2
331
        add
332
        swap 1
333
        call tasmlib_hashing_algebraic_hasher_hash_varlen
334
        return
335
        tasm_langs_hash_varlen_boxed_value___SaltedUtxos:
336
        dup 0
337
        push 0
338
        addi 3
339
        swap 1
340
        addi 3
341
        swap 1
342
        dup 1
343
        read_mem 1
344
        pop 1
345
        push 00000000001073741824
346
        dup 1
347
        lt
348
        assert
349
        addi 1
350
        dup 2
351
        dup 1
352
        add
353
        swap 3
354
        pop 1
355
        add
356
        swap 1
357
        pop 1
358
        call tasmlib_hashing_algebraic_hasher_hash_varlen
359
        return
360
        tasmlib_arithmetic_u32_safeadd:
361
        hint input_lhs: u32 = stack[0]
362
        hint input_rhs: u32 = stack[1]
363
        add
364
        dup 0
365
        split
366
        pop 1
367
        push 0
368
        eq
369
        assert
370
        return
371
        tasmlib_arithmetic_u64_lt:
372
        hint lhs: u64 = stack[0..2]
373
        hint rhs: u64 = stack[2..4]
374
        swap 3
375
        swap 2
376
        dup 2
377
        dup 2
378
        lt
379
        swap 4
380
        lt
381
        swap 2
382
        eq
383
        mul
384
        add
385
        return
386
        tasmlib_hashing_absorb_multiple:
387
        hint len: u32 = stack[0]
388
        hint _sequence: void_pointer = stack[1]
389
        dup 0
390
        push 10
391
        swap 1
392
        div_mod
393
        swap 1
394
        pop 1
395
        swap 1
396
        dup 1
397
        push -1
398
        mul
399
        dup 3
400
        add
401
        add
402
        swap 1
403
        swap 2
404
        push 0
405
        push 0
406
        push 0
407
        push 0
408
        swap 4
409
        call tasmlib_hashing_absorb_multiple_hash_all_full_chunks
410
        pop 5
411
        push -1
412
        add
413
        push 9
414
        dup 2
415
        push -1
416
        mul
417
        add
418
        call tasmlib_hashing_absorb_multiple_pad_varnum_zeros
419
        pop 1
420
        push 1
421
        swap 2
422
        dup 1
423
        add
424
        call tasmlib_hashing_absorb_multiple_read_remainder
425
        pop 2
426
        sponge_absorb
427
        return
428
        tasmlib_hashing_absorb_multiple_hash_all_full_chunks:
429
        dup 5
430
        dup 1
431
        eq
432
        skiz
433
        return
434
        sponge_absorb_mem
435
        recurse
436
        tasmlib_hashing_absorb_multiple_pad_varnum_zeros:
437
        dup 0
438
        push 0
439
        eq
440
        skiz
441
        return
442
        push 0
443
        swap 3
444
        swap 2
445
        swap 1
446
        push -1
447
        add
448
        recurse
449
        tasmlib_hashing_absorb_multiple_read_remainder:
450
        dup 1
451
        dup 1
452
        eq
453
        skiz
454
        return
455
        read_mem 1
456
        swap 1
457
        swap 2
458
        swap 1
459
        recurse
460
        tasmlib_hashing_algebraic_hasher_hash_varlen:
461
        hint length: u32 = stack[0]
462
        hint _addr: void_pointer = stack[1]
463
        sponge_init
464
        call tasmlib_hashing_absorb_multiple
465
        sponge_squeeze
466
        swap 5
467
        pop 1
468
        swap 5
469
        pop 1
470
        swap 5
471
        pop 1
472
        swap 5
473
        pop 1
474
        swap 5
475
        pop 1
476
        return
477
        tasmlib_hashing_eq_digest:
478
        hint input_a4: digest = stack[0..5]
479
        hint input_b4: digest = stack[5..10]
480
        swap 6
481
        eq
482
        swap 6
483
        eq
484
        swap 6
485
        eq
486
        swap 6
487
        eq
488
        swap 2
489
        eq
490
        mul
491
        mul
492
        mul
493
        mul
494
        return
495
        tasmlib_hashing_merkle_verify:
496
        hint leaf: digest = stack[0..5]
497
        hint leaf_index: u32 = stack[5]
498
        hint tree_height: u32 = stack[6]
499
        hint root: digest = stack[7..12]
500
        dup 6
501
        push 2
502
        pow
503
        dup 0
504
        dup 7
505
        lt
506
        assert
507
        dup 6
508
        add
509
        swap 6
510
        pop 1
511
        dup 6
512
        skiz
513
        call tasmlib_hashing_merkle_verify_tree_height_is_not_zero
514
        swap 2
515
        swap 4
516
        swap 6
517
        pop 1
518
        swap 2
519
        swap 4
520
        pop 1
521
        assert_vector
522
        pop 5
523
        return
524
        tasmlib_hashing_merkle_verify_tree_height_is_not_zero:
525
        push 1
526
        swap 7
527
        pop 1
528
        call tasmlib_hashing_merkle_verify_traverse_tree
529
        return
530
        tasmlib_hashing_merkle_verify_traverse_tree:
531
        merkle_step
532
        recurse_or_return
533
        tasmlib_io_read_secin___bfe:
534
        divine 1
535
        return
536
        tasmlib_io_read_stdin___digest:
537
        read_io 5
538
        return
539
        tasmlib_memory_dyn_malloc:
540
        push -1
541
        read_mem 1
542
        pop 1
543
        dup 0
544
        push 0
545
        eq
546
        skiz
547
        call tasmlib_memory_dyn_malloc_initialize
548
        push 00000000002147483647
549
        dup 1
550
        lt
551
        assert
552
        dup 0
553
        push 1
554
        add
555
        push -1
556
        write_mem 1
557
        pop 1
558
        push 00000000004294967296
559
        mul
560
        return
561
        tasmlib_memory_dyn_malloc_initialize:
562
        pop 1
563
        push 1
564
        return
565
        tasmlib_verifier_own_program_digest:
566
        dup 15
567
        dup 15
568
        dup 15
569
        dup 15
570
        dup 15
571
        return
572
        };
573

574
        (library, code)
4,000✔
575
    }
4,000✔
576

577
    fn hash(&self) -> Digest {
342,130✔
578
        static HASH: OnceLock<Digest> = OnceLock::new();
579

580
        *HASH.get_or_init(|| self.program().hash())
342,130✔
581
    }
342,130✔
582
}
583

584
impl TypeScript for TimeLock {
585
    type State = Timestamp;
586
}
587

588
#[derive(Debug, Clone, Deserialize, Serialize, BFieldCodec, GetSize, PartialEq, Eq)]
589
pub struct TimeLockWitness {
590
    /// One timestamp for every input UTXO. Inputs that do not have a time lock are
591
    /// assigned timestamp 0, which is automatically satisfied.
592
    release_dates: Vec<Timestamp>,
593
    input_utxos: SaltedUtxos,
594
    transaction_kernel: TransactionKernel,
595
}
596

597
type TimeLockWitnessMemory = SaltedUtxos;
598

599
impl SecretWitness for TimeLockWitness {
600
    fn nondeterminism(&self) -> NonDeterminism {
3,765✔
601
        let mut memory: HashMap<BFieldElement, BFieldElement> = HashMap::new();
3,765✔
602
        let input_salted_utxos_address = FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS;
3,765✔
603
        let output_salted_utxos_address = encode_to_memory::<TimeLockWitnessMemory>(
3,765✔
604
            &mut memory,
3,765✔
605
            input_salted_utxos_address,
3,765✔
606
            &self.input_utxos,
3,765✔
607
        );
608
        let individual_tokens = vec![
3,765✔
609
            self.transaction_kernel.timestamp.0,
3,765✔
610
            input_salted_utxos_address,
3,765✔
611
            output_salted_utxos_address,
3,765✔
612
        ];
613
        let mast_path = self
3,765✔
614
            .transaction_kernel
3,765✔
615
            .mast_path(TransactionKernelField::Timestamp)
3,765✔
616
            .clone();
3,765✔
617
        NonDeterminism::new(individual_tokens)
3,765✔
618
            .with_digests(mast_path)
3,765✔
619
            .with_ram(memory)
3,765✔
620
    }
3,765✔
621

622
    fn standard_input(&self) -> PublicInput {
162✔
623
        self.type_script_standard_input()
162✔
624
    }
162✔
625

626
    fn program(&self) -> Program {
×
627
        TimeLock.program()
×
628
    }
×
629
}
630

631
impl TypeScriptWitness for TimeLockWitness {
632
    fn transaction_kernel(&self) -> TransactionKernel {
162✔
633
        self.transaction_kernel.clone()
162✔
634
    }
162✔
635

636
    fn salted_input_utxos(&self) -> SaltedUtxos {
162✔
637
        self.input_utxos.clone()
162✔
638
    }
162✔
639

640
    fn salted_output_utxos(&self) -> SaltedUtxos {
162✔
641
        SaltedUtxos::empty()
162✔
642
    }
162✔
643

644
    fn type_script_and_witness(&self) -> TypeScriptAndWitness {
3,603✔
645
        TypeScriptAndWitness::new_with_nondeterminism(TimeLock.program(), self.nondeterminism())
3,603✔
646
    }
3,603✔
647

648
    fn new(
3,603✔
649
        transaction_kernel: TransactionKernel,
3,603✔
650
        salted_input_utxos: SaltedUtxos,
3,603✔
651
        _salted_output_utxos: SaltedUtxos,
3,603✔
652
    ) -> Self {
3,603✔
653
        let release_dates = salted_input_utxos
3,603✔
654
            .utxos
3,603✔
655
            .iter()
3,603✔
656
            .map(TimeLock::extract_release_date)
3,603✔
657
            .collect_vec();
3,603✔
658

659
        Self {
3,603✔
660
            release_dates,
3,603✔
661
            input_utxos: salted_input_utxos,
3,603✔
662
            transaction_kernel,
3,603✔
663
        }
3,603✔
664
    }
3,603✔
665
}
666

667
impl From<PrimitiveWitness> for TimeLockWitness {
668
    fn from(primitive_witness: PrimitiveWitness) -> Self {
81✔
669
        let release_dates = primitive_witness
81✔
670
            .input_utxos
81✔
671
            .utxos
81✔
672
            .iter()
81✔
673
            .map(TimeLock::extract_release_date)
81✔
674
            .collect_vec();
81✔
675
        let transaction_kernel = TransactionKernel::from(primitive_witness.clone());
81✔
676
        let input_utxos = primitive_witness.input_utxos.clone();
81✔
677

678
        Self {
81✔
679
            release_dates,
81✔
680
            input_utxos,
81✔
681
            transaction_kernel,
81✔
682
        }
81✔
683
    }
81✔
684
}
685

686
#[cfg(any(test, feature = "arbitrary-impls"))]
687
pub mod neptune_arbitrary {
688
    use num_traits::CheckedSub;
689
    use proptest::arbitrary::Arbitrary;
690
    use proptest::collection::vec;
691
    use proptest::strategy::BoxedStrategy;
692
    use proptest::strategy::Strategy;
693
    use proptest_arbitrary_interop::arb;
694

695
    use super::super::native_currency_amount::NativeCurrencyAmount;
696
    use super::*;
697
    use crate::models::blockchain::transaction::transaction_kernel::TransactionKernelModifier;
698
    use crate::models::blockchain::transaction::PublicAnnouncement;
699

700
    impl Arbitrary for TimeLockWitness {
701
        /// Parameters are:
702
        ///  - release_dates : `Vec<u64>` One release date per input UTXO. 0 if the time lock
703
        ///    coin is absent.
704
        ///  - num_outputs : usize Number of outputs.
705
        ///  - num_public_announcements : usize Number of public announcements.
706
        ///  - transaction_timestamp: Timestamp determining when the transaction takes place.
707
        type Parameters = (Vec<Timestamp>, usize, usize, Timestamp);
708

709
        type Strategy = BoxedStrategy<Self>;
710

711
        fn arbitrary_with(parameters: Self::Parameters) -> Self::Strategy {
61✔
712
            let (release_dates, num_outputs, num_public_announcements, transaction_timestamp) =
61✔
713
                parameters;
61✔
714
            let num_inputs = release_dates.len();
61✔
715
            (
61✔
716
                vec(arb::<Digest>(), num_inputs),
61✔
717
                vec(NativeCurrencyAmount::arbitrary_non_negative(), num_inputs),
61✔
718
                vec(arb::<Digest>(), num_outputs),
61✔
719
                vec(NativeCurrencyAmount::arbitrary_non_negative(), num_outputs),
61✔
720
                vec(arb::<PublicAnnouncement>(), num_public_announcements),
61✔
721
                NativeCurrencyAmount::arbitrary_coinbase(),
61✔
722
                NativeCurrencyAmount::arbitrary_non_negative(),
61✔
723
            )
61✔
724
                .prop_flat_map(
61✔
725
                    move |(
726
                        input_address_seeds,
727
                        input_amounts,
728
                        output_address_seeds,
729
                        mut output_amounts,
730
                        public_announcements,
731
                        maybe_coinbase,
732
                        mut fee,
733
                    )| {
61✔
734
                        // generate inputs
735
                        let (mut input_utxos, input_lock_scripts_and_witnesses) =
61✔
736
                            PrimitiveWitness::transaction_inputs_from_address_seeds_and_amounts(
61✔
737
                                &input_address_seeds,
61✔
738
                                &input_amounts,
61✔
739
                            );
61✔
740
                        let total_inputs = input_amounts.into_iter().sum::<NativeCurrencyAmount>();
61✔
741

742
                        // add time locks to input UTXOs (changes Utxo hash)
743
                        for (utxo, release_date) in input_utxos.iter_mut().zip(release_dates.iter())
126✔
744
                        {
745
                            if !release_date.is_zero() {
126✔
746
                                let time_lock_coin = TimeLock::until(*release_date);
85✔
747
                                let mut coins = utxo.coins().to_vec();
85✔
748
                                coins.push(time_lock_coin);
85✔
749
                                *utxo = (utxo.lock_script_hash(), coins).into()
85✔
750
                            }
41✔
751
                        }
752

753
                        // generate valid output amounts
754
                        PrimitiveWitness::find_balanced_output_amounts_and_fee(
61✔
755
                            total_inputs,
61✔
756
                            maybe_coinbase,
61✔
757
                            &mut output_amounts,
61✔
758
                            &mut fee,
61✔
759
                        );
760

761
                        // generate output UTXOs
762
                        let output_utxos =
61✔
763
                            PrimitiveWitness::valid_tx_outputs_from_amounts_and_address_seeds(
61✔
764
                                &output_amounts,
61✔
765
                                &output_address_seeds,
61✔
766
                                None,
61✔
767
                            );
768

769
                        // generate primitive transaction witness and time lock witness from there
770
                        PrimitiveWitness::arbitrary_primitive_witness_with(
61✔
771
                            &input_utxos,
61✔
772
                            &input_lock_scripts_and_witnesses,
61✔
773
                            &output_utxos,
61✔
774
                            &public_announcements,
61✔
775
                            NativeCurrencyAmount::zero(),
61✔
776
                            maybe_coinbase,
61✔
777
                        )
778
                        .prop_map(move |mut transaction_primitive_witness| {
61✔
779
                            let modified_kernel = TransactionKernelModifier::default()
61✔
780
                                .timestamp(transaction_timestamp)
61✔
781
                                .modify(transaction_primitive_witness.kernel);
61✔
782

783
                            transaction_primitive_witness.kernel = modified_kernel;
61✔
784
                            TimeLockWitness::from(transaction_primitive_witness)
61✔
785
                        })
61✔
786
                        .boxed()
61✔
787
                    },
61✔
788
                )
789
                .boxed()
61✔
790
        }
61✔
791
    }
792

793
    /// Generate a `Strategy` for a [`PrimitiveWitness`] with the given numbers of
794
    /// inputs, outputs, and public announcements, with active timelocks.
795
    ///
796
    /// The UTXOs are timelocked with a release date set between `now` and six
797
    /// months from `now`.
798
    ///
799
    // Public bc used in benchmarks.
800
    #[doc(hidden)]
801
    pub fn arbitrary_primitive_witness_with_active_timelocks(
75✔
802
        num_inputs: usize,
75✔
803
        num_outputs: usize,
75✔
804
        num_announcements: usize,
75✔
805
        now: Timestamp,
75✔
806
    ) -> BoxedStrategy<PrimitiveWitness> {
75✔
807
        vec(
75✔
808
            Timestamp::arbitrary_between(now, now + Timestamp::months(6)),
75✔
809
            num_inputs + num_outputs,
75✔
810
        )
811
        .prop_flat_map(move |release_dates| {
75✔
812
            arbitrary_primitive_witness_with_timelocks(
75✔
813
                num_inputs,
75✔
814
                num_outputs,
75✔
815
                num_announcements,
75✔
816
                now,
75✔
817
                release_dates,
75✔
818
            )
819
        })
75✔
820
        .boxed()
75✔
821
    }
75✔
822

823
    /// Generate a `Strategy` for a [`PrimitiveWitness`] with the given numbers of
824
    /// inputs, outputs, and public announcements, with expired timelocks.
825
    ///
826
    /// The UTXOs are timelocked with a release date set between six months in the
827
    /// past relative to `now` and `now`.
828
    ///
829
    // Public bc used in benchmarks.
830
    #[doc(hidden)]
831
    pub fn arbitrary_primitive_witness_with_expired_timelocks(
33✔
832
        num_inputs: usize,
33✔
833
        num_outputs: usize,
33✔
834
        num_announcements: usize,
33✔
835
        now: Timestamp,
33✔
836
    ) -> BoxedStrategy<PrimitiveWitness> {
33✔
837
        vec(
33✔
838
            Timestamp::arbitrary_between(now - Timestamp::months(6), now - Timestamp::millis(1)),
33✔
839
            num_inputs + num_outputs,
33✔
840
        )
841
        .prop_flat_map(move |release_dates| {
137✔
842
            arbitrary_primitive_witness_with_timelocks(
137✔
843
                num_inputs,
137✔
844
                num_outputs,
137✔
845
                num_announcements,
137✔
846
                now,
137✔
847
                release_dates,
137✔
848
            )
849
        })
137✔
850
        .boxed()
33✔
851
    }
33✔
852

853
    #[expect(unused_variables, reason = "under development")]
854
    fn arbitrary_primitive_witness_with_timelocks(
212✔
855
        num_inputs: usize,
212✔
856
        num_outputs: usize,
212✔
857
        num_announcements: usize,
212✔
858
        now: Timestamp,
212✔
859
        release_dates: Vec<Timestamp>,
212✔
860
    ) -> BoxedStrategy<PrimitiveWitness> {
212✔
861
        (
212✔
862
            NativeCurrencyAmount::arbitrary_non_negative(),
212✔
863
            vec(arb::<Digest>(), num_inputs),
212✔
864
            vec(arb::<u64>(), num_inputs),
212✔
865
            vec(arb::<Digest>(), num_outputs),
212✔
866
            vec(arb::<u64>(), num_outputs),
212✔
867
            vec(arb::<PublicAnnouncement>(), num_announcements),
212✔
868
            arb::<u64>(),
212✔
869
            arb::<Option<u64>>(),
212✔
870
        )
212✔
871
            .prop_flat_map(
212✔
872
                move |(
873
                    total_amount,
874
                    input_address_seeds,
875
                    input_dist,
876
                    output_address_seeds,
877
                    output_dist,
878
                    public_announcements,
879
                    fee_dist,
880
                    maybe_coinbase_dist,
881
                )| {
212✔
882
                    let maybe_coinbase_dist = if num_inputs.is_zero() {
212✔
UNCOV
883
                        maybe_coinbase_dist
×
884
                    } else {
885
                        None
212✔
886
                    };
887

888
                    // distribute total amount across inputs (+ coinbase)
889
                    let mut input_denominator = input_dist.iter().map(|u| *u as f64).sum::<f64>();
420✔
890
                    if let Some(d) = maybe_coinbase_dist {
212✔
UNCOV
891
                        input_denominator += d as f64;
×
892
                    }
212✔
893
                    let input_weights = input_dist
212✔
894
                        .into_iter()
212✔
895
                        .map(|u| (u as f64) / input_denominator)
420✔
896
                        .collect_vec();
212✔
897
                    let mut input_amounts = input_weights
212✔
898
                        .into_iter()
212✔
899
                        .map(|w| total_amount.to_nau_f64() * w)
420✔
900
                        .map(|f| NativeCurrencyAmount::try_from(f).unwrap())
420✔
901
                        .collect_vec();
212✔
902
                    let maybe_coinbase = if maybe_coinbase_dist.is_some()
212✔
903
                        || input_amounts.is_empty()
212✔
904
                    {
UNCOV
905
                        Some(
×
UNCOV
906
                            total_amount
×
UNCOV
907
                                .checked_sub(
×
UNCOV
908
                                    &input_amounts.iter().copied().sum::<NativeCurrencyAmount>(),
×
UNCOV
909
                                )
×
UNCOV
910
                                .unwrap(),
×
UNCOV
911
                        )
×
912
                    } else {
913
                        let sum_of_all_but_last = input_amounts
212✔
914
                            .iter()
212✔
915
                            .rev()
212✔
916
                            .skip(1)
212✔
917
                            .copied()
212✔
918
                            .sum::<NativeCurrencyAmount>();
212✔
919
                        *input_amounts.last_mut().unwrap() =
212✔
920
                            total_amount.checked_sub(&sum_of_all_but_last).unwrap();
212✔
921
                        None
212✔
922
                    };
923

924
                    // distribute total amount across outputs
925
                    let output_denominator =
212✔
926
                        output_dist.iter().map(|u| *u as f64).sum::<f64>() + (fee_dist as f64);
412✔
927
                    let output_weights = output_dist
212✔
928
                        .into_iter()
212✔
929
                        .map(|u| (u as f64) / output_denominator)
412✔
930
                        .collect_vec();
212✔
931
                    let output_amounts = output_weights
212✔
932
                        .into_iter()
212✔
933
                        .map(|w| total_amount.to_nau_f64() * w)
412✔
934
                        .map(|f| NativeCurrencyAmount::try_from(f).unwrap())
412✔
935
                        .collect_vec();
212✔
936
                    let total_outputs =
212✔
937
                        output_amounts.iter().copied().sum::<NativeCurrencyAmount>();
212✔
938
                    let fee = total_amount.checked_sub(&total_outputs).unwrap();
212✔
939

940
                    let (mut input_utxos, input_lock_scripts_and_witnesses) =
212✔
941
                        PrimitiveWitness::transaction_inputs_from_address_seeds_and_amounts(
212✔
942
                            &input_address_seeds,
212✔
943
                            &input_amounts,
212✔
944
                        );
212✔
945
                    let total_inputs = input_amounts.iter().copied().sum::<NativeCurrencyAmount>();
212✔
946

947
                    assert_eq!(
212✔
948
                        total_inputs + maybe_coinbase.unwrap_or(NativeCurrencyAmount::coins(0)),
212✔
949
                        total_outputs + fee
212✔
950
                    );
951
                    let mut output_utxos =
212✔
952
                        PrimitiveWitness::valid_tx_outputs_from_amounts_and_address_seeds(
212✔
953
                            &output_amounts,
212✔
954
                            &output_address_seeds,
212✔
955
                            None,
212✔
956
                        );
957
                    let mut counter = 0usize;
212✔
958
                    for utxo in &mut input_utxos {
632✔
959
                        let release_date = release_dates[counter];
420✔
960
                        let time_lock = TimeLock::until(release_date);
420✔
961
                        let mut coins = utxo.coins().to_vec();
420✔
962
                        coins.push(time_lock);
420✔
963
                        *utxo = Utxo::from((utxo.lock_script_hash(), coins));
420✔
964
                        counter += 1;
420✔
965
                    }
420✔
966
                    for utxo in &mut output_utxos {
624✔
967
                        let mut coins = utxo.coins().to_vec();
412✔
968
                        coins.push(TimeLock::until(release_dates[counter]));
412✔
969
                        *utxo = Utxo::from((utxo.lock_script_hash(), coins));
412✔
970
                        counter += 1;
412✔
971
                    }
412✔
972
                    let release_dates = release_dates.clone();
212✔
973

974
                    let merge_bit = false;
212✔
975
                    PrimitiveWitness::arbitrary_primitive_witness_with_timestamp_and(
212✔
976
                        &input_utxos,
212✔
977
                        &input_lock_scripts_and_witnesses,
212✔
978
                        &output_utxos,
212✔
979
                        &public_announcements,
212✔
980
                        fee,
212✔
981
                        maybe_coinbase,
212✔
982
                        now,
212✔
983
                        merge_bit,
212✔
984
                    )
985
                    .prop_map(move |primitive_witness_template| {
212✔
986
                        let mut primitive_witness = primitive_witness_template.clone();
212✔
987
                        let modified_kernel = TransactionKernelModifier::default()
212✔
988
                            .timestamp(now)
212✔
989
                            .modify(primitive_witness.kernel);
212✔
990

991
                        primitive_witness.kernel = modified_kernel;
212✔
992
                        primitive_witness
212✔
993
                    })
212✔
994
                },
212✔
995
            )
996
            .boxed()
212✔
997
    }
212✔
998
}
999

1000
#[cfg(test)]
1001
#[cfg_attr(coverage_nightly, coverage(off))]
1002
mod tests {
1003
    use proptest::collection::vec;
1004
    use proptest::prelude::Arbitrary;
1005
    use proptest::prelude::Strategy;
1006
    use proptest::prop_assert;
1007
    use proptest::prop_assert_eq;
1008
    use proptest::strategy::Just;
1009
    use proptest::test_runner::TestRunner;
1010
    use proptest_arbitrary_interop::arb;
1011
    use tasm_lib::twenty_first::math::tip5::Tip5;
1012
    use test_strategy::proptest;
1013

1014
    use super::neptune_arbitrary::arbitrary_primitive_witness_with_active_timelocks;
1015
    use super::neptune_arbitrary::arbitrary_primitive_witness_with_expired_timelocks;
1016
    use super::*;
1017
    use crate::models::proof_abstractions::tasm::builtins as tasm;
1018
    use crate::models::proof_abstractions::tasm::program::tests::test_program_snapshot;
1019
    use crate::models::proof_abstractions::tasm::program::tests::ConsensusProgramSpecification;
1020

1021
    impl ConsensusProgramSpecification for TimeLock {
1022
        #[expect(clippy::needless_return)]
1023
        fn source(&self) {
1024
            // get in the current program's hash digest
1025
            let self_digest: Digest = tasm::own_program_digest();
1026

1027
            // read standard input:
1028
            //  - transaction kernel mast hash
1029
            //  - input salted utxos digest
1030
            //  - output salted utxos digest
1031
            // (All type scripts take this triple as input.)
1032
            let tx_kernel_digest: Digest = tasm::tasmlib_io_read_stdin___digest();
1033
            let input_utxos_digest: Digest = tasm::tasmlib_io_read_stdin___digest();
1034
            let _output_utxos_digest: Digest = tasm::tasmlib_io_read_stdin___digest();
1035

1036
            // divine the timestamp and authenticate it against the kernel mast hash
1037
            let leaf_index: u32 = 5;
1038
            let timestamp: BFieldElement = tasm::tasmlib_io_read_secin___bfe();
1039
            let leaf: Digest = Tip5::hash_varlen(&timestamp.encode());
1040
            let tree_height: u32 = 3;
1041
            tasm::tasmlib_hashing_merkle_verify(tx_kernel_digest, leaf_index, leaf, tree_height);
1042

1043
            // get pointers to objects living in nondeterministic memory:
1044
            //  - input Salted UTXOs
1045
            let input_utxos_pointer: u64 = tasm::tasmlib_io_read_secin___bfe().value();
1046

1047
            // it's important to read the outputs digest too, but we actually don't care about
1048
            // the output UTXOs (in this type script)
1049
            let _output_utxos_pointer: u64 = tasm::tasmlib_io_read_secin___bfe().value();
1050

1051
            // authenticate salted input UTXOs against the digest that was read from stdin
1052
            let input_salted_utxos: SaltedUtxos =
1053
                tasm::decode_from_memory(BFieldElement::new(input_utxos_pointer));
1054
            let input_salted_utxos_digest: Digest = Tip5::hash(&input_salted_utxos);
1055
            assert_eq!(input_salted_utxos_digest, input_utxos_digest);
1056

1057
            // iterate over inputs
1058
            let input_utxos = input_salted_utxos.utxos;
1059
            let mut i = 0;
1060
            while i < input_utxos.len() {
1061
                // get coins
1062
                let coins = input_utxos[i].coins();
1063

1064
                // if this typescript is present
1065
                let mut j: usize = 0;
1066
                while j < coins.len() {
1067
                    let coin: &Coin = &coins[j];
1068
                    if coin.type_script_hash == self_digest {
1069
                        // extract state
1070
                        let state: &Vec<BFieldElement> = &coin.state;
1071

1072
                        // assert format
1073
                        assert!(state.len() == 1);
1074

1075
                        // extract timestamp
1076
                        let release_date: BFieldElement = state[0];
1077

1078
                        // test time lock
1079
                        assert!(release_date.value() < timestamp.value());
1080
                    }
1081
                    j += 1;
1082
                }
1083
                i += 1;
1084
            }
1085

1086
            return;
1087
        }
1088
    }
1089

1090
    #[proptest(cases = 20)]
1091
    fn test_unlocked(
1092
        #[strategy(1usize..=3)] _num_inputs: usize,
1093
        #[strategy(1usize..=3)] _num_outputs: usize,
1094
        #[strategy(1usize..=3)] _num_public_announcements: usize,
1095
        #[strategy(vec(Just(Timestamp::zero()), #_num_inputs))] _release_dates: Vec<Timestamp>,
1096
        #[strategy(Just::<Timestamp>(#_release_dates.iter().copied().min().unwrap()))]
1097
        _transaction_timestamp: Timestamp,
1098
        #[strategy(
1099
            TimeLockWitness::arbitrary_with((
1100
                #_release_dates,
1101
                #_num_outputs,
1102
                #_num_public_announcements,
1103
                #_transaction_timestamp,
1104
            ))
1105
        )]
1106
        time_lock_witness: TimeLockWitness,
1107
    ) {
1108
        let rust_result = TimeLock.run_rust(
1109
            &time_lock_witness.standard_input(),
1110
            time_lock_witness.nondeterminism(),
1111
        );
1112
        prop_assert!(
1113
            rust_result.is_ok(),
1114
            "time lock program did not halt gracefully"
1115
        );
1116
        let tasm_result = TimeLock.run_tasm(
1117
            &time_lock_witness.standard_input(),
1118
            time_lock_witness.nondeterminism(),
1119
        );
1120
        prop_assert!(
1121
            tasm_result.is_ok(),
1122
            "time lock program did not halt gracefully"
1123
        );
1124
        prop_assert_eq!(rust_result.unwrap(), tasm_result.unwrap());
1125
    }
1126

1127
    #[test]
1128
    fn tx_timestamp_same_as_release_time_must_fail() {
1129
        // Verify use of `>`, not `>=`.
1130
        let release_date = Timestamp::now();
1131
        let mut test_runner = TestRunner::deterministic();
1132
        let time_lock_witness =
1133
            TimeLockWitness::arbitrary_with((vec![release_date], 1, 0, release_date))
1134
                .new_tree(&mut test_runner)
1135
                .unwrap()
1136
                .current();
1137
        assert!(
1138
            TimeLock {}
1139
                .run_rust(
1140
                    &time_lock_witness.standard_input(),
1141
                    time_lock_witness.nondeterminism(),
1142
                )
1143
                .is_err(),
1144
            "time lock program failed to panic"
1145
        );
1146
        assert!(
1147
            TimeLock {}
1148
                .run_tasm(
1149
                    &time_lock_witness.standard_input(),
1150
                    time_lock_witness.nondeterminism(),
1151
                )
1152
                .is_err(),
1153
            "time lock program failed to panic"
1154
        );
1155
    }
1156

1157
    #[proptest(cases = 20)]
1158
    fn test_locked(
1159
        #[strategy(1usize..=3)] _num_inputs: usize,
1160
        #[strategy(1usize..=3)] _num_outputs: usize,
1161
        #[strategy(1usize..=3)] _num_public_announcements: usize,
1162
        #[strategy(
1163
            vec(
1164
                Timestamp::arbitrary_between(
1165
                    Timestamp::now() - Timestamp::days(7),
1166
                    Timestamp::now() - Timestamp::days(1),
1167
                ),
1168
                #_num_inputs,
1169
            )
1170
        )]
1171
        _release_dates: Vec<Timestamp>,
1172
        #[strategy(Just::<Timestamp>(#_release_dates.iter().copied().max().unwrap()))]
1173
        _tx_timestamp: Timestamp,
1174
        #[strategy(
1175
            TimeLockWitness::arbitrary_with((
1176
                #_release_dates,
1177
                #_num_outputs,
1178
                #_num_public_announcements,
1179
                #_tx_timestamp,
1180
            ))
1181
        )]
1182
        time_lock_witness: TimeLockWitness,
1183
    ) {
1184
        println!("now: {}", Timestamp::now());
1185
        prop_assert!(
1186
            TimeLock {}
1187
                .run_rust(
1188
                    &time_lock_witness.standard_input(),
1189
                    time_lock_witness.nondeterminism(),
1190
                )
1191
                .is_err(),
1192
            "time lock program failed to panic"
1193
        );
1194
        prop_assert!(
1195
            TimeLock {}
1196
                .run_tasm(
1197
                    &time_lock_witness.standard_input(),
1198
                    time_lock_witness.nondeterminism(),
1199
                )
1200
                .is_err(),
1201
            "time lock program failed to panic"
1202
        );
1203
    }
1204

1205
    #[proptest(cases = 20)]
1206
    fn test_released(
1207
        #[strategy(1usize..=3)] _num_inputs: usize,
1208
        #[strategy(1usize..=3)] _num_outputs: usize,
1209
        #[strategy(1usize..=3)] _num_public_announcements: usize,
1210
        #[strategy(
1211
            vec(
1212
                Timestamp::arbitrary_between(
1213
                    Timestamp::now() - Timestamp::days(7),
1214
                    Timestamp::now() - Timestamp::days(1),
1215
                ),
1216
                #_num_inputs,
1217
            )
1218
        )]
1219
        _release_dates: Vec<Timestamp>,
1220
        #[strategy(Just::<Timestamp>(#_release_dates.iter().copied().max().unwrap()))]
1221
        _tx_timestamp: Timestamp,
1222
        #[strategy(
1223
            TimeLockWitness::arbitrary_with((
1224
                #_release_dates,
1225
                #_num_outputs,
1226
                #_num_public_announcements,
1227
                #_tx_timestamp + Timestamp::days(1),
1228
            ))
1229
        )]
1230
        time_lock_witness: TimeLockWitness,
1231
    ) {
1232
        println!("now: {}", Timestamp::now());
1233
        let rust_result = TimeLock.run_rust(
1234
            &time_lock_witness.standard_input(),
1235
            time_lock_witness.nondeterminism(),
1236
        );
1237
        prop_assert!(
1238
            rust_result.is_ok(),
1239
            "time lock program did not halt gracefully"
1240
        );
1241
        let tasm_result = TimeLock.run_tasm(
1242
            &time_lock_witness.standard_input(),
1243
            time_lock_witness.nondeterminism(),
1244
        );
1245
        prop_assert!(
1246
            tasm_result.is_ok(),
1247
            "time lock program did not halt gracefully"
1248
        );
1249
        prop_assert_eq!(rust_result.unwrap(), tasm_result.unwrap());
1250
    }
1251

1252
    #[proptest(cases = 5)]
1253
    fn primitive_witness_with_active_timelocks_is_invalid(
1254
        #[strategy(arb::<Timestamp>())] _now: Timestamp,
1255
        #[strategy(arbitrary_primitive_witness_with_active_timelocks(2, 2, 2, #_now))]
1256
        primitive_witness: PrimitiveWitness,
1257
    ) {
1258
        // Negative test: Primitive witness spending inputs that are timelocked
1259
        // must fail to validate.
1260
        let rt = crate::tests::tokio_runtime();
1261
        prop_assert!(rt.block_on(primitive_witness.validate()).is_err());
1262
    }
1263

1264
    #[proptest(cases = 10)]
1265
    fn arbitrary_primitive_witness_with_active_timelocks_fails(
1266
        #[strategy(arb::<Timestamp>())] _now: Timestamp,
1267
        #[strategy(arbitrary_primitive_witness_with_active_timelocks(2, 2, 2, #_now))]
1268
        primitive_witness: PrimitiveWitness,
1269
    ) {
1270
        let time_lock_witness = TimeLockWitness::from(primitive_witness);
1271

1272
        prop_assert!(
1273
            TimeLock {}
1274
                .run_rust(
1275
                    &time_lock_witness.standard_input(),
1276
                    time_lock_witness.nondeterminism(),
1277
                )
1278
                .is_err(),
1279
            "time lock program failed to panic"
1280
        );
1281
        prop_assert!(
1282
            TimeLock {}
1283
                .run_tasm(
1284
                    &time_lock_witness.standard_input(),
1285
                    time_lock_witness.nondeterminism(),
1286
                )
1287
                .is_err(),
1288
            "time lock program failed to panic"
1289
        );
1290
    }
1291

1292
    #[proptest(cases = 10)]
1293
    fn arbitrary_primitive_witness_with_expired_timelocks_passes(
1294
        #[strategy(arb::<Timestamp>())] _now: Timestamp,
1295
        #[strategy(arbitrary_primitive_witness_with_expired_timelocks(2, 2, 2, #_now))]
1296
        primitive_witness: PrimitiveWitness,
1297
    ) {
1298
        let time_lock_witness = TimeLockWitness::from(primitive_witness);
1299

1300
        let rust_result = TimeLock.run_rust(
1301
            &time_lock_witness.standard_input(),
1302
            time_lock_witness.nondeterminism(),
1303
        );
1304
        prop_assert!(
1305
            rust_result.is_ok(),
1306
            "time lock program did not halt gracefully"
1307
        );
1308
        let tasm_result = TimeLock.run_tasm(
1309
            &time_lock_witness.standard_input(),
1310
            time_lock_witness.nondeterminism(),
1311
        );
1312
        prop_assert!(
1313
            tasm_result.is_ok(),
1314
            "time lock program did not halt gracefully"
1315
        );
1316
        prop_assert_eq!(tasm_result.unwrap(), rust_result.unwrap());
1317
    }
1318

1319
    test_program_snapshot!(
1320
        TimeLock,
1321
        // snapshot taken from master on 2025-04-11 e2a712efc34f78c6a28801544418e7051127d284
1322
        "4b4d251947a07f9f2c016c1c271c04ce41013ff50031bd42854919be6e0e4849ebf931e856b542ad"
1323
    );
1324
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc