diff --git a/CHANGELOG.md b/CHANGELOG.md index f5bd1db4fb..607d74d322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Removed unused `find_lone_leaf()` function from the Advice Provider (#1262). - [BREAKING] Changed fields type of the `StackOutputs` struct from `Vec` to `Vec` (#1268). - [BREAKING] Migrated to `miden-crypto` v0.9.0 (#1287). +- Added error codes support for the `mtree_verify` instruction (#1328). ## 0.8.0 (02-26-2024) diff --git a/air/src/constraints/chiplets/hasher/mod.rs b/air/src/constraints/chiplets/hasher/mod.rs index e58828d80b..8df5fca0cb 100644 --- a/air/src/constraints/chiplets/hasher/mod.rs +++ b/air/src/constraints/chiplets/hasher/mod.rs @@ -101,9 +101,9 @@ pub fn get_transition_constraint_count() -> usize { /// Enforces constraints for the hasher chiplet. /// /// - The `hasher_flag` determines if the hasher chiplet is currently enabled. It should be -/// computed by the caller and set to `Felt::ONE` +/// computed by the caller and set to `Felt::ONE` /// - The `transition_flag` indicates whether this is the last row this chiplet's execution trace, -/// and therefore the constraints should not be enforced. +/// and therefore the constraints should not be enforced. pub fn enforce_constraints>( frame: &EvaluationFrame, periodic_values: &[E], diff --git a/air/src/constraints/stack/field_ops/mod.rs b/air/src/constraints/stack/field_ops/mod.rs index 4248eea85a..f185e255c9 100644 --- a/air/src/constraints/stack/field_ops/mod.rs +++ b/air/src/constraints/stack/field_ops/mod.rs @@ -188,7 +188,7 @@ pub fn enforce_incr_constraints( /// enforced: /// - The top element should be a binary. It is enforced as a general constraint. /// - The first element of the next frame should be a binary not of the first element of -/// the current frame. s0` + s0 = 1. +/// the current frame. s0` + s0 = 1. pub fn enforce_not_constraints( frame: &EvaluationFrame, result: &mut [E], @@ -207,7 +207,7 @@ pub fn enforce_not_constraints( /// Enforces constraints of the AND operation. The AND operation computes the bitwise and of the /// first two elements in the current trace. Therefore, the following constraints are enforced: /// - The top two element in the current frame of the stack should be binary. s0^2 - s0 = 0, -/// s1^2 - s1 = 0. The top element is binary or not is enforced as a general constraint. +/// s1^2 - s1 = 0. The top element is binary or not is enforced as a general constraint. /// - The first element of the next frame should be a binary and of the first two elements in the /// current frame. s0` - s0 * s1 = 0. pub fn enforce_and_constraints( @@ -234,7 +234,7 @@ pub fn enforce_and_constraints( /// Enforces constraints of the OR operation. The OR operation computes the bitwise or of the /// first two elements in the current trace. Therefore, the following constraints are enforced: /// - The top two element in the current frame of the stack should be binary. s0^2 - s0 = 0, -/// s1^2 - s1 = 0. The top element is binary or not is enforced as a general constraint. +/// s1^2 - s1 = 0. The top element is binary or not is enforced as a general constraint. /// - The first element of the next frame should be a binary or of the first two elements in the /// current frame. s0` - ( s0 + s1 - s0 * s1 ) = 0. pub fn enforce_or_constraints( @@ -324,7 +324,7 @@ pub fn enforce_eqz_constraints( /// constraint. /// - The exp value in the next frame should be the square of exp value in the current frame. /// - The accumulation value in the next frame is the product of the accumulation value in the -/// current frame and the value which needs to be included in this turn. +/// current frame and the value which needs to be included in this turn. /// - The b value is right shifted by 1 bit. pub fn enforce_expacc_constraints( frame: &EvaluationFrame, diff --git a/air/src/constraints/stack/op_flags/mod.rs b/air/src/constraints/stack/op_flags/mod.rs index 8359917d8d..e8ea0b51d7 100644 --- a/air/src/constraints/stack/op_flags/mod.rs +++ b/air/src/constraints/stack/op_flags/mod.rs @@ -100,7 +100,7 @@ impl OpFlags { /// - composite flag for the stack if the stack has been shifted to the right. /// - composite flag if the current operation being executed is a control flow operation or not. /// - composite flag if the current operation being executed has a binary element constraint on - /// the top element in the stack. + /// the top element in the stack. pub fn new(frame: &EvaluationFrame) -> Self { // intermediary array to cache the value of intermediate flags. let mut degree7_op_flags = [E::ZERO; NUM_DEGREE_7_OPS]; @@ -866,7 +866,7 @@ impl OpFlags { /// Operation Flag of MPVERIFY operation. #[inline(always)] pub fn mpverify(&self) -> E { - self.degree5_op_flags[get_op_index(Operation::MpVerify.op_code())] + self.degree5_op_flags[get_op_index(Operation::MpVerify(0).op_code())] } /// Operation Flag of SPLIT operation. diff --git a/air/src/constraints/stack/op_flags/tests.rs b/air/src/constraints/stack/op_flags/tests.rs index e56bbd0324..887c48d71a 100644 --- a/air/src/constraints/stack/op_flags/tests.rs +++ b/air/src/constraints/stack/op_flags/tests.rs @@ -145,7 +145,7 @@ fn degree_4_op_flags() { fn composite_flags() { // ------ no change 0 --------------------------------------------------------------------- - let op_no_change_0 = [Operation::MpVerify, Operation::Span, Operation::Halt]; + let op_no_change_0 = [Operation::MpVerify(0), Operation::Span, Operation::Halt]; for op in op_no_change_0 { // frame initialised with an op operation. let frame = generate_evaluation_frame(op.op_code().into()); @@ -169,7 +169,7 @@ fn composite_flags() { assert_eq!(op_flags.left_shift(), ZERO); assert_eq!(op_flags.top_binary(), ZERO); - if op == Operation::MpVerify { + if op == Operation::MpVerify(0) { assert_eq!(op_flags.control_flow(), ZERO); } else if op == Operation::Span || op == Operation::Halt { assert_eq!(op_flags.control_flow(), ONE); diff --git a/air/src/constraints/stack/overflow/mod.rs b/air/src/constraints/stack/overflow/mod.rs index 346bba5906..4cc319711c 100644 --- a/air/src/constraints/stack/overflow/mod.rs +++ b/air/src/constraints/stack/overflow/mod.rs @@ -93,7 +93,7 @@ pub fn enforce_stack_depth_constraints( /// Enforces constraints on the overflow flag h0. Therefore, the following constraints /// are enforced: /// - If overflow table has values, then, h0 should be set to ONE, otherwise it should -/// be ZERO. +/// be ZERO. pub fn enforce_overflow_flag_constraints( frame: &EvaluationFrame, result: &mut [E], @@ -108,9 +108,9 @@ pub fn enforce_overflow_flag_constraints( /// Enforces constraints on the bookkeeping index `b1`. The following constraints are enforced: /// - In the case of a right shift operation, the next b1 index should be updated with current -/// `clk` value. +/// `clk` value. /// - In the case of a left shift operation, the last stack item should be set to ZERO when the -/// depth of the stack is 16. +/// depth of the stack is 16. pub fn enforce_overflow_index_constraints( frame: &EvaluationFrame, result: &mut [E], diff --git a/air/src/constraints/stack/stack_manipulation/mod.rs b/air/src/constraints/stack/stack_manipulation/mod.rs index ab32a8263c..0678fe615e 100644 --- a/air/src/constraints/stack/stack_manipulation/mod.rs +++ b/air/src/constraints/stack/stack_manipulation/mod.rs @@ -94,7 +94,7 @@ pub fn enforce_pad_constraints( /// at depth n in the stack and pushes the copy onto the stack, whereas MOVUPn opearation moves the /// element at depth n to the top of the stack. Therefore, the following constraints are enforced: /// - The top element in the next frame should be equal to the element at depth n in the -/// current frame. s0` - sn = 0. +/// current frame. s0` - sn = 0. pub fn enforce_dup_movup_n_constraints( frame: &EvaluationFrame, result: &mut [E], @@ -245,7 +245,7 @@ pub fn enforce_swapwx_constraints( /// Enforces constraints of the MOVDNn operation. The MOVDNn operation moves the top element /// to depth n in the stack. Therefore, the following constraints are enforced: /// - The top element in the current frame should be equal to the element at depth n in the -/// next frame. s0 - sn` = 0. +/// next frame. s0 - sn` = 0. pub fn enforce_movdnn_constraints( frame: &EvaluationFrame, result: &mut [E], diff --git a/air/src/constraints/stack/u32_ops/mod.rs b/air/src/constraints/stack/u32_ops/mod.rs index ffe7ee56e0..5f26692fdf 100644 --- a/air/src/constraints/stack/u32_ops/mod.rs +++ b/air/src/constraints/stack/u32_ops/mod.rs @@ -120,7 +120,7 @@ pub fn enforce_u32split_constraints>( /// elements in the current trace of the stack. Therefore, the following constraints are /// enforced: /// - The aggregation of limbs from the helper registers is equal to the sum of the top two -/// element in the stack. +/// element in the stack. pub fn enforce_u32add_constraints>( frame: &EvaluationFrame, result: &mut [E], @@ -141,7 +141,7 @@ pub fn enforce_u32add_constraints>( /// elements in the current trace of the stack. Therefore, the following constraints are /// enforced: /// - The aggregation of limbs from the helper registers is equal to the sum of the top three -/// elements in the stack. +/// elements in the stack. pub fn enforce_u32add3_constraints>( frame: &EvaluationFrame, result: &mut [E], @@ -289,10 +289,9 @@ pub fn enforce_check_element_validity>( /// Enforces constraints of the general operation. The constaints checks if the lower 16-bits limbs /// are aggregated correctly or not. Therefore, the following constraints are enforced: /// - The aggregation of lower two lower 16-bits limbs in the helper registers is equal to the -/// second -/// element in the next row. +/// second element in the next row. /// - The aggregation of lower two upper 16-bits limbs in the helper registers is equal to the first -/// element in the next row. +/// element in the next row. pub fn enforce_limbs_agg>( frame: &EvaluationFrame, result: &mut [E], diff --git a/assembly/src/assembler/instruction/crypto_ops.rs b/assembly/src/assembler/instruction/crypto_ops.rs index d59867fe5b..7f657a6273 100644 --- a/assembly/src/assembler/instruction/crypto_ops.rs +++ b/assembly/src/assembler/instruction/crypto_ops.rs @@ -118,7 +118,7 @@ pub(super) fn mtree_get(span: &mut SpanBuilder) { let ops = [ // verify the node V for root R with depth d and index i // => [V, d, i, R, ...] - MpVerify, + MpVerify(0), // move d, i back to the top of the stack and are dropped since they are // no longer needed => [V, R, ...] @@ -172,21 +172,6 @@ pub(super) fn mtree_merge(span: &mut SpanBuilder) { hmerge(span); } -/// Verifies if the node value `V`, on depth `d` and index `i` opens to the root `R` of a Merkle -/// tree by appending a [Operation::MpVerify]. The stack is expected to be arranged as follows -/// (from the top): -/// - node value `V`, 4 elements -/// - depth of the node `d`, 1 element -/// - index of the node `i`, 1 element -/// - root of the tree `R`, 4 elements -/// -/// After the operation is executed, the stack remains unchanged. -/// -/// This operation takes 1 VM cycle. -pub(super) fn mtree_verify(span: &mut SpanBuilder) { - span.push_op(MpVerify); -} - // MERKLE TREES - HELPERS // ================================================================================================ diff --git a/assembly/src/assembler/instruction/mod.rs b/assembly/src/assembler/instruction/mod.rs index 949e3dd61c..286173b8b8 100644 --- a/assembly/src/assembler/instruction/mod.rs +++ b/assembly/src/assembler/instruction/mod.rs @@ -349,7 +349,10 @@ impl Assembler { Instruction::MTreeGet => crypto_ops::mtree_get(span_builder), Instruction::MTreeSet => crypto_ops::mtree_set(span_builder), Instruction::MTreeMerge => crypto_ops::mtree_merge(span_builder), - Instruction::MTreeVerify => crypto_ops::mtree_verify(span_builder), + Instruction::MTreeVerify => span_builder.push_op(MpVerify(0)), + Instruction::MTreeVerifyWithError(err_code) => { + span_builder.push_op(MpVerify(err_code.expect_value())) + } // ----- STARK proof verification ----------------------------------------------------- Instruction::FriExt2Fold4 => span_builder.push_op(FriE2F4), diff --git a/assembly/src/assembler/mod.rs b/assembly/src/assembler/mod.rs index c8c6dc8229..09d21b2f76 100644 --- a/assembly/src/assembler/mod.rs +++ b/assembly/src/assembler/mod.rs @@ -77,12 +77,12 @@ pub enum ArtifactKind { /// /// /// * If you have a single executable module you want to compile, just call [Assembler::compile] or -/// [Assembler::compile_ast], depending on whether you have source code in raw or parsed form. +/// [Assembler::compile_ast], depending on whether you have source code in raw or parsed form. /// /// * If you want to link your executable to a few other modules that implement supporting -/// procedures, build the assembler with them first, using the various builder methods on -/// [Assembler], e.g. [Assembler::with_module], [Assembler::with_library], etc. Then, call -/// [Assembler::compile] or [Assembler::compile_ast] to get your compiled program. +/// procedures, build the assembler with them first, using the various builder methods on +/// [Assembler], e.g. [Assembler::with_module], [Assembler::with_library], etc. Then, call +/// [Assembler::compile] or [Assembler::compile_ast] to get your compiled program. pub struct Assembler { /// The global [ModuleGraph] for this assembler. All new [AssemblyContext]s inherit this graph /// as a baseline. diff --git a/assembly/src/assembler/module_graph/rewrites/module.rs b/assembly/src/assembler/module_graph/rewrites/module.rs index a4055f5a0d..51145e593b 100644 --- a/assembly/src/assembler/module_graph/rewrites/module.rs +++ b/assembly/src/assembler/module_graph/rewrites/module.rs @@ -18,8 +18,8 @@ use crate::{ /// added to a [ModuleGraph]. These rewrites include: /// /// * Resolving, at least partially, all of the invocation targets in procedures of the module, and -/// rewriting those targets as concretely as possible OR as phantom calls representing procedures -/// referenced by MAST root for which we have no definition. +/// rewriting those targets as concretely as possible OR as phantom calls representing procedures +/// referenced by MAST root for which we have no definition. pub struct ModuleRewriter<'a, 'b: 'a> { resolver: &'a NameResolver<'b>, module_id: ModuleIndex, diff --git a/assembly/src/ast/instruction/deserialize.rs b/assembly/src/ast/instruction/deserialize.rs index cda8820d8d..88189e6911 100644 --- a/assembly/src/ast/instruction/deserialize.rs +++ b/assembly/src/ast/instruction/deserialize.rs @@ -272,6 +272,9 @@ impl Deserializable for Instruction { OpCode::MTreeSet => Ok(Self::MTreeSet), OpCode::MTreeMerge => Ok(Self::MTreeMerge), OpCode::MTreeVerify => Ok(Self::MTreeVerify), + OpCode::MTreeVerifyWithError => { + Ok(Self::MTreeVerifyWithError(source.read_u32()?.into())) + } // ----- STARK proof verification ----------------------------------------------------- OpCode::FriExt2Fold4 => Ok(Self::FriExt2Fold4), diff --git a/assembly/src/ast/instruction/mod.rs b/assembly/src/ast/instruction/mod.rs index f1d25b4d39..0aa83c2088 100644 --- a/assembly/src/ast/instruction/mod.rs +++ b/assembly/src/ast/instruction/mod.rs @@ -251,6 +251,7 @@ pub enum Instruction { MTreeSet, MTreeMerge, MTreeVerify, + MTreeVerifyWithError(ErrorCode), // ----- STARK proof verification ------------------------------------------------------------ FriExt2Fold4, diff --git a/assembly/src/ast/instruction/opcode.rs b/assembly/src/ast/instruction/opcode.rs index 3d8b62aa50..4620d5e69f 100644 --- a/assembly/src/ast/instruction/opcode.rs +++ b/assembly/src/ast/instruction/opcode.rs @@ -241,6 +241,7 @@ pub enum OpCode { MTreeSet, MTreeMerge, MTreeVerify, + MTreeVerifyWithError, // ----- STARK proof verification ------------------------------------------------------------ FriExt2Fold4, diff --git a/assembly/src/ast/instruction/print.rs b/assembly/src/ast/instruction/print.rs index 421e28fd68..5364121f03 100644 --- a/assembly/src/ast/instruction/print.rs +++ b/assembly/src/ast/instruction/print.rs @@ -258,6 +258,9 @@ impl PrettyPrint for Instruction { Self::MTreeSet => const_text("mtree_set"), Self::MTreeMerge => const_text("mtree_merge"), Self::MTreeVerify => const_text("mtree_verify"), + Self::MTreeVerifyWithError(err_code) => { + flatten(const_text("mtree_verify.err") + const_text("=") + display(err_code)) + } // ----- STARK proof verification ----------------------------------------------------- Self::FriExt2Fold4 => const_text("fri_ext2fold4"), diff --git a/assembly/src/ast/instruction/serialize.rs b/assembly/src/ast/instruction/serialize.rs index c95ed13e66..6f851d931e 100644 --- a/assembly/src/ast/instruction/serialize.rs +++ b/assembly/src/ast/instruction/serialize.rs @@ -386,6 +386,10 @@ impl Serializable for Instruction { Self::MTreeSet => OpCode::MTreeSet.write_into(target), Self::MTreeMerge => OpCode::MTreeMerge.write_into(target), Self::MTreeVerify => OpCode::MTreeVerify.write_into(target), + Self::MTreeVerifyWithError(err_code) => { + OpCode::MTreeVerifyWithError.write_into(target); + target.write_u32(err_code.expect_value()); + } // ----- STARK proof verification ----------------------------------------------------- Self::FriExt2Fold4 => OpCode::FriExt2Fold4.write_into(target), diff --git a/assembly/src/ast/module.rs b/assembly/src/ast/module.rs index ffc85dd4f0..1a5b6af251 100644 --- a/assembly/src/ast/module.rs +++ b/assembly/src/ast/module.rs @@ -46,11 +46,11 @@ pub enum ModuleKind { /// A kernel is like a library module, but is special in a few ways: /// /// * Its code always executes in the root context, so it is stateful in a way that normal - /// libraries cannot replicate. This can be used to provide core services that would otherwise - /// not be possible to implement. + /// libraries cannot replicate. This can be used to provide core services that would otherwise + /// not be possible to implement. /// /// * The procedures exported from the kernel may be the target of the `syscall` instruction, - /// and in fact _must_ be called that way. + /// and in fact _must_ be called that way. /// /// * Kernels may not use `syscall` or `call` instructions internally. Kernel = 2, @@ -294,7 +294,7 @@ impl Module { /// /// * The module was constructed in-memory via AST structures, and not derived from source code. /// * The module was serialized without debug info, and then deserialized. Without debug info, - /// the source code is lost when round-tripping through serialization. + /// the source code is lost when round-tripping through serialization. pub fn source_file(&self) -> Option> { self.source_file.clone() } diff --git a/assembly/src/ast/visit.rs b/assembly/src/ast/visit.rs index af19ec4ba3..a8025d59c6 100644 --- a/assembly/src/ast/visit.rs +++ b/assembly/src/ast/visit.rs @@ -22,16 +22,15 @@ //! of the visitor, but here are some examples: //! //! 1. When implementing a visitor that performs constant folding/propagation, you need to visit the -//! operands of an expression before the operator, in order to determine whether it is possible to -//! fold, and if so, what the actual values of the operands are. As a result, this is implemented as -//! a postorder visitor, so that the AST node corresponding to the expression is rewritten after all -//! of it's children. +//! operands of an expression before the operator, in order to determine whether it is possible +//! to fold, and if so, what the actual values of the operands are. As a result, this is +//! implemented as a postorder visitor, so that the AST node corresponding to the expression is +//! rewritten after all of it's children. //! //! 2. When implementing an analysis based on lexical scope, it is necessary to "push down" context -//! from -//! the root to the leaves of the AST - the context being the contents of each AST nodes inherited -//! scope. As a result, this is implemented as a preorder traversal, so that the context at each -//! node can be computed before visiting the children of that node. +//! from the root to the leaves of the AST - the context being the contents of each AST nodes +//! inherited scope. As a result, this is implemented as a preorder traversal, so that the +//! context at each node can be computed before visiting the children of that node. //! //! In both cases, the implementor must call the free function corresponding to the _current_ AST //! node at the appropriate point (i.e. before/after executing the logic for the node), so that the @@ -295,7 +294,8 @@ where | AssertzWithError(ref code) | U32AssertWithError(ref code) | U32Assert2WithError(ref code) - | U32AssertWWithError(ref code) => visitor.visit_immediate_error_code(code), + | U32AssertWWithError(ref code) + | MTreeVerifyWithError(ref code) => visitor.visit_immediate_error_code(code), AddImm(ref imm) | SubImm(ref imm) | MulImm(ref imm) | DivImm(ref imm) | ExpImm(ref imm) | EqImm(ref imm) | NeqImm(ref imm) | Push(ref imm) => visitor.visit_immediate_felt(imm), U32WrappingAddImm(ref imm) @@ -734,7 +734,8 @@ where | AssertzWithError(ref mut code) | U32AssertWithError(ref mut code) | U32Assert2WithError(ref mut code) - | U32AssertWWithError(ref mut code) => visitor.visit_mut_immediate_error_code(code), + | U32AssertWWithError(ref mut code) + | MTreeVerifyWithError(ref mut code) => visitor.visit_mut_immediate_error_code(code), AddImm(ref mut imm) | SubImm(ref mut imm) | MulImm(ref mut imm) | DivImm(ref mut imm) | ExpImm(ref mut imm) | EqImm(ref mut imm) | NeqImm(ref mut imm) | Push(ref mut imm) => { visitor.visit_mut_immediate_felt(imm) diff --git a/assembly/src/parser/grammar.lalrpop b/assembly/src/parser/grammar.lalrpop index e1db565c87..5843474fd2 100644 --- a/assembly/src/parser/grammar.lalrpop +++ b/assembly/src/parser/grammar.lalrpop @@ -397,14 +397,14 @@ MacroInst: SmallOpsVec = { #[inline] Inst: Instruction = { AdviceInjector, - Assert, Call, Debug, - InstWithFeltImmediate, - InstWithU32Immediate, InstWithBitSizeImmediate, + InstWithErrorCode, + InstWithFeltImmediate, InstWithLocalIndex, InstWithStackIndex, + InstWithU32Immediate, ProcRef, "adv_pipe" => Instruction::AdvPipe, "adv_loadw" => Instruction::AdvLoadW, @@ -441,7 +441,6 @@ Inst: Instruction = { "mtree_get" => Instruction::MTreeGet, "mtree_merge" => Instruction::MTreeMerge, "mtree_set" => Instruction::MTreeSet, - "mtree_verify" => Instruction::MTreeVerify, "neg" => Instruction::Neg, "not" => Instruction::Not, "or" => Instruction::Or, @@ -502,7 +501,7 @@ AdviceInjector: Instruction = { } #[inline] -Assert: Instruction = { +InstWithErrorCode: Instruction = { "assert" => error_code.map(Instruction::AssertWithError).unwrap_or(Instruction::Assert), "assertz" => error_code.map(Instruction::AssertzWithError).unwrap_or(Instruction::Assertz), "assert_eq" => error_code.map(Instruction::AssertEqWithError).unwrap_or(Instruction::AssertEq), @@ -510,6 +509,7 @@ Assert: Instruction = { "u32assert" => error_code.map(Instruction::U32AssertWithError).unwrap_or(Instruction::U32Assert), "u32assert2" => error_code.map(Instruction::U32Assert2WithError).unwrap_or(Instruction::U32Assert2), "u32assertw" => error_code.map(Instruction::U32AssertWWithError).unwrap_or(Instruction::U32AssertW), + "mtree_verify" => error_code.map(Instruction::MTreeVerifyWithError).unwrap_or(Instruction::MTreeVerify), } MaybeAssertCode: Option> = { diff --git a/assembly/src/tests.rs b/assembly/src/tests.rs index c7af80d47b..6481a9cddf 100644 --- a/assembly/src/tests.rs +++ b/assembly/src/tests.rs @@ -1121,6 +1121,31 @@ end"; Ok(()) } +#[test] +fn mtree_verify_with_code() -> TestResult { + let source = source_file!( + "\ + const.ERR1=1 + + begin + mtree_verify + mtree_verify.err=ERR1 + mtree_verify.err=2 + end + " + ); + + let mut context = TestContext::default(); + let program = context.assemble(source)?; + + let expected = "\ +begin + span mpverify(0) mpverify(1) mpverify(2) end +end"; + assert_str_eq!(format!("{program}"), expected); + Ok(()) +} + // NESTED CONTROL BLOCKS // ================================================================================================ diff --git a/core/src/operations/mod.rs b/core/src/operations/mod.rs index efefa43f84..a66bcec4db 100644 --- a/core/src/operations/mod.rs +++ b/core/src/operations/mod.rs @@ -402,7 +402,10 @@ pub enum Operation { /// The Merkle path itself is expected to be provided by the prover non-deterministically (via /// merkle sets). If the prover is not able to provide the required path, the operation fails. /// The state of the stack does not change. - MpVerify, + /// + /// The internal value specifies an error code associated with the error in case when the + /// assertion fails. + MpVerify(u32), /// Computes a new root of a Merkle tree where a node at the specified position is updated to /// the specified value. @@ -534,7 +537,7 @@ impl Operation { Self::U32madd => 0b0100_1110, Self::HPerm => 0b0101_0000, - Self::MpVerify => 0b0101_0001, + Self::MpVerify(_) => 0b0101_0001, Self::Pipe => 0b0101_0010, Self::MStream => 0b0101_0011, Self::Split => 0b0101_0100, @@ -713,7 +716,7 @@ impl fmt::Display for Operation { // ----- cryptographic operations ----------------------------------------------------- Self::HPerm => write!(f, "hperm"), - Self::MpVerify => write!(f, "mpverify"), + Self::MpVerify(err_code) => write!(f, "mpverify({err_code})"), Self::MrUpdate => write!(f, "mrupdate"), Self::FriE2F4 => write!(f, "frie2f4"), Self::RCombBase => write!(f, "rcomb1"), diff --git a/docs/src/user_docs/assembly/code_organization.md b/docs/src/user_docs/assembly/code_organization.md index a22af8b044..acb0b628e6 100644 --- a/docs/src/user_docs/assembly/code_organization.md +++ b/docs/src/user_docs/assembly/code_organization.md @@ -141,7 +141,7 @@ end In addition to the locally-defined procedure `foo`, the above module also exports procedures `add` and `mul64` implementations of which will be identical to `add` and `mul` procedures from the `std::math::u64` module respectively. ### Constants -Miden assembly supports constant declarations. These constants are scoped to the module they are defined in and can be used as immediate parameters for Miden assembly instructions. Constants are supported as immediate values for the following instructions: `push`, `assert`, `assertz`, `asert_eq`, `assert_eqw`, `locaddr`, `loc_load`, `loc_loadw`, `loc_store`, `loc_storew`, `mem_load`, `mem_loadw`, `mem_store`, `mem_storew`. +Miden assembly supports constant declarations. These constants are scoped to the module they are defined in and can be used as immediate parameters for Miden assembly instructions. Constants are supported as immediate values for the following instructions: `push`, `assert`, `assertz`, `asert_eq`, `assert_eqw`, `locaddr`, `loc_load`, `loc_loadw`, `loc_store`, `loc_storew`, `mem_load`, `mem_loadw`, `mem_store`, `mem_storew`, `mtree_verify`. Constants must be declared right after module imports and before any procedures or program bodies. A constant's name must start with an upper-case letter and can contain any combination of numbers, upper-case ASCII letters, and underscores (`_`). The number of characters in a constant name cannot exceed 100. diff --git a/docs/src/user_docs/assembly/cryptographic_operations.md b/docs/src/user_docs/assembly/cryptographic_operations.md index 5dc42130e0..8723973120 100644 --- a/docs/src/user_docs/assembly/cryptographic_operations.md +++ b/docs/src/user_docs/assembly/cryptographic_operations.md @@ -12,4 +12,11 @@ Miden assembly provides a set of instructions for performing common cryptographi | mtree_get
- *(9 cycles)* | [d, i, R, ...] | [V, R, ...] | Fetches the node value from the advice provider and runs a verification equivalent to `mtree_verify`, returning the value if succeeded. | | mtree_set
- *(29 cycles)* | [d, i, R, V', ...] | [V, R', ...] | Updates a node in the Merkle tree with root $R$ at depth $d$ and index $i$ to value $V'$. $R'$ is the Merkle root of the resulting tree and $V$ is old value of the node. Merkle tree with root $R$ must be present in the advice provider, otherwise execution fails. At the end of the operation the advice provider will contain both Merkle trees. | | mtree_merge
- *(16 cycles)* | [R, L, ...] | [M, ...] | Merges two Merkle trees with the provided roots R (right), L (left) into a new Merkle tree with root M (merged). The input trees are retained in the advice provider. | -| mtree_verify
- *(1 cycle)* | [V, d, i, R, ...] | [V, d, i, R, ...] | Verifies that a Merkle tree with root $R$ opens to node $V$ at depth $d$ and index $i$. Merkle tree with root $R$ must be present in the advice provider, otherwise execution fails. | +| mtree_verify
- *(1 cycle)* | [V, d, i, R, ...] | [V, d, i, R, ...] | Verifies that a Merkle tree with root $R$ opens to node $V$ at depth $d$ and index $i$. Merkle tree with root $R$ must be present in the advice provider, otherwise execution fails. | + +The `mtree_verify` instruction can also be parametrized with an error code which can be any 32-bit value specified either directly or via a [named constant](./code_organization.md#constants). For example: +``` +mtree_verify.err=123 +mtree_verify.err=MY_CONSTANT +``` +If the error code is omitted, the default value of $0$ is assumed. \ No newline at end of file diff --git a/processor/src/chiplets/aux_trace/mod.rs b/processor/src/chiplets/aux_trace/mod.rs index 0580e2f60a..09768b8973 100644 --- a/processor/src/chiplets/aux_trace/mod.rs +++ b/processor/src/chiplets/aux_trace/mod.rs @@ -38,7 +38,7 @@ const MSTORE: u8 = Operation::MStore.op_code(); const MSTREAM: u8 = Operation::MStream.op_code(); const RCOMBBASE: u8 = Operation::RCombBase.op_code(); const HPERM: u8 = Operation::HPerm.op_code(); -const MPVERIFY: u8 = Operation::MpVerify.op_code(); +const MPVERIFY: u8 = Operation::MpVerify(0).op_code(); const MRUPDATE: u8 = Operation::MrUpdate.op_code(); const NUM_HEADER_ALPHAS: usize = 4; diff --git a/processor/src/chiplets/mod.rs b/processor/src/chiplets/mod.rs index 1a90f1801b..137edc24f3 100644 --- a/processor/src/chiplets/mod.rs +++ b/processor/src/chiplets/mod.rs @@ -38,38 +38,38 @@ mod tests; /// /// The module's trace can be thought of as 5 stacked chiplet segments in the following form: /// * Hasher segment: contains the trace and selector for the hasher chiplet * -/// This segment fills the first rows of the trace up to the length of the hasher `trace_len`. +/// This segment fills the first rows of the trace up to the length of the hasher `trace_len`. /// - column 0: selector column with values set to ZERO /// - columns 1-17: execution trace of hash chiplet /// /// * Bitwise segment: contains the trace and selectors for the bitwise chiplet * -/// This segment begins at the end of the hasher segment and fills the next rows of the trace for -/// the `trace_len` of the bitwise chiplet. +/// This segment begins at the end of the hasher segment and fills the next rows of the trace for +/// the `trace_len` of the bitwise chiplet. /// - column 0: selector column with values set to ONE /// - column 1: selector column with values set to ZERO /// - columns 2-14: execution trace of bitwise chiplet /// - columns 15-17: unused columns padded with ZERO /// /// * Memory segment: contains the trace and selectors for the memory chiplet * -/// This segment begins at the end of the bitwise segment and fills the next rows of the trace for -/// the `trace_len` of the memory chiplet. +/// This segment begins at the end of the bitwise segment and fills the next rows of the trace for +/// the `trace_len` of the memory chiplet. /// - column 0-1: selector columns with values set to ONE /// - column 2: selector column with values set to ZERO /// - columns 3-14: execution trace of memory chiplet /// - columns 15-17: unused column padded with ZERO /// /// * Kernel ROM segment: contains the trace and selectors for the kernel ROM chiplet * -/// This segment begins at the end of the memory segment and fills the next rows of the trace for -/// the `trace_len` of the kernel ROM chiplet. +/// This segment begins at the end of the memory segment and fills the next rows of the trace for +/// the `trace_len` of the kernel ROM chiplet. /// - column 0-2: selector columns with values set to ONE /// - column 3: selector column with values set to ZERO /// - columns 4-9: execution trace of kernel ROM chiplet /// - columns 10-17: unused column padded with ZERO /// /// * Padding segment: unused * -/// This segment begins at the end of the kernel ROM segment and fills the rest of the execution -/// trace minus the number of random rows. When it finishes, the execution trace should have -/// exactly enough rows remaining for the specified number of random rows. +/// This segment begins at the end of the kernel ROM segment and fills the rest of the execution +/// trace minus the number of random rows. When it finishes, the execution trace should have +/// exactly enough rows remaining for the specified number of random rows. /// - columns 0-3: selector columns with values set to ONE /// - columns 3-17: unused columns padded with ZERO /// diff --git a/processor/src/decoder/mod.rs b/processor/src/decoder/mod.rs index 58a4d3f155..d5938d7a4a 100644 --- a/processor/src/decoder/mod.rs +++ b/processor/src/decoder/mod.rs @@ -312,7 +312,7 @@ where /// of these columns contains a single binary value, which together form a single opcode. /// * Hasher state columns h0 through h7. These are multi purpose columns used as follows: /// - When starting decoding of a new code block (e.g., via JOIN, SPLIT, LOOP, SPAN operations) -/// these columns are used for providing inputs for the current block's hash computations. +/// these columns are used for providing inputs for the current block's hash computations. /// - When finishing decoding of a code block (i.e., via END operation), these columns are used to /// record the result of the hash computation. /// - Inside a SPAN block, the first two columns are used to keep track of un-executed operations diff --git a/processor/src/errors.rs b/processor/src/errors.rs index 5d61512bf3..c419edefa6 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -54,6 +54,7 @@ pub enum ExecutionError { value: Word, index: Felt, root: Digest, + err_code: u32, }, MerkleStoreLookupFailed(MerkleError), MerkleStoreMergeFailed(MerkleError), @@ -154,10 +155,15 @@ impl Display for ExecutionError { MemoryAddressOutOfBounds(addr) => { write!(f, "Memory address cannot exceed 2^32 but was {addr}") } - MerklePathVerificationFailed { value, index, root } => { + MerklePathVerificationFailed { + value, + index, + root, + err_code, + } => { let value = to_hex(Felt::elements_as_bytes(value)); let root = to_hex(root.as_bytes()); - write!(f, "Merkle path verification failed for value {value} at index {index}, in the Merkle tree with root {root}") + write!(f, "Merkle path verification failed for value {value} at index {index}, in the Merkle tree with root {root} (error code: {err_code})") } MerkleStoreLookupFailed(reason) => { write!(f, "Advice provider Merkle store backend lookup failed: {reason}") diff --git a/processor/src/host/advice/injectors/dsa.rs b/processor/src/host/advice/injectors/dsa.rs index 778d8e538b..be11d1bd6f 100644 --- a/processor/src/host/advice/injectors/dsa.rs +++ b/processor/src/host/advice/injectors/dsa.rs @@ -10,7 +10,7 @@ use super::super::{ExecutionError, Felt, Word}; /// 2. The expanded public key represented as the coefficients of a polynomial of degree < 512. /// 3. The signature represented as the coefficients of a polynomial of degree < 512. /// 4. The product of the above two polynomials in the ring of polynomials with coefficients -/// in the Miden field. +/// in the Miden field. /// /// # Errors /// Will return an error if either: diff --git a/processor/src/operations/comb_ops.rs b/processor/src/operations/comb_ops.rs index 16d91d50ee..5fa0c098b5 100644 --- a/processor/src/operations/comb_ops.rs +++ b/processor/src/operations/comb_ops.rs @@ -45,17 +45,17 @@ where /// Here: /// /// 1. Ti for i in 0..=7 stands for the the value of the i-th trace polynomial for the current - /// query i.e. T_i(x). + /// query i.e. T_i(x). /// 2. (p0, p1) stands for an extension field element accumulating the values for the quotients - /// with common denominator (x - z). + /// with common denominator (x - z). /// 3. (r0, r1) stands for an extension field element accumulating the values for the quotients - /// with common denominator (x - gz). + /// with common denominator (x - gz). /// 4. x_addr is the memory address from which we are loading the Ti's using the MSTREAM - /// instruction. + /// instruction. /// 5. z_addr is the memory address to the i-th OOD evaluations at z and gz - /// i.e. T_i(z):= (T_i(z)0, T_i(z)1) and T_i(gz):= (T_i(gz)0, T_i(gz)1). + /// i.e. T_i(z):= (T_i(z)0, T_i(z)1) and T_i(gz):= (T_i(gz)0, T_i(gz)1). /// 6. a_addr is the memory address of the i-th random element alpha_i used in batching - /// the trace polynomial quotients. + /// the trace polynomial quotients. /// /// The instruction also makes use of the helper registers to hold the values of T_i(z), T_i(gz) /// and alpha_i during the course of its execution. diff --git a/processor/src/operations/crypto_ops.rs b/processor/src/operations/crypto_ops.rs index 222c343d8c..1150241c7f 100644 --- a/processor/src/operations/crypto_ops.rs +++ b/processor/src/operations/crypto_ops.rs @@ -67,7 +67,7 @@ where /// /// # Panics /// Panics if the computed root does not match the root provided via the stack. - pub(super) fn op_mpverify(&mut self) -> Result<(), ExecutionError> { + pub(super) fn op_mpverify(&mut self, err_code: u32) -> Result<(), ExecutionError> { // read node value, depth, index and root value from the stack let node = [self.stack.get(3), self.stack.get(2), self.stack.get(1), self.stack.get(0)]; let index = self.stack.get(5); @@ -82,7 +82,7 @@ where // save address(r) of the hasher trace from when the computation starts in the decoder // helper registers. - self.decoder.set_user_op_helpers(Operation::MpVerify, &[addr]); + self.decoder.set_user_op_helpers(Operation::MpVerify(err_code), &[addr]); if root != computed_root { // If the hasher chiplet doesn't compute the same root (using the same path), @@ -91,6 +91,7 @@ where value: node, index, root: root.into(), + err_code, }); } @@ -264,7 +265,7 @@ mod tests { let mut process = Process::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs); - process.execute_op(Operation::MpVerify).unwrap(); + process.execute_op(Operation::MpVerify(0)).unwrap(); let expected_stack = build_expected(&[ node[3], node[2], node[1], node[0], depth, index, root[3], root[2], root[1], root[0], ]); diff --git a/processor/src/operations/field_ops.rs b/processor/src/operations/field_ops.rs index b7ae264ffd..a2b4f5fdc6 100644 --- a/processor/src/operations/field_ops.rs +++ b/processor/src/operations/field_ops.rs @@ -171,16 +171,16 @@ where /// Computes a single turn of exp accumulation for the given inputs. The top 4 elements in the /// stack is arranged as follows (from the top): /// - least significant bit of the exponent in the previous trace if there's an expacc call, - /// otherwise ZERO + /// otherwise ZERO /// - exponent of base for this turn /// - accumulated power of base so far /// - number which needs to be shifted to the right /// /// To perform the operation we do the following: /// 1. Pops top three elements off the stack and calculate the least significant bit of the - /// number `b`. + /// number `b`. /// 2. Use this bit to decide if the current `base` raise to the power exponent needs to be - /// included in the accumulator. + /// included in the accumulator. /// 3. Update exponent with its square and the number b with one right shift. /// 4. Pushes the calcuted new values to the stack in the mentioned order. pub(super) fn op_expacc(&mut self) -> Result<(), ExecutionError> { diff --git a/processor/src/operations/io_ops.rs b/processor/src/operations/io_ops.rs index de3d9eca02..2f005fbfdb 100644 --- a/processor/src/operations/io_ops.rs +++ b/processor/src/operations/io_ops.rs @@ -149,8 +149,8 @@ where /// The operation works as follows: /// - The memory address is popped off the stack. /// - The top stack element is saved into the first element of the word located at the specified - /// memory address. The remaining 3 elements of the word are not affected. The element is not - /// removed from the stack. + /// memory address. The remaining 3 elements of the word are not affected. The element is not + /// removed from the stack. /// /// Thus, the net result of the operation is that the stack is shifted left by one item. /// diff --git a/processor/src/operations/mod.rs b/processor/src/operations/mod.rs index db0c45e973..94aa107c89 100644 --- a/processor/src/operations/mod.rs +++ b/processor/src/operations/mod.rs @@ -146,7 +146,7 @@ where // ----- cryptographic operations ----------------------------------------------------- Operation::HPerm => self.op_hperm()?, - Operation::MpVerify => self.op_mpverify()?, + Operation::MpVerify(err_code) => self.op_mpverify(err_code)?, Operation::MrUpdate => self.op_mrupdate()?, Operation::FriE2F4 => self.op_fri_ext2fold4()?, Operation::RCombBase => self.op_rcomb_base()?, diff --git a/processor/src/trace/tests/chiplets/hasher.rs b/processor/src/trace/tests/chiplets/hasher.rs index 963fa96853..429442ccea 100644 --- a/processor/src/trace/tests/chiplets/hasher.rs +++ b/processor/src/trace/tests/chiplets/hasher.rs @@ -428,7 +428,7 @@ fn b_chip_mpverify() { let advice_inputs = AdviceInputs::default().with_merkle_store(store); let trace = - build_trace_from_ops_with_inputs(vec![Operation::MpVerify], stack_inputs, advice_inputs); + build_trace_from_ops_with_inputs(vec![Operation::MpVerify(0)], stack_inputs, advice_inputs); let alphas = rand_array::(); let aux_columns = trace.build_aux_trace(&alphas).unwrap(); let b_chip = aux_columns.get_column(CHIPLETS_AUX_TRACE_OFFSET); diff --git a/processor/src/trace/tests/hasher.rs b/processor/src/trace/tests/hasher.rs index 66d71c581e..0cfc4fa6c3 100644 --- a/processor/src/trace/tests/hasher.rs +++ b/processor/src/trace/tests/hasher.rs @@ -33,7 +33,7 @@ fn hasher_p1_mp_verify() { let advice_inputs = AdviceInputs::default().with_merkle_store(store); // build execution trace and extract the sibling table column from it - let ops = vec![Operation::MpVerify]; + let ops = vec![Operation::MpVerify(0)]; let trace = build_trace_from_ops_with_inputs(ops, stack_inputs, advice_inputs); let alphas = rand_array::(); let aux_columns = trace.build_aux_trace(&alphas).unwrap(); diff --git a/processor/src/trace/utils.rs b/processor/src/trace/utils.rs index e321aaa344..a3f706c874 100644 --- a/processor/src/trace/utils.rs +++ b/processor/src/trace/utils.rs @@ -80,7 +80,7 @@ impl<'a> TraceFragment<'a> { /// - `main_trace_len` contains the length of the main trace. /// - `range_trace_len` contains the length of the range checker trace. /// - `chiplets_trace_len` contains the trace lengths of the all chiplets (hash, bitwise, memory, -/// kernel ROM) +/// kernel ROM) #[derive(Debug, Default, Eq, PartialEq, Clone, Copy)] pub struct TraceLenSummary { main_trace_len: usize, diff --git a/stdlib/tests/crypto/fri/verifier_fri_e2f4.rs b/stdlib/tests/crypto/fri/verifier_fri_e2f4.rs index e8f851f676..004d29f1fd 100644 --- a/stdlib/tests/crypto/fri/verifier_fri_e2f4.rs +++ b/stdlib/tests/crypto/fri/verifier_fri_e2f4.rs @@ -411,7 +411,7 @@ impl UnBatch for MidenFriVerifierChannel(&query); + let x = group_slice_elements::(query); assert_eq!(x.len(), unbatched_proof.len()); let nodes: Vec<[Felt; 4]> = unbatched_proof diff --git a/stdlib/tests/crypto/stark/verifier_recursive/channel.rs b/stdlib/tests/crypto/stark/verifier_recursive/channel.rs index c521ad6a4d..6631e3b03d 100644 --- a/stdlib/tests/crypto/stark/verifier_recursive/channel.rs +++ b/stdlib/tests/crypto/stark/verifier_recursive/channel.rs @@ -243,7 +243,7 @@ impl VerifierChannel { let layer_proof = layer_proofs.remove(0); let mut unbatched_proof = layer_proof.into_paths(&folded_positions).unwrap(); - let x = group_slice_elements::(&query); + let x = group_slice_elements::(query); assert_eq!(x.len(), unbatched_proof.len()); let nodes: Vec<[Felt; 4]> = unbatched_proof diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index 0bbc520c58..3b72a4fe44 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -167,7 +167,7 @@ macro_rules! assert_assembler_diagnostic { /// /// Types of failure tests: /// - Assembly error test: check that attempting to compile the given source causes an -/// AssemblyError which contains the specified substring. +/// AssemblyError which contains the specified substring. /// - Execution error test: check that running a program compiled from the given source causes an /// ExecutionError which contains the specified substring. pub struct Test { diff --git a/test-utils/src/test_builders.rs b/test-utils/src/test_builders.rs index bf49661935..4cc6fde43d 100644 --- a/test-utils/src/test_builders.rs +++ b/test-utils/src/test_builders.rs @@ -9,11 +9,11 @@ /// /// * `source`: a string of one or more operations, e.g. "push.1 push.2". /// * `stack_inputs` (optional): the initial inputs which must be at the top of the stack before -/// executing the `source`. Stack inputs can be provided independently without any advice inputs. +/// executing the `source`. Stack inputs can be provided independently without any advice inputs. /// * `advice_stack` (optional): the initial advice stack values. When provided, `stack_inputs` and -/// `merkle_store` are also expected. +/// `merkle_store` are also expected. /// * `merkle_store` (optional): the initial merkle set values. When provided, `stack_inputs` and -/// `advice_stack` are also expected. +/// `advice_stack` are also expected. #[macro_export] macro_rules! build_op_test { ($op_str:expr) => {{ @@ -34,11 +34,11 @@ macro_rules! build_op_test { /// /// * `source`: a well-formed source string. /// * `stack_inputs` (optional): the initial inputs which must be at the top of the stack before -/// executing the `source`. Stack inputs can be provided independently without any advice inputs. +/// executing the `source`. Stack inputs can be provided independently without any advice inputs. /// * `advice_stack` (optional): the initial advice stack values. When provided, `stack_inputs` and -/// `merkle_store` are also expected. +/// `merkle_store` are also expected. /// * `merkle_store` (optional): the initial merkle set values. When provided, `stack_inputs` and -/// `advice_stack` are also expected. +/// `advice_stack` are also expected. #[macro_export] macro_rules! build_test { ($($params:tt)+) => {{ @@ -54,11 +54,11 @@ macro_rules! build_test { /// /// * `source`: a well-formed source string. /// * `stack_inputs` (optional): the initial inputs which must be at the top of the stack before -/// executing the `source`. Stack inputs can be provided independently without any advice inputs. +/// executing the `source`. Stack inputs can be provided independently without any advice inputs. /// * `advice_stack` (optional): the initial advice stack values. When provided, `stack_inputs` and -/// `merkle_store` are also expected. +/// `merkle_store` are also expected. /// * `merkle_store` (optional): the initial merkle set values. When provided, `stack_inputs` and -/// `advice_stack` are also expected. +/// `advice_stack` are also expected. #[macro_export] macro_rules! build_debug_test { ($($params:tt)+) => {{