Skip to content

Commit f0cd37f

Browse files
committed
feat: implement CheckTimelock scripting operator
1 parent b25ab35 commit f0cd37f

File tree

2 files changed

+101
-26
lines changed

2 files changed

+101
-26
lines changed

stack/src/lib.rs

Lines changed: 86 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub enum MyOperator {
2020
Equal,
2121
Hash160,
2222
CheckMultiSig,
23+
CheckTimeLock,
2324
/// Stop script execution if top-most element of stack is not "true"
2425
Verify,
2526
// Control flow
@@ -143,11 +144,26 @@ fn check_multi_sig(bytes_pkhs: Vec<MyValue>, bytes_keyed_signatures: Vec<MyValue
143144
true
144145
}
145146

147+
fn check_timelock_operator(stack: &mut Stack<MyValue>, block_timestamp: i64) {
148+
let timelock = stack.pop();
149+
match timelock {
150+
MyValue::Integer(timelock) => {
151+
let timelock_ok = i128::from(block_timestamp) >= timelock;
152+
stack.push(MyValue::Boolean(timelock_ok));
153+
}
154+
_ => {
155+
// TODO change panic by error
156+
unreachable!("CheckTimelock should pick an integer as a first value");
157+
}
158+
}
159+
}
160+
146161
// An operator system decides what to do with the stack when each operator is applied on it.
147162
fn my_operator_system(
148163
stack: &mut Stack<MyValue>,
149164
operator: &MyOperator,
150165
if_stack: &mut ConditionStack,
166+
context: &ScriptContext,
151167
) -> MyControlFlow {
152168
if !if_stack.all_true() {
153169
match operator {
@@ -176,6 +192,7 @@ fn my_operator_system(
176192
MyOperator::Equal => equal_operator(stack),
177193
MyOperator::Hash160 => hash_160_operator(stack),
178194
MyOperator::CheckMultiSig => check_multisig_operator(stack),
195+
MyOperator::CheckTimeLock => check_timelock_operator(stack, context.block_timestamp),
179196
MyOperator::Verify => {
180197
let top = stack.pop();
181198
if top != MyValue::Boolean(true) {
@@ -346,15 +363,24 @@ pub fn encode(a: Script<MyOperator, MyValue>) -> Vec<u8> {
346363
serde_json::to_vec(&x).unwrap()
347364
}
348365

349-
fn execute_script(script: Script<MyOperator, MyValue>) -> bool {
366+
#[derive(Default)]
367+
pub struct ScriptContext {
368+
pub block_timestamp: i64,
369+
}
370+
371+
fn execute_script(script: Script<MyOperator, MyValue>, context: &ScriptContext) -> bool {
350372
// Instantiate the machine with a reference to your operator system.
351-
let mut machine = Machine2::new(my_operator_system);
373+
let mut machine = Machine2::new(|a, b, c| my_operator_system(a, b, c, context));
352374
let result = machine.run_script(&script);
353375

354376
result == None || result == Some(&MyValue::Boolean(true))
355377
}
356378

357-
fn execute_locking_script(redeem_bytes: &[u8], locking_bytes: &[u8; 20]) -> bool {
379+
fn execute_locking_script(
380+
redeem_bytes: &[u8],
381+
locking_bytes: &[u8; 20],
382+
context: &ScriptContext,
383+
) -> bool {
358384
// Check locking script
359385
let mut locking_script = vec![
360386
Item::Operator(MyOperator::Hash160),
@@ -366,32 +392,37 @@ fn execute_locking_script(redeem_bytes: &[u8], locking_bytes: &[u8; 20]) -> bool
366392
locking_script.insert(0, Item::Value(MyValue::Bytes(redeem_bytes.to_vec())));
367393

368394
// Execute the script
369-
execute_script(locking_script)
395+
execute_script(locking_script, context)
370396
}
371397

372-
fn execute_redeem_script(witness_bytes: &[u8], redeem_bytes: &[u8]) -> bool {
398+
fn execute_redeem_script(
399+
witness_bytes: &[u8],
400+
redeem_bytes: &[u8],
401+
context: &ScriptContext,
402+
) -> bool {
373403
// Execute witness script concatenated with redeem script
374404
let mut witness_script = decode(witness_bytes);
375405
let redeem_script = decode(redeem_bytes);
376406
witness_script.extend(redeem_script);
377407

378408
// Execute the script
379-
execute_script(witness_script)
409+
execute_script(witness_script, context)
380410
}
381411

382412
pub fn execute_complete_script(
383413
witness_bytes: &[u8],
384414
redeem_bytes: &[u8],
385415
locking_bytes: &[u8; 20],
416+
context: &ScriptContext,
386417
) -> bool {
387418
// Execute locking script
388-
let result = execute_locking_script(redeem_bytes, locking_bytes);
419+
let result = execute_locking_script(redeem_bytes, locking_bytes, context);
389420
if !result {
390421
return false;
391422
}
392423

393424
// Execute witness script concatenated with redeem script
394-
execute_redeem_script(witness_bytes, redeem_bytes)
425+
execute_redeem_script(witness_bytes, redeem_bytes, context)
395426
}
396427

397428
// TODO: use control flow enum from scriptful library when ready
@@ -473,14 +504,14 @@ mod tests {
473504
Item::Value(MyValue::String("patata".to_string())),
474505
Item::Operator(MyOperator::Equal),
475506
];
476-
assert!(execute_script(s));
507+
assert!(execute_script(s, &ScriptContext::default()));
477508

478509
let s = vec![
479510
Item::Value(MyValue::String("patata".to_string())),
480511
Item::Value(MyValue::String("potato".to_string())),
481512
Item::Operator(MyOperator::Equal),
482513
];
483-
assert!(!execute_script(s));
514+
assert!(!execute_script(s, &ScriptContext::default()));
484515
}
485516

486517
#[test]
@@ -489,14 +520,16 @@ mod tests {
489520
let locking_script = EQUAL_OPERATOR_HASH;
490521
assert!(execute_locking_script(
491522
&encode(redeem_script),
492-
&locking_script
523+
&locking_script,
524+
&ScriptContext::default(),
493525
));
494526

495527
let redeem_script = vec![Item::Operator(MyOperator::Equal)];
496528
let locking_script = [1; 20];
497529
assert!(!execute_locking_script(
498530
&encode(redeem_script),
499-
&locking_script
531+
&locking_script,
532+
&ScriptContext::default(),
500533
));
501534
}
502535

@@ -509,7 +542,8 @@ mod tests {
509542
let redeem_script = vec![Item::Operator(MyOperator::Equal)];
510543
assert!(execute_redeem_script(
511544
&encode(witness),
512-
&encode(redeem_script)
545+
&encode(redeem_script),
546+
&ScriptContext::default(),
513547
));
514548

515549
let witness = vec![
@@ -519,7 +553,8 @@ mod tests {
519553
let redeem_script = vec![Item::Operator(MyOperator::Equal)];
520554
assert!(!execute_redeem_script(
521555
&encode(witness),
522-
&encode(redeem_script)
556+
&encode(redeem_script),
557+
&ScriptContext::default(),
523558
));
524559
}
525560

@@ -535,6 +570,7 @@ mod tests {
535570
&encode(witness),
536571
&encode(redeem_script),
537572
&locking_script,
573+
&ScriptContext::default(),
538574
));
539575

540576
let witness = vec![
@@ -547,6 +583,7 @@ mod tests {
547583
&encode(witness),
548584
&encode(redeem_script),
549585
&locking_script,
586+
&ScriptContext::default(),
550587
));
551588

552589
let witness = vec![
@@ -559,6 +596,7 @@ mod tests {
559596
&encode(witness),
560597
&encode(redeem_script),
561598
&locking_script,
599+
&ScriptContext::default(),
562600
));
563601
}
564602

@@ -592,7 +630,8 @@ mod tests {
592630
];
593631
assert!(execute_redeem_script(
594632
&encode(witness),
595-
&encode(redeem_script)
633+
&encode(redeem_script),
634+
&ScriptContext::default(),
596635
));
597636

598637
let other_valid_witness = vec![
@@ -609,7 +648,8 @@ mod tests {
609648
];
610649
assert!(execute_redeem_script(
611650
&encode(other_valid_witness),
612-
&encode(redeem_script)
651+
&encode(redeem_script),
652+
&ScriptContext::default(),
613653
));
614654

615655
let pk_4 = PublicKey::from_bytes([4; 33]);
@@ -628,7 +668,8 @@ mod tests {
628668
];
629669
assert!(!execute_redeem_script(
630670
&encode(invalid_witness),
631-
&encode(redeem_script)
671+
&encode(redeem_script),
672+
&ScriptContext::default(),
632673
));
633674
}
634675

@@ -640,15 +681,15 @@ mod tests {
640681
Item::Operator(MyOperator::Equal),
641682
Item::Operator(MyOperator::Verify),
642683
];
643-
assert!(execute_script(s));
684+
assert!(execute_script(s, &ScriptContext::default()));
644685

645686
let s = vec![
646687
Item::Value(MyValue::String("patata".to_string())),
647688
Item::Value(MyValue::String("potato".to_string())),
648689
Item::Operator(MyOperator::Equal),
649690
Item::Operator(MyOperator::Verify),
650691
];
651-
assert!(!execute_script(s));
692+
assert!(!execute_script(s, &ScriptContext::default()));
652693
}
653694

654695
#[test]
@@ -664,7 +705,7 @@ mod tests {
664705
Item::Operator(MyOperator::Equal),
665706
Item::Operator(MyOperator::Verify),
666707
];
667-
assert!(execute_script(s));
708+
assert!(execute_script(s, &ScriptContext::default()));
668709

669710
let s = vec![
670711
Item::Value(MyValue::String("patata".to_string())),
@@ -677,7 +718,7 @@ mod tests {
677718
Item::Operator(MyOperator::Equal),
678719
Item::Operator(MyOperator::Verify),
679720
];
680-
assert!(!execute_script(s));
721+
assert!(!execute_script(s, &ScriptContext::default()));
681722
}
682723

683724
#[test]
@@ -698,7 +739,7 @@ mod tests {
698739
Item::Operator(MyOperator::Equal),
699740
Item::Operator(MyOperator::Verify),
700741
];
701-
assert!(execute_script(s));
742+
assert!(execute_script(s, &ScriptContext::default()));
702743

703744
let s = vec![
704745
Item::Value(MyValue::String("potato".to_string())),
@@ -716,7 +757,7 @@ mod tests {
716757
Item::Operator(MyOperator::Equal),
717758
Item::Operator(MyOperator::Verify),
718759
];
719-
assert!(execute_script(s));
760+
assert!(execute_script(s, &ScriptContext::default()));
720761
}
721762

722763
#[test]
@@ -731,4 +772,26 @@ mod tests {
731772

732773
assert_eq!(v, vec![0, 1, 3, 2]);
733774
}
775+
776+
#[test]
777+
fn test_execute_script_op_check_timelock() {
778+
let s = vec![
779+
Item::Value(MyValue::Integer(10_000)),
780+
Item::Operator(MyOperator::CheckTimeLock),
781+
Item::Operator(MyOperator::Verify),
782+
];
783+
assert!(!execute_script(s, &ScriptContext { block_timestamp: 0 }));
784+
785+
let s = vec![
786+
Item::Value(MyValue::Integer(10_000)),
787+
Item::Operator(MyOperator::CheckTimeLock),
788+
Item::Operator(MyOperator::Verify),
789+
];
790+
assert!(execute_script(
791+
s,
792+
&ScriptContext {
793+
block_timestamp: 20_000,
794+
}
795+
));
796+
}
734797
}

validations/src/validations.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ use witnet_rad::{
4545
script::{create_radon_script_from_filters_and_reducer, unpack_radon_script},
4646
types::{serial_iter_decode, RadonTypes},
4747
};
48-
use witnet_stack::{execute_complete_script, Item, MyValue};
48+
use witnet_stack::{execute_complete_script, Item, MyValue, ScriptContext};
4949

5050
/// Returns the fee of a value transfer transaction.
5151
///
@@ -308,12 +308,15 @@ pub fn validate_vt_transaction<'a>(
308308
.into());
309309
}
310310

311+
let block_timestamp = epoch_constants.epoch_timestamp(epoch)?;
312+
311313
validate_transaction_signatures(
312314
&vt_tx.witness,
313315
&vt_tx.body.inputs,
314316
vt_tx.hash(),
315317
utxo_diff,
316318
signatures_to_verify,
319+
block_timestamp,
317320
)?;
318321

319322
// A value transfer transaction must have at least one input
@@ -411,12 +414,15 @@ pub fn validate_dr_transaction<'a>(
411414
.map(vtt_signature_to_witness)
412415
.collect();
413416

417+
let block_timestamp = epoch_constants.epoch_timestamp(epoch)?;
418+
414419
validate_transaction_signatures(
415420
&dr_tx_signatures_vec_u8,
416421
&dr_tx.body.inputs,
417422
dr_tx.hash(),
418423
utxo_diff,
419424
signatures_to_verify,
425+
block_timestamp,
420426
)?;
421427

422428
// A data request can only have 0 or 1 outputs
@@ -1210,6 +1216,7 @@ pub fn validate_transaction_signatures(
12101216
tx_hash: Hash,
12111217
utxo_set: &UtxoDiff<'_>,
12121218
signatures_to_verify: &mut Vec<SignaturesToVerify>,
1219+
block_timestamp: i64,
12131220
) -> Result<(), failure::Error> {
12141221
if signatures.len() != inputs.len() {
12151222
return Err(TransactionError::MismatchingSignaturesNumber {
@@ -1258,10 +1265,15 @@ pub fn validate_transaction_signatures(
12581265
output: output_pointer.clone(),
12591266
})?;
12601267
let redeem_script_hash = output.pkh;
1268+
let script_context = ScriptContext { block_timestamp };
12611269
// Script execution assumes that all the signatures are valid, the signatures will be
12621270
// validated later.
1263-
let res =
1264-
execute_complete_script(witness, &input.redeem_script, redeem_script_hash.bytes());
1271+
let res = execute_complete_script(
1272+
witness,
1273+
&input.redeem_script,
1274+
redeem_script_hash.bytes(),
1275+
&script_context,
1276+
);
12651277

12661278
if !res {
12671279
return Err(TransactionError::ScriptExecutionFailed {

0 commit comments

Comments
 (0)