essential_constraint_vm/
lib.rs

1//! The essential constraint checking implementation.
2//!
3//! ## Checking Predicates
4//!
5//! The primary entrypoint for this crate is the [`check_predicate`] function
6//! which allows for checking a contract of constraints associated with a single
7//! predicate against some provided solution data and state slot mutations in
8//! parallel.
9//!
10//! ## Checking Individual Constraints
11//!
12//! Functions are also exposed for checking constraints individually.
13//!
14//! - The [`exec_bytecode`], [`exec_bytecode_iter`] and [`exec_ops`] functions
15//!   allow for executing the constraint and returning the resulting `Stack`.
16//! - The [`eval_bytecode`], [`eval_bytecode_iter`] and [`eval_ops`] functions
17//!   are similar to their `exec_*` counterparts, but expect the top of
18//!   the `Stack` to contain a single boolean value indicating whether the
19//!   constraint was satisfied (`0` for `false`, `1` for `true`) and returns
20//!   this value.
21//!
22//! ## Performing a Single Operation
23//!
24//! The [`step_op`] function (and related `step_op_*` functions) are exposed to
25//! allow for applying a single operation to the given stack. This can be useful
26//! in the case of integrating constraint operations in a downstream VM (e.g.
27//! the essential state read VM).
28//!
29//! ## Understanding the Assembly
30//!
31//! The `essential-constraint-asm` crate is re-exported as the [`asm`] module.
32//! See [this module's documentation][asm] for information about the expected
33//! behaviour of individual operations.
34#![deny(missing_docs, unsafe_code)]
35
36pub use access::{
37    mut_keys, mut_keys_set, mut_keys_slices, Access, SolutionAccess, StateSlotSlice, StateSlots,
38};
39#[doc(inline)]
40pub use bytecode::{BytecodeMapped, BytecodeMappedLazy, BytecodeMappedSlice};
41pub use cached::LazyCache;
42#[doc(inline)]
43pub use error::{CheckResult, ConstraintResult, OpResult, StackResult};
44use error::{ConstraintError, ConstraintErrors, ConstraintsUnsatisfied};
45#[doc(inline)]
46pub use essential_constraint_asm as asm;
47use essential_constraint_asm::Op;
48pub use essential_types as types;
49use essential_types::{convert::bool_from_word, ConstraintBytecode};
50#[doc(inline)]
51pub use memory::Memory;
52#[doc(inline)]
53pub use op_access::OpAccess;
54#[doc(inline)]
55pub use repeat::Repeat;
56#[doc(inline)]
57pub use stack::Stack;
58#[doc(inline)]
59pub use total_control_flow::ProgramControlFlow;
60
61mod access;
62mod alu;
63mod bytecode;
64mod cached;
65mod crypto;
66pub mod error;
67mod memory;
68mod op_access;
69mod pred;
70mod repeat;
71mod sets;
72mod stack;
73mod total_control_flow;
74
75/// Check whether the constraints of a single predicate are met for the given
76/// solution data and state slot mutations. All constraints are checked in
77/// parallel.
78///
79/// In the case that one or more constraints fail or are unsatisfied, the
80/// whole contract of failed/unsatisfied constraint indices are returned within the
81/// [`CheckError`][error::CheckError] type.
82///
83/// The predicate is considered to be satisfied if this function returns `Ok(())`.
84pub fn check_predicate(predicate: &[ConstraintBytecode], access: Access) -> CheckResult<()> {
85    use rayon::{iter::Either, prelude::*};
86    let (failed, unsatisfied): (Vec<_>, Vec<_>) = predicate
87        .par_iter()
88        .map(|bytecode| eval_bytecode_iter(bytecode.iter().copied(), access))
89        .enumerate()
90        .filter_map(|(i, constraint_res)| match constraint_res {
91            Err(err) => Some(Either::Left((i, err))),
92            Ok(b) if !b => Some(Either::Right(i)),
93            _ => None,
94        })
95        .partition_map(|either| either);
96    if !failed.is_empty() {
97        return Err(ConstraintErrors(failed).into());
98    }
99    if !unsatisfied.is_empty() {
100        return Err(ConstraintsUnsatisfied(unsatisfied).into());
101    }
102    Ok(())
103}
104
105/// Evaluate the bytecode of a single constraint and return its boolean result.
106///
107/// This is the same as [`exec_bytecode`], but retrieves the boolean result from the resulting stack.
108pub fn eval_bytecode(bytes: &BytecodeMapped<Op>, access: Access) -> ConstraintResult<bool> {
109    eval(bytes, access)
110}
111
112/// Evaluate the bytecode of a single constraint and return its boolean result.
113///
114/// This is the same as [`eval_bytecode`], but lazily constructs the bytecode
115/// mapping as bytes are parsed.
116pub fn eval_bytecode_iter<I>(bytes: I, access: Access) -> ConstraintResult<bool>
117where
118    I: IntoIterator<Item = u8>,
119{
120    eval(BytecodeMappedLazy::new(bytes), access)
121}
122
123/// Evaluate the operations of a single constraint and return its boolean result.
124///
125/// This is the same as [`exec_ops`], but retrieves the boolean result from the resulting stack.
126pub fn eval_ops(ops: &[Op], access: Access) -> ConstraintResult<bool> {
127    eval(ops, access)
128}
129
130/// Evaluate the operations of a single constraint and return its boolean result.
131///
132/// This is the same as [`exec`], but retrieves the boolean result from the resulting stack.
133pub fn eval<OA>(op_access: OA, access: Access) -> ConstraintResult<bool>
134where
135    OA: OpAccess<Op = Op>,
136    OA::Error: Into<error::OpError>,
137{
138    let stack = exec(op_access, access)?;
139    let word = match stack.last() {
140        Some(&w) => w,
141        None => return Err(ConstraintError::InvalidEvaluation(stack)),
142    };
143    bool_from_word(word).ok_or_else(|| ConstraintError::InvalidEvaluation(stack))
144}
145
146/// Execute the bytecode of a constraint and return the resulting stack.
147pub fn exec_bytecode(bytes: &BytecodeMapped<Op>, access: Access) -> ConstraintResult<Stack> {
148    exec(bytes, access)
149}
150
151/// Execute the bytecode of a constraint and return the resulting stack.
152///
153/// This is the same as [`exec_bytecode`], but lazily constructs the bytecode
154/// mapping as bytes are parsed.
155pub fn exec_bytecode_iter<I>(bytes: I, access: Access) -> ConstraintResult<Stack>
156where
157    I: IntoIterator<Item = u8>,
158{
159    exec(BytecodeMappedLazy::new(bytes), access)
160}
161
162/// Execute the operations of a constraint and return the resulting stack.
163pub fn exec_ops(ops: &[Op], access: Access) -> ConstraintResult<Stack> {
164    exec(ops, access)
165}
166
167/// Execute the operations of a constraint and return the resulting stack.
168pub fn exec<OA>(mut op_access: OA, access: Access) -> ConstraintResult<Stack>
169where
170    OA: OpAccess<Op = Op>,
171    OA::Error: Into<error::OpError>,
172{
173    let mut pc = 0;
174    let mut stack = Stack::default();
175    let mut memory = Memory::new();
176    let mut repeat = Repeat::new();
177    let cache = LazyCache::new();
178    while let Some(res) = op_access.op_access(pc) {
179        let op = res.map_err(|err| ConstraintError::Op(pc, err.into()))?;
180
181        let res = step_op(access, op, &mut stack, &mut memory, pc, &mut repeat, &cache);
182
183        #[cfg(feature = "tracing")]
184        trace_op_res(pc, &op, &stack, &memory, res.as_ref());
185
186        let update = match res {
187            Ok(update) => update,
188            Err(err) => return Err(ConstraintError::Op(pc, err)),
189        };
190
191        match update {
192            Some(ProgramControlFlow::Pc(new_pc)) => pc = new_pc,
193            Some(ProgramControlFlow::Halt) => break,
194            None => pc += 1,
195        }
196    }
197    Ok(stack)
198}
199
200/// Trace the operation at the given program counter.
201///
202/// In the success case, also emits the resulting stack.
203///
204/// In the error case, emits a debug log with the error.
205#[cfg(feature = "tracing")]
206fn trace_op_res<T, E>(pc: usize, op: &Op, stack: &Stack, memory: &Memory, op_res: Result<T, E>)
207where
208    E: core::fmt::Display,
209{
210    let pc_op = format!("0x{pc:02X}: {op:?}");
211    match op_res {
212        Ok(_) => {
213            tracing::trace!("{pc_op}\n  ├── {:?}\n  └── {:?}", &stack, &memory)
214        }
215        Err(ref err) => {
216            tracing::trace!("{pc_op}");
217            tracing::debug!("{err}");
218        }
219    }
220}
221
222/// Step forward constraint checking by the given operation.
223pub fn step_op(
224    access: Access,
225    op: Op,
226    stack: &mut Stack,
227    memory: &mut Memory,
228    pc: usize,
229    repeat: &mut Repeat,
230    cache: &LazyCache,
231) -> OpResult<Option<ProgramControlFlow>> {
232    match op {
233        Op::Access(op) => step_op_access(access, op, stack, repeat, cache).map(|_| None),
234        Op::Alu(op) => step_op_alu(op, stack).map(|_| None),
235        Op::Crypto(op) => step_op_crypto(op, stack).map(|_| None),
236        Op::Pred(op) => step_op_pred(op, stack).map(|_| None),
237        Op::Stack(op) => step_op_stack(op, pc, stack, repeat),
238        Op::TotalControlFlow(op) => step_on_total_control_flow(op, stack, pc),
239        Op::Temporary(op) => step_on_temporary(op, stack, memory).map(|_| None),
240    }
241}
242
243/// Step forward constraint checking by the given access operation.
244pub fn step_op_access(
245    access: Access,
246    op: asm::Access,
247    stack: &mut Stack,
248    repeat: &mut Repeat,
249    cache: &LazyCache,
250) -> OpResult<()> {
251    match op {
252        asm::Access::DecisionVar => {
253            access::decision_var(&access.solution.this_data().decision_variables, stack)
254        }
255        asm::Access::DecisionVarLen => {
256            access::decision_var_len(&access.solution.this_data().decision_variables, stack)
257        }
258        asm::Access::MutKeys => access::push_mut_keys(access.solution, stack),
259        asm::Access::State => access::state(access.state_slots, stack),
260        asm::Access::StateLen => access::state_len(access.state_slots, stack),
261        asm::Access::ThisAddress => access::this_address(access.solution.this_data(), stack),
262        asm::Access::ThisContractAddress => {
263            access::this_contract_address(access.solution.this_data(), stack)
264        }
265        asm::Access::RepeatCounter => access::repeat_counter(stack, repeat),
266        asm::Access::NumSlots => access::num_slots(
267            stack,
268            &access.state_slots,
269            &access.solution.this_data().decision_variables,
270        ),
271        asm::Access::PredicateExists => {
272            access::predicate_exists(stack, access.solution.data, cache)
273        }
274    }
275}
276
277/// Step forward constraint checking by the given ALU operation.
278pub fn step_op_alu(op: asm::Alu, stack: &mut Stack) -> OpResult<()> {
279    match op {
280        asm::Alu::Add => stack.pop2_push1(alu::add),
281        asm::Alu::Sub => stack.pop2_push1(alu::sub),
282        asm::Alu::Mul => stack.pop2_push1(alu::mul),
283        asm::Alu::Div => stack.pop2_push1(alu::div),
284        asm::Alu::Mod => stack.pop2_push1(alu::mod_),
285        asm::Alu::Shl => stack.pop2_push1(alu::shl),
286        asm::Alu::Shr => stack.pop2_push1(alu::shr),
287        asm::Alu::ShrI => stack.pop2_push1(alu::arithmetic_shr),
288    }
289}
290
291/// Step forward constraint checking by the given crypto operation.
292pub fn step_op_crypto(op: asm::Crypto, stack: &mut Stack) -> OpResult<()> {
293    match op {
294        asm::Crypto::Sha256 => crypto::sha256(stack),
295        asm::Crypto::VerifyEd25519 => crypto::verify_ed25519(stack),
296        asm::Crypto::RecoverSecp256k1 => crypto::recover_secp256k1(stack),
297    }
298}
299
300/// Step forward constraint checking by the given predicate operation.
301pub fn step_op_pred(op: asm::Pred, stack: &mut Stack) -> OpResult<()> {
302    match op {
303        asm::Pred::Eq => stack.pop2_push1(|a, b| Ok((a == b).into())),
304        asm::Pred::EqRange => pred::eq_range(stack),
305        asm::Pred::Gt => stack.pop2_push1(|a, b| Ok((a > b).into())),
306        asm::Pred::Lt => stack.pop2_push1(|a, b| Ok((a < b).into())),
307        asm::Pred::Gte => stack.pop2_push1(|a, b| Ok((a >= b).into())),
308        asm::Pred::Lte => stack.pop2_push1(|a, b| Ok((a <= b).into())),
309        asm::Pred::And => stack.pop2_push1(|a, b| Ok((a != 0 && b != 0).into())),
310        asm::Pred::Or => stack.pop2_push1(|a, b| Ok((a != 0 || b != 0).into())),
311        asm::Pred::Not => stack.pop1_push1(|a| Ok((a == 0).into())),
312        asm::Pred::EqSet => pred::eq_set(stack),
313        asm::Pred::BitAnd => stack.pop2_push1(|a, b| Ok(a & b)),
314        asm::Pred::BitOr => stack.pop2_push1(|a, b| Ok(a | b)),
315    }
316}
317
318/// Step forward constraint checking by the given stack operation.
319pub fn step_op_stack(
320    op: asm::Stack,
321    pc: usize,
322    stack: &mut Stack,
323    repeat: &mut Repeat,
324) -> OpResult<Option<ProgramControlFlow>> {
325    if let asm::Stack::RepeatEnd = op {
326        return Ok(repeat.repeat()?.map(ProgramControlFlow::Pc));
327    }
328    let r = match op {
329        asm::Stack::Dup => stack.pop1_push2(|w| Ok([w, w])),
330        asm::Stack::DupFrom => stack.dup_from().map_err(From::from),
331        asm::Stack::Push(word) => stack.push(word).map_err(From::from),
332        asm::Stack::Pop => stack.pop().map(|_| ()).map_err(From::from),
333        asm::Stack::Swap => stack.pop2_push2(|a, b| Ok([b, a])),
334        asm::Stack::SwapIndex => stack.swap_index().map_err(From::from),
335        asm::Stack::Select => stack.select().map_err(From::from),
336        asm::Stack::SelectRange => stack.select_range().map_err(From::from),
337        asm::Stack::Repeat => repeat::repeat(pc, stack, repeat),
338        asm::Stack::Reserve => stack.reserve_zeroed().map_err(From::from),
339        asm::Stack::Load => stack.load().map_err(From::from),
340        asm::Stack::Store => stack.store().map_err(From::from),
341        asm::Stack::RepeatEnd => unreachable!(),
342    };
343    r.map(|_| None)
344}
345
346/// Step forward constraint checking by the given total control flow operation.
347pub fn step_on_total_control_flow(
348    op: asm::TotalControlFlow,
349    stack: &mut Stack,
350    pc: usize,
351) -> OpResult<Option<ProgramControlFlow>> {
352    match op {
353        asm::TotalControlFlow::JumpForwardIf => total_control_flow::jump_forward_if(stack, pc),
354        asm::TotalControlFlow::HaltIf => total_control_flow::halt_if(stack),
355        asm::TotalControlFlow::Halt => Ok(Some(ProgramControlFlow::Halt)),
356        asm::TotalControlFlow::PanicIf => total_control_flow::panic_if(stack).map(|_| None),
357    }
358}
359
360/// Step forward constraint checking by the given temporary operation.
361pub fn step_on_temporary(
362    op: asm::Temporary,
363    stack: &mut Stack,
364    memory: &mut Memory,
365) -> OpResult<()> {
366    match op {
367        asm::Temporary::Alloc => {
368            let w = stack.pop()?;
369            let len = memory.len()?;
370            memory.alloc(w)?;
371            Ok(stack.push(len)?)
372        }
373        asm::Temporary::Store => {
374            let [addr, w] = stack.pop2()?;
375            memory.store(addr, w)
376        }
377        asm::Temporary::Load => stack.pop1_push1(|addr| memory.load(addr)),
378        asm::Temporary::Free => {
379            let addr = stack.pop()?;
380            memory.free(addr)
381        }
382        asm::Temporary::LoadRange => {
383            let [addr, size] = stack.pop2()?;
384            let words = memory.load_range(addr, size)?;
385            Ok(stack.extend(words)?)
386        }
387        asm::Temporary::StoreRange => {
388            let addr = stack.pop()?;
389            stack.pop_len_words(|words| memory.store_range(addr, words))?;
390            Ok(())
391        }
392    }
393}
394
395#[cfg(test)]
396pub(crate) mod test_util {
397    use std::collections::HashSet;
398
399    use asm::Word;
400
401    use crate::{
402        types::{solution::SolutionData, ContentAddress, PredicateAddress},
403        *,
404    };
405
406    pub(crate) const TEST_SET_CA: ContentAddress = ContentAddress([0xFF; 32]);
407    pub(crate) const TEST_PREDICATE_CA: ContentAddress = ContentAddress([0xAA; 32]);
408    pub(crate) const TEST_PREDICATE_ADDR: PredicateAddress = PredicateAddress {
409        contract: TEST_SET_CA,
410        predicate: TEST_PREDICATE_CA,
411    };
412    pub(crate) const TEST_SOLUTION_DATA: SolutionData = SolutionData {
413        predicate_to_solve: TEST_PREDICATE_ADDR,
414        decision_variables: vec![],
415        state_mutations: vec![],
416    };
417
418    pub(crate) fn test_empty_keys() -> &'static HashSet<&'static [Word]> {
419        static INSTANCE: std::sync::LazyLock<HashSet<&[Word]>> =
420            std::sync::LazyLock::new(|| HashSet::with_capacity(0));
421        &INSTANCE
422    }
423
424    pub(crate) fn test_solution_data_arr() -> &'static [SolutionData] {
425        static INSTANCE: std::sync::LazyLock<[SolutionData; 1]> =
426            std::sync::LazyLock::new(|| [TEST_SOLUTION_DATA]);
427        &*INSTANCE
428    }
429
430    pub(crate) fn test_solution_access() -> &'static SolutionAccess<'static> {
431        static INSTANCE: std::sync::LazyLock<SolutionAccess> =
432            std::sync::LazyLock::new(|| SolutionAccess {
433                data: test_solution_data_arr(),
434                index: 0,
435                mutable_keys: test_empty_keys(),
436            });
437        &INSTANCE
438    }
439
440    pub(crate) fn test_access() -> &'static Access<'static> {
441        static INSTANCE: std::sync::LazyLock<Access> = std::sync::LazyLock::new(|| Access {
442            solution: *test_solution_access(),
443            state_slots: StateSlots::EMPTY,
444        });
445        &INSTANCE
446    }
447}
448
449#[cfg(test)]
450mod pred_tests {
451    use crate::{
452        asm::{Pred, Stack},
453        test_util::*,
454        *,
455    };
456
457    #[test]
458    fn pred_eq_false() {
459        let ops = &[
460            Stack::Push(6).into(),
461            Stack::Push(7).into(),
462            Pred::Eq.into(),
463        ];
464        assert!(!eval_ops(ops, *test_access()).unwrap());
465    }
466
467    #[test]
468    fn pred_eq_true() {
469        let ops = &[
470            Stack::Push(42).into(),
471            Stack::Push(42).into(),
472            Pred::Eq.into(),
473        ];
474        assert!(eval_ops(ops, *test_access()).unwrap());
475    }
476
477    #[test]
478    fn pred_gt_false() {
479        let ops = &[
480            Stack::Push(7).into(),
481            Stack::Push(7).into(),
482            Pred::Gt.into(),
483        ];
484        assert!(!eval_ops(ops, *test_access()).unwrap());
485    }
486
487    #[test]
488    fn pred_gt_true() {
489        let ops = &[
490            Stack::Push(7).into(),
491            Stack::Push(6).into(),
492            Pred::Gt.into(),
493        ];
494        assert!(eval_ops(ops, *test_access()).unwrap());
495    }
496
497    #[test]
498    fn pred_lt_false() {
499        let ops = &[
500            Stack::Push(7).into(),
501            Stack::Push(7).into(),
502            Pred::Lt.into(),
503        ];
504        assert!(!eval_ops(ops, *test_access()).unwrap());
505    }
506
507    #[test]
508    fn pred_lt_true() {
509        let ops = &[
510            Stack::Push(6).into(),
511            Stack::Push(7).into(),
512            Pred::Lt.into(),
513        ];
514        assert!(eval_ops(ops, *test_access()).unwrap());
515    }
516
517    #[test]
518    fn pred_gte_false() {
519        let ops = &[
520            Stack::Push(6).into(),
521            Stack::Push(7).into(),
522            Pred::Gte.into(),
523        ];
524        assert!(!eval_ops(ops, *test_access()).unwrap());
525    }
526
527    #[test]
528    fn pred_gte_true() {
529        let ops = &[
530            Stack::Push(7).into(),
531            Stack::Push(7).into(),
532            Pred::Gte.into(),
533        ];
534        assert!(eval_ops(ops, *test_access()).unwrap());
535        let ops = &[
536            Stack::Push(8).into(),
537            Stack::Push(7).into(),
538            Pred::Gte.into(),
539        ];
540        assert!(eval_ops(ops, *test_access()).unwrap());
541    }
542
543    #[test]
544    fn pred_lte_false() {
545        let ops = &[
546            Stack::Push(7).into(),
547            Stack::Push(6).into(),
548            Pred::Lte.into(),
549        ];
550        assert!(!eval_ops(ops, *test_access()).unwrap());
551    }
552
553    #[test]
554    fn pred_lte_true() {
555        let ops = &[
556            Stack::Push(7).into(),
557            Stack::Push(7).into(),
558            Pred::Lte.into(),
559        ];
560        assert!(eval_ops(ops, *test_access()).unwrap());
561        let ops = &[
562            Stack::Push(7).into(),
563            Stack::Push(8).into(),
564            Pred::Lte.into(),
565        ];
566        assert!(eval_ops(ops, *test_access()).unwrap());
567    }
568
569    #[test]
570    fn pred_and_true() {
571        let ops = &[
572            Stack::Push(42).into(),
573            Stack::Push(42).into(),
574            Pred::And.into(),
575        ];
576        assert!(eval_ops(ops, *test_access()).unwrap());
577    }
578
579    #[test]
580    fn pred_and_false() {
581        let ops = &[
582            Stack::Push(42).into(),
583            Stack::Push(0).into(),
584            Pred::And.into(),
585        ];
586        assert!(!eval_ops(ops, *test_access()).unwrap());
587        let ops = &[
588            Stack::Push(0).into(),
589            Stack::Push(0).into(),
590            Pred::And.into(),
591        ];
592        assert!(!eval_ops(ops, *test_access()).unwrap());
593    }
594
595    #[test]
596    fn pred_or_true() {
597        let ops = &[
598            Stack::Push(42).into(),
599            Stack::Push(42).into(),
600            Pred::Or.into(),
601        ];
602        assert!(eval_ops(ops, *test_access()).unwrap());
603        let ops = &[
604            Stack::Push(0).into(),
605            Stack::Push(42).into(),
606            Pred::Or.into(),
607        ];
608        assert!(eval_ops(ops, *test_access()).unwrap());
609        let ops = &[
610            Stack::Push(42).into(),
611            Stack::Push(0).into(),
612            Pred::Or.into(),
613        ];
614        assert!(eval_ops(ops, *test_access()).unwrap());
615    }
616
617    #[test]
618    fn pred_or_false() {
619        let ops = &[
620            Stack::Push(0).into(),
621            Stack::Push(0).into(),
622            Pred::Or.into(),
623        ];
624        assert!(!eval_ops(ops, *test_access()).unwrap());
625    }
626
627    #[test]
628    fn pred_not_true() {
629        let ops = &[Stack::Push(0).into(), Pred::Not.into()];
630        assert!(eval_ops(ops, *test_access()).unwrap());
631    }
632
633    #[test]
634    fn pred_not_false() {
635        let ops = &[Stack::Push(42).into(), Pred::Not.into()];
636        assert!(!eval_ops(ops, *test_access()).unwrap());
637    }
638}