diff --git a/CHANGELOG.md b/CHANGELOG.md index f5bd1db4fb..84c7ec2281 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ #### Stdlib - Added `init_no_padding` procedure to `std::crypto::hashes::native` (#1313). +- [BREAKING] `native` module was renamed to the `pro`, `hash_memory` procedure was renamed to the `hash_memory_words` (#1368). +- Added `hash_memory` procedure to `std::crypto::hashes::rpo` (#1368). #### VM Internals - Removed unused `find_lone_leaf()` function from the Advice Provider (#1262). 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..ce08893ffb 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]; 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..d04b4a75b1 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], @@ -290,9 +290,9 @@ pub fn enforce_check_element_validity>( /// 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. +/// 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/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/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..5c621e1687 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 diff --git a/processor/src/chiplets/mod.rs b/processor/src/chiplets/mod.rs index 1a90f1801b..0a70095389 100644 --- a/processor/src/chiplets/mod.rs +++ b/processor/src/chiplets/mod.rs @@ -37,43 +37,44 @@ mod tests; /// chiplet selectors. /// /// 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`. -/// - column 0: selector column with values set to ZERO -/// - columns 1-17: execution trace of hash chiplet +/// * 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`. +/// - 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. -/// - 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 +/// * 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. +/// - 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. -/// - 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 +/// * 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. +/// - 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. -/// - 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 +/// * 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. +/// - 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. -/// - columns 0-3: selector columns with values set to ONE -/// - columns 3-17: unused columns 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. +/// - columns 0-3: selector columns with values set to ONE +/// - columns 3-17: unused columns padded with ZERO /// /// The following is a pictorial representation of the chiplet module: +/// ```text /// +---+-------------------------------------------------------+-------------+ /// | 0 | | |-------------| /// | . | Hash chiplet | Hash chiplet |-------------| @@ -111,6 +112,7 @@ mod tests; /// | . | . | . | . |---------------------------------------------------------| /// | 1 | 1 | 1 | 1 |---------------------------------------------------------| /// +---+---+---+---+---------------------------------------------------------+ +/// ``` pub struct Chiplets { /// Current clock cycle of the VM. clk: u32, diff --git a/processor/src/decoder/mod.rs b/processor/src/decoder/mod.rs index 03f022fa64..98c814d2b0 100644 --- a/processor/src/decoder/mod.rs +++ b/processor/src/decoder/mod.rs @@ -309,7 +309,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/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/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/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/asm/crypto/hashes/rpo.masm b/stdlib/asm/crypto/hashes/rpo.masm index 27c6b12aec..9b8a3457db 100644 --- a/stdlib/asm/crypto/hashes/rpo.masm +++ b/stdlib/asm/crypto/hashes/rpo.masm @@ -56,7 +56,7 @@ end #! Cycles: #! even words: 49 cycles + 3 * words #! odd words: 61 cycles + 3 * words -export.hash_memory +export.hash_memory_words # enforce `start_addr < end_addr` dup.1 dup.1 u32assert2 u32gt assert @@ -100,3 +100,161 @@ export.hash_memory # drop start_addr/end_addr (4 cycles) movup.4 drop movup.4 drop end + +#! Computes hash of Felt values starting at the specified memory address. +#! +#! This procedure divides the hashing process into two parts: hashing pairs of words using +#! `absorb_double_words_from_memory` procedure and hashing the remaining values using the `hperm` +#! instruction. +#! +#! Inputs: [ptr, num_elements] +#! Outputs: [HASH] +#! Cycles: +#! - If number of elements divides by 8: 47 cycles + 3 * words +#! - Else: 180 cycles + 3 * words +#! +#! Panics if number of inputs equals 0. +export.hash_memory + # move number of inputs to the top of the stack + swap + # => [num_elements, ptr] + + # check that number of inputs greater than 0 + dup eq.0 assertz + # => [num_elements, ptr] + + # get the number of double words + u32divmod.8 swap + # => [num_elements/8, num_elements%8, ptr] + + # get the end_addr for hash_memory_even procedure (end address for pairs of words) + mul.2 dup.2 add movup.2 + # => [ptr, end_addr, num_elements%8] + + # get the capacity element which is equal to num_elements%8 + dup.2 + # => [capacity, ptr, end_addr, num_elements%8] + + # prepare hasher state for RPO permutation + push.0.0.0 padw padw + # => [C, B, A, ptr, end_addr, num_elements%8] + + # hash every pair of words + exec.absorb_double_words_from_memory + # => [C', B', A', ptr', end_addr, num_elements%8] where ptr' = end_addr + + # hash remaining input values if there are any left + # if num_elements%8 is ZERO and there are no elements to hash + dup.14 eq.0 + if.true + # clean the stack + exec.squeeze_digest + swapw drop drop drop movdn.4 + # => [B'] + else + # load the remaining double word + mem_stream + # => [E, D, A', ptr'+2, end_addr, num_elements%8] + + # clean the stack + movup.12 drop movup.12 drop + # => [E, D, A', num_elements%8] + + # get the number of elements we need to drop + # notice that drop_counter could be any number from 1 to 7 + push.8 movup.13 sub movdn.12 + # => [E, D, A', drop_counter] + + ### 0th value ######################################################## + + # we need to drop first value anyway, since number of values is not divisible by 8 + # push the padding 0 on to the stack and move it down to the 6th position + drop push.0 movdn.6 + # => [e_2, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', drop_counter] + + ### 1st value ######################################################## + + # prepare the second element of the E Word for cdrop instruction + push.0 swap + # => [e_2, 0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', drop_counter] + + # push latch variable onto the stack; this will be the control for the cdrop instruction + push.0 + # => [latch = 0, e_2, 0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', drop_counter] + + # get the flag whether the drop counter is equal 1 + dup.14 eq.1 + # => [drop_counter == 1, latch = 0, e_2, 0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', drop_counter] + + # update the latch: if drop_counter == 1, latch will become 1 + or + # => [latch', e_2, 0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', drop_counter] + + # save the latch value + dup movdn.14 + # => [latch', e_2, 0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', latch', drop_counter] + + # if latch == 1, drop 0; otherwise drop e_1 + cdrop + # => [e_2_or_0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', latch', drop_counter] + + # move the calculated value down the stack + movdn.6 + # => [e_1, e_0, d_3, d_2, d_1, 0, e_2_or_0, d_0, A', latch', drop_counter] + + ### 2nd value ######################################################## + + # repeat the above process but now compare drop_counter to 2 + push.0 swap + movup.13 dup.14 eq.2 or + dup movdn.14 + cdrop movdn.6 + # => [e_0, d_3, d_2, d_1, 0, e_2_or_0, e_1_or_0, d_0, A', latch', drop_counter] + + ### 3rd value ######################################################## + + # repeat the above process but now compare drop_counter to 3 + push.0 swap + movup.13 dup.14 eq.3 or + dup movdn.14 + cdrop movdn.6 + # => [d_3, d_2, d_1, 0, e_2_or_0, e_1_or_0, e_0_or_0, d_0, A', latch', drop_counter] + + ### 4th value ######################################################## + + # repeat the above process but now compare drop_counter to 4 + push.0 swap + movup.13 dup.14 eq.4 or + dup movdn.14 + cdrop movdn.6 + # => [d_2, d_1, 0, e_2_or_0, e_1_or_0, e_0_or_0, d_3_or_0, d_0, A', latch', drop_counter] + + ### 5th value ######################################################## + + # repeat the above process but now compare drop_counter to 5 + push.0 swap + movup.13 dup.14 eq.5 or + dup movdn.14 + cdrop movdn.6 + # => [d_1, 0, e_2_or_0, e_1_or_0, e_0_or_0, d_3_or_0, d_2_or_0, d_0, A', latch', drop_counter] + + ### 6th value ######################################################## + + # repeat the above process but now compare drop_counter to 6 + push.0 swap + movup.13 movup.14 eq.6 or + cdrop movdn.6 + # => [0, e_2_or_0, e_1_or_0, e_0_or_0, d_3_or_0, d_2_or_0, d_1_or_0, d_0, A'] + # or in other words + # => [C, B, A', ... ] + # notice that we don't need to check the d_0 value: entering the else branch means that + # we have number of elements not divisible by 8, so we will have at least one element to + # hash here (which turns out to be d_0) + + hperm + # => [F, E, D] + + exec.squeeze_digest + # => [E] + end +end \ No newline at end of file diff --git a/stdlib/docs/crypto/hashes/rpo.md b/stdlib/docs/crypto/hashes/rpo.md index eb10fb3cfb..da22de54cc 100644 --- a/stdlib/docs/crypto/hashes/rpo.md +++ b/stdlib/docs/crypto/hashes/rpo.md @@ -4,4 +4,5 @@ Prepares the top of the stack with the hasher initial state.

This pro | ----------- | ------------- | | squeeze_digest | Given the hasher state, returns the hash output.

Input: [C, B, A, ...]
Ouptut: [HASH, ...]
where: For the native RPO hasher HASH is B.
Cycles: 9
| | absorb_double_words_from_memory | Hashes the memory `start_addr` to `end_addr` given an RPO state specified by 3 words.

This requires that `end_addr=start_addr + 2n + 1`, otherwise the procedure will enter an infinite
loop. `end_addr` is not inclusive.

Stack transition:
Input: [C, B, A, start_addr, end_addr, ...]
Output: [C', B', A', end_addr, end_addr ...]
Cycles: 4 + 3 * words, where `words` is the `start_addr - end_addr - 1`

Where `A` is the capacity word that will be used by the hashing function, and `B'` the hash output.
| -| hash_memory | Hashes the memory `start_addr` to `end_addr`, handles odd number of elements.

Requires `start_addr < end_addr`, `end_addr` is not inclusive.

Stack transition:
Input: [start_addr, end_addr, ...]
Output: [H, ...]
Cycles:
even words: 49 cycles + 3 * words
odd words: 61 cycles + 3 * words
| +| hash_memory_words | Hashes the memory `start_addr` to `end_addr`, handles odd number of elements.

Requires `start_addr < end_addr`, `end_addr` is not inclusive.

Stack transition:
Input: [start_addr, end_addr, ...]
Output: [H, ...]
Cycles:
even words: 49 cycles + 3 * words
odd words: 61 cycles + 3 * words
| +| hash_memory | Computes hash of Felt values starting at the specified memory address.

This procedure divides the hashing process into two parts: hashing pairs of words using
`absorb_double_words_from_memory` procedure and hashing the remaining values using the `hperm`
instruction.

Inputs: [ptr, num_elements]
Outputs: [HASH]
Cycles:
- If number of elements divides by 8: 47 cycles + 3 * words
- Else: 180 cycles + 3 * words

Panics if number of inputs equals 0.
| diff --git a/stdlib/tests/crypto/rpo.rs b/stdlib/tests/crypto/rpo.rs index 51617c238a..d00da8fefc 100644 --- a/stdlib/tests/crypto/rpo.rs +++ b/stdlib/tests/crypto/rpo.rs @@ -11,7 +11,7 @@ fn test_invalid_end_addr() { push.0999 # end address push.1000 # start address - exec.rpo::hash_memory + exec.rpo::hash_memory_words end "; let test = build_test!(empty_range, &[]); @@ -32,7 +32,7 @@ fn test_invalid_end_addr() { push.1000 # end address push.1000 # start address - exec.rpo::hash_memory + exec.rpo::hash_memory_words end "; let test = build_test!(empty_range, &[]); @@ -69,7 +69,7 @@ fn test_hash_empty() { ]).into_iter().map(|e| e.as_int()).collect(); build_test!(two_zeros_mem_stream, &[]).expect_stack(&zero_hash); - // checks the hash compute from 8 zero elements is the same when using hash_memory + // checks the hash compute from 8 zero elements is the same when using hash_memory_words let two_zeros = " use.std::crypto::hashes::rpo @@ -77,7 +77,7 @@ fn test_hash_empty() { push.1002 # end address push.1000 # start address - exec.rpo::hash_memory + exec.rpo::hash_memory_words end "; @@ -110,7 +110,7 @@ fn test_single_iteration() { ]).into_iter().map(|e| e.as_int()).collect(); build_test!(one_memstream, &[]).expect_stack(&one_hash); - // checks the hash of 1 is the same when using hash_memory + // checks the hash of 1 is the same when using hash_memory_words // Note: This is testing the hashing of two words, so no padding is added // here let one_element = " @@ -123,7 +123,7 @@ fn test_single_iteration() { push.1002 # end address push.1000 # start address - exec.rpo::hash_memory + exec.rpo::hash_memory_words end "; @@ -141,7 +141,7 @@ fn test_hash_one_word() { 1, 0, 0, 0, ]).into_iter().map(|e| e.as_int()).collect(); - // checks the hash of 1 is the same when using hash_memory + // checks the hash of 1 is the same when using hash_memory_words let one_element = " use.std::crypto::hashes::rpo @@ -151,7 +151,7 @@ fn test_hash_one_word() { push.1001 # end address push.1000 # start address - exec.rpo::hash_memory + exec.rpo::hash_memory_words end "; @@ -171,7 +171,7 @@ fn test_hash_even_words() { push.1002 # end address push.1000 # start address - exec.rpo::hash_memory + exec.rpo::hash_memory_words end "; @@ -197,7 +197,7 @@ fn test_hash_odd_words() { push.1003 # end address push.1000 # start address - exec.rpo::hash_memory + exec.rpo::hash_memory_words end "; @@ -274,3 +274,96 @@ fn test_squeeze_digest() { build_test!(even_words, &[]).expect_stack(&even_hash); } + +#[test] +fn test_hash_memory() { + // hash fewer than 8 elements + let compute_inputs_hash_5 = " + use.std::crypto::hashes::rpo + + begin + push.1.2.3.4.1000 mem_storew dropw + push.5.0.0.0.1001 mem_storew dropw + push.11 + + push.5.1000 + + exec.rpo::hash_memory + end + "; + + #[rustfmt::skip] + let mut expected_hash: Vec = build_expected_hash(&[ + 1, 2, 3, 4, 5 + ]).into_iter().map(|e| e.as_int()).collect(); + // make sure that value `11` stays unchanged + expected_hash.push(11); + build_test!(compute_inputs_hash_5, &[]).expect_stack(&expected_hash); + + // hash exactly 8 elements + let compute_inputs_hash_8 = " + use.std::crypto::hashes::rpo + + begin + push.1.2.3.4.1000 mem_storew dropw + push.5.6.7.8.1001 mem_storew dropw + push.11 + + push.8.1000 + + exec.rpo::hash_memory + end + "; + + #[rustfmt::skip] + let mut expected_hash: Vec = build_expected_hash(&[ + 1, 2, 3, 4, 5, 6, 7, 8 + ]).into_iter().map(|e| e.as_int()).collect(); + // make sure that value `11` stays unchanged + expected_hash.push(11); + build_test!(compute_inputs_hash_8, &[]).expect_stack(&expected_hash); + + // hash more than 8 elements + let compute_inputs_hash_15 = " + use.std::crypto::hashes::rpo + + begin + push.1.2.3.4.1000 mem_storew dropw + push.5.6.7.8.1001 mem_storew dropw + push.9.10.11.12.1002 mem_storew dropw + push.13.14.15.0.1003 mem_storew dropw + push.11 + + push.15.1000 + + exec.rpo::hash_memory + end + "; + + #[rustfmt::skip] + let mut expected_hash: Vec = build_expected_hash(&[ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15 + ]).into_iter().map(|e| e.as_int()).collect(); + // make sure that value `11` stays unchanged + expected_hash.push(11); + build_test!(compute_inputs_hash_15, &[]).expect_stack(&expected_hash); +} + +#[test] +fn test_hash_memory_fail() { + // try to hash 0 values + let compute_inputs_hash = " + use.std::crypto::hashes::rpo + + begin + push.0.1000 + + exec.rpo::hash_memory + end + "; + + assert!(build_test!(compute_inputs_hash, &[]).execute().is_err()); +} 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)+) => {{