From 8a464b36da1a336369ef7862eaa0e5daf72e83e5 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Fri, 15 Nov 2024 15:31:36 +0200 Subject: [PATCH 01/39] refactor: fix clippy warnings (`String::as_bytes`) (#1576) --- assembly/src/library/namespace.rs | 2 +- assembly/src/library/path.rs | 4 ++-- assembly/src/parser/scanner.rs | 2 +- assembly/src/parser/token.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/assembly/src/library/namespace.rs b/assembly/src/library/namespace.rs index 4a0510618c..529718264c 100644 --- a/assembly/src/library/namespace.rs +++ b/assembly/src/library/namespace.rs @@ -112,7 +112,7 @@ impl LibraryNamespace { if matches!(source, Self::KERNEL_PATH | Self::EXEC_PATH | Self::ANON_PATH) { return Ok(()); } - if source.as_bytes().len() > Self::MAX_LENGTH { + if source.len() > Self::MAX_LENGTH { return Err(LibraryNamespaceError::Length); } if !source.starts_with(|c: char| c.is_ascii_lowercase() && c.is_ascii_alphabetic()) { diff --git a/assembly/src/library/path.rs b/assembly/src/library/path.rs index 8175339dd2..915bc9aaad 100644 --- a/assembly/src/library/path.rs +++ b/assembly/src/library/path.rs @@ -192,8 +192,8 @@ impl LibraryPath { /// Return the size in bytes of this path when displayed as a string pub fn byte_len(&self) -> usize { - self.inner.components.iter().map(|c| c.as_bytes().len()).sum::() - + self.inner.ns.as_str().as_bytes().len() + self.inner.components.iter().map(|c| c.len()).sum::() + + self.inner.ns.as_str().len() + (self.inner.components.len() * 2) } diff --git a/assembly/src/parser/scanner.rs b/assembly/src/parser/scanner.rs index dc90470fa3..c95414374e 100644 --- a/assembly/src/parser/scanner.rs +++ b/assembly/src/parser/scanner.rs @@ -41,7 +41,7 @@ pub struct Scanner<'input> { impl<'input> Scanner<'input> { /// Construct a new [Scanner] for the given `source`. pub fn new(input: &'input str) -> Self { - let end = input.as_bytes().len(); + let end = input.len(); assert!(end < u32::MAX as usize, "file too large"); let mut chars = input.char_indices().peekable(); diff --git a/assembly/src/parser/token.rs b/assembly/src/parser/token.rs index 602e274912..b55201f1a7 100644 --- a/assembly/src/parser/token.rs +++ b/assembly/src/parser/token.rs @@ -860,7 +860,7 @@ impl<'input> Token<'input> { // No match, it's an ident None => Token::Ident(s), // If the match is not exact, it's an ident - Some(matched) if matched.len() != s.as_bytes().len() => Token::Ident(s), + Some(matched) if matched.len() != s.len() => Token::Ident(s), // Otherwise clone the Token corresponding to the keyword that was matched Some(matched) => Self::KEYWORDS[matched.pattern().as_usize()].1.clone(), } From b85121356bebda7f18424d2cfed98da7e6f8058a Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Tue, 5 Nov 2024 14:30:18 -0500 Subject: [PATCH 02/39] fix: return error when 2 memory accesses at the same address in same clock cycle --- CHANGELOG.md | 1 + .../integration/operations/io_ops/mem_ops.rs | 24 ++++++ processor/src/chiplets/memory/mod.rs | 27 +++++-- processor/src/chiplets/memory/segment.rs | 73 ++++++++++++++----- processor/src/chiplets/memory/tests.rs | 50 ++++++------- processor/src/chiplets/mod.rs | 41 ++++++++--- processor/src/errors.rs | 10 +++ processor/src/operations/comb_ops.rs | 44 ++++++----- processor/src/operations/io_ops.rs | 10 +-- 9 files changed, 196 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d37a550c67..1509977ba8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - Fixed the construction of the chiplets virtual table (#1514) (#1556) - Fixed the construction of the chiplets bus (#1516) (#1525) - Decorators are now allowed in empty basic blocks (#1466) +- Return an error if an instruction performs 2 memory accesses at the same memory address in the same cycle (#1561) ## 0.10.6 (2024-09-12) - `miden-processor` crate only diff --git a/miden/tests/integration/operations/io_ops/mem_ops.rs b/miden/tests/integration/operations/io_ops/mem_ops.rs index bb1f1469f1..81542a86d9 100644 --- a/miden/tests/integration/operations/io_ops/mem_ops.rs +++ b/miden/tests/integration/operations/io_ops/mem_ops.rs @@ -1,3 +1,6 @@ +use prover::ExecutionError; +use test_utils::expect_exec_error; + use super::{apply_permutation, build_op_test, build_test, Felt, ToElements, TRUNCATE_STACK_PROC}; // LOADING SINGLE ELEMENT ONTO THE STACK (MLOAD) @@ -228,3 +231,24 @@ fn read_after_write() { let test = build_op_test!("mem_storew.0 dropw mem_loadw.0", &[1, 2, 3, 4, 5, 6, 7, 8]); test.expect_stack(&[8, 7, 6, 5]); } + +// MISC +// ================================================================================================ + +/// Ensures that the processor returns an error when 2 memory operations occur in the same context, +/// at the same address, and in the same clock cycle (which is what RCOMBBASE does when `stack[13] = +/// stack[14] = 0`). +#[test] +fn mem_reads_same_clock_cycle() { + let asm_op = "begin rcomb_base end"; + + let test = build_test!(asm_op); + expect_exec_error!( + test, + ExecutionError::DuplicateMemoryAccess { + ctx: 0_u32.into(), + addr: 0, + clk: 1_u32.into() + } + ); +} diff --git a/processor/src/chiplets/memory/mod.rs b/processor/src/chiplets/memory/mod.rs index 78d20ffaf7..76690130c3 100644 --- a/processor/src/chiplets/memory/mod.rs +++ b/processor/src/chiplets/memory/mod.rs @@ -11,7 +11,7 @@ use super::{ utils::{split_element_u32_into_u16, split_u32_into_u16}, Felt, FieldElement, RangeChecker, TraceFragment, Word, EMPTY_WORD, ONE, }; -use crate::system::ContextId; +use crate::{system::ContextId, ExecutionError}; mod segment; use segment::MemorySegmentTrace; @@ -137,15 +137,32 @@ impl Memory { /// /// If the specified address hasn't been previously written to, four ZERO elements are /// returned. This effectively implies that memory is initialized to ZERO. - pub fn read(&mut self, ctx: ContextId, addr: u32, clk: RowIndex) -> Word { + /// + /// # Errors + /// - Returns an error if the same address is accessed more than once in the same clock cycle. + pub fn read( + &mut self, + ctx: ContextId, + addr: u32, + clk: RowIndex, + ) -> Result { self.num_trace_rows += 1; - self.trace.entry(ctx).or_default().read(addr, Felt::from(clk)) + self.trace.entry(ctx).or_default().read(ctx, addr, Felt::from(clk)) } /// Writes the provided word at the specified context/address. - pub fn write(&mut self, ctx: ContextId, addr: u32, clk: RowIndex, value: Word) { + /// + /// # Errors + /// - Returns an error if the same address is accessed more than once in the same clock cycle. + pub fn write( + &mut self, + ctx: ContextId, + addr: u32, + clk: RowIndex, + value: Word, + ) -> Result<(), ExecutionError> { self.num_trace_rows += 1; - self.trace.entry(ctx).or_default().write(addr, Felt::from(clk), value); + self.trace.entry(ctx).or_default().write(ctx, addr, Felt::from(clk), value) } // EXECUTION TRACE GENERATION diff --git a/processor/src/chiplets/memory/segment.rs b/processor/src/chiplets/memory/segment.rs index 60fc667475..4bee448fc1 100644 --- a/processor/src/chiplets/memory/segment.rs +++ b/processor/src/chiplets/memory/segment.rs @@ -1,4 +1,7 @@ -use alloc::{collections::BTreeMap, vec::Vec}; +use alloc::{ + collections::{btree_map::Entry, BTreeMap}, + vec::Vec, +}; use miden_air::{ trace::chiplets::memory::{Selectors, MEMORY_COPY_READ, MEMORY_INIT_READ, MEMORY_WRITE}, @@ -6,6 +9,7 @@ use miden_air::{ }; use super::{Felt, Word, INIT_MEM_VALUE}; +use crate::{ContextId, ExecutionError}; // MEMORY SEGMENT TRACE // ================================================================================================ @@ -72,37 +76,66 @@ impl MemorySegmentTrace { /// /// If the specified address hasn't been previously written to, four ZERO elements are /// returned. This effectively implies that memory is initialized to ZERO. - pub fn read(&mut self, addr: u32, clk: Felt) -> Word { + /// + /// # Errors + /// - Returns an error if the same address is accessed more than once in the same clock cycle. + pub fn read(&mut self, ctx: ContextId, addr: u32, clk: Felt) -> Result { // look up the previous value in the appropriate address trace and add (clk, prev_value) // to it; if this is the first time we access this address, create address trace for it // with entry (clk, [ZERO, 4]). in both cases, return the last value in the address trace. - self.0 - .entry(addr) - .and_modify(|addr_trace| { - let last_value = addr_trace.last().expect("empty address trace").value(); - let access = MemorySegmentAccess::new(clk, MemoryOperation::CopyRead, last_value); - addr_trace.push(access); - }) - .or_insert_with(|| { + match self.0.entry(addr) { + Entry::Vacant(vacant_entry) => { let access = MemorySegmentAccess::new(clk, MemoryOperation::InitRead, INIT_MEM_VALUE); - vec![access] - }) - .last() - .expect("empty address trace") - .value() + vacant_entry.insert(vec![access]); + Ok(INIT_MEM_VALUE) + }, + Entry::Occupied(mut occupied_entry) => { + let addr_trace = occupied_entry.get_mut(); + if addr_trace.last().expect("empty address trace").clk() == clk { + Err(ExecutionError::DuplicateMemoryAccess { ctx, addr, clk }) + } else { + let last_value = addr_trace.last().expect("empty address trace").value(); + let access = + MemorySegmentAccess::new(clk, MemoryOperation::CopyRead, last_value); + addr_trace.push(access); + + Ok(last_value) + } + }, + } } /// Writes the provided word at the specified address. The memory access is assumed to happen /// at the provided clock cycle. - pub fn write(&mut self, addr: u32, clk: Felt, value: Word) { + /// + /// # Errors + /// - Returns an error if the same address is accessed more than once in the same clock cycle. + pub fn write( + &mut self, + ctx: ContextId, + addr: u32, + clk: Felt, + value: Word, + ) -> Result<(), ExecutionError> { // add a memory access to the appropriate address trace; if this is the first time // we access this address, initialize address trace. let access = MemorySegmentAccess::new(clk, MemoryOperation::Write, value); - self.0 - .entry(addr) - .and_modify(|addr_trace| addr_trace.push(access)) - .or_insert_with(|| vec![access]); + match self.0.entry(addr) { + Entry::Vacant(vacant_entry) => { + vacant_entry.insert(vec![access]); + Ok(()) + }, + Entry::Occupied(mut occupied_entry) => { + let addr_trace = occupied_entry.get_mut(); + if addr_trace.last().expect("empty address trace").clk() == clk { + Err(ExecutionError::DuplicateMemoryAccess { ctx, addr, clk }) + } else { + addr_trace.push(access); + Ok(()) + } + }, + } } // INNER VALUE ACCESSORS diff --git a/processor/src/chiplets/memory/tests.rs b/processor/src/chiplets/memory/tests.rs index 0bcdc40a10..5c169507f5 100644 --- a/processor/src/chiplets/memory/tests.rs +++ b/processor/src/chiplets/memory/tests.rs @@ -28,27 +28,27 @@ fn mem_read() { // read a value from address 0; clk = 1 let addr0 = 0; - let value = mem.read(ContextId::root(), addr0, 1.into()); + let value = mem.read(ContextId::root(), addr0, 1.into()).unwrap(); assert_eq!(EMPTY_WORD, value); assert_eq!(1, mem.size()); assert_eq!(1, mem.trace_len()); // read a value from address 3; clk = 2 let addr3 = 3; - let value = mem.read(ContextId::root(), addr3, 2.into()); + let value = mem.read(ContextId::root(), addr3, 2.into()).unwrap(); assert_eq!(EMPTY_WORD, value); assert_eq!(2, mem.size()); assert_eq!(2, mem.trace_len()); // read a value from address 0 again; clk = 3 - let value = mem.read(ContextId::root(), addr0, 3.into()); + let value = mem.read(ContextId::root(), addr0, 3.into()).unwrap(); assert_eq!(EMPTY_WORD, value); assert_eq!(2, mem.size()); assert_eq!(3, mem.trace_len()); // read a value from address 2; clk = 4 let addr2 = 2; - let value = mem.read(ContextId::root(), addr2, 4.into()); + let value = mem.read(ContextId::root(), addr2, 4.into()).unwrap(); assert_eq!(EMPTY_WORD, value); assert_eq!(3, mem.size()); assert_eq!(4, mem.trace_len()); @@ -81,7 +81,7 @@ fn mem_write() { // write a value into address 0; clk = 1 let addr0 = 0; let value1 = [ONE, ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr0, 1.into(), value1); + mem.write(ContextId::root(), addr0, 1.into(), value1).unwrap(); assert_eq!(value1, mem.get_value(ContextId::root(), addr0).unwrap()); assert_eq!(1, mem.size()); assert_eq!(1, mem.trace_len()); @@ -89,7 +89,7 @@ fn mem_write() { // write a value into address 2; clk = 2 let addr2 = 2; let value5 = [Felt::new(5), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr2, 2.into(), value5); + mem.write(ContextId::root(), addr2, 2.into(), value5).unwrap(); assert_eq!(value5, mem.get_value(ContextId::root(), addr2).unwrap()); assert_eq!(2, mem.size()); assert_eq!(2, mem.trace_len()); @@ -97,14 +97,14 @@ fn mem_write() { // write a value into address 1; clk = 3 let addr1 = 1; let value7 = [Felt::new(7), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr1, 3.into(), value7); + mem.write(ContextId::root(), addr1, 3.into(), value7).unwrap(); assert_eq!(value7, mem.get_value(ContextId::root(), addr1).unwrap()); assert_eq!(3, mem.size()); assert_eq!(3, mem.trace_len()); // write a value into address 0; clk = 4 let value9 = [Felt::new(9), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr0, 4.into(), value9); + mem.write(ContextId::root(), addr0, 4.into(), value9).unwrap(); assert_eq!(value7, mem.get_value(ContextId::root(), addr1).unwrap()); assert_eq!(3, mem.size()); assert_eq!(4, mem.trace_len()); @@ -137,35 +137,35 @@ fn mem_write_read() { // write 1 into address 5; clk = 1 let addr5 = 5; let value1 = [ONE, ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr5, 1.into(), value1); + mem.write(ContextId::root(), addr5, 1.into(), value1).unwrap(); // write 4 into address 2; clk = 2 let addr2 = 2; let value4 = [Felt::new(4), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr2, 2.into(), value4); + mem.write(ContextId::root(), addr2, 2.into(), value4).unwrap(); // read a value from address 5; clk = 3 - mem.read(ContextId::root(), addr5, 3.into()); + mem.read(ContextId::root(), addr5, 3.into()).unwrap(); // write 2 into address 5; clk = 4 let value2 = [Felt::new(2), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr5, 4.into(), value2); + mem.write(ContextId::root(), addr5, 4.into(), value2).unwrap(); // read a value from address 2; clk = 5 - mem.read(ContextId::root(), addr2, 5.into()); + mem.read(ContextId::root(), addr2, 5.into()).unwrap(); // write 7 into address 2; clk = 6 let value7 = [Felt::new(7), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr2, 6.into(), value7); + mem.write(ContextId::root(), addr2, 6.into(), value7).unwrap(); // read a value from address 5; clk = 7 - mem.read(ContextId::root(), addr5, 7.into()); + mem.read(ContextId::root(), addr5, 7.into()).unwrap(); // read a value from address 2; clk = 8 - mem.read(ContextId::root(), addr2, 8.into()); + mem.read(ContextId::root(), addr2, 8.into()).unwrap(); // read a value from address 5; clk = 9 - mem.read(ContextId::root(), addr5, 9.into()); + mem.read(ContextId::root(), addr5, 9.into()).unwrap(); // check generated trace and memory data provided to the ChipletsBus; rows should be sorted by // address and then clock cycle @@ -208,33 +208,33 @@ fn mem_multi_context() { // write a value into ctx = ContextId::root(), addr = 0; clk = 1 let value1 = [ONE, ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), 0, 1.into(), value1); + mem.write(ContextId::root(), 0, 1.into(), value1).unwrap(); assert_eq!(value1, mem.get_value(ContextId::root(), 0).unwrap()); assert_eq!(1, mem.size()); assert_eq!(1, mem.trace_len()); // write a value into ctx = 3, addr = 1; clk = 4 let value2 = [ZERO, ONE, ZERO, ZERO]; - mem.write(3.into(), 1, 4.into(), value2); + mem.write(3.into(), 1, 4.into(), value2).unwrap(); assert_eq!(value2, mem.get_value(3.into(), 1).unwrap()); assert_eq!(2, mem.size()); assert_eq!(2, mem.trace_len()); // read a value from ctx = 3, addr = 1; clk = 6 - let value = mem.read(3.into(), 1, 6.into()); + let value = mem.read(3.into(), 1, 6.into()).unwrap(); assert_eq!(value2, value); assert_eq!(2, mem.size()); assert_eq!(3, mem.trace_len()); // write a value into ctx = 3, addr = 0; clk = 7 let value3 = [ZERO, ZERO, ONE, ZERO]; - mem.write(3.into(), 0, 7.into(), value3); + mem.write(3.into(), 0, 7.into(), value3).unwrap(); assert_eq!(value3, mem.get_value(3.into(), 0).unwrap()); assert_eq!(3, mem.size()); assert_eq!(4, mem.trace_len()); // read a value from ctx = 0, addr = 0; clk = 9 - let value = mem.read(ContextId::root(), 0, 9.into()); + let value = mem.read(ContextId::root(), 0, 9.into()).unwrap(); assert_eq!(value1, value); assert_eq!(3, mem.size()); assert_eq!(5, mem.trace_len()); @@ -270,17 +270,17 @@ fn mem_get_state_at() { // Write 1 into (ctx = 0, addr = 5) at clk = 1. // This means that mem[5] = 1 at the beginning of clk = 2 let value1 = [ONE, ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), 5, 1.into(), value1); + mem.write(ContextId::root(), 5, 1.into(), value1).unwrap(); // Write 4 into (ctx = 0, addr = 2) at clk = 2. // This means that mem[2] = 4 at the beginning of clk = 3 let value4 = [Felt::new(4), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), 2, 2.into(), value4); + mem.write(ContextId::root(), 2, 2.into(), value4).unwrap(); // write 7 into (ctx = 3, addr = 3) at clk = 4 // This means that mem[3] = 7 at the beginning of clk = 4 let value7 = [Felt::new(7), ZERO, ZERO, ZERO]; - mem.write(3.into(), 3, 4.into(), value7); + mem.write(3.into(), 3, 4.into(), value7).unwrap(); // Check memory state at clk = 2 assert_eq!(mem.get_state_at(ContextId::root(), 2.into()), vec![(5, value1)]); diff --git a/processor/src/chiplets/mod.rs b/processor/src/chiplets/mod.rs index b4a4807247..a9fe8b7776 100644 --- a/processor/src/chiplets/mod.rs +++ b/processor/src/chiplets/mod.rs @@ -290,7 +290,7 @@ impl Chiplets { /// /// If the specified address hasn't been previously written to, four ZERO elements are /// returned. This effectively implies that memory is initialized to ZERO. - pub fn read_mem(&mut self, ctx: ContextId, addr: u32) -> Word { + pub fn read_mem(&mut self, ctx: ContextId, addr: u32) -> Result { // read the word from memory self.memory.read(ctx, addr, self.clk) } @@ -300,35 +300,54 @@ impl Chiplets { /// /// If either of the accessed addresses hasn't been previously written to, ZERO elements are /// returned. This effectively implies that memory is initialized to ZERO. - pub fn read_mem_double(&mut self, ctx: ContextId, addr: u32) -> [Word; 2] { + pub fn read_mem_double( + &mut self, + ctx: ContextId, + addr: u32, + ) -> Result<[Word; 2], ExecutionError> { // read two words from memory: from addr and from addr + 1 let addr2 = addr + 1; - [self.memory.read(ctx, addr, self.clk), self.memory.read(ctx, addr2, self.clk)] + Ok([self.memory.read(ctx, addr, self.clk)?, self.memory.read(ctx, addr2, self.clk)?]) } /// Writes the provided word at the specified context/address. - pub fn write_mem(&mut self, ctx: ContextId, addr: u32, word: Word) { - self.memory.write(ctx, addr, self.clk, word); + pub fn write_mem( + &mut self, + ctx: ContextId, + addr: u32, + word: Word, + ) -> Result<(), ExecutionError> { + self.memory.write(ctx, addr, self.clk, word) } /// Writes the provided element into the specified context/address leaving the remaining 3 /// elements of the word previously stored at that address unchanged. - pub fn write_mem_element(&mut self, ctx: ContextId, addr: u32, value: Felt) -> Word { + pub fn write_mem_element( + &mut self, + ctx: ContextId, + addr: u32, + value: Felt, + ) -> Result { let old_word = self.memory.get_old_value(ctx, addr); let new_word = [value, old_word[1], old_word[2], old_word[3]]; - self.memory.write(ctx, addr, self.clk, new_word); + self.memory.write(ctx, addr, self.clk, new_word)?; - old_word + Ok(old_word) } /// Writes the two provided words to two consecutive addresses in memory in the specified /// context, starting at the specified address. - pub fn write_mem_double(&mut self, ctx: ContextId, addr: u32, words: [Word; 2]) { + pub fn write_mem_double( + &mut self, + ctx: ContextId, + addr: u32, + words: [Word; 2], + ) -> Result<(), ExecutionError> { let addr2 = addr + 1; // write two words to memory at addr and addr + 1 - self.memory.write(ctx, addr, self.clk, words[0]); - self.memory.write(ctx, addr2, self.clk, words[1]); + self.memory.write(ctx, addr, self.clk, words[0])?; + self.memory.write(ctx, addr2, self.clk, words[1]) } /// Returns a word located at the specified context/address, or None if the address hasn't diff --git a/processor/src/errors.rs b/processor/src/errors.rs index 230a37a1c9..570d5fad54 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -16,6 +16,7 @@ use super::{ system::{FMP_MAX, FMP_MIN}, Digest, Felt, QuadFelt, Word, }; +use crate::ContextId; // EXECUTION ERROR // ================================================================================================ @@ -31,6 +32,11 @@ pub enum ExecutionError { decorator_id: DecoratorId, }, DivideByZero(RowIndex), + DuplicateMemoryAccess { + ctx: ContextId, + addr: u32, + clk: Felt, + }, DynamicNodeNotFound(Digest), EventError(String), Ext2InttError(Ext2InttError), @@ -110,6 +116,10 @@ impl Display for ExecutionError { write!(f, "Malformed MAST forest, decorator id {decorator_id} doesn't exist") }, DivideByZero(clk) => write!(f, "Division by zero at clock cycle {clk}"), + DuplicateMemoryAccess { ctx, addr, clk } => write!( + f, + "Memory address {addr} in context {ctx} accessed twice in clock cycle {clk}" + ), DynamicNodeNotFound(digest) => { let hex = to_hex(digest.as_bytes()); write!( diff --git a/processor/src/operations/comb_ops.rs b/processor/src/operations/comb_ops.rs index e0b68af198..f1ab1f8d46 100644 --- a/processor/src/operations/comb_ops.rs +++ b/processor/src/operations/comb_ops.rs @@ -64,10 +64,10 @@ where let [t7, t6, t5, t4, t3, t2, t1, t0] = self.get_trace_values(); // --- read the randomness from memory ---------------------------------------------------- - let alpha = self.get_randomness(); + let alpha = self.get_randomness()?; // --- read the OOD values from memory ---------------------------------------------------- - let [tz, tgz] = self.get_ood_values(); + let [tz, tgz] = self.get_ood_values()?; // --- read the accumulator values from stack --------------------------------------------- let [p, r] = self.read_accumulators(); @@ -125,22 +125,23 @@ where } /// Returns randomness. - fn get_randomness(&mut self) -> QuadFelt { + fn get_randomness(&mut self) -> Result { let ctx = self.system.ctx(); let addr = self.stack.get(14); - let word = self.chiplets.read_mem(ctx, addr.as_int() as u32); + let word = self.chiplets.read_mem(ctx, addr.as_int() as u32)?; let a0 = word[0]; let a1 = word[1]; - QuadFelt::new(a0, a1) + + Ok(QuadFelt::new(a0, a1)) } /// Returns the OOD values. - fn get_ood_values(&mut self) -> [QuadFelt; 2] { + fn get_ood_values(&mut self) -> Result<[QuadFelt; 2], ExecutionError> { let ctx = self.system.ctx(); let addr = self.stack.get(13); - let word = self.chiplets.read_mem(ctx, addr.as_int() as u32); + let word = self.chiplets.read_mem(ctx, addr.as_int() as u32)?; - [QuadFelt::new(word[0], word[1]), QuadFelt::new(word[2], word[3])] + Ok([QuadFelt::new(word[0], word[1]), QuadFelt::new(word[2], word[3])]) } /// Reads the accumulator values. @@ -202,18 +203,25 @@ mod tests { // --- setup memory ----------------------------------------------------------------------- let ctx = ContextId::root(); let tztgz = rand_array::(); - process.chiplets.write_mem( - ctx, - inputs[2].as_int().try_into().expect("Shouldn't fail by construction"), - tztgz, - ); + process + .chiplets + .write_mem( + ctx, + inputs[2].as_int().try_into().expect("Shouldn't fail by construction"), + tztgz, + ) + .unwrap(); let a = rand_array::(); - process.chiplets.write_mem( - ctx, - inputs[1].as_int().try_into().expect("Shouldn't fail by construction"), - a, - ); + process + .chiplets + .write_mem( + ctx, + inputs[1].as_int().try_into().expect("Shouldn't fail by construction"), + a, + ) + .unwrap(); + process.execute_op(Operation::Noop).unwrap(); // --- execute RCOMB1 operation ----------------------------------------------------------- process.execute_op(Operation::RCombBase).unwrap(); diff --git a/processor/src/operations/io_ops.rs b/processor/src/operations/io_ops.rs index d6765bb2ac..b3c9d863b9 100644 --- a/processor/src/operations/io_ops.rs +++ b/processor/src/operations/io_ops.rs @@ -90,7 +90,7 @@ where let addr = Self::get_valid_address(self.stack.get(12))?; // load two words from memory - let words = self.chiplets.read_mem_double(ctx, addr); + let words = self.chiplets.read_mem_double(ctx, addr)?; // replace the stack elements with the elements from memory (in stack order) for (i, &mem_value) in words.iter().flat_map(|word| word.iter()).rev().enumerate() { @@ -129,7 +129,7 @@ where let word = [self.stack.get(4), self.stack.get(3), self.stack.get(2), self.stack.get(1)]; // write the word to memory and get the previous word - self.chiplets.write_mem(ctx, addr, word); + self.chiplets.write_mem(ctx, addr, word)?; // reverse the order of the memory word & update the stack state for (i, &value) in word.iter().rev().enumerate() { @@ -160,7 +160,7 @@ where let value = self.stack.get(1); // write the value to the memory and get the previous word - let mut old_word = self.chiplets.write_mem_element(ctx, addr, value); + let mut old_word = self.chiplets.write_mem_element(ctx, addr, value)?; // put the retrieved word into stack order old_word.reverse(); @@ -192,7 +192,7 @@ where let words = self.host.borrow_mut().pop_adv_stack_dword(self)?; // write the words memory - self.chiplets.write_mem_double(ctx, addr, words); + self.chiplets.write_mem_double(ctx, addr, words)?; // replace the elements on the stack with the word elements (in stack order) for (i, &adv_value) in words.iter().flat_map(|word| word.iter()).rev().enumerate() { @@ -252,7 +252,7 @@ where pub(crate) fn read_mem_word(&mut self, addr: Felt) -> Result { let ctx = self.system.ctx(); let mem_addr = Self::get_valid_address(addr)?; - let word_at_addr = self.chiplets.read_mem(ctx, mem_addr); + let word_at_addr = self.chiplets.read_mem(ctx, mem_addr)?; Ok(word_at_addr) } From c68612c7d71e3b05c78347455ecedcada59ad541 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Wed, 6 Nov 2024 10:30:45 -0500 Subject: [PATCH 03/39] refactor: Take `host` out `Process` --- CHANGELOG.md | 3 + miden/benches/program_execution.rs | 2 +- miden/src/cli/debug/executor.rs | 2 +- miden/src/cli/prove.rs | 9 +- miden/src/cli/run.rs | 4 +- miden/src/examples/mod.rs | 8 +- miden/src/repl/mod.rs | 2 +- miden/src/tools/mod.rs | 8 +- processor/src/chiplets/tests.rs | 6 +- processor/src/debug.rs | 9 +- processor/src/decoder/mod.rs | 94 +++++++----- processor/src/decoder/tests.rs | 20 ++- processor/src/lib.rs | 193 ++++++++++++------------- processor/src/operations/comb_ops.rs | 14 +- processor/src/operations/crypto_ops.rs | 54 ++++--- processor/src/operations/ext2_ops.rs | 14 +- processor/src/operations/field_ops.rs | 101 +++++++------ processor/src/operations/fri_ops.rs | 13 +- processor/src/operations/io_ops.rs | 149 ++++++++++--------- processor/src/operations/mod.rs | 61 ++++---- processor/src/operations/stack_ops.rs | 110 +++++++------- processor/src/operations/sys_ops.rs | 93 ++++++------ processor/src/operations/u32_ops.rs | 53 ++++--- processor/src/system/tests.rs | 7 +- processor/src/trace/mod.rs | 26 +--- processor/src/trace/tests/mod.rs | 14 +- prover/src/lib.rs | 9 +- stdlib/tests/collections/mmr.rs | 3 +- stdlib/tests/crypto/falcon.rs | 7 +- stdlib/tests/crypto/stark/mod.rs | 4 +- stdlib/tests/mem/mod.rs | 10 +- test-utils/src/lib.rs | 22 +-- 32 files changed, 576 insertions(+), 548 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1509977ba8..d351dc1e54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +#### Changes +- [BREAKING] `Process` no longer takes ownership of the `Host` (#1563) + ## 0.11.0 (2024-11-04) #### Enhancements diff --git a/miden/benches/program_execution.rs b/miden/benches/program_execution.rs index a8abd7050d..6e3c20d4a4 100644 --- a/miden/benches/program_execution.rs +++ b/miden/benches/program_execution.rs @@ -24,7 +24,7 @@ fn program_execution(c: &mut Criterion) { assembler.add_library(&stdlib).expect("failed to load stdlib"); let program = assembler.assemble_program(source).expect("Failed to compile test source."); bench.iter(|| { - execute(&program, StackInputs::default(), host.clone(), ExecutionOptions::default()) + execute(&program, StackInputs::default(), &mut host, ExecutionOptions::default()) }); }); diff --git a/miden/src/cli/debug/executor.rs b/miden/src/cli/debug/executor.rs index 1b53b2afcd..d2a145fe03 100644 --- a/miden/src/cli/debug/executor.rs +++ b/miden/src/cli/debug/executor.rs @@ -29,7 +29,7 @@ impl DebugExecutor { source_manager: Arc, ) -> Result { let mut vm_state_iter = - processor::execute_iter(&program, stack_inputs, DefaultHost::new(advice_provider)); + processor::execute_iter(&program, stack_inputs, &mut DefaultHost::new(advice_provider)); let vm_state = vm_state_iter .next() .ok_or( diff --git a/miden/src/cli/prove.rs b/miden/src/cli/prove.rs index 377214bb25..92e41356c7 100644 --- a/miden/src/cli/prove.rs +++ b/miden/src/cli/prove.rs @@ -96,15 +96,16 @@ impl ProveCmd { // fetch the stack and program inputs from the arguments let stack_inputs = input_data.parse_stack_inputs().map_err(Report::msg)?; - let host = DefaultHost::new(input_data.parse_advice_provider().map_err(Report::msg)?); + let mut host = DefaultHost::new(input_data.parse_advice_provider().map_err(Report::msg)?); let proving_options = self.get_proof_options().map_err(|err| Report::msg(format!("{err}")))?; // execute program and generate proof - let (stack_outputs, proof) = prover::prove(&program, stack_inputs, host, proving_options) - .into_diagnostic() - .wrap_err("Failed to prove program")?; + let (stack_outputs, proof) = + prover::prove(&program, stack_inputs, &mut host, proving_options) + .into_diagnostic() + .wrap_err("Failed to prove program")?; println!( "Program with hash {} proved in {} ms", diff --git a/miden/src/cli/run.rs b/miden/src/cli/run.rs index afb6c34a6b..d726494d38 100644 --- a/miden/src/cli/run.rs +++ b/miden/src/cli/run.rs @@ -126,12 +126,12 @@ fn run_program(params: &RunCmd) -> Result<(ExecutionTrace, [u8; 32]), Report> { // fetch the stack and program inputs from the arguments let stack_inputs = input_data.parse_stack_inputs().map_err(Report::msg)?; - let host = DefaultHost::new(input_data.parse_advice_provider().map_err(Report::msg)?); + let mut host = DefaultHost::new(input_data.parse_advice_provider().map_err(Report::msg)?); let program_hash: [u8; 32] = program.hash().into(); // execute program and generate outputs - let trace = processor::execute(&program, stack_inputs, host, execution_options) + let trace = processor::execute(&program, stack_inputs, &mut host, execution_options) .into_diagnostic() .wrap_err("Failed to generate execution trace")?; diff --git a/miden/src/examples/mod.rs b/miden/src/examples/mod.rs index c377c8ec7d..ffd37b7a9d 100644 --- a/miden/src/examples/mod.rs +++ b/miden/src/examples/mod.rs @@ -116,7 +116,7 @@ impl ExampleOptions { let Example { program, stack_inputs, - host, + mut host, num_outputs, expected_result, .. @@ -126,7 +126,7 @@ impl ExampleOptions { // execute the program and generate the proof of execution let now = Instant::now(); let (stack_outputs, proof) = - miden_vm::prove(&program, stack_inputs.clone(), host, proof_options).unwrap(); + miden_vm::prove(&program, stack_inputs.clone(), &mut host, proof_options).unwrap(); println!("--------------------------------"); println!( @@ -173,13 +173,13 @@ where let Example { program, stack_inputs, - host, + mut host, num_outputs, expected_result, } = example; let (mut outputs, proof) = - miden_vm::prove(&program, stack_inputs.clone(), host, options).unwrap(); + miden_vm::prove(&program, stack_inputs.clone(), &mut host, options).unwrap(); assert_eq!( expected_result, diff --git a/miden/src/repl/mod.rs b/miden/src/repl/mod.rs index 692e29df52..198a13e358 100644 --- a/miden/src/repl/mod.rs +++ b/miden/src/repl/mod.rs @@ -321,7 +321,7 @@ fn execute( host.load_mast_forest(library.mast_forest().clone()); } - let state_iter = processor::execute_iter(&program, stack_inputs, host); + let state_iter = processor::execute_iter(&program, stack_inputs, &mut host); let (system, _, stack, chiplets, err) = state_iter.into_parts(); if let Some(err) = err { return Err(format!("{err}")); diff --git a/miden/src/tools/mod.rs b/miden/src/tools/mod.rs index 39d9d9ea54..7d932dbf5f 100644 --- a/miden/src/tools/mod.rs +++ b/miden/src/tools/mod.rs @@ -209,7 +209,11 @@ impl fmt::Display for ExecutionDetails { } /// Returns program analysis of a given program. -fn analyze(program: &str, stack_inputs: StackInputs, host: H) -> Result +fn analyze( + program: &str, + stack_inputs: StackInputs, + mut host: H, +) -> Result where H: Host, { @@ -220,7 +224,7 @@ where .assemble_program(program)?; let mut execution_details = ExecutionDetails::default(); - let vm_state_iterator = processor::execute_iter(&program, stack_inputs, host); + let vm_state_iterator = processor::execute_iter(&program, stack_inputs, &mut host); execution_details.set_trace_len_summary(vm_state_iterator.trace_len_summary()); for state in vm_state_iterator { diff --git a/processor/src/chiplets/tests.rs b/processor/src/chiplets/tests.rs index a0eab01620..89253f6916 100644 --- a/processor/src/chiplets/tests.rs +++ b/processor/src/chiplets/tests.rs @@ -113,8 +113,8 @@ fn build_trace( kernel: Kernel, ) -> (ChipletsTrace, usize) { let stack_inputs = StackInputs::try_from_ints(stack_inputs.iter().copied()).unwrap(); - let host = DefaultHost::default(); - let mut process = Process::new(kernel, stack_inputs, host, ExecutionOptions::default()); + let mut host = DefaultHost::default(); + let mut process = Process::new(kernel, stack_inputs, ExecutionOptions::default()); let program = { let mut mast_forest = MastForest::new(); @@ -123,7 +123,7 @@ fn build_trace( Program::new(mast_forest.into(), basic_block_id) }; - process.execute(&program).unwrap(); + process.execute(&program, &mut host).unwrap(); let (trace, ..) = ExecutionTrace::test_finalize_trace(process); let trace_len = trace.num_rows() - ExecutionTrace::NUM_RAND_ROWS; diff --git a/processor/src/debug.rs b/processor/src/debug.rs index c30d53efda..ab908b5609 100644 --- a/processor/src/debug.rs +++ b/processor/src/debug.rs @@ -9,7 +9,7 @@ use vm_core::{AssemblyOp, Operation, StackOutputs, Word}; use crate::{ range::RangeChecker, system::ContextId, Chiplets, ChipletsLengths, Decoder, ExecutionError, - Felt, Host, Process, Stack, System, TraceLenSummary, + Felt, Process, Stack, System, TraceLenSummary, }; /// VmState holds a current process state information at a specific clock cycle. @@ -63,11 +63,8 @@ pub struct VmStateIterator { } impl VmStateIterator { - pub fn new(process: Process, result: Result) -> Self - where - H: Host, - { - let (system, decoder, stack, mut range, chiplets, _) = process.into_parts(); + pub fn new(process: Process, result: Result) -> Self { + let (system, decoder, stack, mut range, chiplets) = process.into_parts(); let trace_len_summary = Self::build_trace_len_summary(&system, &mut range, &chiplets); Self { diff --git a/processor/src/decoder/mod.rs b/processor/src/decoder/mod.rs index a6248d0efb..2b7ee125ad 100644 --- a/processor/src/decoder/mod.rs +++ b/processor/src/decoder/mod.rs @@ -19,9 +19,9 @@ use vm_core::{ }; use super::{ - ExecutionError, Felt, Host, OpBatch, Operation, Process, Word, EMPTY_WORD, MIN_TRACE_LEN, ONE, - ZERO, + ExecutionError, Felt, OpBatch, Operation, Process, Word, EMPTY_WORD, MIN_TRACE_LEN, ONE, ZERO, }; +use crate::Host; mod trace; use trace::DecoderTrace; @@ -47,18 +47,16 @@ const HASH_CYCLE_LEN: Felt = Felt::new(miden_air::trace::chiplets::hasher::HASH_ // DECODER PROCESS EXTENSION // ================================================================================================ -impl Process -where - H: Host, -{ +impl Process { // JOIN NODE // -------------------------------------------------------------------------------------------- /// Starts decoding of a JOIN node. - pub(super) fn start_join_node( + pub(super) fn start_join_node( &mut self, node: &JoinNode, program: &MastForest, + host: &mut H, ) -> Result<(), ExecutionError> { // use the hasher to compute the hash of the JOIN block; the row address returned by the // hasher is used as the ID of the block; the result of the hash is expected to be in @@ -84,16 +82,20 @@ where // start decoding the JOIN block; this appends a row with JOIN operation to the decoder // trace. when JOIN operation is executed, the rest of the VM state does not change self.decoder.start_join(child1_hash, child2_hash, addr); - self.execute_op(Operation::Noop) + self.execute_op(Operation::Noop, host) } /// Ends decoding of a JOIN node. - pub(super) fn end_join_node(&mut self, node: &JoinNode) -> Result<(), ExecutionError> { + pub(super) fn end_join_node( + &mut self, + node: &JoinNode, + host: &mut H, + ) -> Result<(), ExecutionError> { // this appends a row with END operation to the decoder trace. when END operation is // executed the rest of the VM state does not change self.decoder.end_control_block(node.digest().into()); - self.execute_op(Operation::Noop) + self.execute_op(Operation::Noop, host) } // SPLIT NODE @@ -101,10 +103,11 @@ where /// Starts decoding a SPLIT node. This also pops the value from the top of the stack and /// returns it. - pub(super) fn start_split_node( + pub(super) fn start_split_node( &mut self, node: &SplitNode, program: &MastForest, + host: &mut H, ) -> Result { let condition = self.stack.peek(); @@ -131,17 +134,21 @@ where // start decoding the SPLIT block. this appends a row with SPLIT operation to the decoder // trace. we also pop the value off the top of the stack and return it. self.decoder.start_split(child1_hash, child2_hash, addr); - self.execute_op(Operation::Drop)?; + self.execute_op(Operation::Drop, host)?; Ok(condition) } /// Ends decoding of a SPLIT node. - pub(super) fn end_split_node(&mut self, block: &SplitNode) -> Result<(), ExecutionError> { + pub(super) fn end_split_node( + &mut self, + block: &SplitNode, + host: &mut H, + ) -> Result<(), ExecutionError> { // this appends a row with END operation to the decoder trace. when END operation is // executed the rest of the VM state does not change self.decoder.end_control_block(block.digest().into()); - self.execute_op(Operation::Noop) + self.execute_op(Operation::Noop, host) } // LOOP NODE @@ -149,10 +156,11 @@ where /// Starts decoding a LOOP node. This also pops the value from the top of the stack and /// returns it. - pub(super) fn start_loop_node( + pub(super) fn start_loop_node( &mut self, node: &LoopNode, program: &MastForest, + host: &mut H, ) -> Result { let condition = self.stack.peek(); @@ -178,16 +186,17 @@ where // basically, if the top of the stack is ZERO, a LOOP operation should be immediately // followed by an END operation. self.decoder.start_loop(body_hash, addr, condition); - self.execute_op(Operation::Drop)?; + self.execute_op(Operation::Drop, host)?; Ok(condition) } /// Ends decoding of a LOOP block. If pop_stack is set to true, this also removes the /// value at the top of the stack. - pub(super) fn end_loop_node( + pub(super) fn end_loop_node( &mut self, node: &LoopNode, pop_stack: bool, + host: &mut H, ) -> Result<(), ExecutionError> { // this appends a row with END operation to the decoder trace. self.decoder.end_control_block(node.digest().into()); @@ -201,9 +210,9 @@ where #[cfg(debug_assertions)] debug_assert_eq!(ZERO, self.stack.peek()); - self.execute_op(Operation::Drop) + self.execute_op(Operation::Drop, host) } else { - self.execute_op(Operation::Noop) + self.execute_op(Operation::Noop, host) } } @@ -211,10 +220,11 @@ where // -------------------------------------------------------------------------------------------- /// Starts decoding of a CALL or a SYSCALL node. - pub(super) fn start_call_node( + pub(super) fn start_call_node( &mut self, node: &CallNode, program: &MastForest, + host: &mut H, ) -> Result<(), ExecutionError> { // use the hasher to compute the hash of the CALL or SYSCALL block; the row address // returned by the hasher is used as the ID of the block; the result of the hash is @@ -254,11 +264,15 @@ where } // the rest of the VM state does not change - self.execute_op(Operation::Noop) + self.execute_op(Operation::Noop, host) } /// Ends decoding of a CALL or a SYSCALL block. - pub(super) fn end_call_node(&mut self, node: &CallNode) -> Result<(), ExecutionError> { + pub(super) fn end_call_node( + &mut self, + node: &CallNode, + host: &mut H, + ) -> Result<(), ExecutionError> { // when a CALL block ends, stack depth must be exactly 16 let stack_depth = self.stack.depth(); if stack_depth > MIN_STACK_DEPTH { @@ -285,7 +299,7 @@ where ); // the rest of the VM state does not change - self.execute_op(Operation::Noop) + self.execute_op(Operation::Noop, host) } // DYN NODE @@ -295,7 +309,11 @@ where /// /// Note: even though we will write the callee hash to h[0..4] for the chiplets bus and block /// hash table, the issued hash request is still hash([ZERO; 8]). - pub(super) fn start_dyn_node(&mut self, dyn_node: &DynNode) -> Result { + pub(super) fn start_dyn_node( + &mut self, + dyn_node: &DynNode, + host: &mut H, + ) -> Result { debug_assert!(!dyn_node.is_dyncall()); let mem_addr = self.stack.get(0); @@ -313,7 +331,7 @@ where self.decoder.start_dyn(addr, callee_hash); // Pop the memory address off the stack. - self.execute_op(Operation::Drop)?; + self.execute_op(Operation::Drop, host)?; Ok(callee_hash) } @@ -371,16 +389,24 @@ where } /// Ends decoding of a DYN node. - pub(super) fn end_dyn_node(&mut self, dyn_node: &DynNode) -> Result<(), ExecutionError> { + pub(super) fn end_dyn_node( + &mut self, + dyn_node: &DynNode, + host: &mut H, + ) -> Result<(), ExecutionError> { // this appends a row with END operation to the decoder trace. when the END operation is // executed the rest of the VM state does not change self.decoder.end_control_block(dyn_node.digest().into()); - self.execute_op(Operation::Noop) + self.execute_op(Operation::Noop, host) } /// Ends decoding of a DYNCALL node. - pub(super) fn end_dyncall_node(&mut self, dyn_node: &DynNode) -> Result<(), ExecutionError> { + pub(super) fn end_dyncall_node( + &mut self, + dyn_node: &DynNode, + host: &mut H, + ) -> Result<(), ExecutionError> { // when a DYNCALL block ends, stack depth must be exactly 16 let stack_depth = self.stack.depth(); if stack_depth > MIN_STACK_DEPTH { @@ -406,16 +432,17 @@ where ctx_info.parent_next_overflow_addr, ); - self.execute_op(Operation::Noop) + self.execute_op(Operation::Noop, host) } // BASIC BLOCK NODE // -------------------------------------------------------------------------------------------- /// Starts decoding a BASIC BLOCK node. - pub(super) fn start_basic_block_node( + pub(super) fn start_basic_block_node( &mut self, basic_block: &BasicBlockNode, + host: &mut H, ) -> Result<(), ExecutionError> { // use the hasher to compute the hash of the SPAN block; the row address returned by the // hasher is used as the ID of the block; hash of a SPAN block is computed by sequentially @@ -430,19 +457,20 @@ where let num_op_groups = basic_block.num_op_groups(); self.decoder .start_basic_block(&op_batches[0], Felt::new(num_op_groups as u64), addr); - self.execute_op(Operation::Noop) + self.execute_op(Operation::Noop, host) } /// Ends decoding a BASIC BLOCK node. - pub(super) fn end_basic_block_node( + pub(super) fn end_basic_block_node( &mut self, block: &BasicBlockNode, + host: &mut H, ) -> Result<(), ExecutionError> { // this appends a row with END operation to the decoder trace. when END operation is // executed the rest of the VM state does not change self.decoder.end_basic_block(block.digest().into()); - self.execute_op(Operation::Noop) + self.execute_op(Operation::Noop, host) } /// Continues decoding a SPAN block by absorbing the next batch of operations. diff --git a/processor/src/decoder/tests.rs b/processor/src/decoder/tests.rs index c0a90bdec1..664517b7a2 100644 --- a/processor/src/decoder/tests.rs +++ b/processor/src/decoder/tests.rs @@ -1460,10 +1460,9 @@ fn set_user_op_helpers_many() { fn build_trace(stack_inputs: &[u64], program: &Program) -> (DecoderTrace, usize) { let stack_inputs = StackInputs::try_from_ints(stack_inputs.iter().copied()).unwrap(); - let host = DefaultHost::default(); - let mut process = - Process::new(Kernel::default(), stack_inputs, host, ExecutionOptions::default()); - process.execute(program).unwrap(); + let mut host = DefaultHost::default(); + let mut process = Process::new(Kernel::default(), stack_inputs, ExecutionOptions::default()); + process.execute(program, &mut host).unwrap(); let (trace, ..) = ExecutionTrace::test_finalize_trace(process); let trace_len = trace.num_rows() - ExecutionTrace::NUM_RAND_ROWS; @@ -1479,11 +1478,10 @@ fn build_trace(stack_inputs: &[u64], program: &Program) -> (DecoderTrace, usize) fn build_dyn_trace(stack_inputs: &[u64], program: &Program) -> (DecoderTrace, usize) { let stack_inputs = StackInputs::try_from_ints(stack_inputs.iter().copied()).unwrap(); - let host = DefaultHost::default(); - let mut process = - Process::new(Kernel::default(), stack_inputs, host, ExecutionOptions::default()); + let mut host = DefaultHost::default(); + let mut process = Process::new(Kernel::default(), stack_inputs, ExecutionOptions::default()); - process.execute(program).unwrap(); + process.execute(program, &mut host).unwrap(); let (trace, ..) = ExecutionTrace::test_finalize_trace(process); let trace_len = trace.num_rows() - ExecutionTrace::NUM_RAND_ROWS; @@ -1498,11 +1496,11 @@ fn build_dyn_trace(stack_inputs: &[u64], program: &Program) -> (DecoderTrace, us } fn build_call_trace(program: &Program, kernel: Kernel) -> (SystemTrace, DecoderTrace, usize) { - let host = DefaultHost::default(); + let mut host = DefaultHost::default(); let stack_inputs = crate::StackInputs::default(); - let mut process = Process::new(kernel, stack_inputs, host, ExecutionOptions::default()); + let mut process = Process::new(kernel, stack_inputs, ExecutionOptions::default()); - process.execute(program).unwrap(); + process.execute(program, &mut host).unwrap(); let (trace, ..) = ExecutionTrace::test_finalize_trace(process); let trace_len = trace.num_rows() - ExecutionTrace::NUM_RAND_ROWS; diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 037f9269b9..e407d3b11a 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -7,7 +7,6 @@ extern crate alloc; extern crate std; use alloc::vec::Vec; -use core::cell::RefCell; use miden_air::trace::{ CHIPLETS_WIDTH, DECODER_TRACE_WIDTH, MIN_TRACE_LEN, RANGE_CHECK_TRACE_WIDTH, STACK_TRACE_WIDTH, @@ -122,18 +121,18 @@ pub struct ChipletsTrace { /// Returns an execution trace resulting from executing the provided program against the provided /// inputs. +/// +/// The `host` parameter is used to provide the external environment to the program being executed, +/// such as access to the advice provider and libraries that the program depends on. #[tracing::instrument("execute_program", skip_all)] -pub fn execute( +pub fn execute( program: &Program, stack_inputs: StackInputs, - host: H, + host: &mut impl Host, options: ExecutionOptions, -) -> Result -where - H: Host, -{ - let mut process = Process::new(program.kernel().clone(), stack_inputs, host, options); - let stack_outputs = process.execute(program)?; +) -> Result { + let mut process = Process::new(program.kernel().clone(), stack_inputs, options); + let stack_outputs = process.execute(program, host)?; let trace = ExecutionTrace::new(process, stack_outputs); assert_eq!(&program.hash(), trace.program_hash(), "inconsistent program hash"); Ok(trace) @@ -141,12 +140,13 @@ where /// Returns an iterator which allows callers to step through the execution and inspect VM state at /// each execution step. -pub fn execute_iter(program: &Program, stack_inputs: StackInputs, host: H) -> VmStateIterator -where - H: Host, -{ - let mut process = Process::new_debug(program.kernel().clone(), stack_inputs, host); - let result = process.execute(program); +pub fn execute_iter( + program: &Program, + stack_inputs: StackInputs, + host: &mut impl Host, +) -> VmStateIterator { + let mut process = Process::new_debug(program.kernel().clone(), stack_inputs); + let result = process.execute(program, host); if result.is_ok() { assert_eq!( program.hash(), @@ -170,67 +170,49 @@ where /// to construct an instance of [Process] using [Process::new], invoke [Process::execute], and then /// get the execution trace using [ExecutionTrace::new] using the outputs produced by execution. #[cfg(not(any(test, feature = "testing")))] -pub struct Process -where - H: Host, -{ +pub struct Process { system: System, decoder: Decoder, stack: Stack, range: RangeChecker, chiplets: Chiplets, - host: RefCell, max_cycles: u32, enable_tracing: bool, } #[cfg(any(test, feature = "testing"))] -pub struct Process -where - H: Host, -{ +pub struct Process { pub system: System, pub decoder: Decoder, pub stack: Stack, pub range: RangeChecker, pub chiplets: Chiplets, - pub host: RefCell, pub max_cycles: u32, pub enable_tracing: bool, } -impl Process -where - H: Host, -{ +impl Process { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- /// Creates a new process with the provided inputs. pub fn new( kernel: Kernel, stack_inputs: StackInputs, - host: H, execution_options: ExecutionOptions, ) -> Self { - Self::initialize(kernel, stack_inputs, host, execution_options) + Self::initialize(kernel, stack_inputs, execution_options) } /// Creates a new process with provided inputs and debug options enabled. - pub fn new_debug(kernel: Kernel, stack_inputs: StackInputs, host: H) -> Self { + pub fn new_debug(kernel: Kernel, stack_inputs: StackInputs) -> Self { Self::initialize( kernel, stack_inputs, - host, ExecutionOptions::default().with_tracing().with_debugging(), ) } - fn initialize( - kernel: Kernel, - stack: StackInputs, - host: H, - execution_options: ExecutionOptions, - ) -> Self { + fn initialize(kernel: Kernel, stack: StackInputs, execution_options: ExecutionOptions) -> Self { let in_debug_mode = execution_options.enable_debugging(); Self { system: System::new(execution_options.expected_cycles() as usize), @@ -238,7 +220,6 @@ where stack: Stack::new(&stack, execution_options.expected_cycles() as usize, in_debug_mode), range: RangeChecker::new(), chiplets: Chiplets::new(kernel), - host: RefCell::new(host), max_cycles: execution_options.max_cycles(), enable_tracing: execution_options.enable_tracing(), } @@ -248,12 +229,16 @@ where // -------------------------------------------------------------------------------------------- /// Executes the provided [`Program`] in this process. - pub fn execute(&mut self, program: &Program) -> Result { + pub fn execute( + &mut self, + program: &Program, + host: &mut impl Host, + ) -> Result { if self.system.clk() != 0 { return Err(ExecutionError::ProgramAlreadyExecuted); } - self.execute_mast_node(program.entrypoint(), &program.mast_forest().clone())?; + self.execute_mast_node(program.entrypoint(), &program.mast_forest().clone(), host)?; self.stack.build_stack_outputs() } @@ -265,27 +250,26 @@ where &mut self, node_id: MastNodeId, program: &MastForest, + host: &mut impl Host, ) -> Result<(), ExecutionError> { let node = program .get_node_by_id(node_id) .ok_or(ExecutionError::MastNodeNotFoundInForest { node_id })?; for &decorator_id in node.before_enter() { - self.execute_decorator(&program[decorator_id])?; + self.execute_decorator(&program[decorator_id], host)?; } match node { - MastNode::Block(node) => self.execute_basic_block_node(node, program)?, - MastNode::Join(node) => self.execute_join_node(node, program)?, - MastNode::Split(node) => self.execute_split_node(node, program)?, - MastNode::Loop(node) => self.execute_loop_node(node, program)?, - MastNode::Call(node) => self.execute_call_node(node, program)?, - MastNode::Dyn(node) => self.execute_dyn_node(node, program)?, + MastNode::Block(node) => self.execute_basic_block_node(node, program, host)?, + MastNode::Join(node) => self.execute_join_node(node, program, host)?, + MastNode::Split(node) => self.execute_split_node(node, program, host)?, + MastNode::Loop(node) => self.execute_loop_node(node, program, host)?, + MastNode::Call(node) => self.execute_call_node(node, program, host)?, + MastNode::Dyn(node) => self.execute_dyn_node(node, program, host)?, MastNode::External(external_node) => { let node_digest = external_node.digest(); - let mast_forest = self - .host - .borrow() + let mast_forest = host .get_mast_forest(&node_digest) .ok_or(ExecutionError::MastForestNotFound { root_digest: node_digest })?; @@ -301,12 +285,12 @@ where return Err(ExecutionError::CircularExternalNode(node_digest)); } - self.execute_mast_node(root_id, &mast_forest)?; + self.execute_mast_node(root_id, &mast_forest, host)?; }, } for &decorator_id in node.after_exit() { - self.execute_decorator(&program[decorator_id])?; + self.execute_decorator(&program[decorator_id], host)?; } Ok(()) @@ -318,14 +302,15 @@ where &mut self, node: &JoinNode, program: &MastForest, + host: &mut impl Host, ) -> Result<(), ExecutionError> { - self.start_join_node(node, program)?; + self.start_join_node(node, program, host)?; // execute first and then second child of the join block - self.execute_mast_node(node.first(), program)?; - self.execute_mast_node(node.second(), program)?; + self.execute_mast_node(node.first(), program, host)?; + self.execute_mast_node(node.second(), program, host)?; - self.end_join_node(node) + self.end_join_node(node, host) } /// Executes the specified [SplitNode]. @@ -334,20 +319,21 @@ where &mut self, node: &SplitNode, program: &MastForest, + host: &mut impl Host, ) -> Result<(), ExecutionError> { // start the SPLIT block; this also pops the stack and returns the popped element - let condition = self.start_split_node(node, program)?; + let condition = self.start_split_node(node, program, host)?; // execute either the true or the false branch of the split block based on the condition if condition == ONE { - self.execute_mast_node(node.on_true(), program)?; + self.execute_mast_node(node.on_true(), program, host)?; } else if condition == ZERO { - self.execute_mast_node(node.on_false(), program)?; + self.execute_mast_node(node.on_false(), program, host)?; } else { return Err(ExecutionError::NotBinaryValue(condition)); } - self.end_split_node(node) + self.end_split_node(node, host) } /// Executes the specified [LoopNode]. @@ -356,30 +342,31 @@ where &mut self, node: &LoopNode, program: &MastForest, + host: &mut impl Host, ) -> Result<(), ExecutionError> { // start the LOOP block; this also pops the stack and returns the popped element - let condition = self.start_loop_node(node, program)?; + let condition = self.start_loop_node(node, program, host)?; // if the top of the stack is ONE, execute the loop body; otherwise skip the loop body if condition == ONE { // execute the loop body at least once - self.execute_mast_node(node.body(), program)?; + self.execute_mast_node(node.body(), program, host)?; // keep executing the loop body until the condition on the top of the stack is no // longer ONE; each iteration of the loop is preceded by executing REPEAT operation // which drops the condition from the stack while self.stack.peek() == ONE { self.decoder.repeat(); - self.execute_op(Operation::Drop)?; - self.execute_mast_node(node.body(), program)?; + self.execute_op(Operation::Drop, host)?; + self.execute_mast_node(node.body(), program, host)?; } // end the LOOP block and drop the condition from the stack - self.end_loop_node(node, true) + self.end_loop_node(node, true, host) } else if condition == ZERO { // end the LOOP block, but don't drop the condition from the stack because it was // already dropped when we started the LOOP block - self.end_loop_node(node, false) + self.end_loop_node(node, false, host) } else { Err(ExecutionError::NotBinaryValue(condition)) } @@ -391,6 +378,7 @@ where &mut self, call_node: &CallNode, program: &MastForest, + host: &mut impl Host, ) -> Result<(), ExecutionError> { // if this is a syscall, make sure the call target exists in the kernel if call_node.is_syscall() { @@ -400,9 +388,9 @@ where self.chiplets.access_kernel_proc(callee.digest())?; } - self.start_call_node(call_node, program)?; - self.execute_mast_node(call_node.callee(), program)?; - self.end_call_node(call_node) + self.start_call_node(call_node, program, host)?; + self.execute_mast_node(call_node.callee(), program, host)?; + self.end_call_node(call_node, host) } /// Executes the specified [vm_core::mast::DynNode]. @@ -414,22 +402,21 @@ where &mut self, node: &DynNode, program: &MastForest, + host: &mut impl Host, ) -> Result<(), ExecutionError> { let callee_hash = if node.is_dyncall() { self.start_dyncall_node(node)? } else { - self.start_dyn_node(node)? + self.start_dyn_node(node, host)? }; // if the callee is not in the program's MAST forest, try to find a MAST forest for it in // the host (corresponding to an external library loaded in the host); if none are // found, return an error. match program.find_procedure_root(callee_hash.into()) { - Some(callee_id) => self.execute_mast_node(callee_id, program)?, + Some(callee_id) => self.execute_mast_node(callee_id, program, host)?, None => { - let mast_forest = self - .host - .borrow() + let mast_forest = host .get_mast_forest(&callee_hash.into()) .ok_or_else(|| ExecutionError::DynamicNodeNotFound(callee_hash.into()))?; @@ -439,14 +426,14 @@ where ExecutionError::MalformedMastForestInHost { root_digest: callee_hash.into() }, )?; - self.execute_mast_node(root_id, &mast_forest)? + self.execute_mast_node(root_id, &mast_forest, host)? }, } if node.is_dyncall() { - self.end_dyncall_node(node) + self.end_dyncall_node(node, host) } else { - self.end_dyn_node(node) + self.end_dyn_node(node, host) } } @@ -456,8 +443,9 @@ where &mut self, basic_block: &BasicBlockNode, program: &MastForest, + host: &mut impl Host, ) -> Result<(), ExecutionError> { - self.start_basic_block_node(basic_block)?; + self.start_basic_block_node(basic_block, host)?; let mut op_offset = 0; let mut decorator_ids = basic_block.decorator_iter(); @@ -468,6 +456,7 @@ where &mut decorator_ids, op_offset, program, + host, )?; op_offset += basic_block.op_batches()[0].ops().len(); @@ -476,12 +465,12 @@ where // of the stack for op_batch in basic_block.op_batches().iter().skip(1) { self.respan(op_batch); - self.execute_op(Operation::Noop)?; - self.execute_op_batch(op_batch, &mut decorator_ids, op_offset, program)?; + self.execute_op(Operation::Noop, host)?; + self.execute_op_batch(op_batch, &mut decorator_ids, op_offset, program, host)?; op_offset += op_batch.ops().len(); } - self.end_basic_block_node(basic_block)?; + self.end_basic_block_node(basic_block, host)?; // execute any decorators which have not been executed during span ops execution; this // can happen for decorators appearing after all operations in a block. these decorators @@ -491,7 +480,7 @@ where let decorator = program .get_decorator_by_id(decorator_id) .ok_or(ExecutionError::DecoratorNotFoundInForest { decorator_id })?; - self.execute_decorator(decorator)?; + self.execute_decorator(decorator, host)?; } Ok(()) @@ -510,6 +499,7 @@ where decorators: &mut DecoratorIterator, op_offset: usize, program: &MastForest, + host: &mut impl Host, ) -> Result<(), ExecutionError> { let op_counts = batch.op_counts(); let mut op_idx = 0; @@ -527,12 +517,12 @@ where let decorator = program .get_decorator_by_id(decorator_id) .ok_or(ExecutionError::DecoratorNotFoundInForest { decorator_id })?; - self.execute_decorator(decorator)?; + self.execute_decorator(decorator, host)?; } // decode and execute the operation self.decoder.execute_user_op(op, op_idx); - self.execute_op(op)?; + self.execute_op(op, host)?; // if the operation carries an immediate value, the value is stored at the next group // pointer; so, we advance the pointer to the following group @@ -552,7 +542,7 @@ where // bug somewhere in the assembler) debug_assert!(op_idx < OP_GROUP_SIZE - 1, "invalid op index"); self.decoder.execute_user_op(Operation::Noop, op_idx + 1); - self.execute_op(Operation::Noop)?; + self.execute_op(Operation::Noop, host)?; } // then, move to the next group and reset operation index @@ -575,7 +565,7 @@ where // the actual number of operation groups was not a power of two for group_idx in group_idx..num_batch_groups { self.decoder.execute_user_op(Operation::Noop, 0); - self.execute_op(Operation::Noop)?; + self.execute_op(Operation::Noop, host)?; // if we are not at the last group yet, set up the decoder for decoding the next // operation groups. the groups were are processing are just NOOPs - so, the op group @@ -589,14 +579,18 @@ where } /// Executes the specified decorator - fn execute_decorator(&mut self, decorator: &Decorator) -> Result<(), ExecutionError> { + fn execute_decorator( + &mut self, + decorator: &Decorator, + host: &mut impl Host, + ) -> Result<(), ExecutionError> { match decorator { Decorator::Advice(injector) => { - self.host.borrow_mut().set_advice(self, *injector)?; + host.set_advice(self, *injector)?; }, Decorator::Debug(options) => { if self.decoder.in_debug_mode() { - self.host.borrow_mut().on_debug(self, options)?; + host.on_debug(self, options)?; } }, Decorator::AsmOp(assembly_op) => { @@ -606,7 +600,7 @@ where }, Decorator::Trace(id) => { if self.enable_tracing { - self.host.borrow_mut().on_trace(self, *id)?; + host.on_trace(self, *id)?; } }, } @@ -614,21 +608,14 @@ where } // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- + // ================================================================================================ pub const fn kernel(&self) -> &Kernel { self.chiplets.kernel() } - pub fn into_parts(self) -> (System, Decoder, Stack, RangeChecker, Chiplets, H) { - ( - self.system, - self.decoder, - self.stack, - self.range, - self.chiplets, - self.host.into_inner(), - ) + pub fn into_parts(self) -> (System, Decoder, Stack, RangeChecker, Chiplets) { + (self.system, self.decoder, self.stack, self.range, self.chiplets) } } @@ -677,7 +664,7 @@ pub trait ProcessState { fn get_mem_state(&self, ctx: ContextId) -> Vec<(u64, Word)>; } -impl ProcessState for Process { +impl ProcessState for Process { fn clk(&self) -> RowIndex { self.system.clk() } diff --git a/processor/src/operations/comb_ops.rs b/processor/src/operations/comb_ops.rs index f1ab1f8d46..03181d37ac 100644 --- a/processor/src/operations/comb_ops.rs +++ b/processor/src/operations/comb_ops.rs @@ -1,14 +1,11 @@ use vm_core::{Felt, Operation, ONE, ZERO}; -use crate::{ExecutionError, Host, Process, QuadFelt}; +use crate::{ExecutionError, Process, QuadFelt}; // RANDOM LINEAR COMBINATION OPERATIONS // ================================================================================================ -impl Process -where - H: Host, -{ +impl Process { // COMBINE VALUES USING RANDOMNESS // -------------------------------------------------------------------------------------------- /// Performs a single step in the computation of the random linear combination: @@ -177,7 +174,7 @@ mod tests { use test_utils::{build_test, rand::rand_array, TRUNCATE_STACK_PROC}; use vm_core::{Felt, FieldElement, Operation, StackInputs, ONE, ZERO}; - use crate::{ContextId, Process, QuadFelt}; + use crate::{ContextId, DefaultHost, Process, QuadFelt}; #[test] fn rcombine_main() { @@ -197,6 +194,7 @@ mod tests { inputs.reverse(); // --- setup the operand stack ------------------------------------------------------------ + let mut host = DefaultHost::default(); let stack_inputs = StackInputs::new(inputs.to_vec()).expect("inputs lenght too long"); let mut process = Process::new_dummy_with_decoder_helpers(stack_inputs); @@ -221,10 +219,10 @@ mod tests { a, ) .unwrap(); - process.execute_op(Operation::Noop).unwrap(); + process.execute_op(Operation::Noop, &mut host).unwrap(); // --- execute RCOMB1 operation ----------------------------------------------------------- - process.execute_op(Operation::RCombBase).unwrap(); + process.execute_op(Operation::RCombBase, &mut host).unwrap(); // --- check that the top 8 stack elements are correctly rotated -------------------------- let stack_state = process.stack.trace_state(); diff --git a/processor/src/operations/crypto_ops.rs b/processor/src/operations/crypto_ops.rs index ef58cee8fc..9550bb6b20 100644 --- a/processor/src/operations/crypto_ops.rs +++ b/processor/src/operations/crypto_ops.rs @@ -1,15 +1,12 @@ use vm_core::AdviceInjector; -use super::{ExecutionError, Host, Operation, Process}; -use crate::crypto::MerklePath; +use super::{ExecutionError, Operation, Process}; +use crate::{crypto::MerklePath, Host}; // CRYPTOGRAPHIC OPERATIONS // ================================================================================================ -impl Process -where - H: Host, -{ +impl Process { // HASHING OPERATIONS // -------------------------------------------------------------------------------------------- /// Performs a Rescue Prime Optimized permutation to the top 12 elements of the operand stack, @@ -68,7 +65,11 @@ where /// /// # Panics /// Panics if the computed root does not match the root provided via the stack. - pub(super) fn op_mpverify(&mut self, err_code: u32) -> Result<(), ExecutionError> { + pub(super) fn op_mpverify( + &mut self, + err_code: u32, + host: &mut impl Host, + ) -> 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); @@ -76,7 +77,7 @@ where // get a Merkle path from the advice provider for the specified root and node index. // the path is expected to be of the specified depth. - let path = self.host.borrow_mut().get_adv_merkle_path(self)?; + let path = host.get_adv_merkle_path(self)?; // use hasher to compute the Merkle root of the path let (addr, computed_root) = self.chiplets.build_merkle_root(node, &path, index); @@ -134,7 +135,7 @@ where /// /// # Panics /// Panics if the computed old root does not match the input root provided via the stack. - pub(super) fn op_mrupdate(&mut self) -> Result<(), ExecutionError> { + pub(super) fn op_mrupdate(&mut self, host: &mut impl Host) -> Result<(), ExecutionError> { // read old node value, depth, index, tree root and new node values from the stack let old_node = [self.stack.get(3), self.stack.get(2), self.stack.get(1), self.stack.get(0)]; let depth = self.stack.get(4); @@ -147,11 +148,7 @@ where // get a Merkle path to it. the length of the returned path is expected to match the // specified depth. if the new node is the root of a tree, this instruction will append the // whole sub-tree to this node. - let path: MerklePath = self - .host - .borrow_mut() - .set_advice(self, AdviceInjector::UpdateMerkleNode)? - .into(); + let path: MerklePath = host.set_advice(self, AdviceInjector::UpdateMerkleNode)?.into(); assert_eq!(path.len(), depth.as_int() as usize); @@ -194,7 +191,7 @@ mod tests { super::{Felt, Operation}, Process, }; - use crate::{AdviceInputs, StackInputs, Word, ZERO}; + use crate::{AdviceInputs, DefaultHost, StackInputs, Word, ZERO}; #[test] fn op_hperm() { @@ -207,9 +204,10 @@ mod tests { ]; let stack = StackInputs::try_from_ints(inputs).unwrap(); let mut process = Process::new_dummy_with_decoder_helpers(stack); + let mut host = DefaultHost::default(); let expected: [Felt; STATE_WIDTH] = build_expected_perm(&inputs); - process.execute_op(Operation::HPerm).unwrap(); + process.execute_op(Operation::HPerm, &mut host).unwrap(); assert_eq!(expected, &process.stack.trace_state()[0..12]); // --- test hashing 8 random values ------------------------------------------------------- @@ -221,7 +219,7 @@ mod tests { // add the capacity to prepare the input vector let expected: [Felt; STATE_WIDTH] = build_expected_perm(&inputs); - process.execute_op(Operation::HPerm).unwrap(); + process.execute_op(Operation::HPerm, &mut host).unwrap(); assert_eq!(expected, &process.stack.trace_state()[0..12]); // --- test that the rest of the stack isn't affected ------------------------------------- @@ -232,7 +230,7 @@ mod tests { let stack = StackInputs::try_from_ints(inputs).unwrap(); let mut process = Process::new_dummy_with_decoder_helpers(stack); - process.execute_op(Operation::HPerm).unwrap(); + process.execute_op(Operation::HPerm, &mut host).unwrap(); assert_eq!(expected, &process.stack.trace_state()[12..16]); } @@ -265,10 +263,10 @@ mod tests { let advice_inputs = AdviceInputs::default().with_merkle_store(store); let stack_inputs = StackInputs::try_from_ints(stack_inputs).unwrap(); - let mut process = + let (mut process, mut host) = Process::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs); - process.execute_op(Operation::MpVerify(0)).unwrap(); + process.execute_op(Operation::MpVerify(0), &mut host).unwrap(); let expected_stack = build_expected(&[ node[3], node[2], node[1], node[0], depth, index, root[3], root[2], root[1], root[0], ]); @@ -307,11 +305,11 @@ mod tests { let store = MerkleStore::from(&tree); let advice_inputs = AdviceInputs::default().with_merkle_store(store); let stack_inputs = StackInputs::try_from_ints(stack_inputs).unwrap(); - let mut process = + let (mut process, mut host) = Process::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs); // update the Merkle tree but keep the old copy - process.execute_op(Operation::MrUpdate).unwrap(); + process.execute_op(Operation::MrUpdate, &mut host).unwrap(); let expected_stack = build_expected(&[ new_tree.root()[3], new_tree.root()[2], @@ -331,8 +329,8 @@ mod tests { assert_eq!(expected_stack, process.stack.trace_state()); // make sure both Merkle trees are still in the advice provider - assert!(process.host.borrow().advice_provider().has_merkle_root(tree.root())); - assert!(process.host.borrow().advice_provider().has_merkle_root(new_tree.root())); + assert!(host.advice_provider().has_merkle_root(tree.root())); + assert!(host.advice_provider().has_merkle_root(new_tree.root())); } #[test] @@ -385,14 +383,14 @@ mod tests { replaced_node[3].as_int(), ]; let stack_inputs = StackInputs::try_from_ints(stack_inputs).unwrap(); - let mut process = + let (mut process, mut host) = Process::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs); // assert the expected root doesn't exist before the merge operation - assert!(!process.host.borrow().advice_provider().has_merkle_root(expected_root)); + assert!(!host.advice_provider().has_merkle_root(expected_root)); // update the previous root - process.execute_op(Operation::MrUpdate).unwrap(); + process.execute_op(Operation::MrUpdate, &mut host).unwrap(); let expected_stack = build_expected(&[ expected_root[3], expected_root[2], @@ -412,7 +410,7 @@ mod tests { assert_eq!(expected_stack, process.stack.trace_state()); // assert the expected root now exists in the advice provider - assert!(process.host.borrow().advice_provider().has_merkle_root(expected_root)); + assert!(host.advice_provider().has_merkle_root(expected_root)); } // HELPER FUNCTIONS diff --git a/processor/src/operations/ext2_ops.rs b/processor/src/operations/ext2_ops.rs index e10d80f4bd..7317660370 100644 --- a/processor/src/operations/ext2_ops.rs +++ b/processor/src/operations/ext2_ops.rs @@ -1,14 +1,11 @@ -use super::{ExecutionError, Felt, Host, Process}; +use super::{ExecutionError, Felt, Process}; // EXTENSION FIELD OPERATIONS // ================================================================================================ const TWO: Felt = Felt::new(2); -impl Process -where - H: Host, -{ +impl Process { // ARITHMETIC OPERATIONS // -------------------------------------------------------------------------------------------- /// Gets the top four values from the stack [b1, b0, a1, a0], where a = (a1, a0) and @@ -40,7 +37,7 @@ mod tests { super::{Felt, Operation, MIN_STACK_DEPTH}, Process, }; - use crate::{StackInputs, ZERO}; + use crate::{DefaultHost, StackInputs, ZERO}; // ARITHMETIC OPERATIONS // -------------------------------------------------------------------------------------------- @@ -51,10 +48,11 @@ mod tests { let [a0, a1, b0, b1] = [rand_value(); 4]; let stack = StackInputs::new(vec![a0, a1, b0, b1]).expect("inputs lenght too long"); + let mut host = DefaultHost::default(); let mut process = Process::new_dummy(stack); // multiply the top two values - process.execute_op(Operation::Ext2Mul).unwrap(); + process.execute_op(Operation::Ext2Mul, &mut host).unwrap(); let a = QuadFelt::new(a0, a1); let b = QuadFelt::new(b0, b1); let c = (b * a).to_base_elements(); @@ -67,7 +65,7 @@ mod tests { // calling ext2mul with a stack of minimum depth is ok let stack = StackInputs::new(vec![]).expect("inputs lenght too long"); let mut process = Process::new_dummy(stack); - assert!(process.execute_op(Operation::Ext2Mul).is_ok()); + assert!(process.execute_op(Operation::Ext2Mul, &mut host).is_ok()); } // HELPER FUNCTIONS diff --git a/processor/src/operations/field_ops.rs b/processor/src/operations/field_ops.rs index 2fa799ed5e..f874da6bc1 100644 --- a/processor/src/operations/field_ops.rs +++ b/processor/src/operations/field_ops.rs @@ -1,14 +1,11 @@ use vm_core::{Operation, ONE, ZERO}; -use super::{utils::assert_binary, ExecutionError, Felt, FieldElement, Host, Process}; +use super::{utils::assert_binary, ExecutionError, Felt, FieldElement, Process}; // FIELD OPERATIONS // ================================================================================================ -impl Process -where - H: Host, -{ +impl Process { // ARITHMETIC OPERATIONS // -------------------------------------------------------------------------------------------- /// Pops two elements off the stack, adds them together, and pushes the result back onto the @@ -230,7 +227,7 @@ mod tests { super::{Felt, FieldElement, Operation, MIN_STACK_DEPTH}, Process, }; - use crate::{AdviceInputs, StackInputs}; + use crate::{AdviceInputs, DefaultHost, StackInputs}; // ARITHMETIC OPERATIONS // -------------------------------------------------------------------------------------------- @@ -241,9 +238,10 @@ mod tests { let (a, b, c) = get_rand_values(); let stack = StackInputs::try_from_ints([c.as_int(), b.as_int(), a.as_int()]).unwrap(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); // add the top two values - process.execute_op(Operation::Add).unwrap(); + process.execute_op(Operation::Add, &mut host).unwrap(); let expected = build_expected(&[a + b, c]); assert_eq!(MIN_STACK_DEPTH, process.stack.depth()); @@ -252,7 +250,7 @@ mod tests { // calling add with a stack of minimum depth is ok let mut process = Process::new_dummy_with_empty_stack(); - assert!(process.execute_op(Operation::Add).is_ok()); + assert!(process.execute_op(Operation::Add, &mut host).is_ok()); } #[test] @@ -261,9 +259,10 @@ mod tests { let (a, b, c) = get_rand_values(); let stack = StackInputs::try_from_ints([c.as_int(), b.as_int(), a.as_int()]).unwrap(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); // negate the top value - process.execute_op(Operation::Neg).unwrap(); + process.execute_op(Operation::Neg, &mut host).unwrap(); let expected = build_expected(&[-a, b, c]); assert_eq!(expected, process.stack.trace_state()); @@ -277,9 +276,10 @@ mod tests { let (a, b, c) = get_rand_values(); let stack = StackInputs::try_from_ints([c.as_int(), b.as_int(), a.as_int()]).unwrap(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); // add the top two values - process.execute_op(Operation::Mul).unwrap(); + process.execute_op(Operation::Mul, &mut host).unwrap(); let expected = build_expected(&[a * b, c]); assert_eq!(MIN_STACK_DEPTH, process.stack.depth()); @@ -288,7 +288,7 @@ mod tests { // calling mul with a stack of minimum depth is ok let mut process = Process::new_dummy_with_empty_stack(); - assert!(process.execute_op(Operation::Mul).is_ok()); + assert!(process.execute_op(Operation::Mul, &mut host).is_ok()); } #[test] @@ -297,10 +297,11 @@ mod tests { let (a, b, c) = get_rand_values(); let stack = StackInputs::try_from_ints([c.as_int(), b.as_int(), a.as_int()]).unwrap(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); // invert the top value if b != ZERO { - process.execute_op(Operation::Inv).unwrap(); + process.execute_op(Operation::Inv, &mut host).unwrap(); let expected = build_expected(&[a.inv(), b, c]); assert_eq!(MIN_STACK_DEPTH, process.stack.depth()); @@ -309,8 +310,8 @@ mod tests { } // inverting zero should be an error - process.execute_op(Operation::Pad).unwrap(); - assert!(process.execute_op(Operation::Inv).is_err()); + process.execute_op(Operation::Pad, &mut host).unwrap(); + assert!(process.execute_op(Operation::Inv, &mut host).is_err()); } #[test] @@ -319,9 +320,10 @@ mod tests { let (a, b, c) = get_rand_values(); let stack = StackInputs::try_from_ints([c.as_int(), b.as_int(), a.as_int()]).unwrap(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); // negate the top value - process.execute_op(Operation::Incr).unwrap(); + process.execute_op(Operation::Incr, &mut host).unwrap(); let expected = build_expected(&[a + ONE, b, c]); assert_eq!(MIN_STACK_DEPTH, process.stack.depth()); @@ -334,11 +336,12 @@ mod tests { #[test] fn op_and() { + let mut host = DefaultHost::default(); // --- test 0 AND 0 --------------------------------------------------- let stack = StackInputs::try_from_ints([2, 0, 0]).unwrap(); let mut process = Process::new_dummy(stack); - process.execute_op(Operation::And).unwrap(); + process.execute_op(Operation::And, &mut host).unwrap(); let expected = build_expected(&[ZERO, Felt::new(2)]); assert_eq!(expected, process.stack.trace_state()); @@ -346,7 +349,7 @@ mod tests { let stack = StackInputs::try_from_ints([2, 0, 1]).unwrap(); let mut process = Process::new_dummy(stack); - process.execute_op(Operation::And).unwrap(); + process.execute_op(Operation::And, &mut host).unwrap(); let expected = build_expected(&[ZERO, Felt::new(2)]); assert_eq!(expected, process.stack.trace_state()); @@ -354,7 +357,7 @@ mod tests { let stack = StackInputs::try_from_ints([2, 1, 0]).unwrap(); let mut process = Process::new_dummy(stack); - process.execute_op(Operation::And).unwrap(); + process.execute_op(Operation::And, &mut host).unwrap(); let expected = build_expected(&[ZERO, Felt::new(2)]); assert_eq!(expected, process.stack.trace_state()); @@ -362,32 +365,33 @@ mod tests { let stack = StackInputs::try_from_ints([2, 1, 1]).unwrap(); let mut process = Process::new_dummy(stack); - process.execute_op(Operation::And).unwrap(); + process.execute_op(Operation::And, &mut host).unwrap(); let expected = build_expected(&[ONE, Felt::new(2)]); assert_eq!(expected, process.stack.trace_state()); // --- first operand is not binary ------------------------------------ let stack = StackInputs::try_from_ints([2, 1, 2]).unwrap(); let mut process = Process::new_dummy(stack); - assert!(process.execute_op(Operation::And).is_err()); + assert!(process.execute_op(Operation::And, &mut host).is_err()); // --- second operand is not binary ----------------------------------- let stack = StackInputs::try_from_ints([2, 2, 1]).unwrap(); let mut process = Process::new_dummy(stack); - assert!(process.execute_op(Operation::And).is_err()); + assert!(process.execute_op(Operation::And, &mut host).is_err()); // --- calling AND with a stack of minimum depth is ok ---------------- let mut process = Process::new_dummy_with_empty_stack(); - assert!(process.execute_op(Operation::And).is_ok()); + assert!(process.execute_op(Operation::And, &mut host).is_ok()); } #[test] fn op_or() { + let mut host = DefaultHost::default(); // --- test 0 OR 0 --------------------------------------------------- let stack = StackInputs::try_from_ints([2, 0, 0]).unwrap(); let mut process = Process::new_dummy(stack); - process.execute_op(Operation::Or).unwrap(); + process.execute_op(Operation::Or, &mut host).unwrap(); let expected = build_expected(&[ZERO, Felt::new(2)]); assert_eq!(expected, process.stack.trace_state()); @@ -395,7 +399,7 @@ mod tests { let stack = StackInputs::try_from_ints([2, 0, 1]).unwrap(); let mut process = Process::new_dummy(stack); - process.execute_op(Operation::Or).unwrap(); + process.execute_op(Operation::Or, &mut host).unwrap(); let expected = build_expected(&[ONE, Felt::new(2)]); assert_eq!(expected, process.stack.trace_state()); @@ -403,7 +407,7 @@ mod tests { let stack = StackInputs::try_from_ints([2, 1, 0]).unwrap(); let mut process = Process::new_dummy(stack); - process.execute_op(Operation::Or).unwrap(); + process.execute_op(Operation::Or, &mut host).unwrap(); let expected = build_expected(&[ONE, Felt::new(2)]); assert_eq!(expected, process.stack.trace_state()); @@ -411,45 +415,46 @@ mod tests { let stack = StackInputs::try_from_ints([2, 1, 1]).unwrap(); let mut process = Process::new_dummy(stack); - process.execute_op(Operation::Or).unwrap(); + process.execute_op(Operation::Or, &mut host).unwrap(); let expected = build_expected(&[ONE, Felt::new(2)]); assert_eq!(expected, process.stack.trace_state()); // --- first operand is not binary ------------------------------------ let stack = StackInputs::try_from_ints([2, 1, 2]).unwrap(); let mut process = Process::new_dummy(stack); - assert!(process.execute_op(Operation::Or).is_err()); + assert!(process.execute_op(Operation::Or, &mut host).is_err()); // --- second operand is not binary ----------------------------------- let stack = StackInputs::try_from_ints([2, 2, 1]).unwrap(); let mut process = Process::new_dummy(stack); - assert!(process.execute_op(Operation::Or).is_err()); + assert!(process.execute_op(Operation::Or, &mut host).is_err()); // --- calling OR with a stack of minimum depth is a ok ---------------- let mut process = Process::new_dummy_with_empty_stack(); - assert!(process.execute_op(Operation::Or).is_ok()); + assert!(process.execute_op(Operation::Or, &mut host).is_ok()); } #[test] fn op_not() { + let mut host = DefaultHost::default(); // --- test NOT 0 ----------------------------------------------------- let stack = StackInputs::try_from_ints([2, 0]).unwrap(); let mut process = Process::new_dummy(stack); - process.execute_op(Operation::Not).unwrap(); + process.execute_op(Operation::Not, &mut host).unwrap(); let expected = build_expected(&[ONE, Felt::new(2)]); assert_eq!(expected, process.stack.trace_state()); // --- test NOT 1 ---------------------------------------------------- let stack = StackInputs::try_from_ints([2, 1]).unwrap(); let mut process = Process::new_dummy(stack); - process.execute_op(Operation::Not).unwrap(); + process.execute_op(Operation::Not, &mut host).unwrap(); let expected = build_expected(&[ZERO, Felt::new(2)]); assert_eq!(expected, process.stack.trace_state()); // --- operand is not binary ------------------------------------------ let stack = StackInputs::try_from_ints([2, 2]).unwrap(); let mut process = Process::new_dummy(stack); - assert!(process.execute_op(Operation::Not).is_err()); + assert!(process.execute_op(Operation::Not, &mut host).is_err()); } // COMPARISON OPERATIONS @@ -460,29 +465,29 @@ mod tests { // --- test when top two values are equal ----------------------------- let advice_inputs = AdviceInputs::default(); let stack_inputs = StackInputs::try_from_ints([3, 7, 7]).unwrap(); - let mut process = + let (mut process, mut host) = Process::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs); - process.execute_op(Operation::Eq).unwrap(); + process.execute_op(Operation::Eq, &mut host).unwrap(); let expected = build_expected(&[ONE, Felt::new(3)]); assert_eq!(expected, process.stack.trace_state()); // --- test when top two values are not equal ------------------------- let advice_inputs = AdviceInputs::default(); let stack_inputs = StackInputs::try_from_ints([3, 5, 7]).unwrap(); - let mut process = + let (mut process, mut host) = Process::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs); - process.execute_op(Operation::Eq).unwrap(); + process.execute_op(Operation::Eq, &mut host).unwrap(); let expected = build_expected(&[ZERO, Felt::new(3)]); assert_eq!(expected, process.stack.trace_state()); // --- calling EQ with a stack of minimum depth is a ok --------------- let advice_inputs = AdviceInputs::default(); let stack_inputs = StackInputs::default(); - let mut process = + let (mut process, mut host) = Process::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs); - assert!(process.execute_op(Operation::Eq).is_ok()); + assert!(process.execute_op(Operation::Eq, &mut host).is_ok()); } #[test] @@ -490,20 +495,20 @@ mod tests { // --- test when top is zero ------------------------------------------ let advice_inputs = AdviceInputs::default(); let stack_inputs = StackInputs::try_from_ints([3, 0]).unwrap(); - let mut process = + let (mut process, mut host) = Process::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs); - process.execute_op(Operation::Eqz).unwrap(); + process.execute_op(Operation::Eqz, &mut host).unwrap(); let expected = build_expected(&[ONE, Felt::new(3)]); assert_eq!(expected, process.stack.trace_state()); // --- test when top is not zero -------------------------------------- let advice_inputs = AdviceInputs::default(); let stack_inputs = StackInputs::try_from_ints([3, 4]).unwrap(); - let mut process = + let (mut process, mut host) = Process::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs); - process.execute_op(Operation::Eqz).unwrap(); + process.execute_op(Operation::Eqz, &mut host).unwrap(); let expected = build_expected(&[ZERO, Felt::new(3)]); assert_eq!(expected, process.stack.trace_state()); } @@ -521,10 +526,10 @@ mod tests { let advice_inputs = AdviceInputs::default(); let stack_inputs = StackInputs::try_from_ints([a, b, c, 0]).unwrap(); - let mut process = + let (mut process, mut host) = Process::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs); - process.execute_op(Operation::Expacc).unwrap(); + process.execute_op(Operation::Expacc, &mut host).unwrap(); let expected = build_expected(&[ZERO, Felt::new(16), Felt::new(32), Felt::new(a >> 1)]); assert_eq!(expected, process.stack.trace_state()); @@ -536,10 +541,10 @@ mod tests { let advice_inputs = AdviceInputs::default(); let stack_inputs = StackInputs::try_from_ints([a, b, c, 0]).unwrap(); - let mut process = + let (mut process, mut host) = Process::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs); - process.execute_op(Operation::Expacc).unwrap(); + process.execute_op(Operation::Expacc, &mut host).unwrap(); let expected = build_expected(&[ONE, Felt::new(256), Felt::new(16), Felt::new(a >> 1)]); assert_eq!(expected, process.stack.trace_state()); @@ -552,10 +557,10 @@ mod tests { let advice_inputs = AdviceInputs::default(); let stack_inputs = StackInputs::try_from_ints([a, b, c, 0]).unwrap(); - let mut process = + let (mut process, mut host) = Process::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs); - process.execute_op(Operation::Expacc).unwrap(); + process.execute_op(Operation::Expacc, &mut host).unwrap(); let expected = build_expected(&[ONE, Felt::new(390625), Felt::new(3125), Felt::new(a >> 1)]); assert_eq!(expected, process.stack.trace_state()); diff --git a/processor/src/operations/fri_ops.rs b/processor/src/operations/fri_ops.rs index 01189c2d23..cf67ef21cc 100644 --- a/processor/src/operations/fri_ops.rs +++ b/processor/src/operations/fri_ops.rs @@ -1,6 +1,6 @@ use vm_core::{ExtensionOf, FieldElement, StarkField, ONE, ZERO}; -use super::{super::QuadFelt, ExecutionError, Felt, Host, Operation, Process}; +use super::{super::QuadFelt, ExecutionError, Felt, Operation, Process}; // CONSTANTS // ================================================================================================ @@ -20,10 +20,7 @@ const TAU3_INV: Felt = Felt::new(281474976710656); // tau^{-3} // FRI OPERATIONS // ================================================================================================ -impl Process -where - H: Host, -{ +impl Process { // FRI FOLDING OPERATION // -------------------------------------------------------------------------------------------- /// Performs FRI layer folding by a factor of 4 for FRI protocol executed in a degree 2 @@ -253,6 +250,7 @@ mod tests { use super::{ ExtensionOf, Felt, FieldElement, Operation, Process, QuadFelt, StarkField, TWO, TWO_INV, }; + use crate::DefaultHost; #[test] fn fold4() { @@ -331,8 +329,9 @@ mod tests { let stack_inputs = StackInputs::new(inputs[0..16].to_vec()).expect("inputs lenght too long"); let mut process = Process::new_dummy_with_decoder_helpers(stack_inputs); - process.execute_op(Operation::Push(inputs[16])).unwrap(); - process.execute_op(Operation::FriE2F4).unwrap(); + let mut host = DefaultHost::default(); + process.execute_op(Operation::Push(inputs[16]), &mut host).unwrap(); + process.execute_op(Operation::FriE2F4, &mut host).unwrap(); // --- check the stack state------------------------------------------- let stack_state = process.stack.trace_state(); diff --git a/processor/src/operations/io_ops.rs b/processor/src/operations/io_ops.rs index b3c9d863b9..db3f5fd598 100644 --- a/processor/src/operations/io_ops.rs +++ b/processor/src/operations/io_ops.rs @@ -1,13 +1,10 @@ -use super::{ExecutionError, Felt, Host, Operation, Process}; -use crate::Word; +use super::{ExecutionError, Felt, Operation, Process}; +use crate::{Host, Word}; // INPUT / OUTPUT OPERATIONS // ================================================================================================ -impl Process -where - H: Host, -{ +impl Process { // CONSTANT INPUTS // -------------------------------------------------------------------------------------------- @@ -183,13 +180,13 @@ where /// - These words replace the top 8 elements of the stack (element-wise, in stack order). /// - Memory address (in position 12) is incremented by 2. /// - All other stack elements remain the same. - pub(super) fn op_pipe(&mut self) -> Result<(), ExecutionError> { + pub(super) fn op_pipe(&mut self, host: &mut impl Host) -> Result<(), ExecutionError> { // get the address from position 12 on the stack let ctx = self.system.ctx(); let addr = Self::get_valid_address(self.stack.get(12))?; // pop two words from the advice stack - let words = self.host.borrow_mut().pop_adv_stack_dword(self)?; + let words = host.pop_adv_stack_dword(self)?; // write the words memory self.chiplets.write_mem_double(ctx, addr, words)?; @@ -221,8 +218,8 @@ where /// /// # Errors /// Returns an error if the advice stack is empty. - pub(super) fn op_advpop(&mut self) -> Result<(), ExecutionError> { - let value = self.host.borrow_mut().pop_adv_stack(self)?; + pub(super) fn op_advpop(&mut self, host: &mut impl Host) -> Result<(), ExecutionError> { + let value = host.pop_adv_stack(self)?; self.stack.set(0, value); self.stack.shift_right(0); Ok(()) @@ -233,8 +230,8 @@ where /// /// # Errors /// Returns an error if the advice stack contains fewer than four elements. - pub(super) fn op_advpopw(&mut self) -> Result<(), ExecutionError> { - let word: Word = self.host.borrow_mut().pop_adv_stack_word(self)?; + pub(super) fn op_advpopw(&mut self, host: &mut impl Host) -> Result<(), ExecutionError> { + let word: Word = host.pop_adv_stack_word(self)?; self.stack.set(0, word[3]); self.stack.set(1, word[2]); @@ -281,10 +278,11 @@ mod tests { super::{super::AdviceProvider, Operation, MIN_STACK_DEPTH}, Felt, Host, Process, }; - use crate::{AdviceSource, ContextId}; + use crate::{AdviceSource, ContextId, DefaultHost}; #[test] fn op_push() { + let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_empty_stack(); assert_eq!(MIN_STACK_DEPTH, process.stack.depth()); assert_eq!(1, process.stack.current_clk()); @@ -292,7 +290,7 @@ mod tests { // push one item onto the stack let op = Operation::Push(ONE); - process.execute_op(op).unwrap(); + process.execute_op(op, &mut host).unwrap(); let mut expected = [ZERO; 16]; expected[0] = ONE; @@ -302,7 +300,7 @@ mod tests { // push another item onto the stack let op = Operation::Push(Felt::new(3)); - process.execute_op(op).unwrap(); + process.execute_op(op, &mut host).unwrap(); let mut expected = [ZERO; 16]; expected[0] = Felt::new(3); expected[1] = ONE; @@ -316,21 +314,22 @@ mod tests { // -------------------------------------------------------------------------------------------- #[test] fn op_mloadw() { + let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); assert_eq!(0, process.chiplets.get_mem_size()); // push a word onto the stack and save it at address 1 let word = [1, 3, 5, 7].to_elements().try_into().unwrap(); - store_value(&mut process, 1, word); + store_value(&mut process, 1, word, &mut host); // push four zeros onto the stack for _ in 0..4 { - process.execute_op(Operation::Pad).unwrap(); + process.execute_op(Operation::Pad, &mut host).unwrap(); } // push the address onto the stack and load the word - process.execute_op(Operation::Push(ONE)).unwrap(); - process.execute_op(Operation::MLoadW).unwrap(); + process.execute_op(Operation::Push(ONE), &mut host).unwrap(); + process.execute_op(Operation::MLoadW, &mut host).unwrap(); let expected_stack = build_expected_stack(&[7, 5, 3, 1, 7, 5, 3, 1]); assert_eq!(expected_stack, process.stack.trace_state()); @@ -340,26 +339,27 @@ mod tests { assert_eq!(word, process.chiplets.get_mem_value(ContextId::root(), 1).unwrap()); // --- calling MLOADW with address greater than u32::MAX leads to an error ---------------- - process.execute_op(Operation::Push(Felt::new(u64::MAX / 2))).unwrap(); - assert!(process.execute_op(Operation::MLoadW).is_err()); + process.execute_op(Operation::Push(Felt::new(u64::MAX / 2)), &mut host).unwrap(); + assert!(process.execute_op(Operation::MLoadW, &mut host).is_err()); // --- calling MLOADW with a stack of minimum depth is ok ---------------- let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); - assert!(process.execute_op(Operation::MLoadW).is_ok()); + assert!(process.execute_op(Operation::MLoadW, &mut host).is_ok()); } #[test] fn op_mload() { + let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); assert_eq!(0, process.chiplets.get_mem_size()); // push a word onto the stack and save it at address 2 let word = [1, 3, 5, 7].to_elements().try_into().unwrap(); - store_value(&mut process, 2, word); + store_value(&mut process, 2, word, &mut host); // push the address onto the stack and load the element - process.execute_op(Operation::Push(Felt::new(2))).unwrap(); - process.execute_op(Operation::MLoad).unwrap(); + process.execute_op(Operation::Push(Felt::new(2)), &mut host).unwrap(); + process.execute_op(Operation::MLoad, &mut host).unwrap(); let expected_stack = build_expected_stack(&[1, 7, 5, 3, 1]); assert_eq!(expected_stack, process.stack.trace_state()); @@ -369,16 +369,17 @@ mod tests { assert_eq!(word, process.chiplets.get_mem_value(ContextId::root(), 2).unwrap()); // --- calling MLOAD with address greater than u32::MAX leads to an error ----------------- - process.execute_op(Operation::Push(Felt::new(u64::MAX / 2))).unwrap(); - assert!(process.execute_op(Operation::MLoad).is_err()); + process.execute_op(Operation::Push(Felt::new(u64::MAX / 2)), &mut host).unwrap(); + assert!(process.execute_op(Operation::MLoad, &mut host).is_err()); // --- calling MLOAD with a stack of minimum depth is ok ---------------- let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); - assert!(process.execute_op(Operation::MLoad).is_ok()); + assert!(process.execute_op(Operation::MLoad, &mut host).is_ok()); } #[test] fn op_mstream() { + let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); // save two words into memory addresses 1 and 2 @@ -386,8 +387,8 @@ mod tests { let word2 = [26, 25, 24, 23]; let word1_felts: Word = word1.to_elements().try_into().unwrap(); let word2_felts: Word = word2.to_elements().try_into().unwrap(); - store_value(&mut process, 1, word1_felts); - store_value(&mut process, 2, word2_felts); + store_value(&mut process, 1, word1_felts, &mut host); + store_value(&mut process, 2, word2_felts, &mut host); // check memory state assert_eq!(2, process.chiplets.get_mem_size()); @@ -396,7 +397,7 @@ mod tests { // clear the stack for _ in 0..8 { - process.execute_op(Operation::Drop).unwrap(); + process.execute_op(Operation::Drop, &mut host).unwrap(); } // arrange the stack such that: @@ -404,14 +405,14 @@ mod tests { // - 1 (the address) is at position 12 // - values 1 - 12 are at positions 0 - 11. Adding the first 8 of these values to the values // stored in memory should result in 35. - process.execute_op(Operation::Push(Felt::new(101))).unwrap(); - process.execute_op(Operation::Push(ONE)).unwrap(); + process.execute_op(Operation::Push(Felt::new(101)), &mut host).unwrap(); + process.execute_op(Operation::Push(ONE), &mut host).unwrap(); for i in 1..13 { - process.execute_op(Operation::Push(Felt::new(i))).unwrap(); + process.execute_op(Operation::Push(Felt::new(i)), &mut host).unwrap(); } // execute the MSTREAM operation - process.execute_op(Operation::MStream).unwrap(); + process.execute_op(Operation::MStream, &mut host).unwrap(); // the first 8 values should contain the values from memory. the next 4 values should remain // unchanged, and the address should be incremented by 2 (i.e., 1 -> 3). @@ -425,12 +426,13 @@ mod tests { #[test] fn op_mstorew() { + let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); assert_eq!(0, process.chiplets.get_mem_size()); // push the first word onto the stack and save it at address 0 let word1 = [1, 3, 5, 7].to_elements().try_into().unwrap(); - store_value(&mut process, 0, word1); + store_value(&mut process, 0, word1, &mut host); // check stack state let expected_stack = build_expected_stack(&[7, 5, 3, 1]); @@ -442,7 +444,7 @@ mod tests { // push the second word onto the stack and save it at address 3 let word2 = [2, 4, 6, 8].to_elements().try_into().unwrap(); - store_value(&mut process, 3, word2); + store_value(&mut process, 3, word2, &mut host); // check stack state let expected_stack = build_expected_stack(&[8, 6, 4, 2, 7, 5, 3, 1]); @@ -454,23 +456,24 @@ mod tests { assert_eq!(word2, process.chiplets.get_mem_value(ContextId::root(), 3).unwrap()); // --- calling MSTOREW with address greater than u32::MAX leads to an error ---------------- - process.execute_op(Operation::Push(Felt::new(u64::MAX / 2))).unwrap(); - assert!(process.execute_op(Operation::MStoreW).is_err()); + process.execute_op(Operation::Push(Felt::new(u64::MAX / 2)), &mut host).unwrap(); + assert!(process.execute_op(Operation::MStoreW, &mut host).is_err()); // --- calling STOREW with a stack of minimum depth is ok ---------------- let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); - assert!(process.execute_op(Operation::MStoreW).is_ok()); + assert!(process.execute_op(Operation::MStoreW, &mut host).is_ok()); } #[test] fn op_mstore() { + let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); assert_eq!(0, process.chiplets.get_mem_size()); // push new element onto the stack and save it as first element of the word on // uninitialized memory at address 0 let element = Felt::new(10); - store_element(&mut process, 0, element); + store_element(&mut process, 0, element, &mut host); // check stack state let expected_stack = build_expected_stack(&[10]); @@ -483,11 +486,11 @@ mod tests { // push the word onto the stack and save it at address 2 let word_2 = [1, 3, 5, 7].to_elements().try_into().unwrap(); - store_value(&mut process, 2, word_2); + store_value(&mut process, 2, word_2, &mut host); // push new element onto the stack and save it as first element of the word at address 2 let element = Felt::new(12); - store_element(&mut process, 2, element); + store_element(&mut process, 2, element, &mut host); // check stack state let expected_stack = build_expected_stack(&[12, 7, 5, 3, 1, 10]); @@ -499,16 +502,17 @@ mod tests { assert_eq!(mem_2, process.chiplets.get_mem_value(ContextId::root(), 2).unwrap()); // --- calling MSTORE with address greater than u32::MAX leads to an error ---------------- - process.execute_op(Operation::Push(Felt::new(u64::MAX / 2))).unwrap(); - assert!(process.execute_op(Operation::MStore).is_err()); + process.execute_op(Operation::Push(Felt::new(u64::MAX / 2)), &mut host).unwrap(); + assert!(process.execute_op(Operation::MStore, &mut host).is_err()); // --- calling MSTORE with a stack of minimum depth is ok ---------------- let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); - assert!(process.execute_op(Operation::MStore).is_ok()); + assert!(process.execute_op(Operation::MStore, &mut host).is_ok()); } #[test] fn op_pipe() { + let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); // push words onto the advice stack @@ -518,12 +522,7 @@ mod tests { let word2_felts: Word = word2.to_elements().try_into().unwrap(); for element in word2_felts.iter().rev().chain(word1_felts.iter().rev()).copied() { // reverse the word order, since elements are pushed onto the advice stack. - process - .host - .borrow_mut() - .advice_provider_mut() - .push_stack(AdviceSource::Value(element)) - .unwrap(); + host.advice_provider_mut().push_stack(AdviceSource::Value(element)).unwrap(); } // arrange the stack such that: @@ -532,14 +531,14 @@ mod tests { // - values 1 - 12 are at positions 0 - 11. Replacing the first 8 of these values with the // values from the advice stack should result in 30 through 23 in stack order (with 23 at // stack[0]). - process.execute_op(Operation::Push(Felt::new(101))).unwrap(); - process.execute_op(Operation::Push(ONE)).unwrap(); + process.execute_op(Operation::Push(Felt::new(101)), &mut host).unwrap(); + process.execute_op(Operation::Push(ONE), &mut host).unwrap(); for i in 1..13 { - process.execute_op(Operation::Push(Felt::new(i))).unwrap(); + process.execute_op(Operation::Push(Felt::new(i)), &mut host).unwrap(); } // execute the PIPE operation - process.execute_op(Operation::Pipe).unwrap(); + process.execute_op(Operation::Pipe, &mut host).unwrap(); // check memory state contains the words from the advice stack assert_eq!(2, process.chiplets.get_mem_size()); @@ -562,27 +561,27 @@ mod tests { #[test] fn op_advpop() { // popping from the advice stack should push the value onto the operand stack - let mut process = Process::new_dummy_with_advice_stack(&[3]); - process.execute_op(Operation::Push(ONE)).unwrap(); - process.execute_op(Operation::AdvPop).unwrap(); + let (mut process, mut host) = Process::new_dummy_with_advice_stack(&[3]); + process.execute_op(Operation::Push(ONE), &mut host).unwrap(); + process.execute_op(Operation::AdvPop, &mut host).unwrap(); let expected = build_expected_stack(&[3, 1]); assert_eq!(expected, process.stack.trace_state()); // popping again should result in an error because advice stack is empty - assert!(process.execute_op(Operation::AdvPop).is_err()); + assert!(process.execute_op(Operation::AdvPop, &mut host).is_err()); } #[test] fn op_advpopw() { // popping a word from the advice stack should overwrite top 4 elements of the operand // stack - let mut process = Process::new_dummy_with_advice_stack(&[3, 4, 5, 6]); - process.execute_op(Operation::Push(ONE)).unwrap(); - process.execute_op(Operation::Pad).unwrap(); - process.execute_op(Operation::Pad).unwrap(); - process.execute_op(Operation::Pad).unwrap(); - process.execute_op(Operation::Pad).unwrap(); - process.execute_op(Operation::AdvPopW).unwrap(); + let (mut process, mut host) = Process::new_dummy_with_advice_stack(&[3, 4, 5, 6]); + process.execute_op(Operation::Push(ONE), &mut host).unwrap(); + process.execute_op(Operation::Pad, &mut host).unwrap(); + process.execute_op(Operation::Pad, &mut host).unwrap(); + process.execute_op(Operation::Pad, &mut host).unwrap(); + process.execute_op(Operation::Pad, &mut host).unwrap(); + process.execute_op(Operation::AdvPopW, &mut host).unwrap(); let expected = build_expected_stack(&[6, 5, 4, 3, 1]); assert_eq!(expected, process.stack.trace_state()); } @@ -590,26 +589,26 @@ mod tests { // HELPER METHODS // -------------------------------------------------------------------------------------------- - fn store_value(process: &mut Process, addr: u64, value: [Felt; 4]) + fn store_value(process: &mut Process, addr: u64, value: [Felt; 4], host: &mut H) where H: Host, { for &value in value.iter() { - process.execute_op(Operation::Push(value)).unwrap(); + process.execute_op(Operation::Push(value), host).unwrap(); } let addr = Felt::new(addr); - process.execute_op(Operation::Push(addr)).unwrap(); - process.execute_op(Operation::MStoreW).unwrap(); + process.execute_op(Operation::Push(addr), host).unwrap(); + process.execute_op(Operation::MStoreW, host).unwrap(); } - fn store_element(process: &mut Process, addr: u64, value: Felt) + fn store_element(process: &mut Process, addr: u64, value: Felt, host: &mut H) where H: Host, { - process.execute_op(Operation::Push(value)).unwrap(); + process.execute_op(Operation::Push(value), host).unwrap(); let addr = Felt::new(addr); - process.execute_op(Operation::Push(addr)).unwrap(); - process.execute_op(Operation::MStore).unwrap(); + process.execute_op(Operation::Push(addr), host).unwrap(); + process.execute_op(Operation::MStore, host).unwrap(); } fn build_expected_stack(values: &[u64]) -> [Felt; 16] { diff --git a/processor/src/operations/mod.rs b/processor/src/operations/mod.rs index 58216b7e00..cf97cb596b 100644 --- a/processor/src/operations/mod.rs +++ b/processor/src/operations/mod.rs @@ -19,12 +19,13 @@ use super::Kernel; // OPERATION DISPATCHER // ================================================================================================ -impl Process -where - H: Host, -{ +impl Process { /// Executes the specified operation. - pub(super) fn execute_op(&mut self, op: Operation) -> Result<(), ExecutionError> { + pub(super) fn execute_op( + &mut self, + op: Operation, + host: &mut impl Host, + ) -> Result<(), ExecutionError> { // make sure there is enough memory allocated to hold the execution trace self.ensure_trace_capacity(); @@ -32,7 +33,7 @@ where match op { // ----- system operations ------------------------------------------------------------ Operation::Noop => self.stack.copy_state(0), - Operation::Assert(err_code) => self.op_assert(err_code)?, + Operation::Assert(err_code) => self.op_assert(err_code, host)?, Operation::FmpAdd => self.op_fmpadd()?, Operation::FmpUpdate => self.op_fmpupdate()?, @@ -41,7 +42,7 @@ where Operation::Caller => self.op_caller()?, Operation::Clk => self.op_clk()?, - Operation::Emit(event_id) => self.op_emit(event_id)?, + Operation::Emit(event_id) => self.op_emit(event_id, host)?, // ----- flow control operations ------------------------------------------------------ // control flow operations are never executed directly @@ -135,8 +136,8 @@ where // ----- input / output --------------------------------------------------------------- Operation::Push(value) => self.op_push(value)?, - Operation::AdvPop => self.op_advpop()?, - Operation::AdvPopW => self.op_advpopw()?, + Operation::AdvPop => self.op_advpop(host)?, + Operation::AdvPopW => self.op_advpopw(host)?, Operation::MLoadW => self.op_mloadw()?, Operation::MStoreW => self.op_mstorew()?, @@ -145,12 +146,12 @@ where Operation::MStore => self.op_mstore()?, Operation::MStream => self.op_mstream()?, - Operation::Pipe => self.op_pipe()?, + Operation::Pipe => self.op_pipe(host)?, // ----- cryptographic operations ----------------------------------------------------- Operation::HPerm => self.op_hperm()?, - Operation::MpVerify(err_code) => self.op_mpverify(err_code)?, - Operation::MrUpdate => self.op_mrupdate()?, + Operation::MpVerify(err_code) => self.op_mpverify(err_code, host)?, + Operation::MrUpdate => self.op_mrupdate(host)?, Operation::FriE2F4 => self.op_fri_ext2fold4()?, Operation::RCombBase => self.op_rcomb_base()?, } @@ -183,14 +184,14 @@ pub mod testing { use super::*; use crate::{AdviceInputs, DefaultHost, MemAdviceProvider}; - impl Process> { + impl Process { /// Instantiates a new blank process for testing purposes. The stack in the process is /// initialized with the provided values. pub fn new_dummy(stack_inputs: StackInputs) -> Self { - let host = DefaultHost::default(); + let mut host = DefaultHost::default(); let mut process = - Self::new(Kernel::default(), stack_inputs, host, ExecutionOptions::default()); - process.execute_op(Operation::Noop).unwrap(); + Self::new(Kernel::default(), stack_inputs, ExecutionOptions::default()); + process.execute_op(Operation::Noop, &mut host).unwrap(); process } @@ -201,16 +202,19 @@ pub mod testing { } /// Instantiates a new process with an advice stack for testing purposes. - pub fn new_dummy_with_advice_stack(advice_stack: &[u64]) -> Self { + pub fn new_dummy_with_advice_stack( + advice_stack: &[u64], + ) -> (Self, DefaultHost) { let stack_inputs = StackInputs::default(); let advice_inputs = AdviceInputs::default().with_stack_values(advice_stack.iter().copied()).unwrap(); let advice_provider = MemAdviceProvider::from(advice_inputs); - let host = DefaultHost::new(advice_provider); + let mut host = DefaultHost::new(advice_provider); let mut process = - Self::new(Kernel::default(), stack_inputs, host, ExecutionOptions::default()); - process.execute_op(Operation::Noop).unwrap(); - process + Self::new(Kernel::default(), stack_inputs, ExecutionOptions::default()); + process.execute_op(Operation::Noop, &mut host).unwrap(); + + (process, host) } /// Instantiates a new blank process with one decoder trace row for testing purposes. This @@ -226,7 +230,9 @@ pub mod testing { /// The stack in the process is initialized with the provided values. pub fn new_dummy_with_decoder_helpers(stack_inputs: StackInputs) -> Self { let advice_inputs = AdviceInputs::default(); - Self::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs) + let (process, _) = + Self::new_dummy_with_inputs_and_decoder_helpers(stack_inputs, advice_inputs); + process } /// Instantiates a new process having Program inputs along with one decoder trace row @@ -234,14 +240,15 @@ pub mod testing { pub fn new_dummy_with_inputs_and_decoder_helpers( stack_inputs: StackInputs, advice_inputs: AdviceInputs, - ) -> Self { + ) -> (Self, DefaultHost) { let advice_provider = MemAdviceProvider::from(advice_inputs); - let host = DefaultHost::new(advice_provider); + let mut host = DefaultHost::new(advice_provider); let mut process = - Self::new(Kernel::default(), stack_inputs, host, ExecutionOptions::default()); + Self::new(Kernel::default(), stack_inputs, ExecutionOptions::default()); process.decoder.add_dummy_trace_row(); - process.execute_op(Operation::Noop).unwrap(); - process + process.execute_op(Operation::Noop, &mut host).unwrap(); + + (process, host) } } } diff --git a/processor/src/operations/stack_ops.rs b/processor/src/operations/stack_ops.rs index 401294272f..95c1fdc287 100644 --- a/processor/src/operations/stack_ops.rs +++ b/processor/src/operations/stack_ops.rs @@ -1,10 +1,7 @@ -use super::{ExecutionError, Host, Process, MIN_STACK_DEPTH}; +use super::{ExecutionError, Process, MIN_STACK_DEPTH}; use crate::ZERO; -impl Process -where - H: Host, -{ +impl Process { // STACK MANIPULATION // -------------------------------------------------------------------------------------------- /// Pushes a ZERO onto the stack. @@ -306,20 +303,21 @@ mod tests { super::{Operation, Process}, MIN_STACK_DEPTH, }; - use crate::{Felt, StackInputs, ONE, ZERO}; + use crate::{DefaultHost, Felt, StackInputs, ONE, ZERO}; #[test] fn op_pad() { let stack = StackInputs::default(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); // push one item onto the stack - process.execute_op(Operation::Push(ONE)).unwrap(); + process.execute_op(Operation::Push(ONE), &mut host).unwrap(); let expected = build_expected(&[1]); assert_eq!(expected, process.stack.trace_state()); // pad the stack - process.execute_op(Operation::Pad).unwrap(); + process.execute_op(Operation::Pad, &mut host).unwrap(); let expected = build_expected(&[0, 1]); assert_eq!(MIN_STACK_DEPTH + 2, process.stack.depth()); @@ -327,7 +325,7 @@ mod tests { assert_eq!(expected, process.stack.trace_state()); // pad the stack again - process.execute_op(Operation::Pad).unwrap(); + process.execute_op(Operation::Pad, &mut host).unwrap(); let expected = build_expected(&[0, 0, 1]); assert_eq!(MIN_STACK_DEPTH + 3, process.stack.depth()); @@ -340,69 +338,71 @@ mod tests { // push a few items onto the stack let stack = StackInputs::default(); let mut process = Process::new_dummy(stack); - process.execute_op(Operation::Push(ONE)).unwrap(); - process.execute_op(Operation::Push(Felt::new(2))).unwrap(); + let mut host = DefaultHost::default(); + process.execute_op(Operation::Push(ONE), &mut host).unwrap(); + process.execute_op(Operation::Push(Felt::new(2)), &mut host).unwrap(); // drop the first value - process.execute_op(Operation::Drop).unwrap(); + process.execute_op(Operation::Drop, &mut host).unwrap(); let expected = build_expected(&[1]); assert_eq!(expected, process.stack.trace_state()); assert_eq!(MIN_STACK_DEPTH + 1, process.stack.depth()); // drop the next value - process.execute_op(Operation::Drop).unwrap(); + process.execute_op(Operation::Drop, &mut host).unwrap(); let expected = build_expected(&[]); assert_eq!(expected, process.stack.trace_state()); assert_eq!(MIN_STACK_DEPTH, process.stack.depth()); // calling drop with a minimum stack depth should be ok - assert!(process.execute_op(Operation::Drop).is_ok()); + assert!(process.execute_op(Operation::Drop, &mut host).is_ok()); } #[test] fn op_dup() { let stack = StackInputs::default(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); // push one item onto the stack - process.execute_op(Operation::Push(ONE)).unwrap(); + process.execute_op(Operation::Push(ONE), &mut host).unwrap(); let expected = build_expected(&[1]); assert_eq!(expected, process.stack.trace_state()); // duplicate it - process.execute_op(Operation::Dup0).unwrap(); + process.execute_op(Operation::Dup0, &mut host).unwrap(); let expected = build_expected(&[1, 1]); assert_eq!(expected, process.stack.trace_state()); // duplicating non-existent item from the min stack range should be ok - assert!(process.execute_op(Operation::Dup2).is_ok()); + assert!(process.execute_op(Operation::Dup2, &mut host).is_ok()); // drop it again before continuing the tests and stack comparison - process.execute_op(Operation::Drop).unwrap(); + process.execute_op(Operation::Drop, &mut host).unwrap(); // put 15 more items onto the stack let mut expected = [ONE; 16]; for i in 2..17 { - process.execute_op(Operation::Push(Felt::new(i))).unwrap(); + process.execute_op(Operation::Push(Felt::new(i)), &mut host).unwrap(); expected[16 - i as usize] = Felt::new(i); } assert_eq!(expected, process.stack.trace_state()); // duplicate last stack item - process.execute_op(Operation::Dup15).unwrap(); + process.execute_op(Operation::Dup15, &mut host).unwrap(); assert_eq!(ONE, process.stack.trace_state()[0]); assert_eq!(&expected[..15], &process.stack.trace_state()[1..]); // duplicate 8th stack item - process.execute_op(Operation::Dup7).unwrap(); + process.execute_op(Operation::Dup7, &mut host).unwrap(); assert_eq!(Felt::new(10), process.stack.trace_state()[0]); assert_eq!(ONE, process.stack.trace_state()[1]); assert_eq!(&expected[..14], &process.stack.trace_state()[2..]); // remove 4 items off the stack - process.execute_op(Operation::Drop).unwrap(); - process.execute_op(Operation::Drop).unwrap(); - process.execute_op(Operation::Drop).unwrap(); - process.execute_op(Operation::Drop).unwrap(); + process.execute_op(Operation::Drop, &mut host).unwrap(); + process.execute_op(Operation::Drop, &mut host).unwrap(); + process.execute_op(Operation::Drop, &mut host).unwrap(); + process.execute_op(Operation::Drop, &mut host).unwrap(); assert_eq!(MIN_STACK_DEPTH + 15, process.stack.depth()); @@ -416,15 +416,16 @@ mod tests { // push a few items onto the stack let stack = StackInputs::try_from_ints([1, 2, 3]).unwrap(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); - process.execute_op(Operation::Swap).unwrap(); + process.execute_op(Operation::Swap, &mut host).unwrap(); let expected = build_expected(&[2, 3, 1]); assert_eq!(expected, process.stack.trace_state()); // swapping with a minimum stack should be ok let stack = StackInputs::default(); let mut process = Process::new_dummy(stack); - assert!(process.execute_op(Operation::Swap).is_ok()); + assert!(process.execute_op(Operation::Swap, &mut host).is_ok()); } #[test] @@ -432,15 +433,16 @@ mod tests { // push a few items onto the stack let stack = StackInputs::try_from_ints([1, 2, 3, 4, 5, 6, 7, 8, 9]).unwrap(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); - process.execute_op(Operation::SwapW).unwrap(); + process.execute_op(Operation::SwapW, &mut host).unwrap(); let expected = build_expected(&[5, 4, 3, 2, 9, 8, 7, 6, 1]); assert_eq!(expected, process.stack.trace_state()); // swapping with a minimum stack should be ok let stack = StackInputs::default(); let mut process = Process::new_dummy(stack); - assert!(process.execute_op(Operation::SwapW).is_ok()); + assert!(process.execute_op(Operation::SwapW, &mut host).is_ok()); } #[test] @@ -449,15 +451,16 @@ mod tests { let stack = StackInputs::try_from_ints([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]).unwrap(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); - process.execute_op(Operation::SwapW2).unwrap(); + process.execute_op(Operation::SwapW2, &mut host).unwrap(); let expected = build_expected(&[5, 4, 3, 2, 9, 8, 7, 6, 13, 12, 11, 10, 1]); assert_eq!(expected, process.stack.trace_state()); // swapping with a minimum stack should be ok let stack = StackInputs::default(); let mut process = Process::new_dummy(stack); - assert!(process.execute_op(Operation::SwapW2).is_ok()); + assert!(process.execute_op(Operation::SwapW2, &mut host).is_ok()); } #[test] @@ -467,14 +470,15 @@ mod tests { StackInputs::try_from_ints([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]) .unwrap(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); - process.execute_op(Operation::SwapW3).unwrap(); + process.execute_op(Operation::SwapW3, &mut host).unwrap(); let expected = build_expected(&[4, 3, 2, 1, 12, 11, 10, 9, 8, 7, 6, 5, 16, 15, 14, 13]); assert_eq!(expected, process.stack.trace_state()); // swapping with a minimum stack should be ok let mut process = Process::new_dummy_with_empty_stack(); - assert!(process.execute_op(Operation::SwapW3).is_ok()); + assert!(process.execute_op(Operation::SwapW3, &mut host).is_ok()); } #[test] @@ -484,30 +488,31 @@ mod tests { StackInputs::try_from_ints([16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]) .unwrap(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); // movup2 - process.execute_op(Operation::MovUp2).unwrap(); + process.execute_op(Operation::MovUp2, &mut host).unwrap(); let expected = build_expected(&[3, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); assert_eq!(expected, process.stack.trace_state()); // movup3 - process.execute_op(Operation::MovUp3).unwrap(); + process.execute_op(Operation::MovUp3, &mut host).unwrap(); let expected = build_expected(&[4, 3, 1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); assert_eq!(expected, process.stack.trace_state()); // movup7 - process.execute_op(Operation::MovUp7).unwrap(); + process.execute_op(Operation::MovUp7, &mut host).unwrap(); let expected = build_expected(&[8, 4, 3, 1, 2, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16]); assert_eq!(expected, process.stack.trace_state()); // movup8 - process.execute_op(Operation::MovUp8).unwrap(); + process.execute_op(Operation::MovUp8, &mut host).unwrap(); let expected = build_expected(&[9, 8, 4, 3, 1, 2, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16]); assert_eq!(expected, process.stack.trace_state()); // executing movup with a minimum stack depth should be ok let mut process = Process::new_dummy_with_empty_stack(); - assert!(process.execute_op(Operation::MovUp2).is_ok()); + assert!(process.execute_op(Operation::MovUp2, &mut host).is_ok()); } #[test] @@ -517,30 +522,31 @@ mod tests { StackInputs::try_from_ints([16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]) .unwrap(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); // movdn2 - process.execute_op(Operation::MovDn2).unwrap(); + process.execute_op(Operation::MovDn2, &mut host).unwrap(); let expected = build_expected(&[2, 3, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); assert_eq!(expected, process.stack.trace_state()); // movdn3 - process.execute_op(Operation::MovDn3).unwrap(); + process.execute_op(Operation::MovDn3, &mut host).unwrap(); let expected = build_expected(&[3, 1, 4, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); assert_eq!(expected, process.stack.trace_state()); // movdn7 - process.execute_op(Operation::MovDn7).unwrap(); + process.execute_op(Operation::MovDn7, &mut host).unwrap(); let expected = build_expected(&[1, 4, 2, 5, 6, 7, 8, 3, 9, 10, 11, 12, 13, 14, 15, 16]); assert_eq!(expected, process.stack.trace_state()); // movdn15 - process.execute_op(Operation::MovDn8).unwrap(); + process.execute_op(Operation::MovDn8, &mut host).unwrap(); let expected = build_expected(&[4, 2, 5, 6, 7, 8, 3, 9, 1, 10, 11, 12, 13, 14, 15, 16]); assert_eq!(expected, process.stack.trace_state()); // executing movdn with a minimum stack depth should be ok let mut process = Process::new_dummy_with_empty_stack(); - assert!(process.execute_op(Operation::MovDn2).is_ok()); + assert!(process.execute_op(Operation::MovDn2, &mut host).is_ok()); } #[test] @@ -548,23 +554,24 @@ mod tests { // push a few items onto the stack let stack = StackInputs::try_from_ints([4, 3, 2, 1, 0]).unwrap(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); // no swap (top of the stack is 0) - process.execute_op(Operation::CSwap).unwrap(); + process.execute_op(Operation::CSwap, &mut host).unwrap(); let expected = build_expected(&[1, 2, 3, 4]); assert_eq!(expected, process.stack.trace_state()); // swap (top of the stack is 1) - process.execute_op(Operation::CSwap).unwrap(); + process.execute_op(Operation::CSwap, &mut host).unwrap(); let expected = build_expected(&[3, 2, 4]); assert_eq!(expected, process.stack.trace_state()); // error: top of the stack is not binary - assert!(process.execute_op(Operation::CSwap).is_err()); + assert!(process.execute_op(Operation::CSwap, &mut host).is_err()); // executing conditional swap with a minimum stack depth should be ok let mut process = Process::new_dummy_with_empty_stack(); - assert!(process.execute_op(Operation::CSwap).is_ok()); + assert!(process.execute_op(Operation::CSwap, &mut host).is_ok()); } #[test] @@ -572,23 +579,24 @@ mod tests { // push a few items onto the stack let stack = StackInputs::try_from_ints([11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]).unwrap(); let mut process = Process::new_dummy(stack); + let mut host = DefaultHost::default(); // no swap (top of the stack is 0) - process.execute_op(Operation::CSwapW).unwrap(); + process.execute_op(Operation::CSwapW, &mut host).unwrap(); let expected = build_expected(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); assert_eq!(expected, process.stack.trace_state()); // swap (top of the stack is 1) - process.execute_op(Operation::CSwapW).unwrap(); + process.execute_op(Operation::CSwapW, &mut host).unwrap(); let expected = build_expected(&[6, 7, 8, 9, 2, 3, 4, 5, 10, 11]); assert_eq!(expected, process.stack.trace_state()); // error: top of the stack is not binary - assert!(process.execute_op(Operation::CSwapW).is_err()); + assert!(process.execute_op(Operation::CSwapW, &mut host).is_err()); // executing conditional swap with a minimum stack depth should be ok let mut process = Process::new_dummy_with_empty_stack(); - assert!(process.execute_op(Operation::CSwapW).is_ok()); + assert!(process.execute_op(Operation::CSwapW, &mut host).is_ok()); } // HELPER FUNCTIONS diff --git a/processor/src/operations/sys_ops.rs b/processor/src/operations/sys_ops.rs index ea211020fc..b0e356e190 100644 --- a/processor/src/operations/sys_ops.rs +++ b/processor/src/operations/sys_ops.rs @@ -1,27 +1,28 @@ -use vm_core::Operation; +use vm_core::{Felt, Operation}; use super::{ super::{ system::{FMP_MAX, FMP_MIN}, ONE, }, - ExecutionError, Felt, Host, Process, + ExecutionError, Process, }; +use crate::Host; // SYSTEM OPERATIONS // ================================================================================================ -impl Process -where - H: Host, -{ +impl Process { /// Pops a value off the stack and asserts that it is equal to ONE. /// /// # Errors /// Returns an error if the popped value is not ONE. - pub(super) fn op_assert(&mut self, err_code: u32) -> Result<(), ExecutionError> { + pub(super) fn op_assert(&mut self, err_code: u32, host: &mut H) -> Result<(), ExecutionError> + where + H: Host, + { if self.stack.get(0) != ONE { - return Err(self.host.borrow_mut().on_assert_failed(self, err_code)); + return Err(host.on_assert_failed(self, err_code)); } self.stack.shift_left(1); Ok(()) @@ -115,10 +116,13 @@ where // -------------------------------------------------------------------------------------------- /// Forwards the emitted event id to the host. - pub(super) fn op_emit(&mut self, event_id: u32) -> Result<(), ExecutionError> { + pub(super) fn op_emit(&mut self, event_id: u32, host: &mut H) -> Result<(), ExecutionError> + where + H: Host, + { self.stack.copy_state(0); self.decoder.set_user_op_helpers(Operation::Emit(event_id), &[event_id.into()]); - self.host.borrow_mut().on_event(self, event_id)?; + host.on_event(self, event_id)?; Ok(()) } @@ -133,89 +137,92 @@ mod tests { super::{Operation, MIN_STACK_DEPTH}, Felt, Process, FMP_MAX, FMP_MIN, }; - use crate::{StackInputs, ONE, ZERO}; + use crate::{DefaultHost, StackInputs, ONE, ZERO}; const MAX_PROC_LOCALS: u64 = 2_u64.pow(31) - 1; #[test] fn op_assert() { + let mut host = DefaultHost::default(); // calling assert with a minimum stack should be an ok, as long as the top value is ONE let mut process = Process::new_dummy_with_empty_stack(); - process.execute_op(Operation::Push(ONE)).unwrap(); - process.execute_op(Operation::Swap).unwrap(); - process.execute_op(Operation::Drop).unwrap(); + process.execute_op(Operation::Push(ONE), &mut host).unwrap(); + process.execute_op(Operation::Swap, &mut host).unwrap(); + process.execute_op(Operation::Drop, &mut host).unwrap(); - assert!(process.execute_op(Operation::Assert(0)).is_ok()); + assert!(process.execute_op(Operation::Assert(0), &mut host).is_ok()); } #[test] fn op_fmpupdate() { + let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_empty_stack(); // initial value of fmp register should be 2^30 assert_eq!(Felt::new(2_u64.pow(30)), process.system.fmp()); // increment fmp register - process.execute_op(Operation::Push(Felt::new(2))).unwrap(); - process.execute_op(Operation::FmpUpdate).unwrap(); + process.execute_op(Operation::Push(Felt::new(2)), &mut host).unwrap(); + process.execute_op(Operation::FmpUpdate, &mut host).unwrap(); assert_eq!(Felt::new(FMP_MIN + 2), process.system.fmp()); // increment fmp register again - process.execute_op(Operation::Push(Felt::new(3))).unwrap(); - process.execute_op(Operation::FmpUpdate).unwrap(); + process.execute_op(Operation::Push(Felt::new(3)), &mut host).unwrap(); + process.execute_op(Operation::FmpUpdate, &mut host).unwrap(); assert_eq!(Felt::new(FMP_MIN + 5), process.system.fmp()); // decrement fmp register - process.execute_op(Operation::Push(-Felt::new(3))).unwrap(); - process.execute_op(Operation::FmpUpdate).unwrap(); + process.execute_op(Operation::Push(-Felt::new(3)), &mut host).unwrap(); + process.execute_op(Operation::FmpUpdate, &mut host).unwrap(); assert_eq!(Felt::new(FMP_MIN + 2), process.system.fmp()); // decrementing beyond the minimum fmp value should be an error - process.execute_op(Operation::Push(-Felt::new(3))).unwrap(); - assert!(process.execute_op(Operation::FmpUpdate).is_err()); + process.execute_op(Operation::Push(-Felt::new(3)), &mut host).unwrap(); + assert!(process.execute_op(Operation::FmpUpdate, &mut host).is_err()); // going up to the max fmp value should be OK let stack = StackInputs::try_from_ints([MAX_PROC_LOCALS]).unwrap(); let mut process = Process::new_dummy(stack); - process.execute_op(Operation::FmpUpdate).unwrap(); + process.execute_op(Operation::FmpUpdate, &mut host).unwrap(); assert_eq!(Felt::new(FMP_MAX), process.system.fmp()); // but going beyond that should be an error let stack = StackInputs::try_from_ints([MAX_PROC_LOCALS + 1]).unwrap(); let mut process = Process::new_dummy(stack); - assert!(process.execute_op(Operation::FmpUpdate).is_err()); + assert!(process.execute_op(Operation::FmpUpdate, &mut host).is_err()); // should not affect the rest of the stack state let stack = StackInputs::try_from_ints([2, 3]).unwrap(); let mut process = Process::new_dummy(stack); - process.execute_op(Operation::FmpUpdate).unwrap(); + process.execute_op(Operation::FmpUpdate, &mut host).unwrap(); let expected = build_expected_stack(&[2]); assert_eq!(expected, process.stack.trace_state()); // calling fmpupdate with a minimum stack should be ok let mut process = Process::new_dummy_with_empty_stack(); - assert!(process.execute_op(Operation::FmpUpdate).is_ok()); + assert!(process.execute_op(Operation::FmpUpdate, &mut host).is_ok()); } #[test] fn op_fmpadd() { + let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_empty_stack(); // set value of fmp register - process.execute_op(Operation::Push(Felt::new(2))).unwrap(); - process.execute_op(Operation::FmpUpdate).unwrap(); + process.execute_op(Operation::Push(Felt::new(2)), &mut host).unwrap(); + process.execute_op(Operation::FmpUpdate, &mut host).unwrap(); // compute address of the first local - process.execute_op(Operation::Push(-ONE)).unwrap(); - process.execute_op(Operation::FmpAdd).unwrap(); + process.execute_op(Operation::Push(-ONE), &mut host).unwrap(); + process.execute_op(Operation::FmpAdd, &mut host).unwrap(); let expected = build_expected_stack(&[FMP_MIN + 1]); assert_eq!(expected, process.stack.trace_state()); // compute address of second local (also make sure that rest of stack is not affected) - process.execute_op(Operation::Push(-Felt::new(2))).unwrap(); - process.execute_op(Operation::FmpAdd).unwrap(); + process.execute_op(Operation::Push(-Felt::new(2)), &mut host).unwrap(); + process.execute_op(Operation::FmpAdd, &mut host).unwrap(); let expected = build_expected_stack(&[FMP_MIN, FMP_MIN + 1]); assert_eq!(expected, process.stack.trace_state()); @@ -223,22 +230,23 @@ mod tests { #[test] fn op_sdepth() { + let mut host = DefaultHost::default(); // stack is empty let mut process = Process::new_dummy_with_empty_stack(); - process.execute_op(Operation::SDepth).unwrap(); + process.execute_op(Operation::SDepth, &mut host).unwrap(); let expected = build_expected_stack(&[MIN_STACK_DEPTH as u64]); assert_eq!(expected, process.stack.trace_state()); assert_eq!(MIN_STACK_DEPTH + 1, process.stack.depth()); // stack has one item - process.execute_op(Operation::SDepth).unwrap(); + process.execute_op(Operation::SDepth, &mut host).unwrap(); let expected = build_expected_stack(&[MIN_STACK_DEPTH as u64 + 1, MIN_STACK_DEPTH as u64]); assert_eq!(expected, process.stack.trace_state()); assert_eq!(MIN_STACK_DEPTH + 2, process.stack.depth()); // stack has 3 items - process.execute_op(Operation::Pad).unwrap(); - process.execute_op(Operation::SDepth).unwrap(); + process.execute_op(Operation::Pad, &mut host).unwrap(); + process.execute_op(Operation::SDepth, &mut host).unwrap(); let expected = build_expected_stack(&[ MIN_STACK_DEPTH as u64 + 3, 0, @@ -251,22 +259,23 @@ mod tests { #[test] fn op_clk() { + let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_empty_stack(); // initial value of clk register should be 1. - process.execute_op(Operation::Clk).unwrap(); + process.execute_op(Operation::Clk, &mut host).unwrap(); let expected = build_expected_stack(&[1]); assert_eq!(expected, process.stack.trace_state()); // increment clk register. - process.execute_op(Operation::Push(Felt::new(2))).unwrap(); - process.execute_op(Operation::Clk).unwrap(); + process.execute_op(Operation::Push(Felt::new(2)), &mut host).unwrap(); + process.execute_op(Operation::Clk, &mut host).unwrap(); let expected = build_expected_stack(&[3, 2, 1]); assert_eq!(expected, process.stack.trace_state()); // increment clk register again. - process.execute_op(Operation::Push(Felt::new(3))).unwrap(); - process.execute_op(Operation::Clk).unwrap(); + process.execute_op(Operation::Push(Felt::new(3)), &mut host).unwrap(); + process.execute_op(Operation::Clk, &mut host).unwrap(); let expected = build_expected_stack(&[5, 3, 3, 2, 1]); assert_eq!(expected, process.stack.trace_state()); } diff --git a/processor/src/operations/u32_ops.rs b/processor/src/operations/u32_ops.rs index 1800029045..a7dd1b70fb 100644 --- a/processor/src/operations/u32_ops.rs +++ b/processor/src/operations/u32_ops.rs @@ -1,6 +1,6 @@ use super::{ super::utils::{split_element, split_u32_into_u16}, - ExecutionError, Felt, FieldElement, Host, Operation, Process, + ExecutionError, Felt, FieldElement, Operation, Process, }; use crate::ZERO; @@ -20,10 +20,7 @@ macro_rules! require_u32_operand { }}; } -impl Process -where - H: Host, -{ +impl Process { // CASTING OPERATIONS // -------------------------------------------------------------------------------------------- @@ -249,7 +246,7 @@ mod tests { super::{Felt, Operation}, split_u32_into_u16, Process, }; - use crate::{StackInputs, ZERO}; + use crate::{DefaultHost, StackInputs, ZERO}; // CASTING OPERATIONS // -------------------------------------------------------------------------------------------- @@ -257,13 +254,14 @@ mod tests { #[test] fn op_u32split() { // --- test a random value --------------------------------------------- + let mut host = DefaultHost::default(); let a: u64 = rand_value(); let stack = StackInputs::try_from_ints([a]).unwrap(); let mut process = Process::new_dummy_with_decoder_helpers(stack); let hi = a >> 32; let lo = (a as u32) as u64; - process.execute_op(Operation::U32split).unwrap(); + process.execute_op(Operation::U32split, &mut host).unwrap(); let mut expected = [ZERO; 16]; expected[0] = Felt::new(hi); expected[1] = Felt::new(lo); @@ -276,7 +274,7 @@ mod tests { let hi = b >> 32; let lo = (b as u32) as u64; - process.execute_op(Operation::U32split).unwrap(); + process.execute_op(Operation::U32split, &mut host).unwrap(); let mut expected = [ZERO; 16]; expected[0] = Felt::new(hi); expected[1] = Felt::new(lo); @@ -287,11 +285,12 @@ mod tests { #[test] fn op_u32assert2() { // --- test random values ensuring other elements are still values are still intact ------- + let mut host = DefaultHost::default(); let (a, b, c, d) = get_rand_values(); let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap(); let mut process = Process::new_dummy_with_decoder_helpers(stack); - process.execute_op(Operation::U32assert2(0)).unwrap(); + process.execute_op(Operation::U32assert2(0), &mut host).unwrap(); let expected = build_expected(&[a, b, c, d]); assert_eq!(expected, process.stack.trace_state()); } @@ -302,12 +301,13 @@ mod tests { #[test] fn op_u32add() { // --- test random values --------------------------------------------- + let mut host = DefaultHost::default(); let (a, b, c, d) = get_rand_values(); let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap(); let mut process = Process::new_dummy_with_decoder_helpers(stack); let (result, over) = a.overflowing_add(b); - process.execute_op(Operation::U32add).unwrap(); + process.execute_op(Operation::U32add, &mut host).unwrap(); let expected = build_expected(&[over as u32, result, c, d]); assert_eq!(expected, process.stack.trace_state()); @@ -320,7 +320,7 @@ mod tests { let (result, over) = a.overflowing_add(b); let (b1, b0) = split_u32_into_u16(result.into()); - process.execute_op(Operation::U32add).unwrap(); + process.execute_op(Operation::U32add, &mut host).unwrap(); let expected = build_expected(&[over as u32, result]); assert_eq!(expected, process.stack.trace_state()); @@ -331,6 +331,7 @@ mod tests { #[test] fn op_u32add3() { + let mut host = DefaultHost::default(); let a = rand_value::() as u64; let b = rand_value::() as u64; let c = rand_value::() as u64; @@ -344,24 +345,25 @@ mod tests { let lo = result as u32; assert!(hi <= 2); - process.execute_op(Operation::U32add3).unwrap(); + process.execute_op(Operation::U32add3, &mut host).unwrap(); let expected = build_expected(&[hi, lo, d as u32]); assert_eq!(expected, process.stack.trace_state()); // --- test with minimum stack depth ---------------------------------- let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); - assert!(process.execute_op(Operation::U32add3).is_ok()); + assert!(process.execute_op(Operation::U32add3, &mut host).is_ok()); } #[test] fn op_u32sub() { // --- test random values --------------------------------------------- + let mut host = DefaultHost::default(); let (a, b, c, d) = get_rand_values(); let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap(); let mut process = Process::new_dummy_with_decoder_helpers(stack); let (result, under) = b.overflowing_sub(a); - process.execute_op(Operation::U32sub).unwrap(); + process.execute_op(Operation::U32sub, &mut host).unwrap(); let expected = build_expected(&[under as u32, result, c, d]); assert_eq!(expected, process.stack.trace_state()); @@ -373,13 +375,14 @@ mod tests { let mut process = Process::new_dummy_with_decoder_helpers(stack); let (result, under) = a.overflowing_sub(b); - process.execute_op(Operation::U32sub).unwrap(); + process.execute_op(Operation::U32sub, &mut host).unwrap(); let expected = build_expected(&[under as u32, result]); assert_eq!(expected, process.stack.trace_state()); } #[test] fn op_u32mul() { + let mut host = DefaultHost::default(); let (a, b, c, d) = get_rand_values(); let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap(); let mut process = Process::new_dummy_with_decoder_helpers(stack); @@ -387,13 +390,14 @@ mod tests { let hi = (result >> 32) as u32; let lo = result as u32; - process.execute_op(Operation::U32mul).unwrap(); + process.execute_op(Operation::U32mul, &mut host).unwrap(); let expected = build_expected(&[hi, lo, c, d]); assert_eq!(expected, process.stack.trace_state()); } #[test] fn op_u32madd() { + let mut host = DefaultHost::default(); let (a, b, c, d) = get_rand_values(); let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap(); let mut process = Process::new_dummy_with_decoder_helpers(stack); @@ -401,24 +405,25 @@ mod tests { let hi = (result >> 32) as u32; let lo = result as u32; - process.execute_op(Operation::U32madd).unwrap(); + process.execute_op(Operation::U32madd, &mut host).unwrap(); let expected = build_expected(&[hi, lo, d]); assert_eq!(expected, process.stack.trace_state()); // --- test with minimum stack depth ---------------------------------- let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); - assert!(process.execute_op(Operation::U32madd).is_ok()); + assert!(process.execute_op(Operation::U32madd, &mut host).is_ok()); } #[test] fn op_u32div() { + let mut host = DefaultHost::default(); let (a, b, c, d) = get_rand_values(); let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap(); let mut process = Process::new_dummy_with_decoder_helpers(stack); let q = b / a; let r = b % a; - process.execute_op(Operation::U32div).unwrap(); + process.execute_op(Operation::U32div, &mut host).unwrap(); let expected = build_expected(&[r, q, c, d]); assert_eq!(expected, process.stack.trace_state()); } @@ -428,32 +433,34 @@ mod tests { #[test] fn op_u32and() { + let mut host = DefaultHost::default(); let (a, b, c, d) = get_rand_values(); let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap(); let mut process = Process::new_dummy_with_decoder_helpers(stack); - process.execute_op(Operation::U32and).unwrap(); + process.execute_op(Operation::U32and, &mut host).unwrap(); let expected = build_expected(&[a & b, c, d]); assert_eq!(expected, process.stack.trace_state()); // --- test with minimum stack depth ---------------------------------- let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); - assert!(process.execute_op(Operation::U32and).is_ok()); + assert!(process.execute_op(Operation::U32and, &mut host).is_ok()); } #[test] fn op_u32xor() { + let mut host = DefaultHost::default(); let (a, b, c, d) = get_rand_values(); let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap(); let mut process = Process::new_dummy_with_decoder_helpers(stack); - process.execute_op(Operation::U32xor).unwrap(); + process.execute_op(Operation::U32xor, &mut host).unwrap(); let expected = build_expected(&[a ^ b, c, d]); assert_eq!(expected, process.stack.trace_state()); // --- test with minimum stack depth ---------------------------------- let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); - assert!(process.execute_op(Operation::U32xor).is_ok()); + assert!(process.execute_op(Operation::U32xor, &mut host).is_ok()); } // HELPER FUNCTIONS diff --git a/processor/src/system/tests.rs b/processor/src/system/tests.rs index e59598b0d2..ade4e12661 100644 --- a/processor/src/system/tests.rs +++ b/processor/src/system/tests.rs @@ -4,15 +4,14 @@ use crate::{DefaultHost, ExecutionOptions, Kernel, Operation, Process, StackInpu #[test] fn cycles_num_exceeded() { let stack = StackInputs::default(); - let host = DefaultHost::default(); + let mut host = DefaultHost::default(); let mut process = Process::new( Kernel::default(), stack, - host, ExecutionOptions::new(Some(64), 64, false, false).unwrap(), ); for _ in 0..64 { - process.execute_op(Operation::Noop).unwrap(); + process.execute_op(Operation::Noop, &mut host).unwrap(); } - assert!(process.execute_op(Operation::Noop).is_err()); + assert!(process.execute_op(Operation::Noop, &mut host).is_err()); } diff --git a/processor/src/trace/mod.rs b/processor/src/trace/mod.rs index df1e2c4af1..5ad22d0014 100644 --- a/processor/src/trace/mod.rs +++ b/processor/src/trace/mod.rs @@ -13,8 +13,7 @@ use super::{ chiplets::AuxTraceBuilder as ChipletsAuxTraceBuilder, crypto::RpoRandomCoin, decoder::AuxTraceBuilder as DecoderAuxTraceBuilder, range::AuxTraceBuilder as RangeCheckerAuxTraceBuilder, - stack::AuxTraceBuilder as StackAuxTraceBuilder, ColMatrix, Digest, Felt, FieldElement, Host, - Process, + stack::AuxTraceBuilder as StackAuxTraceBuilder, ColMatrix, Digest, Felt, FieldElement, Process, }; mod utils; @@ -68,10 +67,7 @@ impl ExecutionTrace { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- /// Builds an execution trace for the provided process. - pub fn new(process: Process, stack_outputs: StackOutputs) -> Self - where - H: Host, - { + pub fn new(process: Process, stack_outputs: StackOutputs) -> Self { // use program hash to initialize random element generator; this generator will be used // to inject random values at the end of the trace; using program hash here is OK because // we are using random values only to stabilize constraint degrees, and not to achieve @@ -185,12 +181,7 @@ impl ExecutionTrace { } #[cfg(test)] - pub fn test_finalize_trace( - process: Process, - ) -> (MainTrace, AuxTraceBuilders, TraceLenSummary) - where - H: Host, - { + pub fn test_finalize_trace(process: Process) -> (MainTrace, AuxTraceBuilders, TraceLenSummary) { let rng = RpoRandomCoin::new(EMPTY_WORD); finalize_trace(process, rng) } @@ -275,14 +266,11 @@ impl Trace for ExecutionTrace { /// - Inserting random values in the last row of all columns. This helps ensure that there are no /// repeating patterns in each column and each column contains a least two distinct values. This, /// in turn, ensures that polynomial degrees of all columns are stable. -fn finalize_trace( - process: Process, +fn finalize_trace( + process: Process, mut rng: RpoRandomCoin, -) -> (MainTrace, AuxTraceBuilders, TraceLenSummary) -where - H: Host, -{ - let (system, decoder, stack, mut range, chiplets, _) = process.into_parts(); +) -> (MainTrace, AuxTraceBuilders, TraceLenSummary) { + let (system, decoder, stack, mut range, chiplets) = process.into_parts(); let clk = system.clk(); diff --git a/processor/src/trace/tests/mod.rs b/processor/src/trace/tests/mod.rs index 42524d68f9..e6da402639 100644 --- a/processor/src/trace/tests/mod.rs +++ b/processor/src/trace/tests/mod.rs @@ -21,10 +21,9 @@ mod stack; /// Builds a sample trace by executing the provided code block against the provided stack inputs. pub fn build_trace_from_program(program: &Program, stack_inputs: &[u64]) -> ExecutionTrace { let stack_inputs = StackInputs::try_from_ints(stack_inputs.iter().copied()).unwrap(); - let host = DefaultHost::default(); - let mut process = - Process::new(Kernel::default(), stack_inputs, host, ExecutionOptions::default()); - process.execute(program).unwrap(); + let mut host = DefaultHost::default(); + let mut process = Process::new(Kernel::default(), stack_inputs, ExecutionOptions::default()); + process.execute(program, &mut host).unwrap(); ExecutionTrace::new(process, StackOutputs::default()) } @@ -50,9 +49,8 @@ pub fn build_trace_from_ops_with_inputs( advice_inputs: AdviceInputs, ) -> ExecutionTrace { let advice_provider = MemAdviceProvider::from(advice_inputs); - let host = DefaultHost::new(advice_provider); - let mut process = - Process::new(Kernel::default(), stack_inputs, host, ExecutionOptions::default()); + let mut host = DefaultHost::new(advice_provider); + let mut process = Process::new(Kernel::default(), stack_inputs, ExecutionOptions::default()); let mut mast_forest = MastForest::new(); let basic_block_id = mast_forest.add_block(operations, None).unwrap(); @@ -60,6 +58,6 @@ pub fn build_trace_from_ops_with_inputs( let program = Program::new(mast_forest.into(), basic_block_id); - process.execute(&program).unwrap(); + process.execute(&program, &mut host).unwrap(); ExecutionTrace::new(process, StackOutputs::default()) } diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 2ba063bc2d..09ab1de7e1 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -55,15 +55,12 @@ pub use winter_prover::{crypto::MerkleTree as MerkleTreeVC, Proof}; /// Returns an error if program execution or STARK proof generation fails for any reason. #[instrument("prove_program", skip_all)] #[maybe_async] -pub fn prove( +pub fn prove( program: &Program, stack_inputs: StackInputs, - host: H, + host: &mut impl Host, options: ProvingOptions, -) -> Result<(StackOutputs, ExecutionProof), ExecutionError> -where - H: Host, -{ +) -> Result<(StackOutputs, ExecutionProof), ExecutionError> { // execute the program to create an execution trace #[cfg(feature = "std")] let now = Instant::now(); diff --git a/stdlib/tests/collections/mmr.rs b/stdlib/tests/collections/mmr.rs index 36fe1c5486..5d9156c3de 100644 --- a/stdlib/tests/collections/mmr.rs +++ b/stdlib/tests/collections/mmr.rs @@ -522,9 +522,8 @@ fn test_mmr_pack() { expect_data.extend_from_slice(&[Felt::new(3), ZERO, ZERO, ZERO]); // num_leaves expect_data.extend_from_slice(&hash_data); - let process = build_test!(source).execute_process().unwrap(); + let (_, host) = build_test!(source).execute_process().unwrap(); - let host = process.host.borrow_mut(); let advice_data = host.advice_provider().map().get(&hash_u8).unwrap(); assert_eq!(advice_data, &expect_data); } diff --git a/stdlib/tests/crypto/falcon.rs b/stdlib/tests/crypto/falcon.rs index 260136a467..eb27962348 100644 --- a/stdlib/tests/crypto/falcon.rs +++ b/stdlib/tests/crypto/falcon.rs @@ -208,11 +208,12 @@ fn falcon_prove_verify() { let stack_inputs = StackInputs::try_from_ints(op_stack).expect("failed to create stack inputs"); let advice_inputs = AdviceInputs::default().with_map(advice_map); let advice_provider = MemAdviceProvider::from(advice_inputs); - let host = DefaultHost::new(advice_provider); + let mut host = DefaultHost::new(advice_provider); let options = ProvingOptions::with_96_bit_security(false); - let (stack_outputs, proof) = test_utils::prove(&program, stack_inputs.clone(), host, options) - .expect("failed to generate proof"); + let (stack_outputs, proof) = + test_utils::prove(&program, stack_inputs.clone(), &mut host, options) + .expect("failed to generate proof"); let program_info = ProgramInfo::from(program); let result = test_utils::verify(program_info, stack_inputs, stack_outputs, proof); diff --git a/stdlib/tests/crypto/stark/mod.rs b/stdlib/tests/crypto/stark/mod.rs index 6cf9226a2a..b81a6f4b2b 100644 --- a/stdlib/tests/crypto/stark/mod.rs +++ b/stdlib/tests/crypto/stark/mod.rs @@ -50,12 +50,12 @@ pub fn generate_recursive_verifier_data( let stack_inputs = StackInputs::try_from_ints(stack_inputs).unwrap(); let advice_inputs = AdviceInputs::default(); let advice_provider = MemAdviceProvider::from(advice_inputs); - let host = DefaultHost::new(advice_provider); + let mut host = DefaultHost::new(advice_provider); let options = ProvingOptions::new(43, 8, 12, FieldExtension::Quadratic, 4, 7, HashFunction::Rpo256); - let (stack_outputs, proof) = prove(&program, stack_inputs.clone(), host, options).unwrap(); + let (stack_outputs, proof) = prove(&program, stack_inputs.clone(), &mut host, options).unwrap(); let program_info = ProgramInfo::from(program); diff --git a/stdlib/tests/mem/mod.rs b/stdlib/tests/mem/mod.rs index 61c3836f9d..abb280fe7e 100644 --- a/stdlib/tests/mem/mod.rs +++ b/stdlib/tests/mem/mod.rs @@ -33,13 +33,9 @@ fn test_memcopy() { let mut host = DefaultHost::default(); host.load_mast_forest(stdlib.mast_forest().clone()); - let mut process = Process::new( - program.kernel().clone(), - StackInputs::default(), - host, - ExecutionOptions::default(), - ); - process.execute(&program).unwrap(); + let mut process = + Process::new(program.kernel().clone(), StackInputs::default(), ExecutionOptions::default()); + process.execute(&program, &mut host).unwrap(); assert_eq!( process.get_mem_value(ContextId::root(), 1000), diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index c2515702d8..919b300131 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -250,10 +250,9 @@ impl Test { let mut process = Process::new( program.kernel().clone(), self.stack_inputs.clone(), - host, ExecutionOptions::default(), ); - process.execute(&program).unwrap(); + process.execute(&program, &mut host).unwrap(); // validate the memory state for data in expected_mem.chunks(WORD_SIZE) { @@ -343,14 +342,19 @@ impl Test { for library in &self.libraries { host.load_mast_forest(library.mast_forest().clone()); } - processor::execute(&program, self.stack_inputs.clone(), host, ExecutionOptions::default()) + processor::execute( + &program, + self.stack_inputs.clone(), + &mut host, + ExecutionOptions::default(), + ) } /// Compiles the test's source to a Program and executes it with the tests inputs. Returns the /// process once execution is finished. pub fn execute_process( &self, - ) -> Result>, ExecutionError> { + ) -> Result<(Process, DefaultHost), ExecutionError> { let (program, kernel) = self.compile().expect("Failed to compile test source."); let mut host = DefaultHost::new(MemAdviceProvider::from(self.advice_inputs.clone())); if let Some(kernel) = kernel { @@ -363,11 +367,10 @@ impl Test { let mut process = Process::new( program.kernel().clone(), self.stack_inputs.clone(), - host, ExecutionOptions::default(), ); - process.execute(&program)?; - Ok(process) + process.execute(&program, &mut host)?; + Ok((process, host)) } /// Compiles the test's code into a program, then generates and verifies a proof of execution @@ -384,7 +387,8 @@ impl Test { host.load_mast_forest(library.mast_forest().clone()); } let (mut stack_outputs, proof) = - prover::prove(&program, stack_inputs.clone(), host, ProvingOptions::default()).unwrap(); + prover::prove(&program, stack_inputs.clone(), &mut host, ProvingOptions::default()) + .unwrap(); let program_info = ProgramInfo::from(program); if test_fail { @@ -408,7 +412,7 @@ impl Test { for library in &self.libraries { host.load_mast_forest(library.mast_forest().clone()); } - processor::execute_iter(&program, self.stack_inputs.clone(), host) + processor::execute_iter(&program, self.stack_inputs.clone(), &mut host) } /// Returns the last state of the stack after executing a test. From b7e9d44be7de795d8cbf9fc6a9b7bd7e066d1d74 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Wed, 6 Nov 2024 13:25:47 -0500 Subject: [PATCH 04/39] refactor: make `ProcessState` a struct --- CHANGELOG.md | 3 +- .../integration/operations/decorators/mod.rs | 20 ++-- processor/src/chiplets/bitwise/mod.rs | 1 + processor/src/chiplets/hasher/mod.rs | 2 +- processor/src/chiplets/hasher/trace.rs | 2 +- processor/src/chiplets/kernel_rom/mod.rs | 2 + processor/src/chiplets/memory/mod.rs | 2 +- processor/src/chiplets/memory/segment.rs | 2 +- processor/src/chiplets/mod.rs | 1 + .../advice/injectors/adv_map_injectors.rs | 20 ++-- .../advice/injectors/adv_stack_injectors.rs | 48 ++++---- .../injectors/merkle_store_injectors.rs | 4 +- processor/src/host/advice/injectors/smt.rs | 12 +- processor/src/host/advice/mod.rs | 105 ++++++++---------- processor/src/host/advice/providers.rs | 21 ++-- processor/src/host/debug.rs | 15 +-- processor/src/host/mod.rs | 66 +++++------ processor/src/lib.rs | 92 ++++++++------- processor/src/operations/crypto_ops.rs | 5 +- processor/src/operations/io_ops.rs | 6 +- processor/src/operations/sys_ops.rs | 4 +- processor/src/stack/mod.rs | 1 + processor/src/stack/overflow.rs | 1 + processor/src/stack/trace.rs | 1 + processor/src/system/mod.rs | 1 + stdlib/tests/mem/mod.rs | 22 ++-- test-utils/src/lib.rs | 6 +- 27 files changed, 231 insertions(+), 234 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d351dc1e54..a18885e6a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Changelog #### Changes -- [BREAKING] `Process` no longer takes ownership of the `Host` (#1563) +- [BREAKING] `Process` no longer takes ownership of the `Host` (#1571) +- [BREAKING] `ProcessState` was converted from a trait to a struct (#1571) ## 0.11.0 (2024-11-04) diff --git a/miden/tests/integration/operations/decorators/mod.rs b/miden/tests/integration/operations/decorators/mod.rs index ce4fb82afc..9e4a17f353 100644 --- a/miden/tests/integration/operations/decorators/mod.rs +++ b/miden/tests/integration/operations/decorators/mod.rs @@ -31,43 +31,43 @@ impl Default for TestHost { } impl Host for TestHost { - fn get_advice( + fn get_advice( &mut self, - process: &S, + process: ProcessState, extractor: AdviceExtractor, ) -> Result { self.adv_provider.get_advice(process, &extractor) } - fn set_advice( + fn set_advice( &mut self, - process: &S, + process: ProcessState, injector: AdviceInjector, ) -> Result { self.adv_provider.set_advice(process, &injector) } - fn on_event( + fn on_event( &mut self, - _process: &S, + _process: ProcessState, event_id: u32, ) -> Result { self.event_handler.push(event_id); Ok(HostResponse::None) } - fn on_trace( + fn on_trace( &mut self, - _process: &S, + _process: ProcessState, trace_id: u32, ) -> Result { self.trace_handler.push(trace_id); Ok(HostResponse::None) } - fn on_debug( + fn on_debug( &mut self, - _process: &S, + _process: ProcessState, _options: &DebugOptions, ) -> Result { self.debug_handler.push(_options.to_string()); diff --git a/processor/src/chiplets/bitwise/mod.rs b/processor/src/chiplets/bitwise/mod.rs index b6f71c23fb..48addfebc3 100644 --- a/processor/src/chiplets/bitwise/mod.rs +++ b/processor/src/chiplets/bitwise/mod.rs @@ -48,6 +48,7 @@ const INIT_TRACE_CAPACITY: usize = 128; /// significant 4-bit limbs of the input values. With every subsequent row, the next most /// significant 4-bit limb of the result is appended to it. Thus, by the 8th row, column `z` /// contains the full result of the bitwise operation. +#[derive(Debug)] pub struct Bitwise { trace: [Vec; TRACE_WIDTH], } diff --git a/processor/src/chiplets/hasher/mod.rs b/processor/src/chiplets/hasher/mod.rs index 1aefb30b74..eba72ff77a 100644 --- a/processor/src/chiplets/hasher/mod.rs +++ b/processor/src/chiplets/hasher/mod.rs @@ -54,7 +54,7 @@ mod tests; /// the trace of a control or span block that can be copied to be used later for program blocks /// encountered with the same digest instead of building it from scratch everytime. The hash of /// the block is used as the key here after converting it to a bytes array. -#[derive(Default)] +#[derive(Debug, Default)] pub struct Hasher { trace: HasherTrace, memoized_trace_map: BTreeMap<[u8; 32], (usize, usize)>, diff --git a/processor/src/chiplets/hasher/trace.rs b/processor/src/chiplets/hasher/trace.rs index d9dbe59b4f..0c6226b99b 100644 --- a/processor/src/chiplets/hasher/trace.rs +++ b/processor/src/chiplets/hasher/trace.rs @@ -15,7 +15,7 @@ use super::{Felt, HasherState, Selectors, TraceFragment, STATE_WIDTH, TRACE_WIDT /// - 3 selector columns. /// - 12 columns describing hasher state. /// - 1 node index column used for Merkle path related computations. -#[derive(Default)] +#[derive(Debug, Default)] pub struct HasherTrace { selectors: [Vec; 3], hasher_state: [Vec; STATE_WIDTH], diff --git a/processor/src/chiplets/kernel_rom/mod.rs b/processor/src/chiplets/kernel_rom/mod.rs index 4e0a2af849..226d3097a5 100644 --- a/processor/src/chiplets/kernel_rom/mod.rs +++ b/processor/src/chiplets/kernel_rom/mod.rs @@ -38,6 +38,7 @@ type ProcHashBytes = [u8; 32]; /// change. /// - `h0` - `h3` columns contain roots of procedures in a given kernel. Together with `idx` column, /// these form tuples (index, procedure root) for all procedures in the kernel. +#[derive(Debug)] pub struct KernelRom { access_map: BTreeMap, kernel: Kernel, @@ -128,6 +129,7 @@ impl KernelRom { // ================================================================================================ /// Procedure access information for a given kernel procedure. +#[derive(Debug)] struct ProcAccessInfo { proc_hash: Word, num_accesses: usize, diff --git a/processor/src/chiplets/memory/mod.rs b/processor/src/chiplets/memory/mod.rs index 76690130c3..99f78d0a62 100644 --- a/processor/src/chiplets/memory/mod.rs +++ b/processor/src/chiplets/memory/mod.rs @@ -76,7 +76,7 @@ const INIT_MEM_VALUE: Word = EMPTY_WORD; /// clock cycles computed as described above. /// /// For the first row of the trace, values in `d0`, `d1`, and `d_inv` are set to zeros. -#[derive(Default)] +#[derive(Debug, Default)] pub struct Memory { /// Memory segment traces sorted by their execution context ID. trace: BTreeMap, diff --git a/processor/src/chiplets/memory/segment.rs b/processor/src/chiplets/memory/segment.rs index 4bee448fc1..4957286564 100644 --- a/processor/src/chiplets/memory/segment.rs +++ b/processor/src/chiplets/memory/segment.rs @@ -19,7 +19,7 @@ use crate::{ContextId, ExecutionError}; /// A memory segment is an isolated address space accessible from a specific execution context. /// Within each segment, the memory is word-addressable. That is, four field elements are located /// at each memory address, and we can read and write elements to/from memory in batches of four. -#[derive(Default)] +#[derive(Debug, Default)] pub struct MemorySegmentTrace(BTreeMap>); impl MemorySegmentTrace { diff --git a/processor/src/chiplets/mod.rs b/processor/src/chiplets/mod.rs index a9fe8b7776..d2c8c13aae 100644 --- a/processor/src/chiplets/mod.rs +++ b/processor/src/chiplets/mod.rs @@ -113,6 +113,7 @@ mod tests; /// | 1 | 1 | 1 | 1 |---------------------------------------------------------| /// +---+---+---+---+---------------------------------------------------------+ /// ``` +#[derive(Debug)] pub struct Chiplets { /// Current clock cycle of the VM. clk: RowIndex, diff --git a/processor/src/host/advice/injectors/adv_map_injectors.rs b/processor/src/host/advice/injectors/adv_map_injectors.rs index 2577c4037c..4eab49baf6 100644 --- a/processor/src/host/advice/injectors/adv_map_injectors.rs +++ b/processor/src/host/advice/injectors/adv_map_injectors.rs @@ -29,9 +29,9 @@ use crate::ProcessState; /// - `start_addr` is greater than or equal to 2^32. /// - `end_addr` is greater than or equal to 2^32. /// - `start_addr` > `end_addr`. -pub(crate) fn insert_mem_values_into_adv_map( +pub(crate) fn insert_mem_values_into_adv_map( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { let (start_addr, end_addr) = get_mem_addr_range(process, 4, 5)?; let ctx = process.ctx(); @@ -61,9 +61,9 @@ pub(crate) fn insert_mem_values_into_adv_map /// /// Where KEY is computed as hash(A || B, domain), where domain is provided via the immediate /// value. -pub(crate) fn insert_hdword_into_adv_map( +pub(crate) fn insert_hdword_into_adv_map( advice_provider: &mut A, - process: &S, + process: ProcessState, domain: Felt, ) -> Result { // get the top two words from the stack and hash them to compute the key value @@ -94,9 +94,9 @@ pub(crate) fn insert_hdword_into_adv_map( /// /// Where KEY is computed by extracting the digest elements from hperm([C, A, B]). For example, /// if C is [0, d, 0, 0], KEY will be set as hash(A || B, d). -pub(crate) fn insert_hperm_into_adv_map( +pub(crate) fn insert_hperm_into_adv_map( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { // read the state from the stack let mut state = [ @@ -145,9 +145,9 @@ pub(crate) fn insert_hperm_into_adv_map( /// provider (i.e., the input trees are not removed). /// /// It is not checked whether the provided roots exist as Merkle trees in the advide providers. -pub(crate) fn merge_merkle_nodes( +pub(crate) fn merge_merkle_nodes( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { // fetch the arguments from the stack let lhs = process.get_stack_word(1); @@ -164,8 +164,8 @@ pub(crate) fn merge_merkle_nodes( /// Reads (start_addr, end_addr) tuple from the specified elements of the operand stack ( /// without modifying the state of the stack), and verifies that memory range is valid. -fn get_mem_addr_range( - process: &S, +fn get_mem_addr_range( + process: ProcessState, start_idx: usize, end_idx: usize, ) -> Result<(u32, u32), ExecutionError> { diff --git a/processor/src/host/advice/injectors/adv_stack_injectors.rs b/processor/src/host/advice/injectors/adv_stack_injectors.rs index 9363e49c00..60f4678602 100644 --- a/processor/src/host/advice/injectors/adv_stack_injectors.rs +++ b/processor/src/host/advice/injectors/adv_stack_injectors.rs @@ -32,9 +32,9 @@ type QuadFelt = QuadExtension; /// - The specified depth is either zero or greater than the depth of the Merkle tree identified by /// the specified root. /// - Value of the node at the specified depth and index is not known to the advice provider. -pub(crate) fn copy_merkle_node_to_adv_stack( +pub(crate) fn copy_merkle_node_to_adv_stack( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { // read node depth, node index, and tree root from the stack let depth = process.get_stack_item(0); @@ -83,9 +83,9 @@ pub(crate) fn copy_merkle_node_to_adv_stack( /// # Errors /// Returns an error if the required key was not found in the key-value map or if stack offset /// is greater than 12. -pub(crate) fn copy_map_value_to_adv_stack( +pub(crate) fn copy_map_value_to_adv_stack( advice_provider: &mut A, - process: &S, + process: ProcessState, include_len: bool, key_offset: usize, ) -> Result { @@ -122,9 +122,9 @@ pub(crate) fn copy_map_value_to_adv_stack( /// /// # Errors /// Returns an error if the divisor is ZERO. -pub(crate) fn push_u64_div_result( +pub(crate) fn push_u64_div_result( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { let divisor_hi = process.get_stack_item(0).as_int(); let divisor_lo = process.get_stack_item(1).as_int(); @@ -168,9 +168,9 @@ pub(crate) fn push_u64_div_result( /// /// # Errors /// Returns an error if the input is a zero element in the extension field. -pub(crate) fn push_ext2_inv_result( +pub(crate) fn push_ext2_inv_result( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { let coef0 = process.get_stack_item(1); let coef1 = process.get_stack_item(0); @@ -215,9 +215,9 @@ pub(crate) fn push_ext2_inv_result( /// - `output_size` is 0 or is greater than the `input_size`. /// - `input_ptr` is greater than 2^32. /// - `input_ptr + input_size / 2` is greater than 2^32. -pub(crate) fn push_ext2_intt_result( +pub(crate) fn push_ext2_intt_result( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { let output_size = process.get_stack_item(0).as_int() as usize; let input_size = process.get_stack_item(1).as_int() as usize; @@ -284,9 +284,9 @@ pub(crate) fn push_ext2_intt_result( /// - DATA is the needed data for signature verification in the VM. /// /// The advice provider is expected to contain the private key associated to the public key PK. -pub(crate) fn push_signature( +pub(crate) fn push_signature( advice_provider: &mut A, - process: &S, + process: ProcessState, kind: SignatureKind, ) -> Result { let pub_key = process.get_stack_word(0); @@ -307,9 +307,9 @@ pub(crate) fn push_signature( /// Outputs: /// Operand stack: [n, ...] /// Advice stack: [leading_zeros, ...] -pub(crate) fn push_leading_zeros( +pub(crate) fn push_leading_zeros( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { push_transformed_stack_top(advice_provider, process, |stack_top| { Felt::from(stack_top.leading_zeros()) @@ -325,9 +325,9 @@ pub(crate) fn push_leading_zeros( /// Outputs: /// Operand stack: [n, ...] /// Advice stack: [trailing_zeros, ...] -pub(crate) fn push_trailing_zeros( +pub(crate) fn push_trailing_zeros( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { push_transformed_stack_top(advice_provider, process, |stack_top| { Felt::from(stack_top.trailing_zeros()) @@ -343,9 +343,9 @@ pub(crate) fn push_trailing_zeros( /// Outputs: /// Operand stack: [n, ...] /// Advice stack: [leading_ones, ...] -pub(crate) fn push_leading_ones( +pub(crate) fn push_leading_ones( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { push_transformed_stack_top(advice_provider, process, |stack_top| { Felt::from(stack_top.leading_ones()) @@ -361,9 +361,9 @@ pub(crate) fn push_leading_ones( /// Outputs: /// Operand stack: [n, ...] /// Advice stack: [trailing_ones, ...] -pub(crate) fn push_trailing_ones( +pub(crate) fn push_trailing_ones( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { push_transformed_stack_top(advice_provider, process, |stack_top| { Felt::from(stack_top.trailing_ones()) @@ -381,9 +381,9 @@ pub(crate) fn push_trailing_ones( /// /// # Errors /// Returns an error if the logarithm argument (top stack element) equals ZERO. -pub(crate) fn push_ilog2( +pub(crate) fn push_ilog2( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { let n = process.get_stack_item(0).as_int(); if n == 0 { @@ -405,9 +405,9 @@ fn u64_to_u32_elements(value: u64) -> (Felt, Felt) { /// Gets the top stack element, applies a provided function to it and pushes it to the advice /// provider. -fn push_transformed_stack_top( +fn push_transformed_stack_top( advice_provider: &mut A, - process: &S, + process: ProcessState, f: impl FnOnce(u32) -> Felt, ) -> Result { let stack_top = process.get_stack_item(0); diff --git a/processor/src/host/advice/injectors/merkle_store_injectors.rs b/processor/src/host/advice/injectors/merkle_store_injectors.rs index 565afbcd41..bf6166913d 100644 --- a/processor/src/host/advice/injectors/merkle_store_injectors.rs +++ b/processor/src/host/advice/injectors/merkle_store_injectors.rs @@ -1,8 +1,8 @@ use super::super::{AdviceProvider, ExecutionError, HostResponse, ProcessState}; -pub(crate) fn update_operand_stack_merkle_node( +pub(crate) fn update_operand_stack_merkle_node( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { let depth = process.get_stack_item(4); let index = process.get_stack_item(5); diff --git a/processor/src/host/advice/injectors/smt.rs b/processor/src/host/advice/injectors/smt.rs index 8481e11431..544c8ee4b4 100644 --- a/processor/src/host/advice/injectors/smt.rs +++ b/processor/src/host/advice/injectors/smt.rs @@ -30,9 +30,9 @@ use crate::{AdviceProvider, ProcessState}; /// /// # Errors /// Returns an error if the provided Merkle root doesn't exist on the advice provider. -pub(crate) fn push_smtpeek_result( +pub(crate) fn push_smtpeek_result( advice_provider: &mut A, - process: &S, + process: ProcessState, ) -> Result { let empty_leaf = EmptySubtreeRoots::entry(SMT_DEPTH, SMT_DEPTH); // fetch the arguments from the operand stack @@ -68,17 +68,17 @@ pub(crate) fn push_smtpeek_result( } /// Currently unimplemented -pub(crate) fn push_smtget_inputs( +pub(crate) fn push_smtget_inputs( _advice_provider: &mut A, - _process: &S, + _process: ProcessState, ) -> Result { unimplemented!() } /// Currently unimplemented -pub(crate) fn push_smtset_inputs( +pub(crate) fn push_smtset_inputs( _advice_provider: &mut A, - _process: &S, + _process: ProcessState, ) -> Result { unimplemented!() } diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index 4eba93fe7c..8f6c8bddcf 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -52,9 +52,9 @@ pub trait AdviceProvider: Sized { // -------------------------------------------------------------------------------------------- /// Handles the specified advice injector request. - fn set_advice( + fn set_advice( &mut self, - process: &S, + process: ProcessState, advice_injector: &AdviceInjector, ) -> Result { match advice_injector { @@ -86,9 +86,9 @@ pub trait AdviceProvider: Sized { } /// Handles the specified advice extractor request. - fn get_advice( + fn get_advice( &mut self, - process: &S, + process: ProcessState, advice_extractor: &AdviceExtractor, ) -> Result { match advice_extractor { @@ -122,9 +122,9 @@ pub trait AdviceProvider: Sized { /// - `start_addr` is greater than or equal to 2^32. /// - `end_addr` is greater than or equal to 2^32. /// - `start_addr` > `end_addr`. - fn insert_mem_values_into_adv_map( + fn insert_mem_values_into_adv_map( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::adv_map_injectors::insert_mem_values_into_adv_map(self, process) } @@ -142,9 +142,9 @@ pub trait AdviceProvider: Sized { /// /// Where KEY is computed as hash(A || B, domain), where domain is provided via the immediate /// value. - fn insert_hdword_into_adv_map( + fn insert_hdword_into_adv_map( &mut self, - process: &S, + process: ProcessState, domain: Felt, ) -> Result { injectors::adv_map_injectors::insert_hdword_into_adv_map(self, process, domain) @@ -163,9 +163,9 @@ pub trait AdviceProvider: Sized { /// /// Where KEY is computed by extracting the digest elements from hperm([C, A, B]). For example, /// if C is [0, d, 0, 0], KEY will be set as hash(A || B, d). - fn insert_hperm_into_adv_map( + fn insert_hperm_into_adv_map( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::adv_map_injectors::insert_hperm_into_adv_map(self, process) } @@ -185,9 +185,9 @@ pub trait AdviceProvider: Sized { /// provider (i.e., the input trees are not removed). /// /// It is not checked whether the provided roots exist as Merkle trees in the advide providers. - fn merge_merkle_nodes( + fn merge_merkle_nodes( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::adv_map_injectors::merge_merkle_nodes(self, process) } @@ -214,9 +214,9 @@ pub trait AdviceProvider: Sized { /// - The specified depth is either zero or greater than the depth of the Merkle tree identified /// by the specified root. /// - Value of the node at the specified depth and index is not known to the advice provider. - fn copy_merkle_node_to_adv_stack( + fn copy_merkle_node_to_adv_stack( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::adv_stack_injectors::copy_merkle_node_to_adv_stack(self, process) } @@ -245,9 +245,9 @@ pub trait AdviceProvider: Sized { /// # Errors /// Returns an error if the required key was not found in the key-value map or if stack offset /// is greater than 12. - fn copy_map_value_to_adv_stack( + fn copy_map_value_to_adv_stack( &mut self, - process: &S, + process: ProcessState, include_len: bool, key_offset: usize, ) -> Result { @@ -277,9 +277,9 @@ pub trait AdviceProvider: Sized { /// /// # Errors /// Returns an error if the divisor is ZERO. - fn push_u64_div_result( + fn push_u64_div_result( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::adv_stack_injectors::push_u64_div_result(self, process) } @@ -300,9 +300,9 @@ pub trait AdviceProvider: Sized { /// /// # Errors /// Returns an error if the input is a zero element in the extension field. - fn push_ext2_inv_result( + fn push_ext2_inv_result( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::adv_stack_injectors::push_ext2_inv_result(self, process) } @@ -336,9 +336,9 @@ pub trait AdviceProvider: Sized { /// - `output_size` is 0 or is greater than the `input_size`. /// - `input_ptr` is greater than 2^32. /// - `input_ptr + input_size / 2` is greater than 2^32. - fn push_ext2_intt_result( + fn push_ext2_intt_result( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::adv_stack_injectors::push_ext2_intt_result(self, process) } @@ -360,9 +360,9 @@ pub trait AdviceProvider: Sized { /// - DATA is the needed data for signature verification in the VM. /// /// The advice provider is expected to contain the private key associated to the public key PK. - fn push_signature( + fn push_signature( &mut self, - process: &S, + process: ProcessState, kind: SignatureKind, ) -> Result { injectors::adv_stack_injectors::push_signature(self, process, kind) @@ -377,9 +377,9 @@ pub trait AdviceProvider: Sized { /// Outputs: /// Operand stack: [n, ...] /// Advice stack: [leading_zeros, ...] - fn push_leading_zeros( + fn push_leading_zeros( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::adv_stack_injectors::push_leading_zeros(self, process) } @@ -393,9 +393,9 @@ pub trait AdviceProvider: Sized { /// Outputs: /// Operand stack: [n, ...] /// Advice stack: [trailing_zeros, ...] - fn push_trailing_zeros( + fn push_trailing_zeros( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::adv_stack_injectors::push_trailing_zeros(self, process) } @@ -409,10 +409,7 @@ pub trait AdviceProvider: Sized { /// Outputs: /// Operand stack: [n, ...] /// Advice stack: [leading_ones, ...] - fn push_leading_ones( - &mut self, - process: &S, - ) -> Result { + fn push_leading_ones(&mut self, process: ProcessState) -> Result { injectors::adv_stack_injectors::push_leading_ones(self, process) } @@ -425,9 +422,9 @@ pub trait AdviceProvider: Sized { /// Outputs: /// Operand stack: [n, ...] /// Advice stack: [trailing_ones, ...] - fn push_trailing_ones( + fn push_trailing_ones( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::adv_stack_injectors::push_trailing_ones(self, process) } @@ -443,7 +440,7 @@ pub trait AdviceProvider: Sized { /// /// # Errors /// Returns an error if the logarithm argument (top stack element) equals ZERO. - fn push_ilog2(&mut self, process: &S) -> Result { + fn push_ilog2(&mut self, process: ProcessState) -> Result { injectors::adv_stack_injectors::push_ilog2(self, process) } @@ -463,9 +460,9 @@ pub trait AdviceProvider: Sized { /// Advice stack: [...] /// Merkle store: {path, ...} /// Return: \[path\] - fn update_operand_stack_merkle_node( + fn update_operand_stack_merkle_node( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::merkle_store_injectors::update_operand_stack_merkle_node(self, process) } @@ -491,9 +488,9 @@ pub trait AdviceProvider: Sized { /// Advice map: {...} /// Merkle store: {path, ...} /// Return: \[path\] - fn get_operand_stack_merkle_path( + fn get_operand_stack_merkle_path( &mut self, - process: &S, + process: ProcessState, ) -> Result { let depth = process.get_stack_item(4); let index = process.get_stack_item(5); @@ -528,25 +525,25 @@ pub trait AdviceProvider: Sized { /// /// # Panics /// Will panic as unimplemented if the target depth is `64`. - fn push_smtpeek_result( + fn push_smtpeek_result( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::smt::push_smtpeek_result(self, process) } /// Currently unimplemented - fn push_smtget_inputs( + fn push_smtget_inputs( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::smt::push_smtget_inputs(self, process) } /// Currently unimplemented - fn push_smtset_inputs( + fn push_smtset_inputs( &mut self, - process: &S, + process: ProcessState, ) -> Result { injectors::smt::push_smtset_inputs(self, process) } @@ -579,7 +576,7 @@ pub trait AdviceProvider: Sized { /// /// # Errors /// Returns an error if the advice stack is empty. - fn pop_stack(&mut self, process: &S) -> Result; + fn pop_stack(&mut self, process: ProcessState) -> Result; /// Pops a word (4 elements) from the advice stack and returns it. /// @@ -588,7 +585,7 @@ pub trait AdviceProvider: Sized { /// /// # Errors /// Returns an error if the advice stack does not contain a full word. - fn pop_stack_word(&mut self, process: &S) -> Result; + fn pop_stack_word(&mut self, process: ProcessState) -> Result; /// Pops a double word (8 elements) from the advice stack and returns them. /// @@ -598,10 +595,7 @@ pub trait AdviceProvider: Sized { /// /// # Errors /// Returns an error if the advice stack does not contain two words. - fn pop_stack_dword( - &mut self, - process: &S, - ) -> Result<[Word; 2], ExecutionError>; + fn pop_stack_dword(&mut self, process: ProcessState) -> Result<[Word; 2], ExecutionError>; /// Pushes the value(s) specified by the source onto the advice stack. /// @@ -722,18 +716,15 @@ impl AdviceProvider for &mut T where T: AdviceProvider, { - fn pop_stack(&mut self, process: &S) -> Result { + fn pop_stack(&mut self, process: ProcessState) -> Result { T::pop_stack(self, process) } - fn pop_stack_word(&mut self, process: &S) -> Result { + fn pop_stack_word(&mut self, process: ProcessState) -> Result { T::pop_stack_word(self, process) } - fn pop_stack_dword( - &mut self, - process: &S, - ) -> Result<[Word; 2], ExecutionError> { + fn pop_stack_dword(&mut self, process: ProcessState) -> Result<[Word; 2], ExecutionError> { T::pop_stack_dword(self, process) } diff --git a/processor/src/host/advice/providers.rs b/processor/src/host/advice/providers.rs index 1b2bb47089..6cc10efe27 100644 --- a/processor/src/host/advice/providers.rs +++ b/processor/src/host/advice/providers.rs @@ -60,11 +60,11 @@ where // ADVICE STACK // -------------------------------------------------------------------------------------------- - fn pop_stack(&mut self, process: &P) -> Result { + fn pop_stack(&mut self, process: ProcessState) -> Result { self.stack.pop().ok_or(ExecutionError::AdviceStackReadFailed(process.clk())) } - fn pop_stack_word(&mut self, process: &P) -> Result { + fn pop_stack_word(&mut self, process: ProcessState) -> Result { if self.stack.len() < 4 { return Err(ExecutionError::AdviceStackReadFailed(process.clk())); } @@ -78,10 +78,7 @@ where Ok(result) } - fn pop_stack_dword( - &mut self, - process: &P, - ) -> Result<[Word; 2], ExecutionError> { + fn pop_stack_dword(&mut self, process: ProcessState) -> Result<[Word; 2], ExecutionError> { let word0 = self.pop_stack_word(process)?; let word1 = self.pop_stack_word(process)?; @@ -259,15 +256,15 @@ impl MemAdviceProvider { /// TODO: potentially do this via a macro. #[rustfmt::skip] impl AdviceProvider for MemAdviceProvider { - fn pop_stack(&mut self, process: &S)-> Result { + fn pop_stack(&mut self, process: ProcessState)-> Result { self.provider.pop_stack(process) } - fn pop_stack_word(&mut self, process: &S) -> Result { + fn pop_stack_word(&mut self, process: ProcessState) -> Result { self.provider.pop_stack_word(process) } - fn pop_stack_dword(&mut self, process: &S) -> Result<[Word; 2], ExecutionError> { + fn pop_stack_dword(&mut self, process: ProcessState) -> Result<[Word; 2], ExecutionError> { self.provider.pop_stack_dword(process) } @@ -377,15 +374,15 @@ impl RecAdviceProvider { /// TODO: potentially do this via a macro. #[rustfmt::skip] impl AdviceProvider for RecAdviceProvider { - fn pop_stack(&mut self, process: &S) -> Result { + fn pop_stack(&mut self, process: ProcessState) -> Result { self.provider.pop_stack(process) } - fn pop_stack_word(&mut self, process: &S) -> Result { + fn pop_stack_word(&mut self, process: ProcessState) -> Result { self.provider.pop_stack_word(process) } - fn pop_stack_dword(&mut self, process: &S) -> Result<[Word; 2], ExecutionError> { + fn pop_stack_dword(&mut self, process: ProcessState) -> Result<[Word; 2], ExecutionError> { self.provider.pop_stack_dword(process) } diff --git a/processor/src/host/debug.rs b/processor/src/host/debug.rs index 81913f164d..6812d5bc65 100644 --- a/processor/src/host/debug.rs +++ b/processor/src/host/debug.rs @@ -11,7 +11,7 @@ use crate::system::ContextId; // ================================================================================================ /// Prints the info about the VM state specified by the provided options to stdout. -pub fn print_debug_info(process: &S, options: &DebugOptions) { +pub fn print_debug_info(process: ProcessState, options: &DebugOptions) { let printer = Printer::new(process.clk(), process.ctx(), process.fmp()); match options { DebugOptions::StackAll => { @@ -48,7 +48,7 @@ impl Printer { /// Prints the number of stack items specified by `n` if it is provided, otherwise prints /// the whole stack. - fn print_vm_stack(&self, process: &S, n: Option) { + fn print_vm_stack(&self, process: ProcessState, n: Option) { let stack = process.get_stack_state(); // determine how many items to print out @@ -72,7 +72,7 @@ impl Printer { } /// Prints the whole memory state at the cycle `clk` in context `ctx`. - fn print_mem_all(&self, process: &S) { + fn print_mem_all(&self, process: ProcessState) { let mem = process.get_mem_state(self.ctx); let padding = mem.iter().fold(0, |max, value| word_elem_max_len(Some(value.1)).max(max)) as usize; @@ -91,7 +91,7 @@ impl Printer { } /// Prints memory values in the provided addresses interval. - fn print_mem_interval(&self, process: &S, n: u32, m: u32) { + fn print_mem_interval(&self, process: ProcessState, n: u32, m: u32) { let mut mem_interval = Vec::new(); for addr in n..m + 1 { mem_interval.push((addr, process.get_mem_value(self.ctx, addr))); @@ -113,12 +113,7 @@ impl Printer { } /// Prints locals in provided indexes interval. - fn print_local_interval( - &self, - process: &S, - interval: (u32, u32), - num_locals: u32, - ) { + fn print_local_interval(&self, process: ProcessState, interval: (u32, u32), num_locals: u32) { let mut local_mem_interval = Vec::new(); let local_memory_offset = self.fmp - num_locals + 1; diff --git a/processor/src/host/mod.rs b/processor/src/host/mod.rs index 4e4289b331..0a5b7c65b1 100644 --- a/processor/src/host/mod.rs +++ b/processor/src/host/mod.rs @@ -35,16 +35,16 @@ pub trait Host { // -------------------------------------------------------------------------------------------- /// Returns the requested advice, specified by [AdviceExtractor], from the host to the VM. - fn get_advice( + fn get_advice( &mut self, - process: &P, + process: ProcessState, extractor: AdviceExtractor, ) -> Result; /// Sets the requested advice, specified by [AdviceInjector], on the host. - fn set_advice( + fn set_advice( &mut self, - process: &P, + process: ProcessState, injector: AdviceInjector, ) -> Result; @@ -70,9 +70,9 @@ pub trait Host { } /// Handles the event emitted from the VM. - fn on_event( + fn on_event( &mut self, - _process: &S, + _process: ProcessState, _event_id: u32, ) -> Result { #[cfg(feature = "std")] @@ -86,9 +86,9 @@ pub trait Host { } /// Handles the debug request from the VM. - fn on_debug( + fn on_debug( &mut self, - _process: &S, + _process: ProcessState, _options: &DebugOptions, ) -> Result { #[cfg(feature = "std")] @@ -97,9 +97,9 @@ pub trait Host { } /// Handles the trace emitted from the VM. - fn on_trace( + fn on_trace( &mut self, - _process: &S, + _process: ProcessState, _trace_id: u32, ) -> Result { #[cfg(feature = "std")] @@ -113,7 +113,7 @@ pub trait Host { } /// Handles the failure of the assertion instruction. - fn on_assert_failed(&mut self, process: &S, err_code: u32) -> ExecutionError { + fn on_assert_failed(&mut self, process: ProcessState, err_code: u32) -> ExecutionError { ExecutionError::FailedAssertion { clk: process.clk(), err_code, @@ -125,7 +125,7 @@ pub trait Host { /// /// # Errors /// Returns an error if the advice stack is empty. - fn pop_adv_stack(&mut self, process: &S) -> Result { + fn pop_adv_stack(&mut self, process: ProcessState) -> Result { let response = self.get_advice(process, AdviceExtractor::PopStack)?; Ok(response.into()) } @@ -137,7 +137,7 @@ pub trait Host { /// /// # Errors /// Returns an error if the advice stack does not contain a full word. - fn pop_adv_stack_word(&mut self, process: &S) -> Result { + fn pop_adv_stack_word(&mut self, process: ProcessState) -> Result { let response = self.get_advice(process, AdviceExtractor::PopStackWord)?; Ok(response.into()) } @@ -150,10 +150,7 @@ pub trait Host { /// /// # Errors /// Returns an error if the advice stack does not contain two words. - fn pop_adv_stack_dword( - &mut self, - process: &S, - ) -> Result<[Word; 2], ExecutionError> { + fn pop_adv_stack_dword(&mut self, process: ProcessState) -> Result<[Word; 2], ExecutionError> { let response = self.get_advice(process, AdviceExtractor::PopStackDWord)?; Ok(response.into()) } @@ -167,10 +164,7 @@ pub trait Host { /// - The specified depth is either zero or greater than the depth of the Merkle tree identified /// by the specified root. /// - Path to the node at the specified depth and index is not known to this advice provider. - fn get_adv_merkle_path( - &mut self, - process: &S, - ) -> Result { + fn get_adv_merkle_path(&mut self, process: ProcessState) -> Result { let response = self.get_advice(process, AdviceExtractor::GetMerklePath)?; Ok(response.into()) } @@ -180,17 +174,17 @@ impl Host for &mut H where H: Host, { - fn get_advice( + fn get_advice( &mut self, - process: &S, + process: ProcessState, extractor: AdviceExtractor, ) -> Result { H::get_advice(self, process, extractor) } - fn set_advice( + fn set_advice( &mut self, - process: &S, + process: ProcessState, injector: AdviceInjector, ) -> Result { H::set_advice(self, process, injector) @@ -200,31 +194,31 @@ where H::get_mast_forest(self, node_digest) } - fn on_debug( + fn on_debug( &mut self, - process: &S, + process: ProcessState, options: &DebugOptions, ) -> Result { H::on_debug(self, process, options) } - fn on_event( + fn on_event( &mut self, - process: &S, + process: ProcessState, event_id: u32, ) -> Result { H::on_event(self, process, event_id) } - fn on_trace( + fn on_trace( &mut self, - process: &S, + process: ProcessState, trace_id: u32, ) -> Result { H::on_trace(self, process, trace_id) } - fn on_assert_failed(&mut self, process: &S, err_code: u32) -> ExecutionError { + fn on_assert_failed(&mut self, process: ProcessState, err_code: u32) -> ExecutionError { H::on_assert_failed(self, process, err_code) } } @@ -339,17 +333,17 @@ impl Host for DefaultHost where A: AdviceProvider, { - fn get_advice( + fn get_advice( &mut self, - process: &P, + process: ProcessState, extractor: AdviceExtractor, ) -> Result { self.adv_provider.get_advice(process, &extractor) } - fn set_advice( + fn set_advice( &mut self, - process: &P, + process: ProcessState, injector: AdviceInjector, ) -> Result { self.adv_provider.set_advice(process, &injector) diff --git a/processor/src/lib.rs b/processor/src/lib.rs index e407d3b11a..0f3b414727 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -586,11 +586,11 @@ impl Process { ) -> Result<(), ExecutionError> { match decorator { Decorator::Advice(injector) => { - host.set_advice(self, *injector)?; + host.set_advice(self.into(), *injector)?; }, Decorator::Debug(options) => { if self.decoder.in_debug_mode() { - host.on_debug(self, options)?; + host.on_debug(self.into(), options)?; } }, Decorator::AsmOp(assembly_op) => { @@ -600,7 +600,7 @@ impl Process { }, Decorator::Trace(id) => { if self.enable_tracing { - host.on_trace(self, *id)?; + host.on_trace(self.into(), *id)?; } }, } @@ -622,19 +622,33 @@ impl Process { // PROCESS STATE // ================================================================================================ -/// A trait that defines a set of methods which allow access to the state of the process. -pub trait ProcessState { +#[derive(Debug, Clone, Copy)] +pub struct ProcessState<'a> { + system: &'a System, + stack: &'a Stack, + chiplets: &'a Chiplets, +} + +impl ProcessState<'_> { /// Returns the current clock cycle of a process. - fn clk(&self) -> RowIndex; + pub fn clk(&self) -> RowIndex { + self.system.clk() + } /// Returns the current execution context ID. - fn ctx(&self) -> ContextId; + pub fn ctx(&self) -> ContextId { + self.system.ctx() + } /// Returns the current value of the free memory pointer. - fn fmp(&self) -> u64; + pub fn fmp(&self) -> u64 { + self.system.fmp().as_int() + } /// Returns the value located at the specified position on the stack at the current clock cycle. - fn get_stack_item(&self, pos: usize) -> Felt; + pub fn get_stack_item(&self, pos: usize) -> Felt { + self.stack.get(pos) + } /// Returns a word located at the specified word index on the stack. /// @@ -646,54 +660,48 @@ pub trait ProcessState { /// stack will be at the last position in the word. /// /// Creating a word does not change the state of the stack. - fn get_stack_word(&self, word_idx: usize) -> Word; + pub fn get_stack_word(&self, word_idx: usize) -> Word { + self.stack.get_word(word_idx) + } /// Returns stack state at the current clock cycle. This includes the top 16 items of the /// stack + overflow entries. - fn get_stack_state(&self) -> Vec; + pub fn get_stack_state(&self) -> Vec { + self.stack.get_state_at(self.system.clk()) + } /// Returns a word located at the specified context/address, or None if the address hasn't /// been accessed previously. - fn get_mem_value(&self, ctx: ContextId, addr: u32) -> Option; + pub fn get_mem_value(&self, ctx: ContextId, addr: u32) -> Option { + self.chiplets.get_mem_value(ctx, addr) + } /// Returns the entire memory state for the specified execution context at the current clock /// cycle. /// /// The state is returned as a vector of (address, value) tuples, and includes addresses which /// have been accessed at least once. - fn get_mem_state(&self, ctx: ContextId) -> Vec<(u64, Word)>; -} - -impl ProcessState for Process { - fn clk(&self) -> RowIndex { - self.system.clk() - } - - fn ctx(&self) -> ContextId { - self.system.ctx() - } - - fn fmp(&self) -> u64 { - self.system.fmp().as_int() - } - - fn get_stack_item(&self, pos: usize) -> Felt { - self.stack.get(pos) - } - - fn get_stack_word(&self, word_idx: usize) -> Word { - self.stack.get_word(word_idx) - } - - fn get_stack_state(&self) -> Vec { - self.stack.get_state_at(self.system.clk()) + pub fn get_mem_state(&self, ctx: ContextId) -> Vec<(u64, Word)> { + self.chiplets.get_mem_state_at(ctx, self.system.clk()) } +} - fn get_mem_value(&self, ctx: ContextId, addr: u32) -> Option { - self.chiplets.get_mem_value(ctx, addr) +impl<'a> From<&'a Process> for ProcessState<'a> { + fn from(process: &'a Process) -> Self { + Self { + system: &process.system, + stack: &process.stack, + chiplets: &process.chiplets, + } } +} - fn get_mem_state(&self, ctx: ContextId) -> Vec<(u64, Word)> { - self.chiplets.get_mem_state_at(ctx, self.system.clk()) +impl<'a> From<&'a mut Process> for ProcessState<'a> { + fn from(process: &'a mut Process) -> Self { + Self { + system: &process.system, + stack: &process.stack, + chiplets: &process.chiplets, + } } } diff --git a/processor/src/operations/crypto_ops.rs b/processor/src/operations/crypto_ops.rs index 9550bb6b20..5a30499617 100644 --- a/processor/src/operations/crypto_ops.rs +++ b/processor/src/operations/crypto_ops.rs @@ -77,7 +77,7 @@ impl Process { // get a Merkle path from the advice provider for the specified root and node index. // the path is expected to be of the specified depth. - let path = host.get_adv_merkle_path(self)?; + let path = host.get_adv_merkle_path(self.into())?; // use hasher to compute the Merkle root of the path let (addr, computed_root) = self.chiplets.build_merkle_root(node, &path, index); @@ -148,7 +148,8 @@ impl Process { // get a Merkle path to it. the length of the returned path is expected to match the // specified depth. if the new node is the root of a tree, this instruction will append the // whole sub-tree to this node. - let path: MerklePath = host.set_advice(self, AdviceInjector::UpdateMerkleNode)?.into(); + let path: MerklePath = + host.set_advice(self.into(), AdviceInjector::UpdateMerkleNode)?.into(); assert_eq!(path.len(), depth.as_int() as usize); diff --git a/processor/src/operations/io_ops.rs b/processor/src/operations/io_ops.rs index db3f5fd598..ec9e3dddc5 100644 --- a/processor/src/operations/io_ops.rs +++ b/processor/src/operations/io_ops.rs @@ -186,7 +186,7 @@ impl Process { let addr = Self::get_valid_address(self.stack.get(12))?; // pop two words from the advice stack - let words = host.pop_adv_stack_dword(self)?; + let words = host.pop_adv_stack_dword(self.into())?; // write the words memory self.chiplets.write_mem_double(ctx, addr, words)?; @@ -219,7 +219,7 @@ impl Process { /// # Errors /// Returns an error if the advice stack is empty. pub(super) fn op_advpop(&mut self, host: &mut impl Host) -> Result<(), ExecutionError> { - let value = host.pop_adv_stack(self)?; + let value = host.pop_adv_stack(self.into())?; self.stack.set(0, value); self.stack.shift_right(0); Ok(()) @@ -231,7 +231,7 @@ impl Process { /// # Errors /// Returns an error if the advice stack contains fewer than four elements. pub(super) fn op_advpopw(&mut self, host: &mut impl Host) -> Result<(), ExecutionError> { - let word: Word = host.pop_adv_stack_word(self)?; + let word: Word = host.pop_adv_stack_word(self.into())?; self.stack.set(0, word[3]); self.stack.set(1, word[2]); diff --git a/processor/src/operations/sys_ops.rs b/processor/src/operations/sys_ops.rs index b0e356e190..1a198da3b0 100644 --- a/processor/src/operations/sys_ops.rs +++ b/processor/src/operations/sys_ops.rs @@ -22,7 +22,7 @@ impl Process { H: Host, { if self.stack.get(0) != ONE { - return Err(host.on_assert_failed(self, err_code)); + return Err(host.on_assert_failed(self.into(), err_code)); } self.stack.shift_left(1); Ok(()) @@ -122,7 +122,7 @@ impl Process { { self.stack.copy_state(0); self.decoder.set_user_op_helpers(Operation::Emit(event_id), &[event_id.into()]); - host.on_event(self, event_id)?; + host.on_event(self.into(), event_id)?; Ok(()) } diff --git a/processor/src/stack/mod.rs b/processor/src/stack/mod.rs index 6708840b02..ad133e48cb 100644 --- a/processor/src/stack/mod.rs +++ b/processor/src/stack/mod.rs @@ -55,6 +55,7 @@ const MAX_TOP_IDX: usize = MIN_STACK_DEPTH - 1; /// - Helper column h0 is used to ensure that stack depth does not drop below 16. Values in this /// column are set by the prover non-deterministically to 1 / (b0−16) when b0 != 16, and to any /// other value otherwise. +#[derive(Debug)] pub struct Stack { clk: RowIndex, trace: StackTrace, diff --git a/processor/src/stack/overflow.rs b/processor/src/stack/overflow.rs index bd5e5a3142..fafbeee8f5 100644 --- a/processor/src/stack/overflow.rs +++ b/processor/src/stack/overflow.rs @@ -11,6 +11,7 @@ use super::{Felt, FieldElement, ZERO}; /// /// When `trace_enabled` is set to true, we also record all changes to the table so that we can /// reconstruct the overflow table at any clock cycle. This can be used for debugging purposes. +#[derive(Debug)] pub struct OverflowTable { /// A list of all rows that were added to and then removed from the overflow table. all_rows: Vec, diff --git a/processor/src/stack/trace.rs b/processor/src/stack/trace.rs index f1dcc72101..d5102c5fc2 100644 --- a/processor/src/stack/trace.rs +++ b/processor/src/stack/trace.rs @@ -17,6 +17,7 @@ use crate::utils::math::batch_inversion; /// The trace consists of 19 columns grouped logically as follows: /// - 16 stack columns holding the top of the stack. /// - 3 columns for bookkeeping and helper values that manage left and right shifts. +#[derive(Debug)] pub struct StackTrace { stack: [Vec; MIN_STACK_DEPTH], helpers: [Vec; NUM_STACK_HELPER_COLS], diff --git a/processor/src/system/mod.rs b/processor/src/system/mod.rs index 016560b544..d69bcbc999 100644 --- a/processor/src/system/mod.rs +++ b/processor/src/system/mod.rs @@ -40,6 +40,7 @@ pub const FMP_MAX: u64 = 3 * 2_u64.pow(30) - 1; /// - in_syscall flag which indicates whether the execution is currently in a SYSCALL block. /// - hash of the function which initiated the current execution context. if the context was /// initiated from the root context, this will be set to ZEROs. +#[derive(Debug)] pub struct System { clk: RowIndex, ctx: ContextId, diff --git a/stdlib/tests/mem/mod.rs b/stdlib/tests/mem/mod.rs index abb280fe7e..f400e994c6 100644 --- a/stdlib/tests/mem/mod.rs +++ b/stdlib/tests/mem/mod.rs @@ -1,4 +1,4 @@ -use processor::{ContextId, DefaultHost, ProcessState, Program}; +use processor::{ContextId, DefaultHost, Program}; use test_utils::{ build_expected_hash, build_expected_perm, felt_slice_to_ints, ExecutionOptions, Process, StackInputs, ONE, ZERO, @@ -38,53 +38,53 @@ fn test_memcopy() { process.execute(&program, &mut host).unwrap(); assert_eq!( - process.get_mem_value(ContextId::root(), 1000), + process.chiplets.get_mem_value(ContextId::root(), 1000), Some([ZERO, ZERO, ZERO, ONE]), "Address 1000" ); assert_eq!( - process.get_mem_value(ContextId::root(), 1001), + process.chiplets.get_mem_value(ContextId::root(), 1001), Some([ZERO, ZERO, ONE, ZERO]), "Address 1001" ); assert_eq!( - process.get_mem_value(ContextId::root(), 1002), + process.chiplets.get_mem_value(ContextId::root(), 1002), Some([ZERO, ZERO, ONE, ONE]), "Address 1002" ); assert_eq!( - process.get_mem_value(ContextId::root(), 1003), + process.chiplets.get_mem_value(ContextId::root(), 1003), Some([ZERO, ONE, ZERO, ZERO]), "Address 1003" ); assert_eq!( - process.get_mem_value(ContextId::root(), 1004), + process.chiplets.get_mem_value(ContextId::root(), 1004), Some([ZERO, ONE, ZERO, ONE]), "Address 1004" ); assert_eq!( - process.get_mem_value(ContextId::root(), 2000), + process.chiplets.get_mem_value(ContextId::root(), 2000), Some([ZERO, ZERO, ZERO, ONE]), "Address 2000" ); assert_eq!( - process.get_mem_value(ContextId::root(), 2001), + process.chiplets.get_mem_value(ContextId::root(), 2001), Some([ZERO, ZERO, ONE, ZERO]), "Address 2001" ); assert_eq!( - process.get_mem_value(ContextId::root(), 2002), + process.chiplets.get_mem_value(ContextId::root(), 2002), Some([ZERO, ZERO, ONE, ONE]), "Address 2002" ); assert_eq!( - process.get_mem_value(ContextId::root(), 2003), + process.chiplets.get_mem_value(ContextId::root(), 2003), Some([ZERO, ONE, ZERO, ZERO]), "Address 2003" ); assert_eq!( - process.get_mem_value(ContextId::root(), 2004), + process.chiplets.get_mem_value(ContextId::root(), 2004), Some([ZERO, ONE, ZERO, ONE]), "Address 2004" ); diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index 919b300131..71b8c9a63c 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -258,8 +258,10 @@ impl Test { for data in expected_mem.chunks(WORD_SIZE) { // Main memory is zeroed by default, use zeros as a fallback when unwrap to make testing // easier - let mem_state = - process.get_mem_value(ContextId::root(), mem_start_addr).unwrap_or(EMPTY_WORD); + let mem_state = process + .chiplets + .get_mem_value(ContextId::root(), mem_start_addr) + .unwrap_or(EMPTY_WORD); let mem_state = felt_slice_to_ints(&mem_state); assert_eq!( From 8fbc1e0545a6e5b4bc2f39e62bed60b89f9fba23 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Tue, 19 Nov 2024 05:30:16 -0500 Subject: [PATCH 05/39] fix: fix typo in README --- miden/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miden/README.md b/miden/README.md index e8709ed4ca..9623cf088a 100644 --- a/miden/README.md +++ b/miden/README.md @@ -174,7 +174,7 @@ dup.1 // stack state: 2 1 2 add // stack state: 3 2 ``` -Notice that except for the first 2 operations which initialize the stack, the sequence of `swap dup.1 add` operations repeats over and over. In fact, we can repeat these operations an arbitrary number of times to compute an arbitrary Fibonacci number. In Rust, it would look like this (this is actually a simplified version of the example in [fibonacci.rs](src/examples/src/fibonacci.rs)): +Notice that except for the first 2 operations which initialize the stack, the sequence of `swap dup.1 add` operations repeats over and over. In fact, we can repeat these operations an arbitrary number of times to compute an arbitrary Fibonacci number. In Rust, it would look like this (this is actually a simplified version of the example in [fibonacci.rs](src/examples/fibonacci.rs)): ```rust use miden_vm::{Assembler, DefaultHost, Program, ProvingOptions, StackInputs}; From 37430cc4b6216d467e762ab5608cb080ac83ff2c Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Thu, 14 Nov 2024 18:13:41 +0200 Subject: [PATCH 06/39] refactor: move `AdviceMap` to core --- {processor/src/host => core/src}/advice/map.rs | 6 +++--- core/src/advice/mod.rs | 1 + core/src/lib.rs | 3 +++ processor/src/host/advice/inputs.rs | 3 ++- processor/src/host/advice/mod.rs | 3 --- processor/src/lib.rs | 2 +- 6 files changed, 10 insertions(+), 8 deletions(-) rename {processor/src/host => core/src}/advice/map.rs (98%) create mode 100644 core/src/advice/mod.rs diff --git a/processor/src/host/advice/map.rs b/core/src/advice/map.rs similarity index 98% rename from processor/src/host/advice/map.rs rename to core/src/advice/map.rs index 1f6deeb96f..c42a416ef4 100644 --- a/processor/src/host/advice/map.rs +++ b/core/src/advice/map.rs @@ -3,13 +3,13 @@ use alloc::{ vec::Vec, }; -use vm_core::{ +use miden_crypto::Felt; + +use crate::{ crypto::hash::RpoDigest, utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, }; -use super::Felt; - // ADVICE MAP // ================================================================================================ diff --git a/core/src/advice/mod.rs b/core/src/advice/mod.rs new file mode 100644 index 0000000000..5862d8cc11 --- /dev/null +++ b/core/src/advice/mod.rs @@ -0,0 +1 @@ +pub(super) mod map; diff --git a/core/src/lib.rs b/core/src/lib.rs index 15f14969d7..355c07395c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -123,4 +123,7 @@ pub use operations::{ pub mod stack; pub use stack::{StackInputs, StackOutputs}; +mod advice; +pub use advice::map::AdviceMap; + pub mod utils; diff --git a/processor/src/host/advice/inputs.rs b/processor/src/host/advice/inputs.rs index a50cc0408d..2941d4a9df 100644 --- a/processor/src/host/advice/inputs.rs +++ b/processor/src/host/advice/inputs.rs @@ -3,9 +3,10 @@ use alloc::vec::Vec; use vm_core::{ crypto::hash::RpoDigest, utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, + AdviceMap, }; -use super::{AdviceMap, Felt, InnerNodeInfo, InputError, MerkleStore}; +use super::{Felt, InnerNodeInfo, InputError, MerkleStore}; // ADVICE INPUTS // ================================================================================================ diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index 8f6c8bddcf..044f7e8fa7 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -26,9 +26,6 @@ pub use providers::{MemAdviceProvider, RecAdviceProvider}; mod source; pub use source::AdviceSource; -mod map; -pub use map::AdviceMap; - // ADVICE PROVIDER // ================================================================================================ diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 0f3b414727..88b8708917 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -48,7 +48,7 @@ use range::RangeChecker; mod host; pub use host::{ advice::{ - AdviceExtractor, AdviceInputs, AdviceMap, AdviceProvider, AdviceSource, MemAdviceProvider, + AdviceExtractor, AdviceInputs, AdviceProvider, AdviceSource, MemAdviceProvider, RecAdviceProvider, }, DefaultHost, Host, HostResponse, MastForestStore, MemMastForestStore, From d1757845d7fdf2ecd3100fe2b4ae517a703de3de Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Fri, 15 Nov 2024 13:58:50 +0200 Subject: [PATCH 07/39] refactor: make `AdviceProvider::insert_into_map` infallible Since the behavior if the key is already present is to replace the value with the new one there is no other error that can possibly arise in this method. --- processor/src/host/advice/injectors/adv_map_injectors.rs | 6 +++--- processor/src/host/advice/mod.rs | 4 ++-- processor/src/host/advice/providers.rs | 7 +++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/processor/src/host/advice/injectors/adv_map_injectors.rs b/processor/src/host/advice/injectors/adv_map_injectors.rs index 4eab49baf6..11e8efccb2 100644 --- a/processor/src/host/advice/injectors/adv_map_injectors.rs +++ b/processor/src/host/advice/injectors/adv_map_injectors.rs @@ -43,7 +43,7 @@ pub(crate) fn insert_mem_values_into_adv_map( } let key = process.get_stack_word(0); - advice_provider.insert_into_map(key, values)?; + advice_provider.insert_into_map(key, values); Ok(HostResponse::None) } @@ -76,7 +76,7 @@ pub(crate) fn insert_hdword_into_adv_map( let mut values = Vec::with_capacity(2 * WORD_SIZE); values.extend_from_slice(&word1); values.extend_from_slice(&word0); - advice_provider.insert_into_map(key.into(), values)?; + advice_provider.insert_into_map(key.into(), values); Ok(HostResponse::None) } @@ -125,7 +125,7 @@ pub(crate) fn insert_hperm_into_adv_map( .expect("failed to extract digest from state"), ); - advice_provider.insert_into_map(key.into(), values)?; + advice_provider.insert_into_map(key.into(), values); Ok(HostResponse::None) } diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index 044f7e8fa7..f00274c332 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -613,7 +613,7 @@ pub trait AdviceProvider: Sized { /// /// If the specified key is already present in the advice map, the values under the key /// are replaced with the specified values. - fn insert_into_map(&mut self, key: Word, values: Vec) -> Result<(), ExecutionError>; + fn insert_into_map(&mut self, key: Word, values: Vec); /// Returns a signature on a message using a public key. fn get_signature( @@ -729,7 +729,7 @@ where T::push_stack(self, source) } - fn insert_into_map(&mut self, key: Word, values: Vec) -> Result<(), ExecutionError> { + fn insert_into_map(&mut self, key: Word, values: Vec) { T::insert_into_map(self, key, values) } diff --git a/processor/src/host/advice/providers.rs b/processor/src/host/advice/providers.rs index 6cc10efe27..75de9486af 100644 --- a/processor/src/host/advice/providers.rs +++ b/processor/src/host/advice/providers.rs @@ -131,9 +131,8 @@ where self.map.get(key).map(|v| v.as_slice()) } - fn insert_into_map(&mut self, key: Word, values: Vec) -> Result<(), ExecutionError> { + fn insert_into_map(&mut self, key: Word, values: Vec) { self.map.insert(key.into(), values); - Ok(()) } // MERKLE STORE @@ -272,7 +271,7 @@ impl AdviceProvider for MemAdviceProvider { self.provider.push_stack(source) } - fn insert_into_map(&mut self, key: Word, values: Vec) -> Result<(), ExecutionError> { + fn insert_into_map(&mut self, key: Word, values: Vec) { self.provider.insert_into_map(key, values) } @@ -390,7 +389,7 @@ impl AdviceProvider for RecAdviceProvider { self.provider.push_stack(source) } - fn insert_into_map(&mut self, key: Word, values: Vec) -> Result<(), ExecutionError> { + fn insert_into_map(&mut self, key: Word, values: Vec) { self.provider.insert_into_map(key, values) } From 38c70f5a56c27ce3cbfe34528f908a6a9bdd6ff3 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Fri, 15 Nov 2024 14:19:54 +0200 Subject: [PATCH 08/39] feature: introduce `Host::advice_provider*` to access host's advice provider --- .../integration/operations/decorators/mod.rs | 10 +++++++ processor/src/host/mod.rs | 28 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/miden/tests/integration/operations/decorators/mod.rs b/miden/tests/integration/operations/decorators/mod.rs index 9e4a17f353..d5bee48206 100644 --- a/miden/tests/integration/operations/decorators/mod.rs +++ b/miden/tests/integration/operations/decorators/mod.rs @@ -31,6 +31,16 @@ impl Default for TestHost { } impl Host for TestHost { + type AdviceProvider = A; + + fn advice_provider(&self) -> &Self::AdviceProvider { + &self.adv_provider + } + + fn advice_provider_mut(&mut self) -> &mut Self::AdviceProvider { + &mut self.adv_provider + } + fn get_advice( &mut self, process: ProcessState, diff --git a/processor/src/host/mod.rs b/processor/src/host/mod.rs index 0a5b7c65b1..384e79ec21 100644 --- a/processor/src/host/mod.rs +++ b/processor/src/host/mod.rs @@ -31,9 +31,17 @@ pub use mast_forest_store::{MastForestStore, MemMastForestStore}; /// state of the VM ([ProcessState]), which it can use to extract the data required to fulfill the /// request. pub trait Host { + type AdviceProvider: AdviceProvider; + // REQUIRED METHODS // -------------------------------------------------------------------------------------------- + /// Returns a reference to the advice provider. + fn advice_provider(&self) -> &Self::AdviceProvider; + + /// Returns a mutable reference to the advice provider. + fn advice_provider_mut(&mut self) -> &mut Self::AdviceProvider; + /// Returns the requested advice, specified by [AdviceExtractor], from the host to the VM. fn get_advice( &mut self, @@ -174,6 +182,16 @@ impl Host for &mut H where H: Host, { + type AdviceProvider = H::AdviceProvider; + + fn advice_provider(&self) -> &Self::AdviceProvider { + H::advice_provider(self) + } + + fn advice_provider_mut(&mut self) -> &mut Self::AdviceProvider { + H::advice_provider_mut(self) + } + fn get_advice( &mut self, process: ProcessState, @@ -333,6 +351,16 @@ impl Host for DefaultHost where A: AdviceProvider, { + type AdviceProvider = A; + + fn advice_provider(&self) -> &Self::AdviceProvider { + &self.adv_provider + } + + fn advice_provider_mut(&mut self) -> &mut Self::AdviceProvider { + &mut self.adv_provider + } + fn get_advice( &mut self, process: ProcessState, From 93869efbeb45344b6c786f4b6916831c33a81c4a Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Mon, 18 Nov 2024 11:54:32 +0200 Subject: [PATCH 09/39] feature: add `MastForest::advice_map` and load it before the execution When merging forests, merge their advice maps and return error on key collision. --- CHANGELOG.md | 3 ++ core/src/advice/map.rs | 49 +++++++++++++++++++++++-- core/src/mast/merger/mod.rs | 25 ++++++++++--- core/src/mast/merger/tests.rs | 53 +++++++++++++++++++++++++++- core/src/mast/mod.rs | 15 +++++++- core/src/mast/serialization/mod.rs | 6 ++++ core/src/mast/serialization/tests.rs | 21 ++++++++++- miden/benches/program_execution.rs | 2 +- miden/src/examples/blake3.rs | 2 +- miden/src/repl/mod.rs | 3 +- miden/src/tools/mod.rs | 3 +- miden/tests/integration/exec.rs | 53 ++++++++++++++++++++++++++++ miden/tests/integration/main.rs | 1 + processor/src/errors.rs | 5 +++ processor/src/host/mod.rs | 18 ++++++++-- processor/src/lib.rs | 13 ++++++- stdlib/tests/mem/mod.rs | 2 +- test-utils/src/lib.rs | 20 +++++------ 18 files changed, 266 insertions(+), 28 deletions(-) create mode 100644 miden/tests/integration/exec.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a18885e6a7..f680b93fa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ - [BREAKING] `Process` no longer takes ownership of the `Host` (#1571) - [BREAKING] `ProcessState` was converted from a trait to a struct (#1571) +#### Enhancements +- Added `miden_core::mast::MastForest::advice_map` to load it into the advice provider before the `MastForest` execution (#1574). + ## 0.11.0 (2024-11-04) #### Enhancements diff --git a/core/src/advice/map.rs b/core/src/advice/map.rs index c42a416ef4..ad80b23a6e 100644 --- a/core/src/advice/map.rs +++ b/core/src/advice/map.rs @@ -1,9 +1,10 @@ use alloc::{ + boxed::Box, collections::{btree_map::IntoIter, BTreeMap}, vec::Vec, }; -use miden_crypto::Felt; +use miden_crypto::{utils::collections::KvMap, Felt}; use crate::{ crypto::hash::RpoDigest, @@ -38,8 +39,18 @@ impl AdviceMap { } /// Removes the value associated with the key and returns the removed element. - pub fn remove(&mut self, key: RpoDigest) -> Option> { - self.0.remove(&key) + pub fn remove(&mut self, key: &RpoDigest) -> Option> { + self.0.remove(key) + } + + /// Returns the number of key value pairs in the advice map. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns true if the advice map is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() } } @@ -58,6 +69,38 @@ impl IntoIterator for AdviceMap { } } +impl FromIterator<(RpoDigest, Vec)> for AdviceMap { + fn from_iter)>>(iter: T) -> Self { + iter.into_iter().collect::>>().into() + } +} + +impl KvMap> for AdviceMap { + fn get(&self, key: &RpoDigest) -> Option<&Vec> { + self.0.get(key) + } + + fn contains_key(&self, key: &RpoDigest) -> bool { + self.0.contains_key(key) + } + + fn len(&self) -> usize { + self.len() + } + + fn insert(&mut self, key: RpoDigest, value: Vec) -> Option> { + self.insert(key, value) + } + + fn remove(&mut self, key: &RpoDigest) -> Option> { + self.remove(key) + } + + fn iter(&self) -> Box)> + '_> { + Box::new(self.0.iter()) + } +} + impl Extend<(RpoDigest, Vec)> for AdviceMap { fn extend)>>(&mut self, iter: T) { self.0.extend(iter) diff --git a/core/src/mast/merger/mod.rs b/core/src/mast/merger/mod.rs index ae0273371a..563bfbe473 100644 --- a/core/src/mast/merger/mod.rs +++ b/core/src/mast/merger/mod.rs @@ -1,6 +1,6 @@ use alloc::{collections::BTreeMap, vec::Vec}; -use miden_crypto::hash::blake::Blake3Digest; +use miden_crypto::{hash::blake::Blake3Digest, utils::collections::KvMap}; use crate::mast::{ DecoratorId, MastForest, MastForestError, MastNode, MastNodeFingerprint, MastNodeId, @@ -65,10 +65,11 @@ impl MastForestMerger { /// /// It does this in three steps: /// - /// 1. Merge all decorators, which is a case of deduplication and creating a decorator id + /// 1. Merge all advice maps, checking for key collisions. + /// 2. Merge all decorators, which is a case of deduplication and creating a decorator id /// mapping which contains how existing [`DecoratorId`]s map to [`DecoratorId`]s in the /// merged forest. - /// 2. Merge all nodes of forests. + /// 3. Merge all nodes of forests. /// - Similar to decorators, node indices might move during merging, so the merger keeps a /// node id mapping as it merges nodes. /// - This is a depth-first traversal over all forests to ensure all children are processed @@ -90,10 +91,13 @@ impl MastForestMerger { /// `replacement` node. Now we can simply add a mapping from the external node to the /// `replacement` node in our node id mapping which means all nodes that referenced the /// external node will point to the `replacement` instead. - /// 3. Finally, we merge all roots of all forests. Here we map the existing root indices to + /// 4. Finally, we merge all roots of all forests. Here we map the existing root indices to /// their potentially new indices in the merged forest and add them to the forest, /// deduplicating in the process, too. fn merge_inner(&mut self, forests: Vec<&MastForest>) -> Result<(), MastForestError> { + for other_forest in forests.iter() { + self.merge_advice_map(other_forest)?; + } for other_forest in forests.iter() { self.merge_decorators(other_forest)?; } @@ -163,6 +167,19 @@ impl MastForestMerger { Ok(()) } + fn merge_advice_map(&mut self, other_forest: &MastForest) -> Result<(), MastForestError> { + for (digest, values) in other_forest.advice_map.iter() { + if let Some(stored_values) = self.mast_forest.advice_map().get(digest) { + if stored_values != values { + return Err(MastForestError::AdviceMapKeyCollisionOnMerge(*digest)); + } + } else { + self.mast_forest.advice_map_mut().insert(*digest, values.clone()); + } + } + Ok(()) + } + fn merge_node( &mut self, forest_idx: usize, diff --git a/core/src/mast/merger/tests.rs b/core/src/mast/merger/tests.rs index b33ae97296..c9100d9f73 100644 --- a/core/src/mast/merger/tests.rs +++ b/core/src/mast/merger/tests.rs @@ -1,4 +1,4 @@ -use miden_crypto::{hash::rpo::RpoDigest, ONE}; +use miden_crypto::{hash::rpo::RpoDigest, Felt, ONE}; use super::*; use crate::{Decorator, Operation}; @@ -794,3 +794,54 @@ fn mast_forest_merge_invalid_decorator_index() { let err = MastForest::merge([&forest_a, &forest_b]).unwrap_err(); assert_matches!(err, MastForestError::DecoratorIdOverflow(_, _)); } + +/// Tests that forest's advice maps are merged correctly. +#[test] +fn mast_forest_merge_advice_maps_merged() { + let mut forest_a = MastForest::new(); + let id_foo = forest_a.add_node(block_foo()).unwrap(); + let id_call_a = forest_a.add_call(id_foo).unwrap(); + forest_a.make_root(id_call_a); + let key_a = RpoDigest::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]); + let value_a = vec![ONE, ONE]; + forest_a.advice_map_mut().insert(key_a, value_a.clone()); + + let mut forest_b = MastForest::new(); + let id_bar = forest_b.add_node(block_bar()).unwrap(); + let id_call_b = forest_b.add_call(id_bar).unwrap(); + forest_b.make_root(id_call_b); + let key_b = RpoDigest::new([Felt::new(1), Felt::new(3), Felt::new(2), Felt::new(1)]); + let value_b = vec![Felt::new(2), Felt::new(2)]; + forest_b.advice_map_mut().insert(key_b, value_b.clone()); + + let (merged, _root_maps) = MastForest::merge([&forest_a, &forest_b]).unwrap(); + + let merged_advice_map = merged.advice_map(); + assert_eq!(merged_advice_map.len(), 2); + assert_eq!(merged_advice_map.get(&key_a).unwrap(), &value_a); + assert_eq!(merged_advice_map.get(&key_b).unwrap(), &value_b); +} + +/// Tests that an error is returned when advice maps have a key collision. +#[test] +fn mast_forest_merge_advice_maps_collision() { + let mut forest_a = MastForest::new(); + let id_foo = forest_a.add_node(block_foo()).unwrap(); + let id_call_a = forest_a.add_call(id_foo).unwrap(); + forest_a.make_root(id_call_a); + let key_a = RpoDigest::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]); + let value_a = vec![ONE, ONE]; + forest_a.advice_map_mut().insert(key_a, value_a.clone()); + + let mut forest_b = MastForest::new(); + let id_bar = forest_b.add_node(block_bar()).unwrap(); + let id_call_b = forest_b.add_call(id_bar).unwrap(); + forest_b.make_root(id_call_b); + // The key collides with key_a in the forest_a. + let key_b = key_a; + let value_b = vec![Felt::new(2), Felt::new(2)]; + forest_b.advice_map_mut().insert(key_b, value_b.clone()); + + let err = MastForest::merge([&forest_a, &forest_b]).unwrap_err(); + assert_matches!(err, MastForestError::AdviceMapKeyCollisionOnMerge(_)); +} diff --git a/core/src/mast/mod.rs b/core/src/mast/mod.rs index 444fedc5c6..8e32a9c9d7 100644 --- a/core/src/mast/mod.rs +++ b/core/src/mast/mod.rs @@ -16,7 +16,7 @@ pub use node::{ }; use winter_utils::{ByteWriter, DeserializationError, Serializable}; -use crate::{Decorator, DecoratorList, Operation}; +use crate::{AdviceMap, Decorator, DecoratorList, Operation}; mod serialization; @@ -50,6 +50,9 @@ pub struct MastForest { /// All the decorators included in the MAST forest. decorators: Vec, + + /// Advice map to be loaded into the VM prior to executing procedures from this MAST forest. + advice_map: AdviceMap, } // ------------------------------------------------------------------------------------------------ @@ -463,6 +466,14 @@ impl MastForest { pub fn nodes(&self) -> &[MastNode] { &self.nodes } + + pub fn advice_map(&self) -> &AdviceMap { + &self.advice_map + } + + pub fn advice_map_mut(&mut self) -> &mut AdviceMap { + &mut self.advice_map + } } impl Index for MastForest { @@ -689,4 +700,6 @@ pub enum MastForestError { EmptyBasicBlock, #[error("decorator root of child with node id {0} is missing but required for fingerprint computation")] ChildFingerprintMissing(MastNodeId), + #[error("advice map key already exists when merging forests: {0}")] + AdviceMapKeyCollisionOnMerge(RpoDigest), } diff --git a/core/src/mast/serialization/mod.rs b/core/src/mast/serialization/mod.rs index e76f775c5e..7a8119f7cd 100644 --- a/core/src/mast/serialization/mod.rs +++ b/core/src/mast/serialization/mod.rs @@ -31,6 +31,7 @@ use string_table::{StringTable, StringTableBuilder}; use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use super::{DecoratorId, MastForest, MastNode, MastNodeId}; +use crate::AdviceMap; mod decorator; @@ -149,6 +150,8 @@ impl Serializable for MastForest { node_data.write_into(target); string_table.write_into(target); + self.advice_map.write_into(target); + // Write decorator and node infos for decorator_info in decorator_infos { decorator_info.write_into(target); @@ -187,6 +190,7 @@ impl Deserializable for MastForest { let decorator_data: Vec = Deserializable::read_from(source)?; let node_data: Vec = Deserializable::read_from(source)?; let string_table: StringTable = Deserializable::read_from(source)?; + let advice_map = AdviceMap::read_from(source)?; let mut mast_forest = { let mut mast_forest = MastForest::new(); @@ -229,6 +233,8 @@ impl Deserializable for MastForest { mast_forest.make_root(root); } + mast_forest.advice_map = advice_map; + mast_forest }; diff --git a/core/src/mast/serialization/tests.rs b/core/src/mast/serialization/tests.rs index cb6e9e2c09..76a180b796 100644 --- a/core/src/mast/serialization/tests.rs +++ b/core/src/mast/serialization/tests.rs @@ -1,6 +1,6 @@ use alloc::{string::ToString, sync::Arc}; -use miden_crypto::{hash::rpo::RpoDigest, Felt}; +use miden_crypto::{hash::rpo::RpoDigest, Felt, ONE}; use super::*; use crate::{ @@ -435,3 +435,22 @@ fn mast_forest_invalid_node_id() { // Validate normal operations forest.add_join(first, second).unwrap(); } + +/// Test `MastForest::advice_map` serialization and deserialization. +#[test] +fn mast_forest_serialize_deserialize_advice_map() { + let mut forest = MastForest::new(); + let deco0 = forest.add_decorator(Decorator::Trace(0)).unwrap(); + let deco1 = forest.add_decorator(Decorator::Trace(1)).unwrap(); + let first = forest.add_block(vec![Operation::U32add], Some(vec![(0, deco0)])).unwrap(); + let second = forest.add_block(vec![Operation::U32and], Some(vec![(1, deco1)])).unwrap(); + forest.add_join(first, second).unwrap(); + + let key = RpoDigest::new([ONE, ONE, ONE, ONE]); + let value = vec![ONE, ONE]; + + forest.advice_map_mut().insert(key, value); + + let parsed = MastForest::read_from_bytes(&forest.to_bytes()).unwrap(); + assert_eq!(forest.advice_map, parsed.advice_map); +} diff --git a/miden/benches/program_execution.rs b/miden/benches/program_execution.rs index 6e3c20d4a4..d5126da41f 100644 --- a/miden/benches/program_execution.rs +++ b/miden/benches/program_execution.rs @@ -11,7 +11,7 @@ fn program_execution(c: &mut Criterion) { let stdlib = StdLibrary::default(); let mut host = DefaultHost::default(); - host.load_mast_forest(stdlib.as_ref().mast_forest().clone()); + host.load_mast_forest(stdlib.as_ref().mast_forest().clone()).unwrap(); group.bench_function("sha256", |bench| { let source = " diff --git a/miden/src/examples/blake3.rs b/miden/src/examples/blake3.rs index 4d4133b59f..4f6fac6577 100644 --- a/miden/src/examples/blake3.rs +++ b/miden/src/examples/blake3.rs @@ -22,7 +22,7 @@ pub fn get_example(n: usize) -> Example> { ); let mut host = DefaultHost::default(); - host.load_mast_forest(StdLibrary::default().mast_forest().clone()); + host.load_mast_forest(StdLibrary::default().mast_forest().clone()).unwrap(); let stack_inputs = StackInputs::try_from_ints(INITIAL_HASH_VALUE.iter().map(|&v| v as u64)).unwrap(); diff --git a/miden/src/repl/mod.rs b/miden/src/repl/mod.rs index 198a13e358..84dfe8df47 100644 --- a/miden/src/repl/mod.rs +++ b/miden/src/repl/mod.rs @@ -318,7 +318,8 @@ fn execute( let stack_inputs = StackInputs::default(); let mut host = DefaultHost::default(); for library in provided_libraries { - host.load_mast_forest(library.mast_forest().clone()); + host.load_mast_forest(library.mast_forest().clone()) + .map_err(|err| format!("{err}"))?; } let state_iter = processor::execute_iter(&program, stack_inputs, &mut host); diff --git a/miden/src/tools/mod.rs b/miden/src/tools/mod.rs index 7d932dbf5f..621e283b9e 100644 --- a/miden/src/tools/mod.rs +++ b/miden/src/tools/mod.rs @@ -38,7 +38,8 @@ impl Analyze { // fetch the stack and program inputs from the arguments let stack_inputs = input_data.parse_stack_inputs().map_err(Report::msg)?; let mut host = DefaultHost::new(input_data.parse_advice_provider().map_err(Report::msg)?); - host.load_mast_forest(StdLibrary::default().mast_forest().clone()); + host.load_mast_forest(StdLibrary::default().mast_forest().clone()) + .into_diagnostic()?; let execution_details: ExecutionDetails = analyze(program.as_str(), stack_inputs, host) .expect("Could not retrieve execution details"); diff --git a/miden/tests/integration/exec.rs b/miden/tests/integration/exec.rs new file mode 100644 index 0000000000..0297742174 --- /dev/null +++ b/miden/tests/integration/exec.rs @@ -0,0 +1,53 @@ +use assembly::Assembler; +use miden_vm::DefaultHost; +use processor::{ExecutionOptions, MastForest}; +use prover::{Digest, StackInputs}; +use vm_core::{assert_matches, Program, ONE}; + +#[test] +fn advice_map_loaded_before_execution() { + let source = "\ + begin + push.1.1.1.1 + adv.push_mapval + dropw + end"; + + // compile and execute program + let program_without_advice_map: Program = + Assembler::default().assemble_program(source).unwrap(); + + // Test `processor::execute` fails if no advice map provided with the program + let mut host = DefaultHost::default(); + match processor::execute( + &program_without_advice_map, + StackInputs::default(), + &mut host, + ExecutionOptions::default(), + ) { + Ok(_) => panic!("Expected error"), + Err(e) => { + assert_matches!(e, prover::ExecutionError::AdviceMapKeyNotFound(_)); + }, + } + + // Test `processor::execute` works if advice map provided with the program + let mast_forest: MastForest = (**program_without_advice_map.mast_forest()).clone(); + + let key = Digest::new([ONE, ONE, ONE, ONE]); + let value = vec![ONE, ONE]; + + let mut mast_forest = mast_forest.clone(); + mast_forest.advice_map_mut().insert(key, value); + let program_with_advice_map = + Program::new(mast_forest.into(), program_without_advice_map.entrypoint()); + + let mut host = DefaultHost::default(); + processor::execute( + &program_with_advice_map, + StackInputs::default(), + &mut host, + ExecutionOptions::default(), + ) + .unwrap(); +} diff --git a/miden/tests/integration/main.rs b/miden/tests/integration/main.rs index 10720fdb14..0ee815409c 100644 --- a/miden/tests/integration/main.rs +++ b/miden/tests/integration/main.rs @@ -4,6 +4,7 @@ use test_utils::{build_op_test, build_test}; mod air; mod cli; +mod exec; mod exec_iters; mod flow_control; mod operations; diff --git a/processor/src/errors.rs b/processor/src/errors.rs index 570d5fad54..ac86171e2e 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -24,6 +24,7 @@ use crate::ContextId; #[derive(Debug, Clone, PartialEq, Eq)] pub enum ExecutionError { AdviceMapKeyNotFound(Word), + AdviceMapKeyAlreadyPresent(Word), AdviceStackReadFailed(RowIndex), CallerNotInSyscall, CircularExternalNode(Digest), @@ -102,6 +103,10 @@ impl Display for ExecutionError { let hex = to_hex(Felt::elements_as_bytes(key)); write!(f, "Value for key {hex} not present in the advice map") }, + AdviceMapKeyAlreadyPresent(key) => { + let hex = to_hex(Felt::elements_as_bytes(key)); + write!(f, "Value for key {hex} already present in the advice map") + }, AdviceStackReadFailed(step) => write!(f, "Advice stack read failed at step {step}"), CallerNotInSyscall => { write!(f, "Instruction `caller` used outside of kernel context") diff --git a/processor/src/host/mod.rs b/processor/src/host/mod.rs index 384e79ec21..649d114ff5 100644 --- a/processor/src/host/mod.rs +++ b/processor/src/host/mod.rs @@ -3,6 +3,7 @@ use alloc::sync::Arc; use vm_core::{ crypto::{hash::RpoDigest, merkle::MerklePath}, mast::MastForest, + utils::collections::KvMap, AdviceInjector, DebugOptions, Word, }; @@ -328,8 +329,21 @@ where } } - pub fn load_mast_forest(&mut self, mast_forest: Arc) { - self.store.insert(mast_forest) + pub fn load_mast_forest(&mut self, mast_forest: Arc) -> Result<(), ExecutionError> { + // Load the MAST's advice data into the advice provider. + + for (digest, values) in mast_forest.advice_map().iter() { + if let Some(stored_values) = self.advice_provider().get_mapped_values(digest) { + if stored_values != values { + return Err(ExecutionError::AdviceMapKeyAlreadyPresent(digest.into())); + } + } else { + self.advice_provider_mut().insert_into_map(digest.into(), values.clone()); + } + } + + self.store.insert(mast_forest); + Ok(()) } #[cfg(any(test, feature = "testing"))] diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 88b8708917..b68ef5f99b 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -18,7 +18,7 @@ pub use vm_core::{ crypto::merkle::SMT_DEPTH, errors::InputError, mast::{MastForest, MastNode, MastNodeId}, - utils::DeserializationError, + utils::{collections::KvMap, DeserializationError}, AdviceInjector, AssemblyOp, Felt, Kernel, Operation, Program, ProgramInfo, QuadExtension, StackInputs, StackOutputs, Word, EMPTY_WORD, ONE, ZERO, }; @@ -238,6 +238,17 @@ impl Process { return Err(ExecutionError::ProgramAlreadyExecuted); } + // Load the program's advice data into the advice provider + for (digest, values) in program.mast_forest().advice_map().iter() { + if let Some(stored_values) = host.advice_provider().get_mapped_values(digest) { + if stored_values != values { + return Err(ExecutionError::AdviceMapKeyAlreadyPresent(digest.into())); + } + } else { + host.advice_provider_mut().insert_into_map(digest.into(), values.clone()); + } + } + self.execute_mast_node(program.entrypoint(), &program.mast_forest().clone(), host)?; self.stack.build_stack_outputs() diff --git a/stdlib/tests/mem/mod.rs b/stdlib/tests/mem/mod.rs index f400e994c6..8cfe9e4226 100644 --- a/stdlib/tests/mem/mod.rs +++ b/stdlib/tests/mem/mod.rs @@ -31,7 +31,7 @@ fn test_memcopy() { assembler.assemble_program(source).expect("Failed to compile test source."); let mut host = DefaultHost::default(); - host.load_mast_forest(stdlib.mast_forest().clone()); + host.load_mast_forest(stdlib.mast_forest().clone()).unwrap(); let mut process = Process::new(program.kernel().clone(), StackInputs::default(), ExecutionOptions::default()); diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index 71b8c9a63c..c5b3f4ebc5 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -240,10 +240,10 @@ impl Test { let (program, kernel) = self.compile().expect("Failed to compile test source."); let mut host = DefaultHost::new(MemAdviceProvider::from(self.advice_inputs.clone())); if let Some(kernel) = kernel { - host.load_mast_forest(kernel.mast_forest().clone()); + host.load_mast_forest(kernel.mast_forest().clone()).unwrap(); } for library in &self.libraries { - host.load_mast_forest(library.mast_forest().clone()); + host.load_mast_forest(library.mast_forest().clone()).unwrap(); } // execute the test @@ -339,10 +339,10 @@ impl Test { let (program, kernel) = self.compile().expect("Failed to compile test source."); let mut host = DefaultHost::new(MemAdviceProvider::from(self.advice_inputs.clone())); if let Some(kernel) = kernel { - host.load_mast_forest(kernel.mast_forest().clone()); + host.load_mast_forest(kernel.mast_forest().clone()).unwrap(); } for library in &self.libraries { - host.load_mast_forest(library.mast_forest().clone()); + host.load_mast_forest(library.mast_forest().clone()).unwrap(); } processor::execute( &program, @@ -360,10 +360,10 @@ impl Test { let (program, kernel) = self.compile().expect("Failed to compile test source."); let mut host = DefaultHost::new(MemAdviceProvider::from(self.advice_inputs.clone())); if let Some(kernel) = kernel { - host.load_mast_forest(kernel.mast_forest().clone()); + host.load_mast_forest(kernel.mast_forest().clone()).unwrap(); } for library in &self.libraries { - host.load_mast_forest(library.mast_forest().clone()); + host.load_mast_forest(library.mast_forest().clone()).unwrap(); } let mut process = Process::new( @@ -383,10 +383,10 @@ impl Test { let (program, kernel) = self.compile().expect("Failed to compile test source."); let mut host = DefaultHost::new(MemAdviceProvider::from(self.advice_inputs.clone())); if let Some(kernel) = kernel { - host.load_mast_forest(kernel.mast_forest().clone()); + host.load_mast_forest(kernel.mast_forest().clone()).unwrap(); } for library in &self.libraries { - host.load_mast_forest(library.mast_forest().clone()); + host.load_mast_forest(library.mast_forest().clone()).unwrap(); } let (mut stack_outputs, proof) = prover::prove(&program, stack_inputs.clone(), &mut host, ProvingOptions::default()) @@ -409,10 +409,10 @@ impl Test { let (program, kernel) = self.compile().expect("Failed to compile test source."); let mut host = DefaultHost::new(MemAdviceProvider::from(self.advice_inputs.clone())); if let Some(kernel) = kernel { - host.load_mast_forest(kernel.mast_forest().clone()); + host.load_mast_forest(kernel.mast_forest().clone()).unwrap(); } for library in &self.libraries { - host.load_mast_forest(library.mast_forest().clone()); + host.load_mast_forest(library.mast_forest().clone()).unwrap(); } processor::execute_iter(&program, self.stack_inputs.clone(), &mut host) } From 36c5315dea95f9995a4b3c0b49b1c410602ff2fc Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Thu, 7 Nov 2024 09:59:20 -0500 Subject: [PATCH 10/39] refactor: remove `set_advice()` and `get_advice()` methods from `AdviceProvider` trait --- miden/README.md | 10 +- .../integration/operations/decorators/mod.rs | 21 +- processor/README.md | 2 +- processor/src/host/advice/extractors.rs | 106 ----- processor/src/host/advice/mod.rs | 361 +++++++----------- processor/src/host/advice/providers.rs | 23 -- processor/src/host/mod.rs | 129 +------ processor/src/lib.rs | 51 ++- processor/src/operations/crypto_ops.rs | 9 +- processor/src/operations/io_ops.rs | 8 +- prover/README.md | 4 +- 11 files changed, 209 insertions(+), 515 deletions(-) delete mode 100644 processor/src/host/advice/extractors.rs diff --git a/miden/README.md b/miden/README.md index 9623cf088a..f3266c8bf4 100644 --- a/miden/README.md +++ b/miden/README.md @@ -72,7 +72,7 @@ let exec_options = ExecutionOptions::default(); let trace = execute(&program, stack_inputs.clone(), &mut host, exec_options).unwrap(); // now, execute the same program in debug mode and iterate over VM states -for vm_state in execute_iter(&program, stack_inputs, host) { +for vm_state in execute_iter(&program, stack_inputs, &mut host) { match vm_state { Ok(vm_state) => println!("{:?}", vm_state), Err(_) => println!("something went terribly wrong!"), @@ -111,13 +111,13 @@ let program = assembler.assemble_program("begin push.3 push.5 add end").unwrap() let (outputs, proof) = prove( &program, StackInputs::default(), // we won't provide any inputs - DefaultHost::default(), // we'll be using a default host + &mut DefaultHost::default(), // we'll be using a default host ProvingOptions::default(), // we'll be using default options ) .unwrap(); // the output should be 8 -assert_eq!(8, outputs.stack().first().unwrap().as_int()); +assert_eq!(8, outputs.first().unwrap().as_int()); ``` ### Verifying program execution @@ -196,7 +196,7 @@ let mut assembler = Assembler::default(); let program = assembler.assemble_program(&source).unwrap(); // initialize a default host (with an empty advice provider) -let host = DefaultHost::default(); +let mut host = DefaultHost::default(); // initialize the stack with values 0 and 1 let stack_inputs = StackInputs::try_from_ints([0, 1]).unwrap(); @@ -205,7 +205,7 @@ let stack_inputs = StackInputs::try_from_ints([0, 1]).unwrap(); let (outputs, proof) = miden_vm::prove( &program, stack_inputs, - host, + &mut host, ProvingOptions::default(), // use default proving options ) .unwrap(); diff --git a/miden/tests/integration/operations/decorators/mod.rs b/miden/tests/integration/operations/decorators/mod.rs index d5bee48206..d97f66bac2 100644 --- a/miden/tests/integration/operations/decorators/mod.rs +++ b/miden/tests/integration/operations/decorators/mod.rs @@ -1,10 +1,9 @@ use std::sync::Arc; use processor::{ - AdviceExtractor, AdviceProvider, ExecutionError, Host, HostResponse, MastForest, - MemAdviceProvider, ProcessState, + AdviceProvider, ExecutionError, Host, HostResponse, MastForest, MemAdviceProvider, ProcessState, }; -use vm_core::{AdviceInjector, DebugOptions}; +use vm_core::DebugOptions; mod advice; mod asmop; @@ -41,22 +40,6 @@ impl Host for TestHost { &mut self.adv_provider } - fn get_advice( - &mut self, - process: ProcessState, - extractor: AdviceExtractor, - ) -> Result { - self.adv_provider.get_advice(process, &extractor) - } - - fn set_advice( - &mut self, - process: ProcessState, - injector: AdviceInjector, - ) -> Result { - self.adv_provider.set_advice(process, &injector) - } - fn on_event( &mut self, _process: ProcessState, diff --git a/processor/README.md b/processor/README.md index 837bb5fde0..92cebdc72e 100644 --- a/processor/README.md +++ b/processor/README.md @@ -37,7 +37,7 @@ let exec_options = ExecutionOptions::default(); let trace = execute(&program, stack_inputs.clone(), &mut host, exec_options).unwrap(); // now, execute the same program in debug mode and iterate over VM states -for vm_state in execute_iter(&program, stack_inputs, host, exec_options) { +for vm_state in execute_iter(&program, stack_inputs, &mut host, exec_options) { match vm_state { Ok(vm_state) => println!("{:?}", vm_state), Err(_) => println!("something went terribly wrong!"), diff --git a/processor/src/host/advice/extractors.rs b/processor/src/host/advice/extractors.rs deleted file mode 100644 index e71e6e5d3c..0000000000 --- a/processor/src/host/advice/extractors.rs +++ /dev/null @@ -1,106 +0,0 @@ -use core::fmt; - -// ADVICE EXTRACTORS -// ================================================================================================ - -/// Defines a set of actions which can be initiated from the VM to extract data from the advice -/// provider. These actions can only modify the advice stack. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum AdviceExtractor { - /// Pops an element from the advice stack and returns it. - /// - /// # Errors - /// Returns an error if the advice stack is empty. - /// - /// Inputs: - /// Operand stack: [...] - /// Advice stack: [value, ...] - /// Advice map: {...} - /// Merkle store: {...} - /// - /// Outputs: - /// Operand stack: [...] - /// Advice stack: [...] - /// Advice map: {...} - /// Merkle store: {...} - /// Return: \[value\] - PopStack, - - /// Pops a word (4 elements) from the advice stack and returns it. - /// - /// Note: a word is popped off the stack element-by-element. For example, a `[d, c, b, a, ...]` - /// stack (i.e., `d` is at the top of the stack) will yield `[d, c, b, a]`. - /// - /// # Errors - /// Returns an error if the advice stack does not contain a full word. - /// - /// Inputs: - /// Operand stack: [...] - /// Advice stack: [d, c, b, a, ...] - /// Advice map: {...} - /// Merkle store: {...} - /// - /// Outputs: - /// Operand stack: [...] - /// Advice stack: [...] - /// Advice map: {...} - /// Merkle store: {...} - /// Return: [a, b, c, d] - PopStackWord, - - /// Pops a double word (8 elements) from the advice stack and returns them. - /// - /// Note: words are popped off the stack element-by-element. For example, a - /// `[h, g, f, e, d, c, b, a, ...]` stack (i.e., `h` is at the top of the stack) will yield - /// two words: `[h, g, f,e ], [d, c, b, a]`. - /// - /// # Errors - /// Returns an error if the advice stack does not contain two words. - /// - /// Inputs: - /// Operand stack: [...] - /// Advice stack: [h, g, f, e, d, c, b, a, ...] - /// Advice map: {...} - /// Merkle store: {...} - /// - /// Outputs: - /// Operand stack: [...] - /// Advice stack: [...] - /// Advice map: {...} - /// Merkle store: {...} - /// Return: [a, b, c, d, e, f, g, h] - PopStackDWord, - - /// Extracts a Merkle path for the node specified by the values at the top of the operand stack - /// and returns it to the caller. - /// - /// # Errors - /// Returns an error if the Merkle store does not contain the specified Merkle path. - /// - /// Inputs: - /// Operand stack: [WORD, depth, index, ROOT, ...] - /// Advice stack: [...] - /// Advice map: {...} - /// Merkle store: {path, ...} - /// - /// Outputs: - /// Operand stack: [WORD, depth, index, ROOT, ...] - /// Advice stack: [...] - /// Advice map: {...} - /// Merkle store: {path, ...} - /// Return: \[path\] - GetMerklePath, -} - -impl fmt::Display for AdviceExtractor { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::PopStack => write!(f, "pop_stack"), - Self::PopStackWord => write!(f, "pop_stack_word"), - Self::PopStackDWord => write!(f, "pop_stack_dword"), - Self::GetMerklePath => { - write!(f, "get_merkle_path") - }, - } - } -} diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index f00274c332..0123cfd9c1 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -1,20 +1,16 @@ use alloc::vec::Vec; -use core::borrow::Borrow; use vm_core::{ crypto::{ hash::RpoDigest, merkle::{InnerNodeInfo, MerklePath, MerkleStore, NodeIndex, StoreNode}, }, - AdviceInjector, SignatureKind, + SignatureKind, }; use super::HostResponse; use crate::{ExecutionError, Felt, InputError, ProcessState, Word}; -mod extractors; -pub use extractors::AdviceExtractor; - mod inputs; pub use inputs::AdviceInputs; @@ -45,58 +41,144 @@ pub use source::AdviceSource; /// Merkle paths from the store, as well as mutate it by updating or merging nodes contained in /// the store. pub trait AdviceProvider: Sized { - // ADVICE HANDLERS + // REQUIRED METHODS // -------------------------------------------------------------------------------------------- - /// Handles the specified advice injector request. - fn set_advice( - &mut self, - process: ProcessState, - advice_injector: &AdviceInjector, - ) -> Result { - match advice_injector { - AdviceInjector::MerkleNodeMerge => self.merge_merkle_nodes(process), - AdviceInjector::MerkleNodeToStack => self.copy_merkle_node_to_adv_stack(process), - AdviceInjector::MapValueToStack { include_len, key_offset } => { - self.copy_map_value_to_adv_stack(process, *include_len, *key_offset) - }, - AdviceInjector::UpdateMerkleNode => self.update_operand_stack_merkle_node(process), - AdviceInjector::U64Div => self.push_u64_div_result(process), - AdviceInjector::Ext2Inv => self.push_ext2_inv_result(process), - AdviceInjector::Ext2Intt => self.push_ext2_intt_result(process), - AdviceInjector::SmtGet => self.push_smtget_inputs(process), - AdviceInjector::SmtSet => self.push_smtset_inputs(process), - AdviceInjector::SmtPeek => self.push_smtpeek_result(process), - AdviceInjector::U32Clz => self.push_leading_zeros(process), - AdviceInjector::U32Ctz => self.push_trailing_zeros(process), - AdviceInjector::U32Clo => self.push_leading_ones(process), - AdviceInjector::U32Cto => self.push_trailing_ones(process), - AdviceInjector::ILog2 => self.push_ilog2(process), - - AdviceInjector::MemToMap => self.insert_mem_values_into_adv_map(process), - AdviceInjector::HdwordToMap { domain } => { - self.insert_hdword_into_adv_map(process, *domain) - }, - AdviceInjector::HpermToMap => self.insert_hperm_into_adv_map(process), - AdviceInjector::SigToStack { kind } => self.push_signature(process, *kind), - } - } - - /// Handles the specified advice extractor request. - fn get_advice( + // ADVICE STACK + // -------------------------------------------------------------------------------------------- + + /// Pops an element from the advice stack and returns it. + /// + /// # Errors + /// Returns an error if the advice stack is empty. + fn pop_stack(&mut self, process: ProcessState) -> Result; + + /// Pops a word (4 elements) from the advice stack and returns it. + /// + /// Note: a word is popped off the stack element-by-element. For example, a `[d, c, b, a, ...]` + /// stack (i.e., `d` is at the top of the stack) will yield `[d, c, b, a]`. + /// + /// # Errors + /// Returns an error if the advice stack does not contain a full word. + fn pop_stack_word(&mut self, process: ProcessState) -> Result; + + /// Pops a double word (8 elements) from the advice stack and returns them. + /// + /// Note: words are popped off the stack element-by-element. For example, a + /// `[h, g, f, e, d, c, b, a, ...]` stack (i.e., `h` is at the top of the stack) will yield + /// two words: `[h, g, f,e ], [d, c, b, a]`. + /// + /// # Errors + /// Returns an error if the advice stack does not contain two words. + fn pop_stack_dword(&mut self, process: ProcessState) -> Result<[Word; 2], ExecutionError>; + + /// Pushes the value(s) specified by the source onto the advice stack. + /// + /// # Errors + /// Returns an error if the value specified by the advice source cannot be obtained. + fn push_stack(&mut self, source: AdviceSource) -> Result<(), ExecutionError>; + + // ADVICE MAP + // -------------------------------------------------------------------------------------------- + + /// Returns a reference to the value(s) associated with the specified key in the advice map. + fn get_mapped_values(&self, key: &RpoDigest) -> Option<&[Felt]>; + + /// Inserts the provided value into the advice map under the specified key. + /// + /// The values in the advice map can be moved onto the advice stack by invoking + /// [AdviceProvider::push_stack()] method. + /// + /// If the specified key is already present in the advice map, the values under the key + /// are replaced with the specified values. + fn insert_into_map(&mut self, key: Word, values: Vec); + + /// Returns a signature on a message using a public key. + fn get_signature( + &self, + kind: SignatureKind, + pub_key: Word, + msg: Word, + ) -> Result, ExecutionError>; + + // MERKLE STORE + // -------------------------------------------------------------------------------------------- + + /// Returns a node at the specified depth and index in a Merkle tree with the given root. + /// + /// # Errors + /// Returns an error if: + /// - A Merkle tree for the specified root cannot be found in this advice provider. + /// - The specified depth is either zero or greater than the depth of the Merkle tree identified + /// by the specified root. + /// - Value of the node at the specified depth and index is not known to this advice provider. + fn get_tree_node(&self, root: Word, depth: &Felt, index: &Felt) + -> Result; + + /// Returns a path to a node at the specified depth and index in a Merkle tree with the + /// specified root. + /// + /// # Errors + /// Returns an error if: + /// - A Merkle tree for the specified root cannot be found in this advice provider. + /// - The specified depth is either zero or greater than the depth of the Merkle tree identified + /// by the specified root. + /// - Path to the node at the specified depth and index is not known to this advice provider. + fn get_merkle_path( + &self, + root: Word, + depth: &Felt, + index: &Felt, + ) -> Result; + + /// Reconstructs a path from the root until a leaf or empty node and returns its depth. + /// + /// For more information, check [MerkleStore::get_leaf_depth]. + /// + /// # Errors + /// Will return an error if: + /// - The provided `tree_depth` doesn't fit `u8`. + /// - The conditions of [MerkleStore::get_leaf_depth] aren't met. + fn get_leaf_depth( + &self, + root: Word, + tree_depth: &Felt, + index: &Felt, + ) -> Result; + + /// Updates a node at the specified depth and index in a Merkle tree with the specified root; + /// returns the Merkle path from the updated node to the new root, together with the new root. + /// + /// The tree is cloned prior to the update. Thus, the advice provider retains the original and + /// the updated tree. + /// + /// # Errors + /// Returns an error if: + /// - A Merkle tree for the specified root cannot be found in this advice provider. + /// - The specified depth is either zero or greater than the depth of the Merkle tree identified + /// by the specified root. + /// - Path to the leaf at the specified index in the specified Merkle tree is not known to this + /// advice provider. + fn update_merkle_node( &mut self, - process: ProcessState, - advice_extractor: &AdviceExtractor, - ) -> Result { - match advice_extractor { - AdviceExtractor::PopStack => self.pop_stack(process).map(HostResponse::Element), - AdviceExtractor::PopStackDWord => { - self.pop_stack_dword(process).map(HostResponse::DoubleWord) - }, - AdviceExtractor::PopStackWord => self.pop_stack_word(process).map(HostResponse::Word), - AdviceExtractor::GetMerklePath => self.get_operand_stack_merkle_path(process), - } - } + root: Word, + depth: &Felt, + index: &Felt, + value: Word, + ) -> Result<(MerklePath, Word), ExecutionError>; + + /// Creates a new Merkle tree in the advice provider by combining Merkle trees with the + /// specified roots. The root of the new tree is defined as `hash(left_root, right_root)`. + /// + /// After the operation, both the original trees and the new tree remains in the advice + /// provider (i.e., the input trees are not removed). + /// + /// It is not checked whether a Merkle tree for either of the specified roots can be found in + /// this advice provider. + fn merge_roots(&mut self, lhs: Word, rhs: Word) -> Result; + + // PROVIDED METHODS + // -------------------------------------------------------------------------------------------- // DEFAULT ADVICE MAP INJECTORS // -------------------------------------------------------------------------------------------- @@ -544,169 +626,6 @@ pub trait AdviceProvider: Sized { ) -> Result { injectors::smt::push_smtset_inputs(self, process) } - - // ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Creates a "by reference" advice provider for this instance. - /// - /// The returned adapter also implements [AdviceProvider] and will simply mutably borrow this - /// instance. - fn by_ref(&mut self) -> &mut Self { - // this trait follows the same model as - // [io::Read](https://doc.rust-lang.org/std/io/trait.Read.html#method.by_ref). - // - // this approach allows the flexibility to take an advice provider either as owned or by - // mutable reference - both equally compatible with the trait requirements as we implement - // `AdviceProvider` for mutable references of any type that also implements advice - // provider. - self - } - - // REQUIRED METHODS - // -------------------------------------------------------------------------------------------- - - // ADVICE STACK - // -------------------------------------------------------------------------------------------- - - /// Pops an element from the advice stack and returns it. - /// - /// # Errors - /// Returns an error if the advice stack is empty. - fn pop_stack(&mut self, process: ProcessState) -> Result; - - /// Pops a word (4 elements) from the advice stack and returns it. - /// - /// Note: a word is popped off the stack element-by-element. For example, a `[d, c, b, a, ...]` - /// stack (i.e., `d` is at the top of the stack) will yield `[d, c, b, a]`. - /// - /// # Errors - /// Returns an error if the advice stack does not contain a full word. - fn pop_stack_word(&mut self, process: ProcessState) -> Result; - - /// Pops a double word (8 elements) from the advice stack and returns them. - /// - /// Note: words are popped off the stack element-by-element. For example, a - /// `[h, g, f, e, d, c, b, a, ...]` stack (i.e., `h` is at the top of the stack) will yield - /// two words: `[h, g, f,e ], [d, c, b, a]`. - /// - /// # Errors - /// Returns an error if the advice stack does not contain two words. - fn pop_stack_dword(&mut self, process: ProcessState) -> Result<[Word; 2], ExecutionError>; - - /// Pushes the value(s) specified by the source onto the advice stack. - /// - /// # Errors - /// Returns an error if the value specified by the advice source cannot be obtained. - fn push_stack(&mut self, source: AdviceSource) -> Result<(), ExecutionError>; - - // ADVICE MAP - // -------------------------------------------------------------------------------------------- - - /// Returns a reference to the value(s) associated with the specified key in the advice map. - fn get_mapped_values(&self, key: &RpoDigest) -> Option<&[Felt]>; - - /// Inserts the provided value into the advice map under the specified key. - /// - /// The values in the advice map can be moved onto the advice stack by invoking - /// [AdviceProvider::push_stack()] method. - /// - /// If the specified key is already present in the advice map, the values under the key - /// are replaced with the specified values. - fn insert_into_map(&mut self, key: Word, values: Vec); - - /// Returns a signature on a message using a public key. - fn get_signature( - &self, - kind: SignatureKind, - pub_key: Word, - msg: Word, - ) -> Result, ExecutionError>; - - // MERKLE STORE - // -------------------------------------------------------------------------------------------- - - /// Returns a node at the specified depth and index in a Merkle tree with the given root. - /// - /// # Errors - /// Returns an error if: - /// - A Merkle tree for the specified root cannot be found in this advice provider. - /// - The specified depth is either zero or greater than the depth of the Merkle tree identified - /// by the specified root. - /// - Value of the node at the specified depth and index is not known to this advice provider. - fn get_tree_node(&self, root: Word, depth: &Felt, index: &Felt) - -> Result; - - /// Returns a path to a node at the specified depth and index in a Merkle tree with the - /// specified root. - /// - /// # Errors - /// Returns an error if: - /// - A Merkle tree for the specified root cannot be found in this advice provider. - /// - The specified depth is either zero or greater than the depth of the Merkle tree identified - /// by the specified root. - /// - Path to the node at the specified depth and index is not known to this advice provider. - fn get_merkle_path( - &self, - root: Word, - depth: &Felt, - index: &Felt, - ) -> Result; - - /// Reconstructs a path from the root until a leaf or empty node and returns its depth. - /// - /// For more information, check [MerkleStore::get_leaf_depth]. - /// - /// # Errors - /// Will return an error if: - /// - The provided `tree_depth` doesn't fit `u8`. - /// - The conditions of [MerkleStore::get_leaf_depth] aren't met. - fn get_leaf_depth( - &self, - root: Word, - tree_depth: &Felt, - index: &Felt, - ) -> Result; - - /// Updates a node at the specified depth and index in a Merkle tree with the specified root; - /// returns the Merkle path from the updated node to the new root, together with the new root. - /// - /// The tree is cloned prior to the update. Thus, the advice provider retains the original and - /// the updated tree. - /// - /// # Errors - /// Returns an error if: - /// - A Merkle tree for the specified root cannot be found in this advice provider. - /// - The specified depth is either zero or greater than the depth of the Merkle tree identified - /// by the specified root. - /// - Path to the leaf at the specified index in the specified Merkle tree is not known to this - /// advice provider. - fn update_merkle_node( - &mut self, - root: Word, - depth: &Felt, - index: &Felt, - value: Word, - ) -> Result<(MerklePath, Word), ExecutionError>; - - /// Creates a new Merkle tree in the advice provider by combining Merkle trees with the - /// specified roots. The root of the new tree is defined as `hash(left_root, right_root)`. - /// - /// After the operation, both the original trees and the new tree remains in the advice - /// provider (i.e., the input trees are not removed). - /// - /// It is not checked whether a Merkle tree for either of the specified roots can be found in - /// this advice provider. - fn merge_roots(&mut self, lhs: Word, rhs: Word) -> Result; - - /// Returns a subset of this Merkle store such that the returned Merkle store contains all - /// nodes which are descendants of the specified roots. - /// - /// The roots for which no descendants exist in this Merkle store are ignored. - fn get_store_subset(&self, roots: I) -> MerkleStore - where - I: Iterator, - R: Borrow; } impl AdviceProvider for &mut T @@ -786,12 +705,4 @@ where fn merge_roots(&mut self, lhs: Word, rhs: Word) -> Result { T::merge_roots(self, lhs, rhs) } - - fn get_store_subset(&self, roots: I) -> MerkleStore - where - I: Iterator, - R: Borrow, - { - T::get_store_subset(self, roots) - } } diff --git a/processor/src/host/advice/providers.rs b/processor/src/host/advice/providers.rs index 75de9486af..dd8158f949 100644 --- a/processor/src/host/advice/providers.rs +++ b/processor/src/host/advice/providers.rs @@ -200,14 +200,6 @@ where .map(|v| v.into()) .map_err(ExecutionError::MerkleStoreMergeFailed) } - - fn get_store_subset(&self, roots: I) -> MerkleStore - where - I: Iterator, - R: core::borrow::Borrow, - { - self.store.subset(roots).into_inner().into_iter().collect() - } } // MEMORY ADVICE PROVIDER @@ -302,14 +294,6 @@ impl AdviceProvider for MemAdviceProvider { fn merge_roots(&mut self, lhs: Word, rhs: Word) -> Result { self.provider.merge_roots(lhs, rhs) } - - fn get_store_subset(&self, roots: I) -> MerkleStore - where - I: Iterator, - R: core::borrow::Borrow { - self.provider.get_store_subset(roots) - } - } impl MemAdviceProvider { @@ -420,13 +404,6 @@ impl AdviceProvider for RecAdviceProvider { fn merge_roots(&mut self, lhs: Word, rhs: Word) -> Result { self.provider.merge_roots(lhs, rhs) } - - fn get_store_subset(&self, roots: I) -> MerkleStore - where - I: Iterator, - R: core::borrow::Borrow { - self.provider.get_store_subset(roots) - } } impl RecAdviceProvider { diff --git a/processor/src/host/mod.rs b/processor/src/host/mod.rs index 649d114ff5..cc255bb333 100644 --- a/processor/src/host/mod.rs +++ b/processor/src/host/mod.rs @@ -3,15 +3,14 @@ use alloc::sync::Arc; use vm_core::{ crypto::{hash::RpoDigest, merkle::MerklePath}, mast::MastForest, - utils::collections::KvMap, - AdviceInjector, DebugOptions, Word, + DebugOptions, Word, }; use super::{ExecutionError, Felt, ProcessState}; -use crate::MemAdviceProvider; +use crate::{KvMap, MemAdviceProvider}; pub(super) mod advice; -use advice::{AdviceExtractor, AdviceProvider}; +use advice::AdviceProvider; #[cfg(feature = "std")] mod debug; @@ -22,15 +21,13 @@ pub use mast_forest_store::{MastForestStore, MemMastForestStore}; // HOST TRAIT // ================================================================================================ -/// Defines an interface by which the VM can make requests to the host. +/// Defines an interface by which the VM can interact with the host. /// -/// There are three variants of requests, these can get advice, set advice and invoke the -/// debug handler. The requests are specified by the [AdviceExtractor], [AdviceInjector] and -/// [DebugOptions] enums which target the `get_advice`, `set_advice` and `on_debug` methods -/// respectively. The host is responsible for handling the requests and returning the results to -/// the VM in the form of [HostResponse]. The host is provided with a reference to the current -/// state of the VM ([ProcessState]), which it can use to extract the data required to fulfill the -/// request. +/// There are four main categories of interactions between the VM and the host: +/// 1. accessing the advice provider, +/// 2. getting a library's MAST forest, +/// 3. handling advice events (which internally mutates the advice provider), and +/// 4. handling debug and trace events. pub trait Host { type AdviceProvider: AdviceProvider; @@ -43,20 +40,6 @@ pub trait Host { /// Returns a mutable reference to the advice provider. fn advice_provider_mut(&mut self) -> &mut Self::AdviceProvider; - /// Returns the requested advice, specified by [AdviceExtractor], from the host to the VM. - fn get_advice( - &mut self, - process: ProcessState, - extractor: AdviceExtractor, - ) -> Result; - - /// Sets the requested advice, specified by [AdviceInjector], on the host. - fn set_advice( - &mut self, - process: ProcessState, - injector: AdviceInjector, - ) -> Result; - /// Returns MAST forest corresponding to the specified digest, or None if the MAST forest for /// this digest could not be found in this [Host]. fn get_mast_forest(&self, node_digest: &RpoDigest) -> Option>; @@ -64,20 +47,6 @@ pub trait Host { // PROVIDED METHODS // -------------------------------------------------------------------------------------------- - /// Creates a "by reference" host for this instance. - /// - /// The returned adapter also implements [Host] and will simply mutably borrow this - /// instance. - fn by_ref(&mut self) -> &mut Self { - // this trait follows the same model as - // [io::Read](https://doc.rust-lang.org/std/io/trait.Read.html#method.by_ref). - // - // this approach allows the flexibility to take a host either as owned or by mutable - // reference - both equally compatible with the trait requirements as we implement - // `Host` for mutable references of any type that also implements `Host`. - self - } - /// Handles the event emitted from the VM. fn on_event( &mut self, @@ -129,54 +98,6 @@ pub trait Host { err_msg: None, } } - - /// Pops an element from the advice stack and returns it. - /// - /// # Errors - /// Returns an error if the advice stack is empty. - fn pop_adv_stack(&mut self, process: ProcessState) -> Result { - let response = self.get_advice(process, AdviceExtractor::PopStack)?; - Ok(response.into()) - } - - /// Pops a word (4 elements) from the advice stack and returns it. - /// - /// Note: a word is popped off the stack element-by-element. For example, a `[d, c, b, a, ...]` - /// stack (i.e., `d` is at the top of the stack) will yield `[d, c, b, a]`. - /// - /// # Errors - /// Returns an error if the advice stack does not contain a full word. - fn pop_adv_stack_word(&mut self, process: ProcessState) -> Result { - let response = self.get_advice(process, AdviceExtractor::PopStackWord)?; - Ok(response.into()) - } - - /// Pops a double word (8 elements) from the advice stack and returns them. - /// - /// Note: words are popped off the stack element-by-element. For example, a - /// `[h, g, f, e, d, c, b, a, ...]` stack (i.e., `h` is at the top of the stack) will yield - /// two words: `[h, g, f,e ], [d, c, b, a]`. - /// - /// # Errors - /// Returns an error if the advice stack does not contain two words. - fn pop_adv_stack_dword(&mut self, process: ProcessState) -> Result<[Word; 2], ExecutionError> { - let response = self.get_advice(process, AdviceExtractor::PopStackDWord)?; - Ok(response.into()) - } - - /// Returns a path to a node at the specified depth and index in a Merkle tree with the - /// specified root. - /// - /// # Errors - /// Returns an error if: - /// - A Merkle tree for the specified root cannot be found in this advice provider. - /// - The specified depth is either zero or greater than the depth of the Merkle tree identified - /// by the specified root. - /// - Path to the node at the specified depth and index is not known to this advice provider. - fn get_adv_merkle_path(&mut self, process: ProcessState) -> Result { - let response = self.get_advice(process, AdviceExtractor::GetMerklePath)?; - Ok(response.into()) - } } impl Host for &mut H @@ -193,22 +114,6 @@ where H::advice_provider_mut(self) } - fn get_advice( - &mut self, - process: ProcessState, - extractor: AdviceExtractor, - ) -> Result { - H::get_advice(self, process, extractor) - } - - fn set_advice( - &mut self, - process: ProcessState, - injector: AdviceInjector, - ) -> Result { - H::set_advice(self, process, injector) - } - fn get_mast_forest(&self, node_digest: &RpoDigest) -> Option> { H::get_mast_forest(self, node_digest) } @@ -375,22 +280,6 @@ where &mut self.adv_provider } - fn get_advice( - &mut self, - process: ProcessState, - extractor: AdviceExtractor, - ) -> Result { - self.adv_provider.get_advice(process, &extractor) - } - - fn set_advice( - &mut self, - process: ProcessState, - injector: AdviceInjector, - ) -> Result { - self.adv_provider.set_advice(process, &injector) - } - fn get_mast_forest(&self, node_digest: &RpoDigest) -> Option> { self.store.get(node_digest) } diff --git a/processor/src/lib.rs b/processor/src/lib.rs index b68ef5f99b..9b1d46c15d 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -47,10 +47,7 @@ use range::RangeChecker; mod host; pub use host::{ - advice::{ - AdviceExtractor, AdviceInputs, AdviceProvider, AdviceSource, MemAdviceProvider, - RecAdviceProvider, - }, + advice::{AdviceInputs, AdviceProvider, AdviceSource, MemAdviceProvider, RecAdviceProvider}, DefaultHost, Host, HostResponse, MastForestStore, MemMastForestStore, }; @@ -597,7 +594,51 @@ impl Process { ) -> Result<(), ExecutionError> { match decorator { Decorator::Advice(injector) => { - host.set_advice(self.into(), *injector)?; + let advice_provider = host.advice_provider_mut(); + let process_state: ProcessState = self.into(); + let _ = match injector { + AdviceInjector::MerkleNodeMerge => { + advice_provider.merge_merkle_nodes(process_state)? + }, + AdviceInjector::MerkleNodeToStack => { + advice_provider.copy_merkle_node_to_adv_stack(process_state)? + }, + AdviceInjector::MapValueToStack { include_len, key_offset } => advice_provider + .copy_map_value_to_adv_stack(process_state, *include_len, *key_offset)?, + AdviceInjector::UpdateMerkleNode => { + advice_provider.update_operand_stack_merkle_node(process_state)? + }, + AdviceInjector::U64Div => advice_provider.push_u64_div_result(process_state)?, + AdviceInjector::Ext2Inv => { + advice_provider.push_ext2_inv_result(process_state)? + }, + AdviceInjector::Ext2Intt => { + advice_provider.push_ext2_intt_result(process_state)? + }, + AdviceInjector::SmtGet => advice_provider.push_smtget_inputs(process_state)?, + AdviceInjector::SmtSet => advice_provider.push_smtset_inputs(process_state)?, + AdviceInjector::SmtPeek => { + advice_provider.push_smtpeek_result(process_state)? + }, + AdviceInjector::U32Clz => advice_provider.push_leading_zeros(process_state)?, + AdviceInjector::U32Ctz => advice_provider.push_trailing_zeros(process_state)?, + AdviceInjector::U32Clo => advice_provider.push_leading_ones(process_state)?, + AdviceInjector::U32Cto => advice_provider.push_trailing_ones(process_state)?, + AdviceInjector::ILog2 => advice_provider.push_ilog2(process_state)?, + + AdviceInjector::MemToMap => { + advice_provider.insert_mem_values_into_adv_map(process_state)? + }, + AdviceInjector::HdwordToMap { domain } => { + advice_provider.insert_hdword_into_adv_map(process_state, *domain)? + }, + AdviceInjector::HpermToMap => { + advice_provider.insert_hperm_into_adv_map(process_state)? + }, + AdviceInjector::SigToStack { kind } => { + advice_provider.push_signature(process_state, *kind)? + }, + }; }, Decorator::Debug(options) => { if self.decoder.in_debug_mode() { diff --git a/processor/src/operations/crypto_ops.rs b/processor/src/operations/crypto_ops.rs index 5a30499617..f9405cd6df 100644 --- a/processor/src/operations/crypto_ops.rs +++ b/processor/src/operations/crypto_ops.rs @@ -1,7 +1,5 @@ -use vm_core::AdviceInjector; - use super::{ExecutionError, Operation, Process}; -use crate::{crypto::MerklePath, Host}; +use crate::{crypto::MerklePath, AdviceProvider, Host}; // CRYPTOGRAPHIC OPERATIONS // ================================================================================================ @@ -77,7 +75,8 @@ impl Process { // get a Merkle path from the advice provider for the specified root and node index. // the path is expected to be of the specified depth. - let path = host.get_adv_merkle_path(self.into())?; + let path: MerklePath = + host.advice_provider_mut().get_operand_stack_merkle_path(self.into())?.into(); // use hasher to compute the Merkle root of the path let (addr, computed_root) = self.chiplets.build_merkle_root(node, &path, index); @@ -149,7 +148,7 @@ impl Process { // specified depth. if the new node is the root of a tree, this instruction will append the // whole sub-tree to this node. let path: MerklePath = - host.set_advice(self.into(), AdviceInjector::UpdateMerkleNode)?.into(); + host.advice_provider_mut().update_operand_stack_merkle_node(self.into())?.into(); assert_eq!(path.len(), depth.as_int() as usize); diff --git a/processor/src/operations/io_ops.rs b/processor/src/operations/io_ops.rs index ec9e3dddc5..96c98c0b81 100644 --- a/processor/src/operations/io_ops.rs +++ b/processor/src/operations/io_ops.rs @@ -1,5 +1,5 @@ use super::{ExecutionError, Felt, Operation, Process}; -use crate::{Host, Word}; +use crate::{AdviceProvider, Host, Word}; // INPUT / OUTPUT OPERATIONS // ================================================================================================ @@ -186,7 +186,7 @@ impl Process { let addr = Self::get_valid_address(self.stack.get(12))?; // pop two words from the advice stack - let words = host.pop_adv_stack_dword(self.into())?; + let words = host.advice_provider_mut().pop_stack_dword(self.into())?; // write the words memory self.chiplets.write_mem_double(ctx, addr, words)?; @@ -219,7 +219,7 @@ impl Process { /// # Errors /// Returns an error if the advice stack is empty. pub(super) fn op_advpop(&mut self, host: &mut impl Host) -> Result<(), ExecutionError> { - let value = host.pop_adv_stack(self.into())?; + let value = host.advice_provider_mut().pop_stack(self.into())?; self.stack.set(0, value); self.stack.shift_right(0); Ok(()) @@ -231,7 +231,7 @@ impl Process { /// # Errors /// Returns an error if the advice stack contains fewer than four elements. pub(super) fn op_advpopw(&mut self, host: &mut impl Host) -> Result<(), ExecutionError> { - let word: Word = host.pop_adv_stack_word(self.into())?; + let word: Word = host.advice_provider_mut().pop_stack_word(self.into())?; self.stack.set(0, word[3]); self.stack.set(1, word[2]); diff --git a/prover/README.md b/prover/README.md index be22bbe55b..b1465b21ac 100644 --- a/prover/README.md +++ b/prover/README.md @@ -30,13 +30,13 @@ let program = assembler.compile("begin push.3 push.5 add end").unwrap(); let (outputs, proof) = prove( &program, StackInputs::default(), // we won't provide any stack inputs - DefaultHost::default(), // we'll be using a default host + &mut DefaultHost::default(), // we'll be using a default host &ProvingOptions::default(), // we'll be using default options ) .unwrap(); // the output should be 8 -assert_eq!(8, outputs.stack().first().unwrap().as_int()); +assert_eq!(8, outputs.first().unwrap().as_int()); ``` ## Crate features From 09cbeb61cfbd53eed0d23ce3f83c7232a1cfa7c1 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Thu, 7 Nov 2024 10:38:27 -0500 Subject: [PATCH 11/39] refactor: remove unused `push_smtget` and `push_smtset` from `AdviceProvider` trait --- assembly/src/ast/instruction/advice.rs | 6 ------ assembly/src/ast/tests.rs | 3 +-- assembly/src/ast/visit.rs | 4 ---- assembly/src/parser/grammar.lalrpop | 6 ------ core/src/mast/serialization/decorator.rs | 12 ------------ core/src/mast/serialization/tests.rs | 4 ---- core/src/operations/decorators/advice.rs | 8 -------- processor/src/host/advice/injectors/smt.rs | 16 ---------------- processor/src/host/advice/mod.rs | 16 ---------------- processor/src/lib.rs | 2 -- 10 files changed, 1 insertion(+), 76 deletions(-) diff --git a/assembly/src/ast/instruction/advice.rs b/assembly/src/ast/instruction/advice.rs index c0f13cbeb9..d917686ed2 100644 --- a/assembly/src/ast/instruction/advice.rs +++ b/assembly/src/ast/instruction/advice.rs @@ -16,8 +16,6 @@ use crate::{ast::ImmU8, Felt, ZERO}; pub enum AdviceInjectorNode { PushU64Div, PushExt2intt, - PushSmtGet, - PushSmtSet, PushSmtPeek, PushMapVal, PushMapValImm { offset: ImmU8 }, @@ -37,8 +35,6 @@ impl From<&AdviceInjectorNode> for AdviceInjector { match value { PushU64Div => Self::U64Div, PushExt2intt => Self::Ext2Intt, - PushSmtGet => Self::SmtGet, - PushSmtSet => Self::SmtSet, PushSmtPeek => Self::SmtPeek, PushMapVal => Self::MapValueToStack { include_len: false, key_offset: 0 }, PushMapValImm { offset: ImmU8::Value(offset) } => Self::MapValueToStack { @@ -76,8 +72,6 @@ impl fmt::Display for AdviceInjectorNode { match self { Self::PushU64Div => write!(f, "push_u64div"), Self::PushExt2intt => write!(f, "push_ext2intt"), - Self::PushSmtGet => write!(f, "push_smtget"), - Self::PushSmtSet => write!(f, "push_smtset"), Self::PushSmtPeek => write!(f, "push_smtpeek"), Self::PushMapVal => write!(f, "push_mapval"), Self::PushMapValImm { offset } => write!(f, "push_mapval.{offset}"), diff --git a/assembly/src/ast/tests.rs b/assembly/src/ast/tests.rs index 7fff35c587..a289a65251 100644 --- a/assembly/src/ast/tests.rs +++ b/assembly/src/ast/tests.rs @@ -472,12 +472,11 @@ fn test_ast_parsing_adv_injection() -> Result<(), Report> { let context = TestContext::new(); let source = source_file!( &context, - "begin adv.push_u64div adv.push_mapval adv.push_smtget adv.insert_mem end" + "begin adv.push_u64div adv.push_mapval adv.insert_mem end" ); let forms = module!(begin!( inst!(AdvInject(PushU64Div)), inst!(AdvInject(PushMapVal)), - inst!(AdvInject(PushSmtGet)), inst!(AdvInject(InsertMem)) )); assert_eq!(context.parse_forms(source)?, forms); diff --git a/assembly/src/ast/visit.rs b/assembly/src/ast/visit.rs index ecd11fb86e..637a82f81c 100644 --- a/assembly/src/ast/visit.rs +++ b/assembly/src/ast/visit.rs @@ -361,8 +361,6 @@ where }, AdviceInjectorNode::PushU64Div | AdviceInjectorNode::PushExt2intt - | AdviceInjectorNode::PushSmtGet - | AdviceInjectorNode::PushSmtSet | AdviceInjectorNode::PushSmtPeek | AdviceInjectorNode::PushMapVal | AdviceInjectorNode::PushMapValN @@ -813,8 +811,6 @@ where }, AdviceInjectorNode::PushU64Div | AdviceInjectorNode::PushExt2intt - | AdviceInjectorNode::PushSmtGet - | AdviceInjectorNode::PushSmtSet | AdviceInjectorNode::PushSmtPeek | AdviceInjectorNode::PushMapVal | AdviceInjectorNode::PushMapValN diff --git a/assembly/src/parser/grammar.lalrpop b/assembly/src/parser/grammar.lalrpop index 65b78ae427..dcb469d475 100644 --- a/assembly/src/parser/grammar.lalrpop +++ b/assembly/src/parser/grammar.lalrpop @@ -54,8 +54,6 @@ extern { "push_mtnode" => Token::PushMtnode, "push_sig" => Token::PushSig, "push_smtpeek" => Token::PushSmtpeek, - "push_smtget" => Token::PushSmtget, - "push_smtset" => Token::PushSmtset, "push_u64div" => Token::PushU64Div, "and" => Token::And, "assert" => Token::Assert, @@ -685,8 +683,6 @@ AdviceInjector: Instruction = { "adv" "." "push_mtnode" => Instruction::AdvInject(AdviceInjectorNode::PushMtNode), "adv" "." "push_sig" "." => Instruction::AdvInject(AdviceInjectorNode::PushSignature { kind }), "adv" "." "push_smtpeek" => Instruction::AdvInject(AdviceInjectorNode::PushSmtPeek), - "adv" "." "push_smtget" => Instruction::AdvInject(AdviceInjectorNode::PushSmtGet), - "adv" "." "push_smtset" => Instruction::AdvInject(AdviceInjectorNode::PushSmtSet), "adv" "." "push_u64div" => Instruction::AdvInject(AdviceInjectorNode::PushU64Div), } @@ -1681,9 +1677,7 @@ Opcode: &'static str = { "movdnw" => "movdnw", "movup" => "movup", "movupw" => "movupw", - "mtree_get" => "mtree_get", "mtree_merge" => "mtree_merge", - "mtree_set" => "mtree_set", "mtree_verify" => "mtree_verify", "mul" => "mul", "neg" => "neg", diff --git a/core/src/mast/serialization/decorator.rs b/core/src/mast/serialization/decorator.rs index a8b2041e3c..5dfed28d3b 100644 --- a/core/src/mast/serialization/decorator.rs +++ b/core/src/mast/serialization/decorator.rs @@ -72,12 +72,6 @@ impl DecoratorInfo { EncodedDecoratorVariant::AdviceInjectorExt2Intt => { Ok(Decorator::Advice(AdviceInjector::Ext2Intt)) }, - EncodedDecoratorVariant::AdviceInjectorSmtGet => { - Ok(Decorator::Advice(AdviceInjector::SmtGet)) - }, - EncodedDecoratorVariant::AdviceInjectorSmtSet => { - Ok(Decorator::Advice(AdviceInjector::SmtSet)) - }, EncodedDecoratorVariant::AdviceInjectorSmtPeek => { Ok(Decorator::Advice(AdviceInjector::SmtPeek)) }, @@ -222,8 +216,6 @@ pub enum EncodedDecoratorVariant { AdviceInjectorU64Div, AdviceInjectorExt2Inv, AdviceInjectorExt2Intt, - AdviceInjectorSmtGet, - AdviceInjectorSmtSet, AdviceInjectorSmtPeek, AdviceInjectorU32Clz, AdviceInjectorU32Ctz, @@ -271,8 +263,6 @@ impl From<&Decorator> for EncodedDecoratorVariant { AdviceInjector::U64Div => Self::AdviceInjectorU64Div, AdviceInjector::Ext2Inv => Self::AdviceInjectorExt2Inv, AdviceInjector::Ext2Intt => Self::AdviceInjectorExt2Intt, - AdviceInjector::SmtGet => Self::AdviceInjectorSmtGet, - AdviceInjector::SmtSet => Self::AdviceInjectorSmtSet, AdviceInjector::SmtPeek => Self::AdviceInjectorSmtPeek, AdviceInjector::U32Clz => Self::AdviceInjectorU32Clz, AdviceInjector::U32Ctz => Self::AdviceInjectorU32Ctz, @@ -366,8 +356,6 @@ impl DecoratorDataBuilder { | AdviceInjector::U64Div | AdviceInjector::Ext2Inv | AdviceInjector::Ext2Intt - | AdviceInjector::SmtGet - | AdviceInjector::SmtSet | AdviceInjector::SmtPeek | AdviceInjector::U32Clz | AdviceInjector::U32Ctz diff --git a/core/src/mast/serialization/tests.rs b/core/src/mast/serialization/tests.rs index 76a180b796..d4b9d3b8b7 100644 --- a/core/src/mast/serialization/tests.rs +++ b/core/src/mast/serialization/tests.rs @@ -117,8 +117,6 @@ fn confirm_operation_and_decorator_structure() { AdviceInjector::U64Div => (), AdviceInjector::Ext2Inv => (), AdviceInjector::Ext2Intt => (), - AdviceInjector::SmtGet => (), - AdviceInjector::SmtSet => (), AdviceInjector::SmtPeek => (), AdviceInjector::U32Clz => (), AdviceInjector::U32Ctz => (), @@ -256,8 +254,6 @@ fn serialize_deserialize_all_nodes() { (1, Decorator::Advice(AdviceInjector::U64Div)), (3, Decorator::Advice(AdviceInjector::Ext2Inv)), (5, Decorator::Advice(AdviceInjector::Ext2Intt)), - (5, Decorator::Advice(AdviceInjector::SmtGet)), - (5, Decorator::Advice(AdviceInjector::SmtSet)), (5, Decorator::Advice(AdviceInjector::SmtPeek)), (5, Decorator::Advice(AdviceInjector::U32Clz)), (10, Decorator::Advice(AdviceInjector::U32Ctz)), diff --git a/core/src/operations/decorators/advice.rs b/core/src/operations/decorators/advice.rs index bc70906c3f..916ffec3cc 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/operations/decorators/advice.rs @@ -142,12 +142,6 @@ pub enum AdviceInjector { /// degree coefficients are located at the top of the advice stack. Ext2Intt, - /// Currently unimplemented - SmtGet, - - /// Currently unimplemented - SmtSet, - /// Pushes onto the advice stack the value associated with the specified key in a Sparse /// Merkle Tree defined by the specified root. /// @@ -303,8 +297,6 @@ impl fmt::Display for AdviceInjector { Self::U64Div => write!(f, "div_u64"), Self::Ext2Inv => write!(f, "ext2_inv"), Self::Ext2Intt => write!(f, "ext2_intt"), - Self::SmtGet => write!(f, "smt_get"), - Self::SmtSet => write!(f, "smt_set"), Self::SmtPeek => write!(f, "smt_peek"), Self::U32Clz => write!(f, "u32clz"), Self::U32Ctz => write!(f, "u32ctz"), diff --git a/processor/src/host/advice/injectors/smt.rs b/processor/src/host/advice/injectors/smt.rs index 544c8ee4b4..e3ba8ec6a6 100644 --- a/processor/src/host/advice/injectors/smt.rs +++ b/processor/src/host/advice/injectors/smt.rs @@ -67,22 +67,6 @@ pub(crate) fn push_smtpeek_result( Ok(HostResponse::None) } -/// Currently unimplemented -pub(crate) fn push_smtget_inputs( - _advice_provider: &mut A, - _process: ProcessState, -) -> Result { - unimplemented!() -} - -/// Currently unimplemented -pub(crate) fn push_smtset_inputs( - _advice_provider: &mut A, - _process: ProcessState, -) -> Result { - unimplemented!() -} - // HELPER METHODS // -------------------------------------------------------------------------------------------- diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index 0123cfd9c1..f1d5234e17 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -610,22 +610,6 @@ pub trait AdviceProvider: Sized { ) -> Result { injectors::smt::push_smtpeek_result(self, process) } - - /// Currently unimplemented - fn push_smtget_inputs( - &mut self, - process: ProcessState, - ) -> Result { - injectors::smt::push_smtget_inputs(self, process) - } - - /// Currently unimplemented - fn push_smtset_inputs( - &mut self, - process: ProcessState, - ) -> Result { - injectors::smt::push_smtset_inputs(self, process) - } } impl AdviceProvider for &mut T diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 9b1d46c15d..0fa4b3ee74 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -615,8 +615,6 @@ impl Process { AdviceInjector::Ext2Intt => { advice_provider.push_ext2_intt_result(process_state)? }, - AdviceInjector::SmtGet => advice_provider.push_smtget_inputs(process_state)?, - AdviceInjector::SmtSet => advice_provider.push_smtset_inputs(process_state)?, AdviceInjector::SmtPeek => { advice_provider.push_smtpeek_result(process_state)? }, From f40dffc5072a35919fc60fea3e6fd17719523b11 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Thu, 7 Nov 2024 11:07:57 -0500 Subject: [PATCH 12/39] refactor: inline `AdviceProvider` provided methods --- CHANGELOG.md | 1 + assembly/src/ast/tests.rs | 5 +- .../integration/operations/decorators/mod.rs | 22 +- .../src/host/advice/{injectors => }/dsa.rs | 2 +- .../advice/injectors/adv_map_injectors.rs | 187 -------- .../advice/injectors/adv_stack_injectors.rs | 421 ---------------- .../injectors/merkle_store_injectors.rs | 23 - processor/src/host/advice/injectors/mod.rs | 5 - processor/src/host/advice/injectors/smt.rs | 96 ---- processor/src/host/advice/mod.rs | 450 ++++++++++++++---- processor/src/host/advice/providers.rs | 30 +- processor/src/host/mod.rs | 91 +--- processor/src/lib.rs | 18 +- processor/src/operations/crypto_ops.rs | 4 +- 14 files changed, 401 insertions(+), 954 deletions(-) rename processor/src/host/advice/{injectors => }/dsa.rs (98%) delete mode 100644 processor/src/host/advice/injectors/adv_map_injectors.rs delete mode 100644 processor/src/host/advice/injectors/adv_stack_injectors.rs delete mode 100644 processor/src/host/advice/injectors/merkle_store_injectors.rs delete mode 100644 processor/src/host/advice/injectors/mod.rs delete mode 100644 processor/src/host/advice/injectors/smt.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f680b93fa3..d67d6b24d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Changes - [BREAKING] `Process` no longer takes ownership of the `Host` (#1571) - [BREAKING] `ProcessState` was converted from a trait to a struct (#1571) +- [BREAKING] `Host` and `AdviceProvider` traits simplified (#1572) #### Enhancements - Added `miden_core::mast::MastForest::advice_map` to load it into the advice provider before the `MastForest` execution (#1574). diff --git a/assembly/src/ast/tests.rs b/assembly/src/ast/tests.rs index a289a65251..e2c81b08d7 100644 --- a/assembly/src/ast/tests.rs +++ b/assembly/src/ast/tests.rs @@ -470,10 +470,7 @@ fn test_ast_parsing_adv_injection() -> Result<(), Report> { use super::AdviceInjectorNode::*; let context = TestContext::new(); - let source = source_file!( - &context, - "begin adv.push_u64div adv.push_mapval adv.insert_mem end" - ); + let source = source_file!(&context, "begin adv.push_u64div adv.push_mapval adv.insert_mem end"); let forms = module!(begin!( inst!(AdvInject(PushU64Div)), inst!(AdvInject(PushMapVal)), diff --git a/miden/tests/integration/operations/decorators/mod.rs b/miden/tests/integration/operations/decorators/mod.rs index d97f66bac2..43e862751a 100644 --- a/miden/tests/integration/operations/decorators/mod.rs +++ b/miden/tests/integration/operations/decorators/mod.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use processor::{ - AdviceProvider, ExecutionError, Host, HostResponse, MastForest, MemAdviceProvider, ProcessState, + AdviceProvider, ExecutionError, Host, MastForest, MemAdviceProvider, ProcessState, }; use vm_core::DebugOptions; @@ -40,31 +40,23 @@ impl Host for TestHost { &mut self.adv_provider } - fn on_event( - &mut self, - _process: ProcessState, - event_id: u32, - ) -> Result { + fn on_event(&mut self, _process: ProcessState, event_id: u32) -> Result<(), ExecutionError> { self.event_handler.push(event_id); - Ok(HostResponse::None) + Ok(()) } - fn on_trace( - &mut self, - _process: ProcessState, - trace_id: u32, - ) -> Result { + fn on_trace(&mut self, _process: ProcessState, trace_id: u32) -> Result<(), ExecutionError> { self.trace_handler.push(trace_id); - Ok(HostResponse::None) + Ok(()) } fn on_debug( &mut self, _process: ProcessState, _options: &DebugOptions, - ) -> Result { + ) -> Result<(), ExecutionError> { self.debug_handler.push(_options.to_string()); - Ok(HostResponse::None) + Ok(()) } fn get_mast_forest(&self, _node_digest: &prover::Digest) -> Option> { diff --git a/processor/src/host/advice/injectors/dsa.rs b/processor/src/host/advice/dsa.rs similarity index 98% rename from processor/src/host/advice/injectors/dsa.rs rename to processor/src/host/advice/dsa.rs index e0f8056134..0311999c10 100644 --- a/processor/src/host/advice/injectors/dsa.rs +++ b/processor/src/host/advice/dsa.rs @@ -1,6 +1,6 @@ use alloc::vec::Vec; -use super::super::{ExecutionError, Felt, Word}; +use super::{ExecutionError, Felt, Word}; /// Gets as input a vector containing a secret key, and a word representing a message and outputs a /// vector of values to be pushed onto the advice stack. diff --git a/processor/src/host/advice/injectors/adv_map_injectors.rs b/processor/src/host/advice/injectors/adv_map_injectors.rs deleted file mode 100644 index 11e8efccb2..0000000000 --- a/processor/src/host/advice/injectors/adv_map_injectors.rs +++ /dev/null @@ -1,187 +0,0 @@ -use alloc::vec::Vec; - -use vm_core::{ - crypto::hash::{Rpo256, RpoDigest}, - EMPTY_WORD, WORD_SIZE, -}; - -use super::super::{AdviceProvider, ExecutionError, Felt, HostResponse}; -use crate::ProcessState; - -// ADVICE MAP INJECTORS -// ================================================================================================ - -/// Reads words from memory at the specified range and inserts them into the advice map under -/// the key `KEY` located at the top of the stack. -/// -/// Inputs: -/// Operand stack: [KEY, start_addr, end_addr, ...] -/// Advice map: {...} -/// -/// Outputs: -/// Operand stack: [KEY, start_addr, end_addr, ...] -/// Advice map: {KEY: values} -/// -/// Where `values` are the elements located in memory[start_addr..end_addr]. -/// -/// # Errors -/// Returns an error: -/// - `start_addr` is greater than or equal to 2^32. -/// - `end_addr` is greater than or equal to 2^32. -/// - `start_addr` > `end_addr`. -pub(crate) fn insert_mem_values_into_adv_map( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - let (start_addr, end_addr) = get_mem_addr_range(process, 4, 5)?; - let ctx = process.ctx(); - - let mut values = Vec::with_capacity(((end_addr - start_addr) as usize) * WORD_SIZE); - for addr in start_addr..end_addr { - let mem_value = process.get_mem_value(ctx, addr).unwrap_or(EMPTY_WORD); - values.extend_from_slice(&mem_value); - } - - let key = process.get_stack_word(0); - advice_provider.insert_into_map(key, values); - - Ok(HostResponse::None) -} - -/// Reads two word from the operand stack and inserts them into the advice map under the key -/// defined by the hash of these words. -/// -/// Inputs: -/// Operand stack: [B, A, ...] -/// Advice map: {...} -/// -/// Outputs: -/// Operand stack: [B, A, ...] -/// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} -/// -/// Where KEY is computed as hash(A || B, domain), where domain is provided via the immediate -/// value. -pub(crate) fn insert_hdword_into_adv_map( - advice_provider: &mut A, - process: ProcessState, - domain: Felt, -) -> Result { - // get the top two words from the stack and hash them to compute the key value - let word0 = process.get_stack_word(0); - let word1 = process.get_stack_word(1); - let key = Rpo256::merge_in_domain(&[word1.into(), word0.into()], domain); - - // build a vector of values from the two word and insert it into the advice map under the - // computed key - let mut values = Vec::with_capacity(2 * WORD_SIZE); - values.extend_from_slice(&word1); - values.extend_from_slice(&word0); - advice_provider.insert_into_map(key.into(), values); - - Ok(HostResponse::None) -} - -/// Reads three words from the operand stack and inserts the top two words into the advice map -/// under the key defined by applying an RPO permutation to all three words. -/// -/// Inputs: -/// Operand stack: [B, A, C, ...] -/// Advice map: {...} -/// -/// Outputs: -/// Operand stack: [B, A, C, ...] -/// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} -/// -/// Where KEY is computed by extracting the digest elements from hperm([C, A, B]). For example, -/// if C is [0, d, 0, 0], KEY will be set as hash(A || B, d). -pub(crate) fn insert_hperm_into_adv_map( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - // read the state from the stack - let mut state = [ - process.get_stack_item(11), - process.get_stack_item(10), - process.get_stack_item(9), - process.get_stack_item(8), - process.get_stack_item(7), - process.get_stack_item(6), - process.get_stack_item(5), - process.get_stack_item(4), - process.get_stack_item(3), - process.get_stack_item(2), - process.get_stack_item(1), - process.get_stack_item(0), - ]; - - // get the values to be inserted into the advice map from the state - let values = state[Rpo256::RATE_RANGE].to_vec(); - - // apply the permutation to the state and extract the key from it - Rpo256::apply_permutation(&mut state); - let key = RpoDigest::new( - state[Rpo256::DIGEST_RANGE] - .try_into() - .expect("failed to extract digest from state"), - ); - - advice_provider.insert_into_map(key.into(), values); - - Ok(HostResponse::None) -} - -/// Creates a new Merkle tree in the advice provider by combining Merkle trees with the -/// specified roots. The root of the new tree is defined as `Hash(LEFT_ROOT, RIGHT_ROOT)`. -/// -/// Inputs: -/// Operand stack: [RIGHT_ROOT, LEFT_ROOT, ...] -/// Merkle store: {RIGHT_ROOT, LEFT_ROOT} -/// -/// Outputs: -/// Operand stack: [RIGHT_ROOT, LEFT_ROOT, ...] -/// Merkle store: {RIGHT_ROOT, LEFT_ROOT, hash(LEFT_ROOT, RIGHT_ROOT)} -/// -/// After the operation, both the original trees and the new tree remains in the advice -/// provider (i.e., the input trees are not removed). -/// -/// It is not checked whether the provided roots exist as Merkle trees in the advide providers. -pub(crate) fn merge_merkle_nodes( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - // fetch the arguments from the stack - let lhs = process.get_stack_word(1); - let rhs = process.get_stack_word(0); - - // perform the merge - advice_provider.merge_roots(lhs, rhs)?; - - Ok(HostResponse::None) -} - -// HELPER METHODS -// -------------------------------------------------------------------------------------------- - -/// Reads (start_addr, end_addr) tuple from the specified elements of the operand stack ( -/// without modifying the state of the stack), and verifies that memory range is valid. -fn get_mem_addr_range( - process: ProcessState, - start_idx: usize, - end_idx: usize, -) -> Result<(u32, u32), ExecutionError> { - let start_addr = process.get_stack_item(start_idx).as_int(); - let end_addr = process.get_stack_item(end_idx).as_int(); - - if start_addr > u32::MAX as u64 { - return Err(ExecutionError::MemoryAddressOutOfBounds(start_addr)); - } - if end_addr > u32::MAX as u64 { - return Err(ExecutionError::MemoryAddressOutOfBounds(end_addr)); - } - - if start_addr > end_addr { - return Err(ExecutionError::InvalidMemoryRange { start_addr, end_addr }); - } - - Ok((start_addr as u32, end_addr as u32)) -} diff --git a/processor/src/host/advice/injectors/adv_stack_injectors.rs b/processor/src/host/advice/injectors/adv_stack_injectors.rs deleted file mode 100644 index 60f4678602..0000000000 --- a/processor/src/host/advice/injectors/adv_stack_injectors.rs +++ /dev/null @@ -1,421 +0,0 @@ -use alloc::vec::Vec; - -use vm_core::{QuadExtension, SignatureKind}; -use winter_prover::math::fft; - -use super::super::{AdviceSource, ExecutionError, Felt, HostResponse}; -use crate::{AdviceProvider, Ext2InttError, FieldElement, ProcessState, ZERO}; - -// TYPE ALIASES -// ================================================================================================ -type QuadFelt = QuadExtension; - -// ADVICE STACK INJECTORS -// ================================================================================================ - -/// Pushes a node of the Merkle tree specified by the values on the top of the operand stack -/// onto the advice stack. -/// -/// Inputs: -/// Operand stack: [depth, index, TREE_ROOT, ...] -/// Advice stack: [...] -/// Merkle store: {TREE_ROOT<-NODE} -/// -/// Outputs: -/// Operand stack: [depth, index, TREE_ROOT, ...] -/// Advice stack: [NODE, ...] -/// Merkle store: {TREE_ROOT<-NODE} -/// -/// # Errors -/// Returns an error if: -/// - Merkle tree for the specified root cannot be found in the advice provider. -/// - The specified depth is either zero or greater than the depth of the Merkle tree identified by -/// the specified root. -/// - Value of the node at the specified depth and index is not known to the advice provider. -pub(crate) fn copy_merkle_node_to_adv_stack( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - // read node depth, node index, and tree root from the stack - let depth = process.get_stack_item(0); - let index = process.get_stack_item(1); - let root = [ - process.get_stack_item(5), - process.get_stack_item(4), - process.get_stack_item(3), - process.get_stack_item(2), - ]; - - // look up the node in the advice provider - let node = advice_provider.get_tree_node(root, &depth, &index)?; - - // push the node onto the advice stack with the first element pushed last so that it can - // be popped first (i.e. stack behavior for word) - advice_provider.push_stack(AdviceSource::Value(node[3]))?; - advice_provider.push_stack(AdviceSource::Value(node[2]))?; - advice_provider.push_stack(AdviceSource::Value(node[1]))?; - advice_provider.push_stack(AdviceSource::Value(node[0]))?; - - Ok(HostResponse::None) -} - -/// Pushes a list of field elements onto the advice stack. The list is looked up in the advice -/// map using the specified word from the operand stack as the key. If `include_len` is set to -/// true, the number of elements in the value is also pushed onto the advice stack. -/// -/// Inputs: -/// Operand stack: [..., KEY, ...] -/// Advice stack: [...] -/// Advice map: {KEY: values} -/// -/// Outputs: -/// Operand stack: [..., KEY, ...] -/// Advice stack: [values_len?, values, ...] -/// Advice map: {KEY: values} -/// -/// The `key_offset` value specifies the location of the `KEY` on the stack. For example, -/// offset value of 0 indicates that the top word on the stack should be used as the key, the -/// offset value of 4, indicates that the second word on the stack should be used as the key -/// etc. -/// -/// The valid values of `key_offset` are 0 through 12 (inclusive). -/// -/// # Errors -/// Returns an error if the required key was not found in the key-value map or if stack offset -/// is greater than 12. -pub(crate) fn copy_map_value_to_adv_stack( - advice_provider: &mut A, - process: ProcessState, - include_len: bool, - key_offset: usize, -) -> Result { - if key_offset > 12 { - return Err(ExecutionError::InvalidStackWordOffset(key_offset)); - } - - let key = [ - process.get_stack_item(key_offset + 3), - process.get_stack_item(key_offset + 2), - process.get_stack_item(key_offset + 1), - process.get_stack_item(key_offset), - ]; - advice_provider.push_stack(AdviceSource::Map { key, include_len })?; - - Ok(HostResponse::None) -} - -/// Pushes the result of [u64] division (both the quotient and the remainder) onto the advice -/// stack. -/// -/// Inputs: -/// Operand stack: [b1, b0, a1, a0, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [b1, b0, a1, a0, ...] -/// Advice stack: [q0, q1, r0, r1, ...] -/// -/// Where (a0, a1) and (b0, b1) are the 32-bit limbs of the dividend and the divisor -/// respectively (with a0 representing the 32 lest significant bits and a1 representing the -/// 32 most significant bits). Similarly, (q0, q1) and (r0, r1) represent the quotient and -/// the remainder respectively. -/// -/// # Errors -/// Returns an error if the divisor is ZERO. -pub(crate) fn push_u64_div_result( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - let divisor_hi = process.get_stack_item(0).as_int(); - let divisor_lo = process.get_stack_item(1).as_int(); - let divisor = (divisor_hi << 32) + divisor_lo; - - if divisor == 0 { - return Err(ExecutionError::DivideByZero(process.clk())); - } - - let dividend_hi = process.get_stack_item(2).as_int(); - let dividend_lo = process.get_stack_item(3).as_int(); - let dividend = (dividend_hi << 32) + dividend_lo; - - let quotient = dividend / divisor; - let remainder = dividend - quotient * divisor; - - let (q_hi, q_lo) = u64_to_u32_elements(quotient); - let (r_hi, r_lo) = u64_to_u32_elements(remainder); - - advice_provider.push_stack(AdviceSource::Value(r_hi))?; - advice_provider.push_stack(AdviceSource::Value(r_lo))?; - advice_provider.push_stack(AdviceSource::Value(q_hi))?; - advice_provider.push_stack(AdviceSource::Value(q_lo))?; - - Ok(HostResponse::None) -} - -/// Given an element in a quadratic extension field on the top of the stack (i.e., a0, b1), -/// computes its multiplicative inverse and push the result onto the advice stack. -/// -/// Inputs: -/// Operand stack: [a1, a0, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [a1, a0, ...] -/// Advice stack: [b0, b1...] -/// -/// Where (b0, b1) is the multiplicative inverse of the extension field element (a0, a1) at the -/// top of the stack. -/// -/// # Errors -/// Returns an error if the input is a zero element in the extension field. -pub(crate) fn push_ext2_inv_result( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - let coef0 = process.get_stack_item(1); - let coef1 = process.get_stack_item(0); - - let element = QuadFelt::new(coef0, coef1); - if element == QuadFelt::ZERO { - return Err(ExecutionError::DivideByZero(process.clk())); - } - let result = element.inv().to_base_elements(); - - advice_provider.push_stack(AdviceSource::Value(result[1]))?; - advice_provider.push_stack(AdviceSource::Value(result[0]))?; - - Ok(HostResponse::None) -} - -/// Given evaluations of a polynomial over some specified domain, interpolates the evaluations -/// into a polynomial in coefficient form and pushes the result into the advice stack. -/// -/// The interpolation is performed using the iNTT algorithm. The evaluations are expected to be -/// in the quadratic extension. -/// -/// Inputs: -/// Operand stack: [output_size, input_size, input_start_ptr, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [output_size, input_size, input_start_ptr, ...] -/// Advice stack: [coefficients...] -/// -/// - `input_size` is the number of evaluations (each evaluation is 2 base field elements). Must be -/// a power of 2 and greater 1. -/// - `output_size` is the number of coefficients in the interpolated polynomial (each coefficient -/// is 2 base field elements). Must be smaller than or equal to the number of input evaluations. -/// - `input_start_ptr` is the memory address of the first evaluation. -/// - `coefficients` are the coefficients of the interpolated polynomial such that lowest degree -/// coefficients are located at the top of the advice stack. -/// -/// # Errors -/// Returns an error if: -/// - `input_size` less than or equal to 1, or is not a power of 2. -/// - `output_size` is 0 or is greater than the `input_size`. -/// - `input_ptr` is greater than 2^32. -/// - `input_ptr + input_size / 2` is greater than 2^32. -pub(crate) fn push_ext2_intt_result( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - let output_size = process.get_stack_item(0).as_int() as usize; - let input_size = process.get_stack_item(1).as_int() as usize; - let input_start_ptr = process.get_stack_item(2).as_int(); - - if input_size <= 1 { - return Err(Ext2InttError::DomainSizeTooSmall(input_size as u64).into()); - } - if !input_size.is_power_of_two() { - return Err(Ext2InttError::DomainSizeNotPowerOf2(input_size as u64).into()); - } - if input_start_ptr >= u32::MAX as u64 { - return Err(Ext2InttError::InputStartAddressTooBig(input_start_ptr).into()); - } - if input_size > u32::MAX as usize { - return Err(Ext2InttError::InputSizeTooBig(input_size as u64).into()); - } - - let input_end_ptr = input_start_ptr + (input_size / 2) as u64; - if input_end_ptr > u32::MAX as u64 { - return Err(Ext2InttError::InputEndAddressTooBig(input_end_ptr).into()); - } - - if output_size == 0 { - return Err(Ext2InttError::OutputSizeIsZero.into()); - } - if output_size > input_size { - return Err(Ext2InttError::OutputSizeTooBig(output_size, input_size).into()); - } - - let mut poly = Vec::with_capacity(input_size); - for addr in (input_start_ptr as u32)..(input_end_ptr as u32) { - let word = process - .get_mem_value(process.ctx(), addr) - .ok_or(Ext2InttError::UninitializedMemoryAddress(addr))?; - - poly.push(QuadFelt::new(word[0], word[1])); - poly.push(QuadFelt::new(word[2], word[3])); - } - - let twiddles = fft::get_inv_twiddles::(input_size); - fft::interpolate_poly::(&mut poly, &twiddles); - - for element in QuadFelt::slice_as_base_elements(&poly[..output_size]).iter().rev() { - advice_provider.push_stack(AdviceSource::Value(*element))?; - } - - Ok(HostResponse::None) -} - -/// Pushes values onto the advice stack which are required for verification of a DSA in Miden VM. -/// -/// Inputs: -/// Operand stack: [PK, MSG, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [PK, MSG, ...] -/// Advice stack: [DATA] -/// -/// Where: -/// - PK is the digest of an expanded public. -/// - MSG is the digest of the message to be signed. -/// - DATA is the needed data for signature verification in the VM. -/// -/// The advice provider is expected to contain the private key associated to the public key PK. -pub(crate) fn push_signature( - advice_provider: &mut A, - process: ProcessState, - kind: SignatureKind, -) -> Result { - let pub_key = process.get_stack_word(0); - let msg = process.get_stack_word(1); - let result: Vec = advice_provider.get_signature(kind, pub_key, msg)?; - for r in result { - advice_provider.push_stack(AdviceSource::Value(r))?; - } - Ok(HostResponse::None) -} - -/// Pushes the number of the leading zeros of the top stack element onto the advice stack. -/// -/// Inputs: -/// Operand stack: [n, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [n, ...] -/// Advice stack: [leading_zeros, ...] -pub(crate) fn push_leading_zeros( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - push_transformed_stack_top(advice_provider, process, |stack_top| { - Felt::from(stack_top.leading_zeros()) - }) -} - -/// Pushes the number of the trailing zeros of the top stack element onto the advice stack. -/// -/// Inputs: -/// Operand stack: [n, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [n, ...] -/// Advice stack: [trailing_zeros, ...] -pub(crate) fn push_trailing_zeros( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - push_transformed_stack_top(advice_provider, process, |stack_top| { - Felt::from(stack_top.trailing_zeros()) - }) -} - -/// Pushes the number of the leading ones of the top stack element onto the advice stack. -/// -/// Inputs: -/// Operand stack: [n, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [n, ...] -/// Advice stack: [leading_ones, ...] -pub(crate) fn push_leading_ones( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - push_transformed_stack_top(advice_provider, process, |stack_top| { - Felt::from(stack_top.leading_ones()) - }) -} - -/// Pushes the number of the trailing ones of the top stack element onto the advice stack. -/// -/// Inputs: -/// Operand stack: [n, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [n, ...] -/// Advice stack: [trailing_ones, ...] -pub(crate) fn push_trailing_ones( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - push_transformed_stack_top(advice_provider, process, |stack_top| { - Felt::from(stack_top.trailing_ones()) - }) -} - -/// Pushes the base 2 logarithm of the top stack element, rounded down. -/// Inputs: -/// Operand stack: [n, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [n, ...] -/// Advice stack: [ilog2(n), ...] -/// -/// # Errors -/// Returns an error if the logarithm argument (top stack element) equals ZERO. -pub(crate) fn push_ilog2( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - let n = process.get_stack_item(0).as_int(); - if n == 0 { - return Err(ExecutionError::LogArgumentZero(process.clk())); - } - let ilog2 = Felt::from(n.ilog2()); - advice_provider.push_stack(AdviceSource::Value(ilog2))?; - Ok(HostResponse::None) -} - -// HELPER FUNCTIONS -// ================================================================================================ - -fn u64_to_u32_elements(value: u64) -> (Felt, Felt) { - let hi = Felt::from((value >> 32) as u32); - let lo = Felt::from(value as u32); - (hi, lo) -} - -/// Gets the top stack element, applies a provided function to it and pushes it to the advice -/// provider. -fn push_transformed_stack_top( - advice_provider: &mut A, - process: ProcessState, - f: impl FnOnce(u32) -> Felt, -) -> Result { - let stack_top = process.get_stack_item(0); - let stack_top: u32 = stack_top - .as_int() - .try_into() - .map_err(|_| ExecutionError::NotU32Value(stack_top, ZERO))?; - let transformed_stack_top = f(stack_top); - advice_provider.push_stack(AdviceSource::Value(transformed_stack_top))?; - Ok(HostResponse::None) -} diff --git a/processor/src/host/advice/injectors/merkle_store_injectors.rs b/processor/src/host/advice/injectors/merkle_store_injectors.rs deleted file mode 100644 index bf6166913d..0000000000 --- a/processor/src/host/advice/injectors/merkle_store_injectors.rs +++ /dev/null @@ -1,23 +0,0 @@ -use super::super::{AdviceProvider, ExecutionError, HostResponse, ProcessState}; - -pub(crate) fn update_operand_stack_merkle_node( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - let depth = process.get_stack_item(4); - let index = process.get_stack_item(5); - let old_root = [ - process.get_stack_item(9), - process.get_stack_item(8), - process.get_stack_item(7), - process.get_stack_item(6), - ]; - let new_node = [ - process.get_stack_item(13), - process.get_stack_item(12), - process.get_stack_item(11), - process.get_stack_item(10), - ]; - let (path, _) = advice_provider.update_merkle_node(old_root, &depth, &index, new_node)?; - Ok(HostResponse::MerklePath(path)) -} diff --git a/processor/src/host/advice/injectors/mod.rs b/processor/src/host/advice/injectors/mod.rs deleted file mode 100644 index 79e38bd268..0000000000 --- a/processor/src/host/advice/injectors/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub(super) mod adv_map_injectors; -pub(super) mod adv_stack_injectors; -pub(super) mod dsa; -pub(super) mod merkle_store_injectors; -pub(super) mod smt; diff --git a/processor/src/host/advice/injectors/smt.rs b/processor/src/host/advice/injectors/smt.rs deleted file mode 100644 index e3ba8ec6a6..0000000000 --- a/processor/src/host/advice/injectors/smt.rs +++ /dev/null @@ -1,96 +0,0 @@ -use alloc::vec::Vec; - -use vm_core::{ - crypto::{ - hash::RpoDigest, - merkle::{EmptySubtreeRoots, Smt, SMT_DEPTH}, - }, - WORD_SIZE, -}; - -use super::super::{AdviceSource, ExecutionError, Felt, HostResponse, Word}; -use crate::{AdviceProvider, ProcessState}; - -// SMT INJECTORS -// ================================================================================================ - -/// Pushes onto the advice stack the value associated with the specified key in a Sparse -/// Merkle Tree defined by the specified root. -/// -/// If no value was previously associated with the specified key, [ZERO; 4] is pushed onto -/// the advice stack. -/// -/// Inputs: -/// Operand stack: [KEY, ROOT, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [KEY, ROOT, ...] -/// Advice stack: [VALUE, ...] -/// -/// # Errors -/// Returns an error if the provided Merkle root doesn't exist on the advice provider. -pub(crate) fn push_smtpeek_result( - advice_provider: &mut A, - process: ProcessState, -) -> Result { - let empty_leaf = EmptySubtreeRoots::entry(SMT_DEPTH, SMT_DEPTH); - // fetch the arguments from the operand stack - let key = process.get_stack_word(0); - let root = process.get_stack_word(1); - - // get the node from the SMT for the specified key; this node can be either a leaf node, - // or a root of an empty subtree at the returned depth - let node = advice_provider.get_tree_node(root, &Felt::new(SMT_DEPTH as u64), &key[3])?; - - if node == Word::from(empty_leaf) { - // if the node is a root of an empty subtree, then there is no value associated with - // the specified key - advice_provider.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE))?; - } else { - let leaf_preimage = get_smt_leaf_preimage(advice_provider, node)?; - - for (key_in_leaf, value_in_leaf) in leaf_preimage { - if key == key_in_leaf { - // Found key - push value associated with key, and return - advice_provider.push_stack(AdviceSource::Word(value_in_leaf))?; - - return Ok(HostResponse::None); - } - } - - // if we can't find any key in the leaf that matches `key`, it means no value is associated - // with `key` - advice_provider.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE))?; - } - - Ok(HostResponse::None) -} - -// HELPER METHODS -// -------------------------------------------------------------------------------------------- - -fn get_smt_leaf_preimage( - advice_provider: &A, - node: Word, -) -> Result, ExecutionError> { - let node_bytes = RpoDigest::from(node); - - let kv_pairs = advice_provider - .get_mapped_values(&node_bytes) - .ok_or(ExecutionError::SmtNodeNotFound(node))?; - - if kv_pairs.len() % WORD_SIZE * 2 != 0 { - return Err(ExecutionError::SmtNodePreImageNotValid(node, kv_pairs.len())); - } - - Ok(kv_pairs - .chunks_exact(WORD_SIZE * 2) - .map(|kv_chunk| { - let key = [kv_chunk[0], kv_chunk[1], kv_chunk[2], kv_chunk[3]]; - let value = [kv_chunk[4], kv_chunk[5], kv_chunk[6], kv_chunk[7]]; - - (key, value) - }) - .collect()) -} diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index f1d5234e17..d85838d2a1 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -2,20 +2,23 @@ use alloc::vec::Vec; use vm_core::{ crypto::{ - hash::RpoDigest, - merkle::{InnerNodeInfo, MerklePath, MerkleStore, NodeIndex, StoreNode}, + hash::{Rpo256, RpoDigest}, + merkle::{ + EmptySubtreeRoots, InnerNodeInfo, MerklePath, MerkleStore, NodeIndex, Smt, StoreNode, + SMT_DEPTH, + }, }, - SignatureKind, + FieldElement, SignatureKind, EMPTY_WORD, WORD_SIZE, ZERO, }; +use winter_prover::math::fft; -use super::HostResponse; -use crate::{ExecutionError, Felt, InputError, ProcessState, Word}; +use crate::{ExecutionError, Ext2InttError, Felt, InputError, ProcessState, QuadFelt, Word}; + +mod dsa; mod inputs; pub use inputs::AdviceInputs; -mod injectors; - mod providers; pub use providers::{MemAdviceProvider, RecAdviceProvider}; @@ -93,14 +96,6 @@ pub trait AdviceProvider: Sized { /// are replaced with the specified values. fn insert_into_map(&mut self, key: Word, values: Vec); - /// Returns a signature on a message using a public key. - fn get_signature( - &self, - kind: SignatureKind, - pub_key: Word, - msg: Word, - ) -> Result, ExecutionError>; - // MERKLE STORE // -------------------------------------------------------------------------------------------- @@ -204,8 +199,20 @@ pub trait AdviceProvider: Sized { fn insert_mem_values_into_adv_map( &mut self, process: ProcessState, - ) -> Result { - injectors::adv_map_injectors::insert_mem_values_into_adv_map(self, process) + ) -> Result<(), ExecutionError> { + let (start_addr, end_addr) = get_mem_addr_range(process, 4, 5)?; + let ctx = process.ctx(); + + let mut values = Vec::with_capacity(((end_addr - start_addr) as usize) * WORD_SIZE); + for addr in start_addr..end_addr { + let mem_value = process.get_mem_value(ctx, addr).unwrap_or(EMPTY_WORD); + values.extend_from_slice(&mem_value); + } + + let key = process.get_stack_word(0); + self.insert_into_map(key, values); + + Ok(()) } /// Reads two word from the operand stack and inserts them into the advice map under the key @@ -225,8 +232,20 @@ pub trait AdviceProvider: Sized { &mut self, process: ProcessState, domain: Felt, - ) -> Result { - injectors::adv_map_injectors::insert_hdword_into_adv_map(self, process, domain) + ) -> Result<(), ExecutionError> { + // get the top two words from the stack and hash them to compute the key value + let word0 = process.get_stack_word(0); + let word1 = process.get_stack_word(1); + let key = Rpo256::merge_in_domain(&[word1.into(), word0.into()], domain); + + // build a vector of values from the two word and insert it into the advice map under the + // computed key + let mut values = Vec::with_capacity(2 * WORD_SIZE); + values.extend_from_slice(&word1); + values.extend_from_slice(&word0); + self.insert_into_map(key.into(), values); + + Ok(()) } /// Reads three words from the operand stack and inserts the top two words into the advice map @@ -242,11 +261,37 @@ pub trait AdviceProvider: Sized { /// /// Where KEY is computed by extracting the digest elements from hperm([C, A, B]). For example, /// if C is [0, d, 0, 0], KEY will be set as hash(A || B, d). - fn insert_hperm_into_adv_map( - &mut self, - process: ProcessState, - ) -> Result { - injectors::adv_map_injectors::insert_hperm_into_adv_map(self, process) + fn insert_hperm_into_adv_map(&mut self, process: ProcessState) -> Result<(), ExecutionError> { + // read the state from the stack + let mut state = [ + process.get_stack_item(11), + process.get_stack_item(10), + process.get_stack_item(9), + process.get_stack_item(8), + process.get_stack_item(7), + process.get_stack_item(6), + process.get_stack_item(5), + process.get_stack_item(4), + process.get_stack_item(3), + process.get_stack_item(2), + process.get_stack_item(1), + process.get_stack_item(0), + ]; + + // get the values to be inserted into the advice map from the state + let values = state[Rpo256::RATE_RANGE].to_vec(); + + // apply the permutation to the state and extract the key from it + Rpo256::apply_permutation(&mut state); + let key = RpoDigest::new( + state[Rpo256::DIGEST_RANGE] + .try_into() + .expect("failed to extract digest from state"), + ); + + self.insert_into_map(key.into(), values); + + Ok(()) } /// Creates a new Merkle tree in the advice provider by combining Merkle trees with the @@ -264,11 +309,15 @@ pub trait AdviceProvider: Sized { /// provider (i.e., the input trees are not removed). /// /// It is not checked whether the provided roots exist as Merkle trees in the advide providers. - fn merge_merkle_nodes( - &mut self, - process: ProcessState, - ) -> Result { - injectors::adv_map_injectors::merge_merkle_nodes(self, process) + fn merge_merkle_nodes(&mut self, process: ProcessState) -> Result<(), ExecutionError> { + // fetch the arguments from the stack + let lhs = process.get_stack_word(1); + let rhs = process.get_stack_word(0); + + // perform the merge + self.merge_roots(lhs, rhs)?; + + Ok(()) } // DEFAULT ADVICE STACK INJECTORS @@ -296,8 +345,24 @@ pub trait AdviceProvider: Sized { fn copy_merkle_node_to_adv_stack( &mut self, process: ProcessState, - ) -> Result { - injectors::adv_stack_injectors::copy_merkle_node_to_adv_stack(self, process) + ) -> Result<(), ExecutionError> { + let depth = process.get_stack_item(0); + let index = process.get_stack_item(1); + let root = [ + process.get_stack_item(5), + process.get_stack_item(4), + process.get_stack_item(3), + process.get_stack_item(2), + ]; + + let node = self.get_tree_node(root, &depth, &index)?; + + self.push_stack(AdviceSource::Value(node[3]))?; + self.push_stack(AdviceSource::Value(node[2]))?; + self.push_stack(AdviceSource::Value(node[1]))?; + self.push_stack(AdviceSource::Value(node[0]))?; + + Ok(()) } /// Pushes a list of field elements onto the advice stack. The list is looked up in the advice @@ -329,13 +394,20 @@ pub trait AdviceProvider: Sized { process: ProcessState, include_len: bool, key_offset: usize, - ) -> Result { - injectors::adv_stack_injectors::copy_map_value_to_adv_stack( - self, - process, - include_len, - key_offset, - ) + ) -> Result<(), ExecutionError> { + if key_offset > 12 { + return Err(ExecutionError::InvalidStackWordOffset(key_offset)); + } + + let key = [ + process.get_stack_item(key_offset + 3), + process.get_stack_item(key_offset + 2), + process.get_stack_item(key_offset + 1), + process.get_stack_item(key_offset), + ]; + self.push_stack(AdviceSource::Map { key, include_len })?; + + Ok(()) } /// Pushes the result of [u64] division (both the quotient and the remainder) onto the advice @@ -356,11 +428,31 @@ pub trait AdviceProvider: Sized { /// /// # Errors /// Returns an error if the divisor is ZERO. - fn push_u64_div_result( - &mut self, - process: ProcessState, - ) -> Result { - injectors::adv_stack_injectors::push_u64_div_result(self, process) + fn push_u64_div_result(&mut self, process: ProcessState) -> Result<(), ExecutionError> { + let divisor_hi = process.get_stack_item(0).as_int(); + let divisor_lo = process.get_stack_item(1).as_int(); + let divisor = (divisor_hi << 32) + divisor_lo; + + if divisor == 0 { + return Err(ExecutionError::DivideByZero(process.clk())); + } + + let dividend_hi = process.get_stack_item(2).as_int(); + let dividend_lo = process.get_stack_item(3).as_int(); + let dividend = (dividend_hi << 32) + dividend_lo; + + let quotient = dividend / divisor; + let remainder = dividend - quotient * divisor; + + let (q_hi, q_lo) = u64_to_u32_elements(quotient); + let (r_hi, r_lo) = u64_to_u32_elements(remainder); + + self.push_stack(AdviceSource::Value(r_hi))?; + self.push_stack(AdviceSource::Value(r_lo))?; + self.push_stack(AdviceSource::Value(q_hi))?; + self.push_stack(AdviceSource::Value(q_lo))?; + + Ok(()) } /// Given an element in a quadratic extension field on the top of the stack (i.e., a0, b1), @@ -379,11 +471,20 @@ pub trait AdviceProvider: Sized { /// /// # Errors /// Returns an error if the input is a zero element in the extension field. - fn push_ext2_inv_result( - &mut self, - process: ProcessState, - ) -> Result { - injectors::adv_stack_injectors::push_ext2_inv_result(self, process) + fn push_ext2_inv_result(&mut self, process: ProcessState) -> Result<(), ExecutionError> { + let coef0 = process.get_stack_item(1); + let coef1 = process.get_stack_item(0); + + let element = QuadFelt::new(coef0, coef1); + if element == QuadFelt::ZERO { + return Err(ExecutionError::DivideByZero(process.clk())); + } + let result = element.inv().to_base_elements(); + + self.push_stack(AdviceSource::Value(result[1]))?; + self.push_stack(AdviceSource::Value(result[0]))?; + + Ok(()) } /// Given evaluations of a polynomial over some specified domain, interpolates the evaluations @@ -415,11 +516,54 @@ pub trait AdviceProvider: Sized { /// - `output_size` is 0 or is greater than the `input_size`. /// - `input_ptr` is greater than 2^32. /// - `input_ptr + input_size / 2` is greater than 2^32. - fn push_ext2_intt_result( - &mut self, - process: ProcessState, - ) -> Result { - injectors::adv_stack_injectors::push_ext2_intt_result(self, process) + fn push_ext2_intt_result(&mut self, process: ProcessState) -> Result<(), ExecutionError> { + let output_size = process.get_stack_item(0).as_int() as usize; + let input_size = process.get_stack_item(1).as_int() as usize; + let input_start_ptr = process.get_stack_item(2).as_int(); + + if input_size <= 1 { + return Err(Ext2InttError::DomainSizeTooSmall(input_size as u64).into()); + } + if !input_size.is_power_of_two() { + return Err(Ext2InttError::DomainSizeNotPowerOf2(input_size as u64).into()); + } + if input_start_ptr >= u32::MAX as u64 { + return Err(Ext2InttError::InputStartAddressTooBig(input_start_ptr).into()); + } + if input_size > u32::MAX as usize { + return Err(Ext2InttError::InputSizeTooBig(input_size as u64).into()); + } + + let input_end_ptr = input_start_ptr + (input_size / 2) as u64; + if input_end_ptr > u32::MAX as u64 { + return Err(Ext2InttError::InputEndAddressTooBig(input_end_ptr).into()); + } + + if output_size == 0 { + return Err(Ext2InttError::OutputSizeIsZero.into()); + } + if output_size > input_size { + return Err(Ext2InttError::OutputSizeTooBig(output_size, input_size).into()); + } + + let mut poly = Vec::with_capacity(input_size); + for addr in (input_start_ptr as u32)..(input_end_ptr as u32) { + let word = process + .get_mem_value(process.ctx(), addr) + .ok_or(Ext2InttError::UninitializedMemoryAddress(addr))?; + + poly.push(QuadFelt::new(word[0], word[1])); + poly.push(QuadFelt::new(word[2], word[3])); + } + + let twiddles = fft::get_inv_twiddles::(input_size); + fft::interpolate_poly::(&mut poly, &twiddles); + + for element in QuadFelt::slice_as_base_elements(&poly[..output_size]).iter().rev() { + self.push_stack(AdviceSource::Value(*element))?; + } + + Ok(()) } /// Pushes values onto the advice stack which are required for verification of a DSA in Miden @@ -443,8 +587,14 @@ pub trait AdviceProvider: Sized { &mut self, process: ProcessState, kind: SignatureKind, - ) -> Result { - injectors::adv_stack_injectors::push_signature(self, process, kind) + ) -> Result<(), ExecutionError> { + let pub_key = process.get_stack_word(0); + let msg = process.get_stack_word(1); + let result: Vec = self.get_signature(kind, pub_key, msg)?; + for r in result { + self.push_stack(AdviceSource::Value(r))?; + } + Ok(()) } /// Pushes the number of the leading zeros of the top stack element onto the advice stack. @@ -456,11 +606,8 @@ pub trait AdviceProvider: Sized { /// Outputs: /// Operand stack: [n, ...] /// Advice stack: [leading_zeros, ...] - fn push_leading_zeros( - &mut self, - process: ProcessState, - ) -> Result { - injectors::adv_stack_injectors::push_leading_zeros(self, process) + fn push_leading_zeros(&mut self, process: ProcessState) -> Result<(), ExecutionError> { + push_transformed_stack_top(self, process, |stack_top| Felt::from(stack_top.leading_zeros())) } /// Pushes the number of the trailing zeros of the top stack element onto the advice stack. @@ -472,11 +619,10 @@ pub trait AdviceProvider: Sized { /// Outputs: /// Operand stack: [n, ...] /// Advice stack: [trailing_zeros, ...] - fn push_trailing_zeros( - &mut self, - process: ProcessState, - ) -> Result { - injectors::adv_stack_injectors::push_trailing_zeros(self, process) + fn push_trailing_zeros(&mut self, process: ProcessState) -> Result<(), ExecutionError> { + push_transformed_stack_top(self, process, |stack_top| { + Felt::from(stack_top.trailing_zeros()) + }) } /// Pushes the number of the leading ones of the top stack element onto the advice stack. @@ -488,8 +634,8 @@ pub trait AdviceProvider: Sized { /// Outputs: /// Operand stack: [n, ...] /// Advice stack: [leading_ones, ...] - fn push_leading_ones(&mut self, process: ProcessState) -> Result { - injectors::adv_stack_injectors::push_leading_ones(self, process) + fn push_leading_ones(&mut self, process: ProcessState) -> Result<(), ExecutionError> { + push_transformed_stack_top(self, process, |stack_top| Felt::from(stack_top.leading_ones())) } /// Pushes the number of the trailing ones of the top stack element onto the advice stack. @@ -501,11 +647,8 @@ pub trait AdviceProvider: Sized { /// Outputs: /// Operand stack: [n, ...] /// Advice stack: [trailing_ones, ...] - fn push_trailing_ones( - &mut self, - process: ProcessState, - ) -> Result { - injectors::adv_stack_injectors::push_trailing_ones(self, process) + fn push_trailing_ones(&mut self, process: ProcessState) -> Result<(), ExecutionError> { + push_transformed_stack_top(self, process, |stack_top| Felt::from(stack_top.trailing_ones())) } /// Pushes the base 2 logarithm of the top stack element, rounded down. @@ -519,8 +662,14 @@ pub trait AdviceProvider: Sized { /// /// # Errors /// Returns an error if the logarithm argument (top stack element) equals ZERO. - fn push_ilog2(&mut self, process: ProcessState) -> Result { - injectors::adv_stack_injectors::push_ilog2(self, process) + fn push_ilog2(&mut self, process: ProcessState) -> Result<(), ExecutionError> { + let n = process.get_stack_item(0).as_int(); + if n == 0 { + return Err(ExecutionError::LogArgumentZero(process.clk())); + } + let ilog2 = Felt::from(n.ilog2()); + self.push_stack(AdviceSource::Value(ilog2))?; + Ok(()) } // DEFAULT MERKLE STORE INJECTORS @@ -542,8 +691,23 @@ pub trait AdviceProvider: Sized { fn update_operand_stack_merkle_node( &mut self, process: ProcessState, - ) -> Result { - injectors::merkle_store_injectors::update_operand_stack_merkle_node(self, process) + ) -> Result { + let depth = process.get_stack_item(4); + let index = process.get_stack_item(5); + let old_root = [ + process.get_stack_item(9), + process.get_stack_item(8), + process.get_stack_item(7), + process.get_stack_item(6), + ]; + let new_node = [ + process.get_stack_item(13), + process.get_stack_item(12), + process.get_stack_item(11), + process.get_stack_item(10), + ]; + let (path, _) = self.update_merkle_node(old_root, &depth, &index, new_node)?; + Ok(path) } // DEFAULT MERKLE STORE EXTRACTORS @@ -570,7 +734,7 @@ pub trait AdviceProvider: Sized { fn get_operand_stack_merkle_path( &mut self, process: ProcessState, - ) -> Result { + ) -> Result { let depth = process.get_stack_item(4); let index = process.get_stack_item(5); let root = [ @@ -579,7 +743,7 @@ pub trait AdviceProvider: Sized { process.get_stack_item(7), process.get_stack_item(6), ]; - self.get_merkle_path(root, &depth, &index).map(HostResponse::MerklePath) + self.get_merkle_path(root, &depth, &index) } // DEFAULT SMT INJECTORS @@ -604,11 +768,56 @@ pub trait AdviceProvider: Sized { /// /// # Panics /// Will panic as unimplemented if the target depth is `64`. - fn push_smtpeek_result( - &mut self, - process: ProcessState, - ) -> Result { - injectors::smt::push_smtpeek_result(self, process) + fn push_smtpeek_result(&mut self, process: ProcessState) -> Result<(), ExecutionError> { + let empty_leaf = EmptySubtreeRoots::entry(SMT_DEPTH, SMT_DEPTH); + // fetch the arguments from the operand stack + let key = process.get_stack_word(0); + let root = process.get_stack_word(1); + + // get the node from the SMT for the specified key; this node can be either a leaf node, + // or a root of an empty subtree at the returned depth + let node = self.get_tree_node(root, &Felt::new(SMT_DEPTH as u64), &key[3])?; + + if node == Word::from(empty_leaf) { + // if the node is a root of an empty subtree, then there is no value associated with + // the specified key + self.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE))?; + } else { + let leaf_preimage = get_smt_leaf_preimage(self, node)?; + + for (key_in_leaf, value_in_leaf) in leaf_preimage { + if key == key_in_leaf { + // Found key - push value associated with key, and return + self.push_stack(AdviceSource::Word(value_in_leaf))?; + + return Ok(()); + } + } + + // if we can't find any key in the leaf that matches `key`, it means no value is + // associated with `key` + self.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE))?; + } + Ok(()) + } + + // DEFAULT MERKLE STORE EXTRACTORS + // -------------------------------------------------------------------------------------------- + + /// Returns a signature on a message using a public key. + fn get_signature( + &self, + kind: SignatureKind, + pub_key: Word, + msg: Word, + ) -> Result, ExecutionError> { + let pk_sk = self + .get_mapped_values(&pub_key.into()) + .ok_or(ExecutionError::AdviceMapKeyNotFound(pub_key))?; + + match kind { + SignatureKind::RpoFalcon512 => dsa::falcon_sign(pk_sk, msg), + } } } @@ -690,3 +899,78 @@ where T::merge_roots(self, lhs, rhs) } } + +// HELPER METHODS +// -------------------------------------------------------------------------------------------- + +/// Reads (start_addr, end_addr) tuple from the specified elements of the operand stack ( +/// without modifying the state of the stack), and verifies that memory range is valid. +fn get_mem_addr_range( + process: ProcessState, + start_idx: usize, + end_idx: usize, +) -> Result<(u32, u32), ExecutionError> { + let start_addr = process.get_stack_item(start_idx).as_int(); + let end_addr = process.get_stack_item(end_idx).as_int(); + + if start_addr > u32::MAX as u64 { + return Err(ExecutionError::MemoryAddressOutOfBounds(start_addr)); + } + if end_addr > u32::MAX as u64 { + return Err(ExecutionError::MemoryAddressOutOfBounds(end_addr)); + } + + if start_addr > end_addr { + return Err(ExecutionError::InvalidMemoryRange { start_addr, end_addr }); + } + + Ok((start_addr as u32, end_addr as u32)) +} + +fn u64_to_u32_elements(value: u64) -> (Felt, Felt) { + let hi = Felt::from((value >> 32) as u32); + let lo = Felt::from(value as u32); + (hi, lo) +} + +/// Gets the top stack element, applies a provided function to it and pushes it to the advice +/// provider. +fn push_transformed_stack_top( + advice_provider: &mut A, + process: ProcessState, + f: impl FnOnce(u32) -> Felt, +) -> Result<(), ExecutionError> { + let stack_top = process.get_stack_item(0); + let stack_top: u32 = stack_top + .as_int() + .try_into() + .map_err(|_| ExecutionError::NotU32Value(stack_top, ZERO))?; + let transformed_stack_top = f(stack_top); + advice_provider.push_stack(AdviceSource::Value(transformed_stack_top))?; + Ok(()) +} + +fn get_smt_leaf_preimage( + advice_provider: &A, + node: Word, +) -> Result, ExecutionError> { + let node_bytes = RpoDigest::from(node); + + let kv_pairs = advice_provider + .get_mapped_values(&node_bytes) + .ok_or(ExecutionError::SmtNodeNotFound(node))?; + + if kv_pairs.len() % WORD_SIZE * 2 != 0 { + return Err(ExecutionError::SmtNodePreImageNotValid(node, kv_pairs.len())); + } + + Ok(kv_pairs + .chunks_exact(WORD_SIZE * 2) + .map(|kv_chunk| { + let key = [kv_chunk[0], kv_chunk[1], kv_chunk[2], kv_chunk[3]]; + let value = [kv_chunk[4], kv_chunk[5], kv_chunk[6], kv_chunk[7]]; + + (key, value) + }) + .collect()) +} diff --git a/processor/src/host/advice/providers.rs b/processor/src/host/advice/providers.rs index dd8158f949..9a1717c9e6 100644 --- a/processor/src/host/advice/providers.rs +++ b/processor/src/host/advice/providers.rs @@ -1,10 +1,8 @@ use alloc::{collections::BTreeMap, vec::Vec}; -use vm_core::SignatureKind; - use super::{ - injectors, AdviceInputs, AdviceProvider, AdviceSource, ExecutionError, Felt, MerklePath, - MerkleStore, NodeIndex, RpoDigest, StoreNode, Word, + AdviceInputs, AdviceProvider, AdviceSource, ExecutionError, Felt, MerklePath, MerkleStore, + NodeIndex, RpoDigest, StoreNode, Word, }; use crate::{ utils::collections::{KvMap, RecordingMap}, @@ -108,22 +106,6 @@ where Ok(()) } - fn get_signature( - &self, - kind: SignatureKind, - pub_key: Word, - msg: Word, - ) -> Result, ExecutionError> { - let pk_sk = self - .map - .get(&pub_key.into()) - .ok_or(ExecutionError::AdviceMapKeyNotFound(pub_key))?; - - match kind { - SignatureKind::RpoFalcon512 => injectors::dsa::falcon_sign(pk_sk, msg), - } - } - // ADVICE MAP // -------------------------------------------------------------------------------------------- @@ -267,10 +249,6 @@ impl AdviceProvider for MemAdviceProvider { self.provider.insert_into_map(key, values) } - fn get_signature(&self, kind: SignatureKind, pub_key: Word, msg: Word) -> Result, ExecutionError> { - self.provider.get_signature(kind, pub_key, msg) - } - fn get_mapped_values(&self, key: &RpoDigest) -> Option<&[Felt]> { self.provider.get_mapped_values(key) } @@ -377,10 +355,6 @@ impl AdviceProvider for RecAdviceProvider { self.provider.insert_into_map(key, values) } - fn get_signature(&self, kind: SignatureKind, pub_key: Word, msg: Word) -> Result, ExecutionError> { - self.provider.get_signature(kind, pub_key, msg) - } - fn get_mapped_values(&self, key: &RpoDigest) -> Option<&[Felt]> { self.provider.get_mapped_values(key) } diff --git a/processor/src/host/mod.rs b/processor/src/host/mod.rs index cc255bb333..2fc6ba48e3 100644 --- a/processor/src/host/mod.rs +++ b/processor/src/host/mod.rs @@ -1,12 +1,8 @@ use alloc::sync::Arc; -use vm_core::{ - crypto::{hash::RpoDigest, merkle::MerklePath}, - mast::MastForest, - DebugOptions, Word, -}; +use vm_core::{crypto::hash::RpoDigest, mast::MastForest, DebugOptions}; -use super::{ExecutionError, Felt, ProcessState}; +use super::{ExecutionError, ProcessState}; use crate::{KvMap, MemAdviceProvider}; pub(super) mod advice; @@ -48,11 +44,7 @@ pub trait Host { // -------------------------------------------------------------------------------------------- /// Handles the event emitted from the VM. - fn on_event( - &mut self, - _process: ProcessState, - _event_id: u32, - ) -> Result { + fn on_event(&mut self, _process: ProcessState, _event_id: u32) -> Result<(), ExecutionError> { #[cfg(feature = "std")] std::println!( "Event with id {} emitted at step {} in context {}", @@ -60,7 +52,7 @@ pub trait Host { _process.clk(), _process.ctx() ); - Ok(HostResponse::None) + Ok(()) } /// Handles the debug request from the VM. @@ -68,18 +60,14 @@ pub trait Host { &mut self, _process: ProcessState, _options: &DebugOptions, - ) -> Result { + ) -> Result<(), ExecutionError> { #[cfg(feature = "std")] debug::print_debug_info(_process, _options); - Ok(HostResponse::None) + Ok(()) } /// Handles the trace emitted from the VM. - fn on_trace( - &mut self, - _process: ProcessState, - _trace_id: u32, - ) -> Result { + fn on_trace(&mut self, _process: ProcessState, _trace_id: u32) -> Result<(), ExecutionError> { #[cfg(feature = "std")] std::println!( "Trace with id {} emitted at step {} in context {}", @@ -87,7 +75,7 @@ pub trait Host { _process.clk(), _process.ctx() ); - Ok(HostResponse::None) + Ok(()) } /// Handles the failure of the assertion instruction. @@ -122,23 +110,15 @@ where &mut self, process: ProcessState, options: &DebugOptions, - ) -> Result { + ) -> Result<(), ExecutionError> { H::on_debug(self, process, options) } - fn on_event( - &mut self, - process: ProcessState, - event_id: u32, - ) -> Result { + fn on_event(&mut self, process: ProcessState, event_id: u32) -> Result<(), ExecutionError> { H::on_event(self, process, event_id) } - fn on_trace( - &mut self, - process: ProcessState, - trace_id: u32, - ) -> Result { + fn on_trace(&mut self, process: ProcessState, trace_id: u32) -> Result<(), ExecutionError> { H::on_trace(self, process, trace_id) } @@ -147,55 +127,6 @@ where } } -// HOST RESPONSE -// ================================================================================================ - -/// Response returned by the host upon successful execution of a [Host] function. -#[derive(Debug)] -pub enum HostResponse { - MerklePath(MerklePath), - DoubleWord([Word; 2]), - Word(Word), - Element(Felt), - None, -} - -impl From for MerklePath { - fn from(response: HostResponse) -> Self { - match response { - HostResponse::MerklePath(path) => path, - _ => panic!("expected MerklePath, but got {:?}", response), - } - } -} - -impl From for Word { - fn from(response: HostResponse) -> Self { - match response { - HostResponse::Word(word) => word, - _ => panic!("expected Word, but got {:?}", response), - } - } -} - -impl From for [Word; 2] { - fn from(response: HostResponse) -> Self { - match response { - HostResponse::DoubleWord(word) => word, - _ => panic!("expected DoubleWord, but got {:?}", response), - } - } -} - -impl From for Felt { - fn from(response: HostResponse) -> Self { - match response { - HostResponse::Element(element) => element, - _ => panic!("expected Element, but got {:?}", response), - } - } -} - // DEFAULT HOST IMPLEMENTATION // ================================================================================================ diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 0fa4b3ee74..1ab1b1c47b 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -48,7 +48,7 @@ use range::RangeChecker; mod host; pub use host::{ advice::{AdviceInputs, AdviceProvider, AdviceSource, MemAdviceProvider, RecAdviceProvider}, - DefaultHost, Host, HostResponse, MastForestStore, MemMastForestStore, + DefaultHost, Host, MastForestStore, MemMastForestStore, }; mod chiplets; @@ -596,9 +596,9 @@ impl Process { Decorator::Advice(injector) => { let advice_provider = host.advice_provider_mut(); let process_state: ProcessState = self.into(); - let _ = match injector { + match injector { AdviceInjector::MerkleNodeMerge => { - advice_provider.merge_merkle_nodes(process_state)? + advice_provider.merge_merkle_nodes(process_state)?; }, AdviceInjector::MerkleNodeToStack => { advice_provider.copy_merkle_node_to_adv_stack(process_state)? @@ -606,7 +606,7 @@ impl Process { AdviceInjector::MapValueToStack { include_len, key_offset } => advice_provider .copy_map_value_to_adv_stack(process_state, *include_len, *key_offset)?, AdviceInjector::UpdateMerkleNode => { - advice_provider.update_operand_stack_merkle_node(process_state)? + let _ = advice_provider.update_operand_stack_merkle_node(process_state)?; }, AdviceInjector::U64Div => advice_provider.push_u64_div_result(process_state)?, AdviceInjector::Ext2Inv => { @@ -625,18 +625,18 @@ impl Process { AdviceInjector::ILog2 => advice_provider.push_ilog2(process_state)?, AdviceInjector::MemToMap => { - advice_provider.insert_mem_values_into_adv_map(process_state)? + advice_provider.insert_mem_values_into_adv_map(process_state)?; }, AdviceInjector::HdwordToMap { domain } => { - advice_provider.insert_hdword_into_adv_map(process_state, *domain)? + advice_provider.insert_hdword_into_adv_map(process_state, *domain)?; }, AdviceInjector::HpermToMap => { - advice_provider.insert_hperm_into_adv_map(process_state)? + advice_provider.insert_hperm_into_adv_map(process_state)?; }, AdviceInjector::SigToStack { kind } => { advice_provider.push_signature(process_state, *kind)? }, - }; + } }, Decorator::Debug(options) => { if self.decoder.in_debug_mode() { @@ -653,7 +653,7 @@ impl Process { host.on_trace(self.into(), *id)?; } }, - } + }; Ok(()) } diff --git a/processor/src/operations/crypto_ops.rs b/processor/src/operations/crypto_ops.rs index f9405cd6df..acbecd545a 100644 --- a/processor/src/operations/crypto_ops.rs +++ b/processor/src/operations/crypto_ops.rs @@ -76,7 +76,7 @@ impl Process { // get a Merkle path from the advice provider for the specified root and node index. // the path is expected to be of the specified depth. let path: MerklePath = - host.advice_provider_mut().get_operand_stack_merkle_path(self.into())?.into(); + host.advice_provider_mut().get_operand_stack_merkle_path(self.into())?; // use hasher to compute the Merkle root of the path let (addr, computed_root) = self.chiplets.build_merkle_root(node, &path, index); @@ -148,7 +148,7 @@ impl Process { // specified depth. if the new node is the root of a tree, this instruction will append the // whole sub-tree to this node. let path: MerklePath = - host.advice_provider_mut().update_operand_stack_merkle_node(self.into())?.into(); + host.advice_provider_mut().update_operand_stack_merkle_node(self.into())?; assert_eq!(path.len(), depth.as_int() as usize); From 469f28e22f1e6fc3b57e562680f5df15cc017287 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Wed, 20 Nov 2024 08:28:06 -0500 Subject: [PATCH 13/39] refactor: remove parameter from `AdviceInjector::SigToStack` Also renames the variant to `FalconSigToStack` --- assembly/src/ast/instruction/advice.rs | 4 +++- core/src/mast/serialization/decorator.rs | 20 +++++++------------- core/src/mast/serialization/tests.rs | 9 +++------ core/src/operations/decorators/advice.rs | 6 +++--- processor/src/lib.rs | 7 +++---- 5 files changed, 19 insertions(+), 27 deletions(-) diff --git a/assembly/src/ast/instruction/advice.rs b/assembly/src/ast/instruction/advice.rs index d917686ed2..cb3a60a745 100644 --- a/assembly/src/ast/instruction/advice.rs +++ b/assembly/src/ast/instruction/advice.rs @@ -56,7 +56,9 @@ impl From<&AdviceInjectorNode> for AdviceInjector { }, InsertHdwordImm { domain } => panic!("unresolved constant '{domain}'"), InsertHperm => Self::HpermToMap, - PushSignature { kind } => Self::SigToStack { kind: (*kind).into() }, + PushSignature { kind } => match kind { + SignatureKind::RpoFalcon512 => Self::FalconSigToStack, + }, } } } diff --git a/core/src/mast/serialization/decorator.rs b/core/src/mast/serialization/decorator.rs index 5dfed28d3b..0c6e24e633 100644 --- a/core/src/mast/serialization/decorator.rs +++ b/core/src/mast/serialization/decorator.rs @@ -11,7 +11,7 @@ use super::{ string_table::{StringTable, StringTableBuilder}, DecoratorDataOffset, }; -use crate::{AdviceInjector, AssemblyOp, DebugOptions, Decorator, SignatureKind}; +use crate::{AdviceInjector, AssemblyOp, DebugOptions, Decorator}; /// Represents a serialized [`Decorator`]. /// @@ -106,10 +106,8 @@ impl DecoratorInfo { EncodedDecoratorVariant::AdviceInjectorHpermToMap => { Ok(Decorator::Advice(AdviceInjector::HpermToMap)) }, - EncodedDecoratorVariant::AdviceInjectorSigToStack => { - Ok(Decorator::Advice(AdviceInjector::SigToStack { - kind: SignatureKind::RpoFalcon512, - })) + EncodedDecoratorVariant::AdviceInjectorFalconSigToStack => { + Ok(Decorator::Advice(AdviceInjector::FalconSigToStack)) }, EncodedDecoratorVariant::AssemblyOp => { let num_cycles = data_reader.read_u8()?; @@ -225,7 +223,7 @@ pub enum EncodedDecoratorVariant { AdviceInjectorMemToMap, AdviceInjectorHdwordToMap, AdviceInjectorHpermToMap, - AdviceInjectorSigToStack, + AdviceInjectorFalconSigToStack, AssemblyOp, DebugOptionsStackAll, DebugOptionsStackTop, @@ -272,7 +270,7 @@ impl From<&Decorator> for EncodedDecoratorVariant { AdviceInjector::MemToMap => Self::AdviceInjectorMemToMap, AdviceInjector::HdwordToMap { domain: _ } => Self::AdviceInjectorHdwordToMap, AdviceInjector::HpermToMap => Self::AdviceInjectorHpermToMap, - AdviceInjector::SigToStack { kind: _ } => Self::AdviceInjectorSigToStack, + AdviceInjector::FalconSigToStack => Self::AdviceInjectorFalconSigToStack, }, Decorator::AsmOp(_) => Self::AssemblyOp, Decorator::Debug(debug_options) => match debug_options { @@ -345,11 +343,6 @@ impl DecoratorDataBuilder { Some(data_offset) }, - - // Note: Since there is only 1 variant, we don't need to write any extra bytes. - AdviceInjector::SigToStack { kind } => match kind { - SignatureKind::RpoFalcon512 => None, - }, AdviceInjector::MerkleNodeMerge | AdviceInjector::MerkleNodeToStack | AdviceInjector::UpdateMerkleNode @@ -363,7 +356,8 @@ impl DecoratorDataBuilder { | AdviceInjector::U32Cto | AdviceInjector::ILog2 | AdviceInjector::MemToMap - | AdviceInjector::HpermToMap => None, + | AdviceInjector::HpermToMap + | AdviceInjector::FalconSigToStack => None, }, Decorator::AsmOp(assembly_op) => { self.decorator_data.push(assembly_op.num_cycles()); diff --git a/core/src/mast/serialization/tests.rs b/core/src/mast/serialization/tests.rs index d4b9d3b8b7..5f2007130a 100644 --- a/core/src/mast/serialization/tests.rs +++ b/core/src/mast/serialization/tests.rs @@ -5,7 +5,7 @@ use miden_crypto::{hash::rpo::RpoDigest, Felt, ONE}; use super::*; use crate::{ mast::MastForestError, operations::Operation, AdviceInjector, AssemblyOp, DebugOptions, - Decorator, SignatureKind, + Decorator, }; /// If this test fails to compile, it means that `Operation` or `Decorator` was changed. Make sure @@ -126,7 +126,7 @@ fn confirm_operation_and_decorator_structure() { AdviceInjector::MemToMap => (), AdviceInjector::HdwordToMap { domain: _ } => (), AdviceInjector::HpermToMap => (), - AdviceInjector::SigToStack { kind: _ } => (), + AdviceInjector::FalconSigToStack => (), }, Decorator::AsmOp(_) => (), Decorator::Debug(debug_options) => match debug_options { @@ -263,10 +263,7 @@ fn serialize_deserialize_all_nodes() { (10, Decorator::Advice(AdviceInjector::MemToMap)), (10, Decorator::Advice(AdviceInjector::HdwordToMap { domain: Felt::new(423) })), (15, Decorator::Advice(AdviceInjector::HpermToMap)), - ( - 15, - Decorator::Advice(AdviceInjector::SigToStack { kind: SignatureKind::RpoFalcon512 }), - ), + (15, Decorator::Advice(AdviceInjector::FalconSigToStack)), ( 15, Decorator::AsmOp(AssemblyOp::new( diff --git a/core/src/operations/decorators/advice.rs b/core/src/operations/decorators/advice.rs index 916ffec3cc..4bcc27b492 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/operations/decorators/advice.rs @@ -258,7 +258,7 @@ pub enum AdviceInjector { HpermToMap, /// Reads two words from the stack and pushes values onto the advice stack which are required - /// for verification of a DSA in Miden VM. + /// for verification of Falcon DSA in Miden VM. /// /// Inputs: /// Operand stack: [PK, MSG, ...] @@ -270,7 +270,7 @@ pub enum AdviceInjector { /// /// Where PK is the public key corresponding to the signing key, MSG is the message, SIG_DATA /// is the signature data. - SigToStack { kind: SignatureKind }, + FalconSigToStack, } impl crate::prettier::PrettyPrint for AdviceInjector { @@ -306,7 +306,7 @@ impl fmt::Display for AdviceInjector { Self::MemToMap => write!(f, "mem_to_map"), Self::HdwordToMap { domain } => write!(f, "hdword_to_map.{domain}"), Self::HpermToMap => write!(f, "hperm_to_map"), - Self::SigToStack { kind } => write!(f, "sig_to_stack.{kind}"), + Self::FalconSigToStack => write!(f, "sig_to_stack.{}", SignatureKind::RpoFalcon512), } } } diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 1ab1b1c47b..040a56b6fe 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -26,7 +26,7 @@ use vm_core::{ mast::{ BasicBlockNode, CallNode, DynNode, JoinNode, LoopNode, OpBatch, SplitNode, OP_GROUP_SIZE, }, - Decorator, DecoratorIterator, FieldElement, + Decorator, DecoratorIterator, FieldElement, SignatureKind, }; pub use winter_prover::matrix::ColMatrix; @@ -633,9 +633,8 @@ impl Process { AdviceInjector::HpermToMap => { advice_provider.insert_hperm_into_adv_map(process_state)?; }, - AdviceInjector::SigToStack { kind } => { - advice_provider.push_signature(process_state, *kind)? - }, + AdviceInjector::FalconSigToStack => advice_provider + .push_signature(process_state, SignatureKind::RpoFalcon512)?, } }, Decorator::Debug(options) => { From 0e4054e3c40145322843f7e5e844d9da481770f3 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Wed, 20 Nov 2024 08:51:47 -0500 Subject: [PATCH 14/39] refactor: remove `key_offset` parameter from `AdviceInjector::MapValueToStack` In preparation for converting advice injectors to instructions. This parameter was never used, and is not crucial - the caller can always put the key on top of the stack before calling this injector. --- assembly/src/ast/instruction/advice.rs | 18 +------ assembly/src/ast/visit.rs | 10 +--- assembly/src/parser/grammar.lalrpop | 10 +--- core/src/mast/serialization/decorator.rs | 8 ++- core/src/mast/serialization/tests.rs | 10 +--- core/src/operations/decorators/advice.rs | 19 +++---- docs/src/user_docs/assembly/io_operations.md | 4 +- .../operations/decorators/advice.rs | 53 ------------------- processor/src/errors.rs | 4 -- processor/src/host/advice/mod.rs | 13 ++--- processor/src/lib.rs | 4 +- 11 files changed, 25 insertions(+), 128 deletions(-) diff --git a/assembly/src/ast/instruction/advice.rs b/assembly/src/ast/instruction/advice.rs index cb3a60a745..1bd1d34b64 100644 --- a/assembly/src/ast/instruction/advice.rs +++ b/assembly/src/ast/instruction/advice.rs @@ -18,9 +18,7 @@ pub enum AdviceInjectorNode { PushExt2intt, PushSmtPeek, PushMapVal, - PushMapValImm { offset: ImmU8 }, PushMapValN, - PushMapValNImm { offset: ImmU8 }, PushMtNode, InsertMem, InsertHdword, @@ -36,18 +34,8 @@ impl From<&AdviceInjectorNode> for AdviceInjector { PushU64Div => Self::U64Div, PushExt2intt => Self::Ext2Intt, PushSmtPeek => Self::SmtPeek, - PushMapVal => Self::MapValueToStack { include_len: false, key_offset: 0 }, - PushMapValImm { offset: ImmU8::Value(offset) } => Self::MapValueToStack { - include_len: false, - key_offset: offset.into_inner() as usize, - }, - PushMapValImm { offset } => panic!("unresolved constant '{offset}'"), - PushMapValN => Self::MapValueToStack { include_len: true, key_offset: 0 }, - PushMapValNImm { offset: ImmU8::Value(offset) } => Self::MapValueToStack { - include_len: true, - key_offset: offset.into_inner() as usize, - }, - PushMapValNImm { offset } => panic!("unresolved constant '{offset}'"), + PushMapVal => Self::MapValueToStack { include_len: false }, + PushMapValN => Self::MapValueToStack { include_len: true }, PushMtNode => Self::MerkleNodeToStack, InsertMem => Self::MemToMap, InsertHdword => Self::HdwordToMap { domain: ZERO }, @@ -76,9 +64,7 @@ impl fmt::Display for AdviceInjectorNode { Self::PushExt2intt => write!(f, "push_ext2intt"), Self::PushSmtPeek => write!(f, "push_smtpeek"), Self::PushMapVal => write!(f, "push_mapval"), - Self::PushMapValImm { offset } => write!(f, "push_mapval.{offset}"), Self::PushMapValN => write!(f, "push_mapvaln"), - Self::PushMapValNImm { offset } => write!(f, "push_mapvaln.{offset}"), Self::PushMtNode => write!(f, "push_mtnode"), Self::InsertMem => write!(f, "insert_mem"), Self::InsertHdword => write!(f, "insert_hdword"), diff --git a/assembly/src/ast/visit.rs b/assembly/src/ast/visit.rs index 637a82f81c..c16388526a 100644 --- a/assembly/src/ast/visit.rs +++ b/assembly/src/ast/visit.rs @@ -354,11 +354,7 @@ where V: ?Sized + Visit, { match node.into_inner() { - AdviceInjectorNode::PushMapValImm { offset: ref imm } - | AdviceInjectorNode::PushMapValNImm { offset: ref imm } - | AdviceInjectorNode::InsertHdwordImm { domain: ref imm } => { - visitor.visit_immediate_u8(imm) - }, + AdviceInjectorNode::InsertHdwordImm { domain: ref imm } => visitor.visit_immediate_u8(imm), AdviceInjectorNode::PushU64Div | AdviceInjectorNode::PushExt2intt | AdviceInjectorNode::PushSmtPeek @@ -804,9 +800,7 @@ where V: ?Sized + VisitMut, { match node.into_inner() { - AdviceInjectorNode::PushMapValImm { offset: ref mut imm } - | AdviceInjectorNode::PushMapValNImm { offset: ref mut imm } - | AdviceInjectorNode::InsertHdwordImm { domain: ref mut imm } => { + AdviceInjectorNode::InsertHdwordImm { domain: ref mut imm } => { visitor.visit_mut_immediate_u8(imm) }, AdviceInjectorNode::PushU64Div diff --git a/assembly/src/parser/grammar.lalrpop b/assembly/src/parser/grammar.lalrpop index dcb469d475..3717d822b5 100644 --- a/assembly/src/parser/grammar.lalrpop +++ b/assembly/src/parser/grammar.lalrpop @@ -672,14 +672,8 @@ AdviceInjector: Instruction = { "adv" "." "insert_hperm" => Instruction::AdvInject(AdviceInjectorNode::InsertHperm), "adv" "." "insert_mem" => Instruction::AdvInject(AdviceInjectorNode::InsertMem), "adv" "." "push_ext2intt" => Instruction::AdvInject(AdviceInjectorNode::PushExt2intt), - "adv" "." "push_mapval" > => { - i.map(|offset| Instruction::AdvInject(AdviceInjectorNode::PushMapValImm { offset })) - .unwrap_or(Instruction::AdvInject(AdviceInjectorNode::PushMapVal)) - }, - "adv" "." "push_mapvaln" > => { - i.map(|offset| Instruction::AdvInject(AdviceInjectorNode::PushMapValNImm { offset })) - .unwrap_or(Instruction::AdvInject(AdviceInjectorNode::PushMapValN)) - }, + "adv" "." "push_mapval" => Instruction::AdvInject(AdviceInjectorNode::PushMapVal), + "adv" "." "push_mapvaln" => Instruction::AdvInject(AdviceInjectorNode::PushMapValN), "adv" "." "push_mtnode" => Instruction::AdvInject(AdviceInjectorNode::PushMtNode), "adv" "." "push_sig" "." => Instruction::AdvInject(AdviceInjectorNode::PushSignature { kind }), "adv" "." "push_smtpeek" => Instruction::AdvInject(AdviceInjectorNode::PushSmtPeek), diff --git a/core/src/mast/serialization/decorator.rs b/core/src/mast/serialization/decorator.rs index 0c6e24e633..25af53db4a 100644 --- a/core/src/mast/serialization/decorator.rs +++ b/core/src/mast/serialization/decorator.rs @@ -59,9 +59,8 @@ impl DecoratorInfo { }, EncodedDecoratorVariant::AdviceInjectorMapValueToStack => { let include_len = data_reader.read_bool()?; - let key_offset = data_reader.read_usize()?; - Ok(Decorator::Advice(AdviceInjector::MapValueToStack { include_len, key_offset })) + Ok(Decorator::Advice(AdviceInjector::MapValueToStack { include_len })) }, EncodedDecoratorVariant::AdviceInjectorU64Div => { Ok(Decorator::Advice(AdviceInjector::U64Div)) @@ -255,7 +254,7 @@ impl From<&Decorator> for EncodedDecoratorVariant { AdviceInjector::MerkleNodeMerge => Self::AdviceInjectorMerkleNodeMerge, AdviceInjector::MerkleNodeToStack => Self::AdviceInjectorMerkleNodeToStack, AdviceInjector::UpdateMerkleNode => Self::AdviceInjectorUpdateMerkleNode, - AdviceInjector::MapValueToStack { include_len: _, key_offset: _ } => { + AdviceInjector::MapValueToStack { include_len: _ } => { Self::AdviceInjectorMapValueToStack }, AdviceInjector::U64Div => Self::AdviceInjectorU64Div, @@ -332,9 +331,8 @@ impl DecoratorDataBuilder { match decorator { Decorator::Advice(advice_injector) => match advice_injector { - AdviceInjector::MapValueToStack { include_len, key_offset } => { + AdviceInjector::MapValueToStack { include_len } => { self.decorator_data.write_bool(*include_len); - self.decorator_data.write_usize(*key_offset); Some(data_offset) }, diff --git a/core/src/mast/serialization/tests.rs b/core/src/mast/serialization/tests.rs index 5f2007130a..02f0cd22ce 100644 --- a/core/src/mast/serialization/tests.rs +++ b/core/src/mast/serialization/tests.rs @@ -113,7 +113,7 @@ fn confirm_operation_and_decorator_structure() { AdviceInjector::MerkleNodeMerge => (), AdviceInjector::MerkleNodeToStack => (), AdviceInjector::UpdateMerkleNode => (), - AdviceInjector::MapValueToStack { include_len: _, key_offset: _ } => (), + AdviceInjector::MapValueToStack { include_len: _ } => (), AdviceInjector::U64Div => (), AdviceInjector::Ext2Inv => (), AdviceInjector::Ext2Intt => (), @@ -244,13 +244,7 @@ fn serialize_deserialize_all_nodes() { (0, Decorator::Advice(AdviceInjector::MerkleNodeMerge)), (0, Decorator::Advice(AdviceInjector::MerkleNodeToStack)), (0, Decorator::Advice(AdviceInjector::UpdateMerkleNode)), - ( - 0, - Decorator::Advice(AdviceInjector::MapValueToStack { - include_len: true, - key_offset: 1023, - }), - ), + (0, Decorator::Advice(AdviceInjector::MapValueToStack { include_len: true })), (1, Decorator::Advice(AdviceInjector::U64Div)), (3, Decorator::Advice(AdviceInjector::Ext2Inv)), (5, Decorator::Advice(AdviceInjector::Ext2Intt)), diff --git a/core/src/operations/decorators/advice.rs b/core/src/operations/decorators/advice.rs index 4bcc27b492..29c8d4a744 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/operations/decorators/advice.rs @@ -69,22 +69,15 @@ pub enum AdviceInjector { /// true, the number of elements in the value is also pushed onto the advice stack. /// /// Inputs: - /// Operand stack: [..., KEY, ...] + /// Operand stack: [KEY, ...] /// Advice stack: [...] /// Advice map: {KEY: values} /// /// Outputs: - /// Operand stack: [..., KEY, ...] + /// Operand stack: [KEY, ...] /// Advice stack: [values_len?, values, ...] /// Advice map: {KEY: values} - /// - /// The `key_offset` value specifies the location of the `KEY` on the stack. For example, - /// offset value of 0 indicates that the top word on the stack should be used as the key, the - /// offset value of 4, indicates that the second word on the stack should be used as the key - /// etc. - /// - /// The valid values of `key_offset` are 0 through 12 (inclusive). - MapValueToStack { include_len: bool, key_offset: usize }, + MapValueToStack { include_len: bool }, /// Pushes the result of [u64] division (both the quotient and the remainder) onto the advice /// stack. @@ -287,11 +280,11 @@ impl fmt::Display for AdviceInjector { Self::UpdateMerkleNode => { write!(f, "update_merkle_node") }, - Self::MapValueToStack { include_len, key_offset } => { + Self::MapValueToStack { include_len } => { if *include_len { - write!(f, "map_value_to_stack_with_len.{key_offset}") + write!(f, "map_value_to_stack_with_len") } else { - write!(f, "map_value_to_stack.{key_offset}") + write!(f, "map_value_to_stack") } }, Self::U64Div => write!(f, "div_u64"), diff --git a/docs/src/user_docs/assembly/io_operations.md b/docs/src/user_docs/assembly/io_operations.md index fc8c08608c..ca180adc63 100644 --- a/docs/src/user_docs/assembly/io_operations.md +++ b/docs/src/user_docs/assembly/io_operations.md @@ -49,8 +49,8 @@ Advice injectors fall into two categories: (1) injectors which push new data ont | Instruction | Stack_input | Stack_output | Notes | | -------------------------------------------- | -------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| adv.push_mapval
adv.push_mapval.*s* | [K, ... ] | [K, ... ] | Pushes a list of field elements onto the advice stack. The list is looked up in the advice map using word $K$ as the key. If offset $s$ is provided, the key is taken starting from item $s$ on the stack. | -| adv.push_mapvaln
adv.push_mapvaln.*s* | [K, ... ] | [K, ... ] | Pushes a list of field elements together with the number of elements onto the advice stack. The list is looked up in the advice map using word $K$ as the key. If offset $s$ is provided, the key is taken starting from item $s$ on the stack. | +| adv.push_mapval | [K, ... ] | [K, ... ] | Pushes a list of field elements onto the advice stack. The list is looked up in the advice map using word $K$ as the key. | +| adv.push_mapvaln | [K, ... ] | [K, ... ] | Pushes a list of field elements together with the number of elements onto the advice stack (`[n, ele1, ele2, ...]`, where `n` is the number of elements pushed). The list is looked up in the advice map using word $K$ as the key. | | adv.push_mtnode | [d, i, R, ... ] | [d, i, R, ... ] | Pushes a node of a Merkle tree with root $R$ at depth $d$ and index $i$ from Merkle store onto the advice stack. | | adv.push_u64div | [b1, b0, a1, a0, ...] | [b1, b0, a1, a0, ...] | Pushes the result of `u64` division $a / b$ onto the advice stack. Both $a$ and $b$ are represented using 32-bit limbs. The result consists of both the quotient and the remainder. | | adv.push_ext2intt | [osize, isize, iptr, ... ] | [osize, isize, iptr, ... ] | Given evaluations of a polynomial over some specified domain, interpolates the evaluations into a polynomial in coefficient form and pushes the result into the advice stack. | diff --git a/miden/tests/integration/operations/decorators/advice.rs b/miden/tests/integration/operations/decorators/advice.rs index 4c80720f07..868fb338ae 100644 --- a/miden/tests/integration/operations/decorators/advice.rs +++ b/miden/tests/integration/operations/decorators/advice.rs @@ -251,32 +251,6 @@ fn advice_push_mapval() { let test = build_test!(source, &stack_inputs, [], MerkleStore::default(), adv_map); test.expect_stack(&[5, 6, 7, 8]); - // --- test adv.mapval with offset ---------------------------------------- - let source: &str = " - begin - # stack: [4, 3, 2, 1, ...] - - # shift the key on the stack by 2 slots - push.0 push.0 - - # load the advice stack with values from the advice map and drop the key - adv.push_mapval.2 - dropw drop drop - - # move the values from the advice stack to the operand stack - adv_push.4 - swapw dropw - end"; - - let stack_inputs = [1, 2, 3, 4]; - let adv_map = [( - RpoDigest::try_from(stack_inputs).unwrap(), - vec![Felt::new(8), Felt::new(7), Felt::new(6), Felt::new(5)], - )]; - - let test = build_test!(source, &stack_inputs, [], MerkleStore::default(), adv_map); - test.expect_stack(&[5, 6, 7, 8]); - // --- test simple adv.mapvaln -------------------------------------------- let source: &str = " begin @@ -300,33 +274,6 @@ fn advice_push_mapval() { let test = build_test!(source, &stack_inputs, [], MerkleStore::default(), adv_map); test.expect_stack(&[15, 14, 13, 12, 11, 5]); - - // --- test adv.mapval with offset ---------------------------------------- - let source: &str = " - begin - # stack: [4, 3, 2, 1, ...] - - # shift the key on the stack by 2 slots - push.0 push.0 - - # load the advice stack with values from the advice map (including the number - # of elements) and drop the key - adv.push_mapvaln.2 - dropw drop drop - - # move the values from the advice stack to the operand stack - adv_push.6 - swapdw dropw dropw - end"; - - let stack_inputs = [1, 2, 3, 4]; - let adv_map = [( - RpoDigest::try_from(stack_inputs).unwrap(), - vec![Felt::new(11), Felt::new(12), Felt::new(13), Felt::new(14), Felt::new(15)], - )]; - - let test = build_test!(source, &stack_inputs, [], MerkleStore::default(), adv_map); - test.expect_stack(&[15, 14, 13, 12, 11, 5]); } #[test] diff --git a/processor/src/errors.rs b/processor/src/errors.rs index ac86171e2e..ae1f548ecb 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -55,7 +55,6 @@ pub enum ExecutionError { end_addr: u64, }, InvalidStackDepthOnReturn(usize), - InvalidStackWordOffset(usize), InvalidTreeDepth { depth: Felt, }, @@ -162,9 +161,6 @@ impl Display for ExecutionError { InvalidStackDepthOnReturn(depth) => { write!(f, "When returning from a call, stack depth must be {MIN_STACK_DEPTH}, but was {depth}") }, - InvalidStackWordOffset(offset) => { - write!(f, "Stack word offset cannot exceed 12, but was {offset}") - }, InvalidTreeDepth { depth } => { write!(f, "The provided {depth} is out of bounds and cannot be represented as an unsigned 8-bits integer") }, diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index d85838d2a1..f6f548b07c 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -393,17 +393,12 @@ pub trait AdviceProvider: Sized { &mut self, process: ProcessState, include_len: bool, - key_offset: usize, ) -> Result<(), ExecutionError> { - if key_offset > 12 { - return Err(ExecutionError::InvalidStackWordOffset(key_offset)); - } - let key = [ - process.get_stack_item(key_offset + 3), - process.get_stack_item(key_offset + 2), - process.get_stack_item(key_offset + 1), - process.get_stack_item(key_offset), + process.get_stack_item(3), + process.get_stack_item(2), + process.get_stack_item(1), + process.get_stack_item(0), ]; self.push_stack(AdviceSource::Map { key, include_len })?; diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 040a56b6fe..ef65b0cde7 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -603,8 +603,8 @@ impl Process { AdviceInjector::MerkleNodeToStack => { advice_provider.copy_merkle_node_to_adv_stack(process_state)? }, - AdviceInjector::MapValueToStack { include_len, key_offset } => advice_provider - .copy_map_value_to_adv_stack(process_state, *include_len, *key_offset)?, + AdviceInjector::MapValueToStack { include_len } => advice_provider + .copy_map_value_to_adv_stack(process_state, *include_len, 0)?, AdviceInjector::UpdateMerkleNode => { let _ = advice_provider.update_operand_stack_merkle_node(process_state)?; }, From 3dfdd2003ed5ce60949988359d7d4d1349943810 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Wed, 20 Nov 2024 09:13:16 -0500 Subject: [PATCH 15/39] refactor: split `AdviceInjector::MapValueToStack` into `MapValueToStack{N}` --- assembly/src/ast/instruction/advice.rs | 4 +-- core/src/mast/serialization/decorator.rs | 22 ++++++++--------- core/src/mast/serialization/tests.rs | 6 +++-- core/src/operations/decorators/advice.rs | 31 +++++++++++++++--------- processor/src/lib.rs | 8 ++++-- 5 files changed, 42 insertions(+), 29 deletions(-) diff --git a/assembly/src/ast/instruction/advice.rs b/assembly/src/ast/instruction/advice.rs index 1bd1d34b64..472d70e507 100644 --- a/assembly/src/ast/instruction/advice.rs +++ b/assembly/src/ast/instruction/advice.rs @@ -34,8 +34,8 @@ impl From<&AdviceInjectorNode> for AdviceInjector { PushU64Div => Self::U64Div, PushExt2intt => Self::Ext2Intt, PushSmtPeek => Self::SmtPeek, - PushMapVal => Self::MapValueToStack { include_len: false }, - PushMapValN => Self::MapValueToStack { include_len: true }, + PushMapVal => Self::MapValueToStack, + PushMapValN => Self::MapValueToStackN, PushMtNode => Self::MerkleNodeToStack, InsertMem => Self::MemToMap, InsertHdword => Self::HdwordToMap { domain: ZERO }, diff --git a/core/src/mast/serialization/decorator.rs b/core/src/mast/serialization/decorator.rs index 25af53db4a..0004e90801 100644 --- a/core/src/mast/serialization/decorator.rs +++ b/core/src/mast/serialization/decorator.rs @@ -58,9 +58,10 @@ impl DecoratorInfo { Ok(Decorator::Advice(AdviceInjector::UpdateMerkleNode)) }, EncodedDecoratorVariant::AdviceInjectorMapValueToStack => { - let include_len = data_reader.read_bool()?; - - Ok(Decorator::Advice(AdviceInjector::MapValueToStack { include_len })) + Ok(Decorator::Advice(AdviceInjector::MapValueToStack)) + }, + EncodedDecoratorVariant::AdviceInjectorMapValueToStackN => { + Ok(Decorator::Advice(AdviceInjector::MapValueToStackN)) }, EncodedDecoratorVariant::AdviceInjectorU64Div => { Ok(Decorator::Advice(AdviceInjector::U64Div)) @@ -210,6 +211,7 @@ pub enum EncodedDecoratorVariant { AdviceInjectorMerkleNodeToStack, AdviceInjectorUpdateMerkleNode, AdviceInjectorMapValueToStack, + AdviceInjectorMapValueToStackN, AdviceInjectorU64Div, AdviceInjectorExt2Inv, AdviceInjectorExt2Intt, @@ -254,9 +256,8 @@ impl From<&Decorator> for EncodedDecoratorVariant { AdviceInjector::MerkleNodeMerge => Self::AdviceInjectorMerkleNodeMerge, AdviceInjector::MerkleNodeToStack => Self::AdviceInjectorMerkleNodeToStack, AdviceInjector::UpdateMerkleNode => Self::AdviceInjectorUpdateMerkleNode, - AdviceInjector::MapValueToStack { include_len: _ } => { - Self::AdviceInjectorMapValueToStack - }, + AdviceInjector::MapValueToStack => Self::AdviceInjectorMapValueToStack, + AdviceInjector::MapValueToStackN => Self::AdviceInjectorMapValueToStackN, AdviceInjector::U64Div => Self::AdviceInjectorU64Div, AdviceInjector::Ext2Inv => Self::AdviceInjectorExt2Inv, AdviceInjector::Ext2Intt => Self::AdviceInjectorExt2Intt, @@ -331,17 +332,14 @@ impl DecoratorDataBuilder { match decorator { Decorator::Advice(advice_injector) => match advice_injector { - AdviceInjector::MapValueToStack { include_len } => { - self.decorator_data.write_bool(*include_len); - - Some(data_offset) - }, AdviceInjector::HdwordToMap { domain } => { self.decorator_data.extend(domain.as_int().to_le_bytes()); Some(data_offset) }, - AdviceInjector::MerkleNodeMerge + AdviceInjector::MapValueToStack + | AdviceInjector::MapValueToStackN + | AdviceInjector::MerkleNodeMerge | AdviceInjector::MerkleNodeToStack | AdviceInjector::UpdateMerkleNode | AdviceInjector::U64Div diff --git a/core/src/mast/serialization/tests.rs b/core/src/mast/serialization/tests.rs index 02f0cd22ce..3c0b2adf78 100644 --- a/core/src/mast/serialization/tests.rs +++ b/core/src/mast/serialization/tests.rs @@ -113,7 +113,8 @@ fn confirm_operation_and_decorator_structure() { AdviceInjector::MerkleNodeMerge => (), AdviceInjector::MerkleNodeToStack => (), AdviceInjector::UpdateMerkleNode => (), - AdviceInjector::MapValueToStack { include_len: _ } => (), + AdviceInjector::MapValueToStack => (), + AdviceInjector::MapValueToStackN => (), AdviceInjector::U64Div => (), AdviceInjector::Ext2Inv => (), AdviceInjector::Ext2Intt => (), @@ -244,7 +245,8 @@ fn serialize_deserialize_all_nodes() { (0, Decorator::Advice(AdviceInjector::MerkleNodeMerge)), (0, Decorator::Advice(AdviceInjector::MerkleNodeToStack)), (0, Decorator::Advice(AdviceInjector::UpdateMerkleNode)), - (0, Decorator::Advice(AdviceInjector::MapValueToStack { include_len: true })), + (0, Decorator::Advice(AdviceInjector::MapValueToStack)), + (0, Decorator::Advice(AdviceInjector::MapValueToStackN)), (1, Decorator::Advice(AdviceInjector::U64Div)), (3, Decorator::Advice(AdviceInjector::Ext2Inv)), (5, Decorator::Advice(AdviceInjector::Ext2Intt)), diff --git a/core/src/operations/decorators/advice.rs b/core/src/operations/decorators/advice.rs index 29c8d4a744..66210952b9 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/operations/decorators/advice.rs @@ -65,8 +65,7 @@ pub enum AdviceInjector { UpdateMerkleNode, /// Pushes a list of field elements onto the advice stack. The list is looked up in the advice - /// map using the specified word from the operand stack as the key. If `include_len` is set to - /// true, the number of elements in the value is also pushed onto the advice stack. + /// map using the specified word from the operand stack as the key. /// /// Inputs: /// Operand stack: [KEY, ...] @@ -75,9 +74,24 @@ pub enum AdviceInjector { /// /// Outputs: /// Operand stack: [KEY, ...] - /// Advice stack: [values_len?, values, ...] + /// Advice stack: [values, ...] /// Advice map: {KEY: values} - MapValueToStack { include_len: bool }, + MapValueToStack, + + /// Pushes a list of field elements onto the advice stack, and then the number of elements + /// pushed. The list is looked up in the advice map using the specified word from the operand + /// stack as the key. + /// + /// Inputs: + /// Operand stack: [KEY, ...] + /// Advice stack: [...] + /// Advice map: {KEY: values} + /// + /// Outputs: + /// Operand stack: [KEY, ...] + /// Advice stack: [num_values, values, ...] + /// Advice map: {KEY: values} + MapValueToStackN, /// Pushes the result of [u64] division (both the quotient and the remainder) onto the advice /// stack. @@ -280,13 +294,8 @@ impl fmt::Display for AdviceInjector { Self::UpdateMerkleNode => { write!(f, "update_merkle_node") }, - Self::MapValueToStack { include_len } => { - if *include_len { - write!(f, "map_value_to_stack_with_len") - } else { - write!(f, "map_value_to_stack") - } - }, + Self::MapValueToStack => write!(f, "map_value_to_stack"), + Self::MapValueToStackN => write!(f, "map_value_to_stack_with_len"), Self::U64Div => write!(f, "div_u64"), Self::Ext2Inv => write!(f, "ext2_inv"), Self::Ext2Intt => write!(f, "ext2_intt"), diff --git a/processor/src/lib.rs b/processor/src/lib.rs index ef65b0cde7..279a860005 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -603,8 +603,12 @@ impl Process { AdviceInjector::MerkleNodeToStack => { advice_provider.copy_merkle_node_to_adv_stack(process_state)? }, - AdviceInjector::MapValueToStack { include_len } => advice_provider - .copy_map_value_to_adv_stack(process_state, *include_len, 0)?, + AdviceInjector::MapValueToStack => { + advice_provider.copy_map_value_to_adv_stack(process_state, false, 0)? + }, + AdviceInjector::MapValueToStackN => { + advice_provider.copy_map_value_to_adv_stack(process_state, true, 0)? + }, AdviceInjector::UpdateMerkleNode => { let _ = advice_provider.update_operand_stack_merkle_node(process_state)?; }, From 7333d55bada584d94d633800826cc3eaeaa7f90c Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Wed, 20 Nov 2024 12:37:11 -0500 Subject: [PATCH 16/39] refactor: split `AdviceInjector::HdWordToMap` into `HdwordToMap{WithDomain}` --- assembly/src/ast/instruction/advice.rs | 13 ++---- assembly/src/ast/visit.rs | 38 +++-------------- assembly/src/parser/grammar.lalrpop | 7 ++-- assembly/src/parser/token.rs | 4 ++ core/src/mast/serialization/decorator.rs | 42 ++++--------------- core/src/mast/serialization/tests.rs | 6 ++- core/src/operations/decorators/advice.rs | 23 +++++++--- docs/src/user_docs/assembly/io_operations.md | 5 ++- .../operations/decorators/advice.rs | 8 ++-- processor/src/lib.rs | 12 ++++-- 10 files changed, 63 insertions(+), 95 deletions(-) diff --git a/assembly/src/ast/instruction/advice.rs b/assembly/src/ast/instruction/advice.rs index 472d70e507..b8b7b0d4b5 100644 --- a/assembly/src/ast/instruction/advice.rs +++ b/assembly/src/ast/instruction/advice.rs @@ -2,8 +2,6 @@ use core::fmt; use vm_core::AdviceInjector; -use crate::{ast::ImmU8, Felt, ZERO}; - // ADVICE INJECTOR NODE // ================================================================================================ @@ -22,7 +20,7 @@ pub enum AdviceInjectorNode { PushMtNode, InsertMem, InsertHdword, - InsertHdwordImm { domain: ImmU8 }, + InsertHdwordWithDomain, InsertHperm, PushSignature { kind: SignatureKind }, } @@ -38,11 +36,8 @@ impl From<&AdviceInjectorNode> for AdviceInjector { PushMapValN => Self::MapValueToStackN, PushMtNode => Self::MerkleNodeToStack, InsertMem => Self::MemToMap, - InsertHdword => Self::HdwordToMap { domain: ZERO }, - InsertHdwordImm { domain: ImmU8::Value(domain) } => { - Self::HdwordToMap { domain: Felt::from(domain.into_inner()) } - }, - InsertHdwordImm { domain } => panic!("unresolved constant '{domain}'"), + InsertHdword => Self::HdwordToMap, + InsertHdwordWithDomain => Self::HdwordToMapWithDomain, InsertHperm => Self::HpermToMap, PushSignature { kind } => match kind { SignatureKind::RpoFalcon512 => Self::FalconSigToStack, @@ -68,7 +63,7 @@ impl fmt::Display for AdviceInjectorNode { Self::PushMtNode => write!(f, "push_mtnode"), Self::InsertMem => write!(f, "insert_mem"), Self::InsertHdword => write!(f, "insert_hdword"), - Self::InsertHdwordImm { domain } => write!(f, "insert_hdword.{domain}"), + Self::InsertHdwordWithDomain => write!(f, "insert_hdword_d"), Self::InsertHperm => writeln!(f, "insert_hperm"), Self::PushSignature { kind } => write!(f, "push_sig.{kind}"), } diff --git a/assembly/src/ast/visit.rs b/assembly/src/ast/visit.rs index c16388526a..9a9afd8d2f 100644 --- a/assembly/src/ast/visit.rs +++ b/assembly/src/ast/visit.rs @@ -347,25 +347,13 @@ where } pub fn visit_advice_injector( - visitor: &mut V, - node: Span<&AdviceInjectorNode>, + _visitor: &mut V, + _node: Span<&AdviceInjectorNode>, ) -> ControlFlow where V: ?Sized + Visit, { - match node.into_inner() { - AdviceInjectorNode::InsertHdwordImm { domain: ref imm } => visitor.visit_immediate_u8(imm), - AdviceInjectorNode::PushU64Div - | AdviceInjectorNode::PushExt2intt - | AdviceInjectorNode::PushSmtPeek - | AdviceInjectorNode::PushMapVal - | AdviceInjectorNode::PushMapValN - | AdviceInjectorNode::PushMtNode - | AdviceInjectorNode::InsertMem - | AdviceInjectorNode::InsertHdword - | AdviceInjectorNode::InsertHperm - | AdviceInjectorNode::PushSignature { .. } => ControlFlow::Continue(()), - } + ControlFlow::Continue(()) } pub fn visit_debug_options(visitor: &mut V, options: Span<&DebugOptions>) -> ControlFlow @@ -793,27 +781,13 @@ where } pub fn visit_mut_advice_injector( - visitor: &mut V, - node: Span<&mut AdviceInjectorNode>, + _visitor: &mut V, + _node: Span<&mut AdviceInjectorNode>, ) -> ControlFlow where V: ?Sized + VisitMut, { - match node.into_inner() { - AdviceInjectorNode::InsertHdwordImm { domain: ref mut imm } => { - visitor.visit_mut_immediate_u8(imm) - }, - AdviceInjectorNode::PushU64Div - | AdviceInjectorNode::PushExt2intt - | AdviceInjectorNode::PushSmtPeek - | AdviceInjectorNode::PushMapVal - | AdviceInjectorNode::PushMapValN - | AdviceInjectorNode::PushMtNode - | AdviceInjectorNode::InsertMem - | AdviceInjectorNode::InsertHdword - | AdviceInjectorNode::InsertHperm - | AdviceInjectorNode::PushSignature { .. } => ControlFlow::Continue(()), - } + ControlFlow::Continue(()) } pub fn visit_mut_debug_options( diff --git a/assembly/src/parser/grammar.lalrpop b/assembly/src/parser/grammar.lalrpop index 3717d822b5..f0b6d882f0 100644 --- a/assembly/src/parser/grammar.lalrpop +++ b/assembly/src/parser/grammar.lalrpop @@ -43,6 +43,7 @@ extern { "add" => Token::Add, "adv" => Token::Adv, "insert_hdword" => Token::InsertHdword, + "insert_hdword_d" => Token::InsertHdwordWithDomain, "insert_hperm" => Token::InsertHperm, "insert_mem" => Token::InsertMem, "adv_loadw" => Token::AdvLoadw, @@ -665,10 +666,8 @@ Inst: Instruction = { #[inline] AdviceInjector: Instruction = { - "adv" "." "insert_hdword" > => { - domain.map(|domain| Instruction::AdvInject(AdviceInjectorNode::InsertHdwordImm { domain })) - .unwrap_or(Instruction::AdvInject(AdviceInjectorNode::InsertHdword)) - }, + "adv" "." "insert_hdword" => Instruction::AdvInject(AdviceInjectorNode::InsertHdword), + "adv" "." "insert_hdword_d" => Instruction::AdvInject(AdviceInjectorNode::InsertHdwordWithDomain), "adv" "." "insert_hperm" => Instruction::AdvInject(AdviceInjectorNode::InsertHperm), "adv" "." "insert_mem" => Instruction::AdvInject(AdviceInjectorNode::InsertMem), "adv" "." "push_ext2intt" => Instruction::AdvInject(AdviceInjectorNode::PushExt2intt), diff --git a/assembly/src/parser/token.rs b/assembly/src/parser/token.rs index b55201f1a7..94c4f73fd4 100644 --- a/assembly/src/parser/token.rs +++ b/assembly/src/parser/token.rs @@ -138,6 +138,7 @@ pub enum Token<'input> { Add, Adv, InsertHdword, + InsertHdwordWithDomain, InsertHperm, InsertMem, AdvLoadw, @@ -322,6 +323,7 @@ impl fmt::Display for Token<'_> { Token::Add => write!(f, "add"), Token::Adv => write!(f, "adv"), Token::InsertHdword => write!(f, "insert_hdword"), + Token::InsertHdwordWithDomain => write!(f, "insert_hdword_d"), Token::InsertHperm => write!(f, "insert_hperm"), Token::InsertMem => write!(f, "insert_mem"), Token::AdvLoadw => write!(f, "adv_loadw"), @@ -514,6 +516,7 @@ impl<'input> Token<'input> { Token::Add | Token::Adv | Token::InsertHdword + | Token::InsertHdwordWithDomain | Token::InsertHperm | Token::InsertMem | Token::AdvLoadw @@ -658,6 +661,7 @@ impl<'input> Token<'input> { ("add", Token::Add), ("adv", Token::Adv), ("insert_hdword", Token::InsertHdword), + ("insert_hdword_d", Token::InsertHdwordWithDomain), ("insert_hperm", Token::InsertHperm), ("insert_mem", Token::InsertMem), ("adv_loadw", Token::AdvLoadw), diff --git a/core/src/mast/serialization/decorator.rs b/core/src/mast/serialization/decorator.rs index 0004e90801..5815b988cf 100644 --- a/core/src/mast/serialization/decorator.rs +++ b/core/src/mast/serialization/decorator.rs @@ -1,6 +1,5 @@ use alloc::vec::Vec; -use miden_crypto::Felt; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use winter_utils::{ @@ -94,14 +93,10 @@ impl DecoratorInfo { Ok(Decorator::Advice(AdviceInjector::MemToMap)) }, EncodedDecoratorVariant::AdviceInjectorHdwordToMap => { - let domain = data_reader.read_u64()?; - let domain = Felt::try_from(domain).map_err(|err| { - DeserializationError::InvalidValue(format!( - "Error when deserializing HdwordToMap decorator domain: {err}" - )) - })?; - - Ok(Decorator::Advice(AdviceInjector::HdwordToMap { domain })) + Ok(Decorator::Advice(AdviceInjector::HdwordToMap)) + }, + EncodedDecoratorVariant::AdviceInjectorHdwordToMapWithDomain => { + Ok(Decorator::Advice(AdviceInjector::HdwordToMapWithDomain)) }, EncodedDecoratorVariant::AdviceInjectorHpermToMap => { Ok(Decorator::Advice(AdviceInjector::HpermToMap)) @@ -223,6 +218,7 @@ pub enum EncodedDecoratorVariant { AdviceInjectorILog2, AdviceInjectorMemToMap, AdviceInjectorHdwordToMap, + AdviceInjectorHdwordToMapWithDomain, AdviceInjectorHpermToMap, AdviceInjectorFalconSigToStack, AssemblyOp, @@ -268,7 +264,8 @@ impl From<&Decorator> for EncodedDecoratorVariant { AdviceInjector::U32Cto => Self::AdviceInjectorU32Cto, AdviceInjector::ILog2 => Self::AdviceInjectorILog2, AdviceInjector::MemToMap => Self::AdviceInjectorMemToMap, - AdviceInjector::HdwordToMap { domain: _ } => Self::AdviceInjectorHdwordToMap, + AdviceInjector::HdwordToMap => Self::AdviceInjectorHdwordToMap, + AdviceInjector::HdwordToMapWithDomain => Self::AdviceInjectorHdwordToMapWithDomain, AdviceInjector::HpermToMap => Self::AdviceInjectorHpermToMap, AdviceInjector::FalconSigToStack => Self::AdviceInjectorFalconSigToStack, }, @@ -331,30 +328,7 @@ impl DecoratorDataBuilder { let data_offset = self.decorator_data.len() as DecoratorDataOffset; match decorator { - Decorator::Advice(advice_injector) => match advice_injector { - AdviceInjector::HdwordToMap { domain } => { - self.decorator_data.extend(domain.as_int().to_le_bytes()); - - Some(data_offset) - }, - AdviceInjector::MapValueToStack - | AdviceInjector::MapValueToStackN - | AdviceInjector::MerkleNodeMerge - | AdviceInjector::MerkleNodeToStack - | AdviceInjector::UpdateMerkleNode - | AdviceInjector::U64Div - | AdviceInjector::Ext2Inv - | AdviceInjector::Ext2Intt - | AdviceInjector::SmtPeek - | AdviceInjector::U32Clz - | AdviceInjector::U32Ctz - | AdviceInjector::U32Clo - | AdviceInjector::U32Cto - | AdviceInjector::ILog2 - | AdviceInjector::MemToMap - | AdviceInjector::HpermToMap - | AdviceInjector::FalconSigToStack => None, - }, + Decorator::Advice(_) => None, Decorator::AsmOp(assembly_op) => { self.decorator_data.push(assembly_op.num_cycles()); self.decorator_data.write_bool(assembly_op.should_break()); diff --git a/core/src/mast/serialization/tests.rs b/core/src/mast/serialization/tests.rs index 3c0b2adf78..cf02a6eaf1 100644 --- a/core/src/mast/serialization/tests.rs +++ b/core/src/mast/serialization/tests.rs @@ -125,7 +125,8 @@ fn confirm_operation_and_decorator_structure() { AdviceInjector::U32Cto => (), AdviceInjector::ILog2 => (), AdviceInjector::MemToMap => (), - AdviceInjector::HdwordToMap { domain: _ } => (), + AdviceInjector::HdwordToMap => (), + AdviceInjector::HdwordToMapWithDomain => (), AdviceInjector::HpermToMap => (), AdviceInjector::FalconSigToStack => (), }, @@ -257,7 +258,8 @@ fn serialize_deserialize_all_nodes() { (10, Decorator::Advice(AdviceInjector::U32Cto)), (10, Decorator::Advice(AdviceInjector::ILog2)), (10, Decorator::Advice(AdviceInjector::MemToMap)), - (10, Decorator::Advice(AdviceInjector::HdwordToMap { domain: Felt::new(423) })), + (10, Decorator::Advice(AdviceInjector::HdwordToMap)), + (10, Decorator::Advice(AdviceInjector::HdwordToMapWithDomain)), (15, Decorator::Advice(AdviceInjector::HpermToMap)), (15, Decorator::Advice(AdviceInjector::FalconSigToStack)), ( diff --git a/core/src/operations/decorators/advice.rs b/core/src/operations/decorators/advice.rs index 66210952b9..4cc60ec4bd 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/operations/decorators/advice.rs @@ -1,7 +1,6 @@ use core::fmt; use super::SignatureKind; -use crate::Felt; // ADVICE INJECTORS // ================================================================================================ @@ -245,9 +244,22 @@ pub enum AdviceInjector { /// Operand stack: [B, A, ...] /// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} /// - /// Where KEY is computed as hash(A || B, domain), where domain is provided via the immediate - /// value. - HdwordToMap { domain: Felt }, + /// Where KEY is computed as hash(A || B, domain=0) + HdwordToMap, + + /// Reads two word from the operand stack and inserts them into the advice map under the key + /// defined by the hash of these words (using `d` as the domain). + /// + /// Inputs: + /// Operand stack: [B, A, d, ...] + /// Advice map: {...} + /// + /// Outputs: + /// Operand stack: [B, A, d, ...] + /// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} + /// + /// Where KEY is computed as hash(A || B, d). + HdwordToMapWithDomain, /// Reads three words from the operand stack and inserts the top two words into the advice map /// under the key defined by applying an RPO permutation to all three words. @@ -306,7 +318,8 @@ impl fmt::Display for AdviceInjector { Self::U32Cto => write!(f, "u32cto"), Self::ILog2 => write!(f, "ilog2"), Self::MemToMap => write!(f, "mem_to_map"), - Self::HdwordToMap { domain } => write!(f, "hdword_to_map.{domain}"), + Self::HdwordToMap => write!(f, "hdword_to_map"), + Self::HdwordToMapWithDomain => write!(f, "hdword_to_map_with_domain"), Self::HpermToMap => write!(f, "hperm_to_map"), Self::FalconSigToStack => write!(f, "sig_to_stack.{}", SignatureKind::RpoFalcon512), } diff --git a/docs/src/user_docs/assembly/io_operations.md b/docs/src/user_docs/assembly/io_operations.md index ca180adc63..23e31bd181 100644 --- a/docs/src/user_docs/assembly/io_operations.md +++ b/docs/src/user_docs/assembly/io_operations.md @@ -57,8 +57,9 @@ Advice injectors fall into two categories: (1) injectors which push new data ont | adv.push_sig.*kind* | [K, M, ...] | [K, M, ...] | Pushes values onto the advice stack which are required for verification of a DSA with scheme specified by *kind* against the public key commitment $K$ and message $M$. | | adv.push_smtpeek | [K, R, ... ] | [K, R, ... ] | Pushes value onto the advice stack which is associated with key $K$ in a Sparse Merkle Tree with root $R$. | | adv.insert_mem | [K, a, b, ... ] | [K, a, b, ... ] | Reads words $data \leftarrow mem[a] .. mem[b]$ from memory, and save the data into $advice\_map[K] \leftarrow data$. | -| adv.insert_hdword
adv.insert_hdword.*d* | [B, A, ... ] | [B, A, ... ] | Reads top two words from the stack, computes a key as $K \leftarrow hash(A || b, d)$, and saves the data into $advice\_map[K] \leftarrow [A, B]$. $d$ is an optional domain value which can be between $0$ and $255$, default value $0$. | -| adv.insert_hperm | [B, A, C, ...] | [B, A, C, ...] | Reads top three words from the stack, computes a key as $K \leftarrow permute(C, A, B).digest$, and saves data into $advice\_mpa[K] \leftarrow [A, B]$. | +| adv.insert_hdword | [B, A, ... ] | [B, A, ... ] | Reads top two words from the stack, computes a key as $K \leftarrow hash(A || b, domain=0)$, and saves the data into $advice\_map[K] \leftarrow [A, B]$. | +| adv.insert_hdword_d | [B, A, d, ... ] | [B, A, d, ... ] | Reads top two words from the stack, computes a key as $K \leftarrow hash(A || b, domain=d)$, and saves the data into $advice\_map[K] \leftarrow [A, B]$. $d$ is the domain value, where changing the domain changes the resulting hash given the same `A` and `B`. | +| adv.insert_hperm | [B, A, C, ...] | [B, A, C, ...] | Reads top three words from the stack, computes a key as $K \leftarrow permute(C, A, B).digest$, and saves data into $advice\_map[K] \leftarrow [A, B]$. | ### Random access memory diff --git a/miden/tests/integration/operations/decorators/advice.rs b/miden/tests/integration/operations/decorators/advice.rs index 868fb338ae..3e3c6a6bda 100644 --- a/miden/tests/integration/operations/decorators/advice.rs +++ b/miden/tests/integration/operations/decorators/advice.rs @@ -305,13 +305,13 @@ fn advice_insert_hdword() { // --- test hashing with domain ------------------------------------------- let source: &str = " begin - # stack: [1, 2, 3, 4, 5, 6, 7, 8, ...] + # stack: [1, 2, 3, 4, 5, 6, 7, 8, 9, ...] # hash and insert top two words into the advice map - adv.insert_hdword.3 + adv.insert_hdword_d # manually compute the hash of the two words - push.0.3.0.0 + push.0.9.0.0 swapw.2 swapw hperm dropw swapw dropw @@ -325,7 +325,7 @@ fn advice_insert_hdword() { adv_push.8 swapdw dropw dropw end"; - let stack_inputs = [8, 7, 6, 5, 4, 3, 2, 1]; + let stack_inputs = [9, 8, 7, 6, 5, 4, 3, 2, 1]; let test = build_test!(source, &stack_inputs); test.expect_stack(&[1, 2, 3, 4, 5, 6, 7, 8]); } diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 279a860005..ce6a15bc33 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -595,7 +595,7 @@ impl Process { match decorator { Decorator::Advice(injector) => { let advice_provider = host.advice_provider_mut(); - let process_state: ProcessState = self.into(); + let process_state: ProcessState = (&*self).into(); match injector { AdviceInjector::MerkleNodeMerge => { advice_provider.merge_merkle_nodes(process_state)?; @@ -631,8 +631,14 @@ impl Process { AdviceInjector::MemToMap => { advice_provider.insert_mem_values_into_adv_map(process_state)?; }, - AdviceInjector::HdwordToMap { domain } => { - advice_provider.insert_hdword_into_adv_map(process_state, *domain)?; + AdviceInjector::HdwordToMap => { + advice_provider.insert_hdword_into_adv_map(process_state, ZERO)?; + }, + AdviceInjector::HdwordToMapWithDomain => { + // TODO(plafer): get domain from stack + let domain = self.stack.get(8); + advice_provider.insert_hdword_into_adv_map(process_state, domain)?; + todo!() }, AdviceInjector::HpermToMap => { advice_provider.insert_hperm_into_adv_map(process_state)?; From 1b2f23e19fedc3bd9ea6113c0b38be584710ce4c Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Wed, 20 Nov 2024 13:12:32 -0500 Subject: [PATCH 17/39] refactor: compile advice injectors down to `Operation::Emit` --- CHANGELOG.md | 1 + assembly/src/assembler/basic_block_builder.rs | 11 +- assembly/src/assembler/instruction/adv_ops.rs | 5 +- .../src/assembler/instruction/crypto_ops.rs | 22 ++-- .../src/assembler/instruction/ext2_ops.rs | 8 +- .../src/assembler/instruction/field_ops.rs | 6 +- assembly/src/assembler/instruction/mod.rs | 18 ++-- assembly/src/assembler/instruction/u32_ops.rs | 20 ++-- core/src/mast/serialization/decorator.rs | 100 +----------------- core/src/mast/serialization/tests.rs | 49 +-------- core/src/operations/decorators/advice.rs | 80 +++++++++++++- core/src/operations/decorators/mod.rs | 6 +- processor/src/lib.rs | 56 +--------- processor/src/operations/sys_ops.rs | 61 ++++++++++- 14 files changed, 180 insertions(+), 263 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d67d6b24d7..51771a5666 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - [BREAKING] `DYNCALL` operation fixed, and now expects a memory address pointing to the procedure hash (#1535). - Permit child `MastNodeId`s to exceed the `MastNodeId`s of their parents (#1542). - Don't validate export names on `Library` deserialization (#1554) +- Compile advice injectors down to `Emit` operations (#1581) #### Fixes diff --git a/assembly/src/assembler/basic_block_builder.rs b/assembly/src/assembler/basic_block_builder.rs index 3246484d21..85f886f97f 100644 --- a/assembly/src/assembler/basic_block_builder.rs +++ b/assembly/src/assembler/basic_block_builder.rs @@ -93,6 +93,12 @@ impl BasicBlockBuilder<'_> { let new_len = self.ops.len() + n; self.ops.resize(new_len, op); } + + /// Converts the advice injector into its corresponding event ID, and adds an `Emit` operation + /// to the list of basic block operations. + pub fn push_advice_injector(&mut self, injector: AdviceInjector) { + self.push_op(Operation::Emit(injector.into_event_id())) + } } /// Decorators @@ -105,11 +111,6 @@ impl BasicBlockBuilder<'_> { Ok(()) } - /// Adds the specified advice injector to the list of basic block decorators. - pub fn push_advice_injector(&mut self, injector: AdviceInjector) -> Result<(), AssemblyError> { - self.push_decorator(Decorator::Advice(injector)) - } - /// Adds an AsmOp decorator to the list of basic block decorators. /// /// This indicates that the provided instruction should be tracked and the cycle count for diff --git a/assembly/src/assembler/instruction/adv_ops.rs b/assembly/src/assembler/instruction/adv_ops.rs index 86a38c1fa8..049ace4e41 100644 --- a/assembly/src/assembler/instruction/adv_ops.rs +++ b/assembly/src/assembler/instruction/adv_ops.rs @@ -23,9 +23,6 @@ pub fn adv_push(block_builder: &mut BasicBlockBuilder, n: u8) -> Result<(), Asse // ================================================================================================ /// Appends advice injector decorator to the span. -pub fn adv_inject( - block_builder: &mut BasicBlockBuilder, - injector: &AdviceInjectorNode, -) -> Result<(), AssemblyError> { +pub fn adv_inject(block_builder: &mut BasicBlockBuilder, injector: &AdviceInjectorNode) { block_builder.push_advice_injector(injector.into()) } diff --git a/assembly/src/assembler/instruction/crypto_ops.rs b/assembly/src/assembler/instruction/crypto_ops.rs index 54e6a2acff..a89015a06a 100644 --- a/assembly/src/assembler/instruction/crypto_ops.rs +++ b/assembly/src/assembler/instruction/crypto_ops.rs @@ -113,10 +113,10 @@ pub(super) fn hmerge(block_builder: &mut BasicBlockBuilder) { /// - root of the tree, 4 elements. /// /// This operation takes 9 VM cycles. -pub(super) fn mtree_get(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { +pub(super) fn mtree_get(block_builder: &mut BasicBlockBuilder) { // stack: [d, i, R, ...] // pops the value of the node we are looking for from the advice stack - read_mtree_node(block_builder)?; + read_mtree_node(block_builder); #[rustfmt::skip] let ops = [ // verify the node V for root R with depth d and index i @@ -128,8 +128,6 @@ pub(super) fn mtree_get(block_builder: &mut BasicBlockBuilder) -> Result<(), Ass MovUp4, Drop, MovUp4, Drop, ]; block_builder.push_ops(ops); - - Ok(()) } /// Appends the MRUPDATE op with a parameter of "false" and stack manipulations to the span block @@ -165,18 +163,16 @@ pub(super) fn mtree_set(block_builder: &mut BasicBlockBuilder) -> Result<(), Ass /// It is not checked whether the provided roots exist as Merkle trees in the advide providers. /// /// This operation takes 16 VM cycles. -pub(super) fn mtree_merge(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { +pub(super) fn mtree_merge(block_builder: &mut BasicBlockBuilder) { // stack input: [R_rhs, R_lhs, ...] // stack output: [R_merged, ...] // invoke the advice provider function to merge 2 Merkle trees defined by the roots on the top // of the operand stack - block_builder.push_advice_injector(AdviceInjector::MerkleNodeMerge)?; + block_builder.push_advice_injector(AdviceInjector::MerkleNodeMerge); // perform the `hmerge`, updating the operand stack - hmerge(block_builder); - - Ok(()) + hmerge(block_builder) } // MERKLE TREES - HELPERS @@ -199,20 +195,18 @@ pub(super) fn mtree_merge(block_builder: &mut BasicBlockBuilder) -> Result<(), A /// - new value of the node, 4 elements (only in the case of mtree_set) /// /// This operation takes 4 VM cycles. -fn read_mtree_node(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { +fn read_mtree_node(block_builder: &mut BasicBlockBuilder) { // The stack should be arranged in the following way: [d, i, R, ...] so that the decorator // can fetch the node value from the root. In the `mtree.get` operation we have the stack in // the following format: [d, i, R], whereas in the case of `mtree.set` we would also have the // new node value post the tree root: [d, i, R, V_new] // // pops the value of the node we are looking for from the advice stack - block_builder.push_advice_injector(AdviceInjector::MerkleNodeToStack)?; + block_builder.push_advice_injector(AdviceInjector::MerkleNodeToStack); // pops the old node value from advice the stack => MPVERIFY: [V_old, d, i, R, ...] // MRUPDATE: [V_old, d, i, R, V_new, ...] block_builder.push_op_many(AdvPop, 4); - - Ok(()) } /// Update a node in the merkle tree. This operation will always copy the tree into a new instance, @@ -225,7 +219,7 @@ fn update_mtree(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyErr // Inject the old node value onto the stack for the call to MRUPDATE. // stack: [V_old, d, i, R_old, V_new, ...] (4 cycles) - read_mtree_node(block_builder)?; + read_mtree_node(block_builder); #[rustfmt::skip] let ops = [ diff --git a/assembly/src/assembler/instruction/ext2_ops.rs b/assembly/src/assembler/instruction/ext2_ops.rs index 6cc3ae88a4..b2729553e4 100644 --- a/assembly/src/assembler/instruction/ext2_ops.rs +++ b/assembly/src/assembler/instruction/ext2_ops.rs @@ -53,8 +53,8 @@ pub fn ext2_mul(block_builder: &mut BasicBlockBuilder) { /// operations outputs the result c = (c1, c0) where c = a * b^-1. /// /// This operation takes 11 VM cycles. -pub fn ext2_div(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(Ext2Inv)?; +pub fn ext2_div(block_builder: &mut BasicBlockBuilder) { + block_builder.push_advice_injector(Ext2Inv); #[rustfmt::skip] let ops = [ AdvPop, // [b0', b1, b0, a1, a0, ...] @@ -70,8 +70,6 @@ pub fn ext2_div(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyErr Drop // [a1*b1', a0*b0'...] ]; block_builder.push_ops(ops); - - Ok(()) } /// Given a stack with initial configuration given by [a1, a0, ...] where a = (a0, a1) represents @@ -116,7 +114,7 @@ pub fn ext2_neg(block_builder: &mut BasicBlockBuilder) { /// /// This operation takes 8 VM cycles. pub fn ext2_inv(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(Ext2Inv)?; + block_builder.push_advice_injector(Ext2Inv); #[rustfmt::skip] let ops = [ AdvPop, // [a0', a1, a0, ...] diff --git a/assembly/src/assembler/instruction/field_ops.rs b/assembly/src/assembler/instruction/field_ops.rs index 74063bb489..7033a92eff 100644 --- a/assembly/src/assembler/instruction/field_ops.rs +++ b/assembly/src/assembler/instruction/field_ops.rs @@ -259,8 +259,8 @@ fn perform_exp_for_small_power(span_builder: &mut BasicBlockBuilder, pow: u64) { /// /// # Errors /// Returns an error if the logarithm argument (top stack element) equals ZERO. -pub fn ilog2(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(AdviceInjector::ILog2)?; +pub fn ilog2(block_builder: &mut BasicBlockBuilder) { + block_builder.push_advice_injector(AdviceInjector::ILog2); block_builder.push_op(AdvPop); // [ilog2, n, ...] // compute the power-of-two for the value given in the advice tape (17 cycles) @@ -290,8 +290,6 @@ pub fn ilog2(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> ]; block_builder.push_ops(ops); - - Ok(()) } // COMPARISON OPERATIONS diff --git a/assembly/src/assembler/instruction/mod.rs b/assembly/src/assembler/instruction/mod.rs index 49e8bec164..76de7147ce 100644 --- a/assembly/src/assembler/instruction/mod.rs +++ b/assembly/src/assembler/instruction/mod.rs @@ -89,7 +89,7 @@ impl Assembler { Instruction::ExpBitLength(num_pow_bits) => { field_ops::exp(block_builder, *num_pow_bits)? }, - Instruction::ILog2 => field_ops::ilog2(block_builder)?, + Instruction::ILog2 => field_ops::ilog2(block_builder), Instruction::Not => block_builder.push_op(Not), Instruction::And => block_builder.push_op(And), @@ -111,7 +111,7 @@ impl Assembler { Instruction::Ext2Add => ext2_ops::ext2_add(block_builder), Instruction::Ext2Sub => ext2_ops::ext2_sub(block_builder), Instruction::Ext2Mul => ext2_ops::ext2_mul(block_builder), - Instruction::Ext2Div => ext2_ops::ext2_div(block_builder)?, + Instruction::Ext2Div => ext2_ops::ext2_div(block_builder), Instruction::Ext2Neg => ext2_ops::ext2_neg(block_builder), Instruction::Ext2Inv => ext2_ops::ext2_inv(block_builder)?, @@ -190,10 +190,10 @@ impl Assembler { Instruction::U32Rotr => u32_ops::u32rotr(block_builder, None)?, Instruction::U32RotrImm(v) => u32_ops::u32rotr(block_builder, Some(v.expect_value()))?, Instruction::U32Popcnt => u32_ops::u32popcnt(block_builder), - Instruction::U32Clz => u32_ops::u32clz(block_builder)?, - Instruction::U32Ctz => u32_ops::u32ctz(block_builder)?, - Instruction::U32Clo => u32_ops::u32clo(block_builder)?, - Instruction::U32Cto => u32_ops::u32cto(block_builder)?, + Instruction::U32Clz => u32_ops::u32clz(block_builder), + Instruction::U32Ctz => u32_ops::u32ctz(block_builder), + Instruction::U32Clo => u32_ops::u32clo(block_builder), + Instruction::U32Cto => u32_ops::u32cto(block_builder), Instruction::U32Lt => u32_ops::u32lt(block_builder), Instruction::U32Lte => u32_ops::u32lte(block_builder), Instruction::U32Gt => u32_ops::u32gt(block_builder), @@ -361,15 +361,15 @@ impl Assembler { false, )?, - Instruction::AdvInject(injector) => adv_ops::adv_inject(block_builder, injector)?, + Instruction::AdvInject(injector) => adv_ops::adv_inject(block_builder, injector), // ----- cryptographic instructions --------------------------------------------------- Instruction::Hash => crypto_ops::hash(block_builder), Instruction::HPerm => block_builder.push_op(HPerm), Instruction::HMerge => crypto_ops::hmerge(block_builder), - Instruction::MTreeGet => crypto_ops::mtree_get(block_builder)?, + Instruction::MTreeGet => crypto_ops::mtree_get(block_builder), Instruction::MTreeSet => crypto_ops::mtree_set(block_builder)?, - Instruction::MTreeMerge => crypto_ops::mtree_merge(block_builder)?, + Instruction::MTreeMerge => crypto_ops::mtree_merge(block_builder), Instruction::MTreeVerify => block_builder.push_op(MpVerify(0)), Instruction::MTreeVerifyWithError(err_code) => { block_builder.push_op(MpVerify(err_code.expect_value())) diff --git a/assembly/src/assembler/instruction/u32_ops.rs b/assembly/src/assembler/instruction/u32_ops.rs index 8826d05939..23801be09a 100644 --- a/assembly/src/assembler/instruction/u32_ops.rs +++ b/assembly/src/assembler/instruction/u32_ops.rs @@ -298,12 +298,11 @@ pub fn u32popcnt(span_builder: &mut BasicBlockBuilder) { /// provider). /// /// This operation takes 42 VM cycles. -pub fn u32clz(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(AdviceInjector::U32Clz)?; +pub fn u32clz(block_builder: &mut BasicBlockBuilder) { + block_builder.push_advice_injector(AdviceInjector::U32Clz); block_builder.push_op(AdvPop); // [clz, n, ...] verify_clz(block_builder); - Ok(()) } /// Translates `u32ctz` assembly instruction to VM operations. `u32ctz` counts the number of @@ -311,12 +310,11 @@ pub fn u32clz(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError /// provider). /// /// This operation takes 34 VM cycles. -pub fn u32ctz(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(AdviceInjector::U32Ctz)?; +pub fn u32ctz(block_builder: &mut BasicBlockBuilder) { + block_builder.push_advice_injector(AdviceInjector::U32Ctz); block_builder.push_op(AdvPop); // [ctz, n, ...] verify_ctz(block_builder); - Ok(()) } /// Translates `u32clo` assembly instruction to VM operations. `u32clo` counts the number of @@ -324,12 +322,11 @@ pub fn u32ctz(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError /// provider). /// /// This operation takes 41 VM cycles. -pub fn u32clo(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(AdviceInjector::U32Clo)?; +pub fn u32clo(block_builder: &mut BasicBlockBuilder) { + block_builder.push_advice_injector(AdviceInjector::U32Clo); block_builder.push_op(AdvPop); // [clo, n, ...] verify_clo(block_builder); - Ok(()) } /// Translates `u32cto` assembly instruction to VM operations. `u32cto` counts the number of @@ -337,12 +334,11 @@ pub fn u32clo(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError /// provider). /// /// This operation takes 33 VM cycles. -pub fn u32cto(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(AdviceInjector::U32Cto)?; +pub fn u32cto(block_builder: &mut BasicBlockBuilder) { + block_builder.push_advice_injector(AdviceInjector::U32Cto); block_builder.push_op(AdvPop); // [cto, n, ...] verify_cto(block_builder); - Ok(()) } /// Specifically handles these specific inputs per the spec. diff --git a/core/src/mast/serialization/decorator.rs b/core/src/mast/serialization/decorator.rs index 5815b988cf..f4e2f31a06 100644 --- a/core/src/mast/serialization/decorator.rs +++ b/core/src/mast/serialization/decorator.rs @@ -10,7 +10,7 @@ use super::{ string_table::{StringTable, StringTableBuilder}, DecoratorDataOffset, }; -use crate::{AdviceInjector, AssemblyOp, DebugOptions, Decorator}; +use crate::{AssemblyOp, DebugOptions, Decorator}; /// Represents a serialized [`Decorator`]. /// @@ -47,63 +47,6 @@ impl DecoratorInfo { let mut data_reader = SliceReader::new(&decorator_data[self.decorator_data_offset as usize..]); match self.variant { - EncodedDecoratorVariant::AdviceInjectorMerkleNodeMerge => { - Ok(Decorator::Advice(AdviceInjector::MerkleNodeMerge)) - }, - EncodedDecoratorVariant::AdviceInjectorMerkleNodeToStack => { - Ok(Decorator::Advice(AdviceInjector::MerkleNodeToStack)) - }, - EncodedDecoratorVariant::AdviceInjectorUpdateMerkleNode => { - Ok(Decorator::Advice(AdviceInjector::UpdateMerkleNode)) - }, - EncodedDecoratorVariant::AdviceInjectorMapValueToStack => { - Ok(Decorator::Advice(AdviceInjector::MapValueToStack)) - }, - EncodedDecoratorVariant::AdviceInjectorMapValueToStackN => { - Ok(Decorator::Advice(AdviceInjector::MapValueToStackN)) - }, - EncodedDecoratorVariant::AdviceInjectorU64Div => { - Ok(Decorator::Advice(AdviceInjector::U64Div)) - }, - EncodedDecoratorVariant::AdviceInjectorExt2Inv => { - Ok(Decorator::Advice(AdviceInjector::Ext2Inv)) - }, - EncodedDecoratorVariant::AdviceInjectorExt2Intt => { - Ok(Decorator::Advice(AdviceInjector::Ext2Intt)) - }, - EncodedDecoratorVariant::AdviceInjectorSmtPeek => { - Ok(Decorator::Advice(AdviceInjector::SmtPeek)) - }, - EncodedDecoratorVariant::AdviceInjectorU32Clz => { - Ok(Decorator::Advice(AdviceInjector::U32Clz)) - }, - EncodedDecoratorVariant::AdviceInjectorU32Ctz => { - Ok(Decorator::Advice(AdviceInjector::U32Ctz)) - }, - EncodedDecoratorVariant::AdviceInjectorU32Clo => { - Ok(Decorator::Advice(AdviceInjector::U32Clo)) - }, - EncodedDecoratorVariant::AdviceInjectorU32Cto => { - Ok(Decorator::Advice(AdviceInjector::U32Cto)) - }, - EncodedDecoratorVariant::AdviceInjectorILog2 => { - Ok(Decorator::Advice(AdviceInjector::ILog2)) - }, - EncodedDecoratorVariant::AdviceInjectorMemToMap => { - Ok(Decorator::Advice(AdviceInjector::MemToMap)) - }, - EncodedDecoratorVariant::AdviceInjectorHdwordToMap => { - Ok(Decorator::Advice(AdviceInjector::HdwordToMap)) - }, - EncodedDecoratorVariant::AdviceInjectorHdwordToMapWithDomain => { - Ok(Decorator::Advice(AdviceInjector::HdwordToMapWithDomain)) - }, - EncodedDecoratorVariant::AdviceInjectorHpermToMap => { - Ok(Decorator::Advice(AdviceInjector::HpermToMap)) - }, - EncodedDecoratorVariant::AdviceInjectorFalconSigToStack => { - Ok(Decorator::Advice(AdviceInjector::FalconSigToStack)) - }, EncodedDecoratorVariant::AssemblyOp => { let num_cycles = data_reader.read_u8()?; let should_break = data_reader.read_bool()?; @@ -202,25 +145,6 @@ impl Deserializable for DecoratorInfo { #[derive(Debug, FromPrimitive, ToPrimitive)] #[repr(u8)] pub enum EncodedDecoratorVariant { - AdviceInjectorMerkleNodeMerge, - AdviceInjectorMerkleNodeToStack, - AdviceInjectorUpdateMerkleNode, - AdviceInjectorMapValueToStack, - AdviceInjectorMapValueToStackN, - AdviceInjectorU64Div, - AdviceInjectorExt2Inv, - AdviceInjectorExt2Intt, - AdviceInjectorSmtPeek, - AdviceInjectorU32Clz, - AdviceInjectorU32Ctz, - AdviceInjectorU32Clo, - AdviceInjectorU32Cto, - AdviceInjectorILog2, - AdviceInjectorMemToMap, - AdviceInjectorHdwordToMap, - AdviceInjectorHdwordToMapWithDomain, - AdviceInjectorHpermToMap, - AdviceInjectorFalconSigToStack, AssemblyOp, DebugOptionsStackAll, DebugOptionsStackTop, @@ -248,27 +172,6 @@ impl EncodedDecoratorVariant { impl From<&Decorator> for EncodedDecoratorVariant { fn from(decorator: &Decorator) -> Self { match decorator { - Decorator::Advice(advice_injector) => match advice_injector { - AdviceInjector::MerkleNodeMerge => Self::AdviceInjectorMerkleNodeMerge, - AdviceInjector::MerkleNodeToStack => Self::AdviceInjectorMerkleNodeToStack, - AdviceInjector::UpdateMerkleNode => Self::AdviceInjectorUpdateMerkleNode, - AdviceInjector::MapValueToStack => Self::AdviceInjectorMapValueToStack, - AdviceInjector::MapValueToStackN => Self::AdviceInjectorMapValueToStackN, - AdviceInjector::U64Div => Self::AdviceInjectorU64Div, - AdviceInjector::Ext2Inv => Self::AdviceInjectorExt2Inv, - AdviceInjector::Ext2Intt => Self::AdviceInjectorExt2Intt, - AdviceInjector::SmtPeek => Self::AdviceInjectorSmtPeek, - AdviceInjector::U32Clz => Self::AdviceInjectorU32Clz, - AdviceInjector::U32Ctz => Self::AdviceInjectorU32Ctz, - AdviceInjector::U32Clo => Self::AdviceInjectorU32Clo, - AdviceInjector::U32Cto => Self::AdviceInjectorU32Cto, - AdviceInjector::ILog2 => Self::AdviceInjectorILog2, - AdviceInjector::MemToMap => Self::AdviceInjectorMemToMap, - AdviceInjector::HdwordToMap => Self::AdviceInjectorHdwordToMap, - AdviceInjector::HdwordToMapWithDomain => Self::AdviceInjectorHdwordToMapWithDomain, - AdviceInjector::HpermToMap => Self::AdviceInjectorHpermToMap, - AdviceInjector::FalconSigToStack => Self::AdviceInjectorFalconSigToStack, - }, Decorator::AsmOp(_) => Self::AssemblyOp, Decorator::Debug(debug_options) => match debug_options { DebugOptions::StackAll => Self::DebugOptionsStackAll, @@ -328,7 +231,6 @@ impl DecoratorDataBuilder { let data_offset = self.decorator_data.len() as DecoratorDataOffset; match decorator { - Decorator::Advice(_) => None, Decorator::AsmOp(assembly_op) => { self.decorator_data.push(assembly_op.num_cycles()); self.decorator_data.write_bool(assembly_op.should_break()); diff --git a/core/src/mast/serialization/tests.rs b/core/src/mast/serialization/tests.rs index cf02a6eaf1..4962f99599 100644 --- a/core/src/mast/serialization/tests.rs +++ b/core/src/mast/serialization/tests.rs @@ -3,10 +3,7 @@ use alloc::{string::ToString, sync::Arc}; use miden_crypto::{hash::rpo::RpoDigest, Felt, ONE}; use super::*; -use crate::{ - mast::MastForestError, operations::Operation, AdviceInjector, AssemblyOp, DebugOptions, - Decorator, -}; +use crate::{mast::MastForestError, operations::Operation, AssemblyOp, DebugOptions, Decorator}; /// If this test fails to compile, it means that `Operation` or `Decorator` was changed. Make sure /// that all tests in this file are updated accordingly. For example, if a new `Operation` variant @@ -109,27 +106,6 @@ fn confirm_operation_and_decorator_structure() { }; match Decorator::Trace(0) { - Decorator::Advice(advice) => match advice { - AdviceInjector::MerkleNodeMerge => (), - AdviceInjector::MerkleNodeToStack => (), - AdviceInjector::UpdateMerkleNode => (), - AdviceInjector::MapValueToStack => (), - AdviceInjector::MapValueToStackN => (), - AdviceInjector::U64Div => (), - AdviceInjector::Ext2Inv => (), - AdviceInjector::Ext2Intt => (), - AdviceInjector::SmtPeek => (), - AdviceInjector::U32Clz => (), - AdviceInjector::U32Ctz => (), - AdviceInjector::U32Clo => (), - AdviceInjector::U32Cto => (), - AdviceInjector::ILog2 => (), - AdviceInjector::MemToMap => (), - AdviceInjector::HdwordToMap => (), - AdviceInjector::HdwordToMapWithDomain => (), - AdviceInjector::HpermToMap => (), - AdviceInjector::FalconSigToStack => (), - }, Decorator::AsmOp(_) => (), Decorator::Debug(debug_options) => match debug_options { DebugOptions::StackAll => (), @@ -243,27 +219,8 @@ fn serialize_deserialize_all_nodes() { let num_operations = operations.len(); let decorators = vec![ - (0, Decorator::Advice(AdviceInjector::MerkleNodeMerge)), - (0, Decorator::Advice(AdviceInjector::MerkleNodeToStack)), - (0, Decorator::Advice(AdviceInjector::UpdateMerkleNode)), - (0, Decorator::Advice(AdviceInjector::MapValueToStack)), - (0, Decorator::Advice(AdviceInjector::MapValueToStackN)), - (1, Decorator::Advice(AdviceInjector::U64Div)), - (3, Decorator::Advice(AdviceInjector::Ext2Inv)), - (5, Decorator::Advice(AdviceInjector::Ext2Intt)), - (5, Decorator::Advice(AdviceInjector::SmtPeek)), - (5, Decorator::Advice(AdviceInjector::U32Clz)), - (10, Decorator::Advice(AdviceInjector::U32Ctz)), - (10, Decorator::Advice(AdviceInjector::U32Clo)), - (10, Decorator::Advice(AdviceInjector::U32Cto)), - (10, Decorator::Advice(AdviceInjector::ILog2)), - (10, Decorator::Advice(AdviceInjector::MemToMap)), - (10, Decorator::Advice(AdviceInjector::HdwordToMap)), - (10, Decorator::Advice(AdviceInjector::HdwordToMapWithDomain)), - (15, Decorator::Advice(AdviceInjector::HpermToMap)), - (15, Decorator::Advice(AdviceInjector::FalconSigToStack)), ( - 15, + 0, Decorator::AsmOp(AssemblyOp::new( Some(crate::debuginfo::Location { path: Arc::from("test"), @@ -276,7 +233,7 @@ fn serialize_deserialize_all_nodes() { false, )), ), - (15, Decorator::Debug(DebugOptions::StackAll)), + (0, Decorator::Debug(DebugOptions::StackAll)), (15, Decorator::Debug(DebugOptions::StackTop(255))), (15, Decorator::Debug(DebugOptions::MemAll)), (15, Decorator::Debug(DebugOptions::MemInterval(0, 16))), diff --git a/core/src/operations/decorators/advice.rs b/core/src/operations/decorators/advice.rs index 4cc60ec4bd..5ce7dceadb 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/operations/decorators/advice.rs @@ -1,10 +1,35 @@ use core::fmt; - use super::SignatureKind; // ADVICE INJECTORS // ================================================================================================ +// Randomly generated constant values for the VM's native events. All values were sampled +// between 0 and 2^32. +pub use constants::*; +#[rustfmt::skip] +mod constants { + pub const EVENT_MERKLE_NODE_MERGE: u32 = 276124218; + pub const EVENT_MERKLE_NODE_TO_STACK: u32 = 361943238; + pub const EVENT_UPDATE_MERKLE_NODE: u32 = 483702215; + pub const EVENT_MAP_VALUE_TO_STACK: u32 = 574478993; + pub const EVENT_MAP_VALUE_TO_STACK_N: u32 = 630847990; + pub const EVENT_U64_DIV: u32 = 678156251; + pub const EVENT_EXT2_INV: u32 = 1251967401; + pub const EVENT_EXT2_INTT: u32 = 1347499010; + pub const EVENT_SMT_PEEK: u32 = 1889584556; + pub const EVENT_U32_CLZ: u32 = 1951932030; + pub const EVENT_U32_CTZ: u32 = 2008979519; + pub const EVENT_U32_CLO: u32 = 2032895094; + pub const EVENT_U32_CTO: u32 = 2083700134; + pub const EVENT_ILOG2: u32 = 2297972669; + pub const EVENT_MEM_TO_MAP: u32 = 2389394361; + pub const EVENT_HDWORD_TO_MAP: u32 = 2391452729; + pub const EVENT_HDWORD_TO_MAP_WITH_DOMAIN: u32 = 2822590340; + pub const EVENT_HPERM_TO_MAP: u32 = 3297060969; + pub const EVENT_FALCON_SIG_TO_STACK: u32 = 3419226139; +} + /// Defines a set of actions which can be initiated from the VM to inject new data into the advice /// provider. /// @@ -292,6 +317,59 @@ pub enum AdviceInjector { FalconSigToStack, } +impl AdviceInjector { + pub fn into_event_id(self) -> u32 { + match self { + AdviceInjector::MerkleNodeMerge => EVENT_MERKLE_NODE_MERGE, + AdviceInjector::MerkleNodeToStack => EVENT_MERKLE_NODE_TO_STACK, + AdviceInjector::UpdateMerkleNode => EVENT_UPDATE_MERKLE_NODE, + AdviceInjector::MapValueToStack => EVENT_MAP_VALUE_TO_STACK, + AdviceInjector::MapValueToStackN => EVENT_MAP_VALUE_TO_STACK_N, + AdviceInjector::U64Div => EVENT_U64_DIV, + AdviceInjector::Ext2Inv => EVENT_EXT2_INV, + AdviceInjector::Ext2Intt => EVENT_EXT2_INTT, + AdviceInjector::SmtPeek => EVENT_SMT_PEEK, + AdviceInjector::U32Clz => EVENT_U32_CLZ, + AdviceInjector::U32Ctz => EVENT_U32_CTZ, + AdviceInjector::U32Clo => EVENT_U32_CLO, + AdviceInjector::U32Cto => EVENT_U32_CTO, + AdviceInjector::ILog2 => EVENT_ILOG2, + AdviceInjector::MemToMap => EVENT_MEM_TO_MAP, + AdviceInjector::HdwordToMap => EVENT_HDWORD_TO_MAP, + AdviceInjector::HdwordToMapWithDomain => EVENT_HDWORD_TO_MAP_WITH_DOMAIN, + AdviceInjector::HpermToMap => EVENT_HPERM_TO_MAP, + AdviceInjector::FalconSigToStack => EVENT_FALCON_SIG_TO_STACK, + } + } + + /// Returns an advice injector corresponding to the specified event ID, or `None` if the event + /// ID is not recognized. + pub fn from_event_id(event_id: u32) -> Option { + match event_id { + EVENT_MERKLE_NODE_MERGE => Some(AdviceInjector::MerkleNodeMerge), + EVENT_MERKLE_NODE_TO_STACK => Some(AdviceInjector::MerkleNodeToStack), + EVENT_UPDATE_MERKLE_NODE => Some(AdviceInjector::UpdateMerkleNode), + EVENT_MAP_VALUE_TO_STACK => Some(AdviceInjector::MapValueToStack), + EVENT_MAP_VALUE_TO_STACK_N => Some(AdviceInjector::MapValueToStackN), + EVENT_U64_DIV => Some(AdviceInjector::U64Div), + EVENT_EXT2_INV => Some(AdviceInjector::Ext2Inv), + EVENT_EXT2_INTT => Some(AdviceInjector::Ext2Intt), + EVENT_SMT_PEEK => Some(AdviceInjector::SmtPeek), + EVENT_U32_CLZ => Some(AdviceInjector::U32Clz), + EVENT_U32_CTZ => Some(AdviceInjector::U32Ctz), + EVENT_U32_CLO => Some(AdviceInjector::U32Clo), + EVENT_U32_CTO => Some(AdviceInjector::U32Cto), + EVENT_ILOG2 => Some(AdviceInjector::ILog2), + EVENT_MEM_TO_MAP => Some(AdviceInjector::MemToMap), + EVENT_HDWORD_TO_MAP => Some(AdviceInjector::HdwordToMap), + EVENT_HDWORD_TO_MAP_WITH_DOMAIN => Some(AdviceInjector::HdwordToMapWithDomain), + EVENT_HPERM_TO_MAP => Some(AdviceInjector::HpermToMap), + EVENT_FALCON_SIG_TO_STACK => Some(AdviceInjector::FalconSigToStack), + _ => None, + } + } +} + impl crate::prettier::PrettyPrint for AdviceInjector { fn render(&self) -> crate::prettier::Document { crate::prettier::display(self) diff --git a/core/src/operations/decorators/mod.rs b/core/src/operations/decorators/mod.rs index 02e054eb0a..9a12789e15 100644 --- a/core/src/operations/decorators/mod.rs +++ b/core/src/operations/decorators/mod.rs @@ -21,14 +21,12 @@ use crate::mast::{DecoratorFingerprint, DecoratorId}; /// A set of decorators which can be executed by the VM. /// /// Executing a decorator does not affect the state of the main VM components such as operand stack -/// and memory. However, decorators may modify the advice provider. +/// and memory. /// /// Executing decorators does not advance the VM clock. As such, many decorators can be executed in /// a single VM cycle. #[derive(Clone, Debug, Eq, PartialEq)] pub enum Decorator { - /// Injects new data into the advice provider, as specified by the injector. - Advice(AdviceInjector), /// Adds information about the assembly instruction at a particular index (only applicable in /// debug mode). AsmOp(AssemblyOp), @@ -42,7 +40,6 @@ pub enum Decorator { impl Decorator { pub fn fingerprint(&self) -> DecoratorFingerprint { match self { - Self::Advice(advice) => Blake3_256::hash(advice.to_string().as_bytes()), Self::AsmOp(asm_op) => { let mut bytes_to_hash = Vec::new(); if let Some(location) = asm_op.location() { @@ -72,7 +69,6 @@ impl crate::prettier::PrettyPrint for Decorator { impl fmt::Display for Decorator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Advice(injector) => write!(f, "advice({injector})"), Self::AsmOp(assembly_op) => { write!(f, "asmOp({}, {})", assembly_op.op(), assembly_op.num_cycles()) }, diff --git a/processor/src/lib.rs b/processor/src/lib.rs index ce6a15bc33..76a07dd5ca 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -26,7 +26,7 @@ use vm_core::{ mast::{ BasicBlockNode, CallNode, DynNode, JoinNode, LoopNode, OpBatch, SplitNode, OP_GROUP_SIZE, }, - Decorator, DecoratorIterator, FieldElement, SignatureKind, + Decorator, DecoratorIterator, FieldElement, }; pub use winter_prover::matrix::ColMatrix; @@ -593,60 +593,6 @@ impl Process { host: &mut impl Host, ) -> Result<(), ExecutionError> { match decorator { - Decorator::Advice(injector) => { - let advice_provider = host.advice_provider_mut(); - let process_state: ProcessState = (&*self).into(); - match injector { - AdviceInjector::MerkleNodeMerge => { - advice_provider.merge_merkle_nodes(process_state)?; - }, - AdviceInjector::MerkleNodeToStack => { - advice_provider.copy_merkle_node_to_adv_stack(process_state)? - }, - AdviceInjector::MapValueToStack => { - advice_provider.copy_map_value_to_adv_stack(process_state, false, 0)? - }, - AdviceInjector::MapValueToStackN => { - advice_provider.copy_map_value_to_adv_stack(process_state, true, 0)? - }, - AdviceInjector::UpdateMerkleNode => { - let _ = advice_provider.update_operand_stack_merkle_node(process_state)?; - }, - AdviceInjector::U64Div => advice_provider.push_u64_div_result(process_state)?, - AdviceInjector::Ext2Inv => { - advice_provider.push_ext2_inv_result(process_state)? - }, - AdviceInjector::Ext2Intt => { - advice_provider.push_ext2_intt_result(process_state)? - }, - AdviceInjector::SmtPeek => { - advice_provider.push_smtpeek_result(process_state)? - }, - AdviceInjector::U32Clz => advice_provider.push_leading_zeros(process_state)?, - AdviceInjector::U32Ctz => advice_provider.push_trailing_zeros(process_state)?, - AdviceInjector::U32Clo => advice_provider.push_leading_ones(process_state)?, - AdviceInjector::U32Cto => advice_provider.push_trailing_ones(process_state)?, - AdviceInjector::ILog2 => advice_provider.push_ilog2(process_state)?, - - AdviceInjector::MemToMap => { - advice_provider.insert_mem_values_into_adv_map(process_state)?; - }, - AdviceInjector::HdwordToMap => { - advice_provider.insert_hdword_into_adv_map(process_state, ZERO)?; - }, - AdviceInjector::HdwordToMapWithDomain => { - // TODO(plafer): get domain from stack - let domain = self.stack.get(8); - advice_provider.insert_hdword_into_adv_map(process_state, domain)?; - todo!() - }, - AdviceInjector::HpermToMap => { - advice_provider.insert_hperm_into_adv_map(process_state)?; - }, - AdviceInjector::FalconSigToStack => advice_provider - .push_signature(process_state, SignatureKind::RpoFalcon512)?, - } - }, Decorator::Debug(options) => { if self.decoder.in_debug_mode() { host.on_debug(self.into(), options)?; diff --git a/processor/src/operations/sys_ops.rs b/processor/src/operations/sys_ops.rs index 1a198da3b0..f4e70b5f7e 100644 --- a/processor/src/operations/sys_ops.rs +++ b/processor/src/operations/sys_ops.rs @@ -1,4 +1,4 @@ -use vm_core::{Felt, Operation}; +use vm_core::{AdviceInjector, Felt, Operation, SignatureKind, ZERO}; use super::{ super::{ @@ -7,7 +7,7 @@ use super::{ }, ExecutionError, Process, }; -use crate::Host; +use crate::{AdviceProvider, Host, ProcessState}; // SYSTEM OPERATIONS // ================================================================================================ @@ -122,9 +122,62 @@ impl Process { { self.stack.copy_state(0); self.decoder.set_user_op_helpers(Operation::Emit(event_id), &[event_id.into()]); - host.on_event(self.into(), event_id)?; - Ok(()) + // If it's a native event, handle it directly. Otherwise, forward it to the host. + if let Some(advice_injector) = AdviceInjector::from_event_id(event_id) { + self.handle_advice_injector(advice_injector, host) + } else { + host.on_event(self.into(), event_id) + } + } + + fn handle_advice_injector( + &self, + advice_injector: AdviceInjector, + host: &mut impl Host, + ) -> Result<(), ExecutionError> { + let advice_provider = host.advice_provider_mut(); + let process_state: ProcessState = self.into(); + match advice_injector { + AdviceInjector::MerkleNodeMerge => advice_provider.merge_merkle_nodes(process_state), + AdviceInjector::MerkleNodeToStack => { + advice_provider.copy_merkle_node_to_adv_stack(process_state) + }, + AdviceInjector::MapValueToStack => { + advice_provider.copy_map_value_to_adv_stack(process_state, false) + }, + AdviceInjector::MapValueToStackN => { + advice_provider.copy_map_value_to_adv_stack(process_state, true) + }, + AdviceInjector::UpdateMerkleNode => { + let _ = advice_provider.update_operand_stack_merkle_node(process_state)?; + Ok(()) + }, + AdviceInjector::U64Div => advice_provider.push_u64_div_result(process_state), + AdviceInjector::Ext2Inv => advice_provider.push_ext2_inv_result(process_state), + AdviceInjector::Ext2Intt => advice_provider.push_ext2_intt_result(process_state), + AdviceInjector::SmtPeek => advice_provider.push_smtpeek_result(process_state), + AdviceInjector::U32Clz => advice_provider.push_leading_zeros(process_state), + AdviceInjector::U32Ctz => advice_provider.push_trailing_zeros(process_state), + AdviceInjector::U32Clo => advice_provider.push_leading_ones(process_state), + AdviceInjector::U32Cto => advice_provider.push_trailing_ones(process_state), + AdviceInjector::ILog2 => advice_provider.push_ilog2(process_state), + + AdviceInjector::MemToMap => { + advice_provider.insert_mem_values_into_adv_map(process_state) + }, + AdviceInjector::HdwordToMap => { + advice_provider.insert_hdword_into_adv_map(process_state, ZERO) + }, + AdviceInjector::HdwordToMapWithDomain => { + let domain = self.stack.get(8); + advice_provider.insert_hdword_into_adv_map(process_state, domain) + }, + AdviceInjector::HpermToMap => advice_provider.insert_hperm_into_adv_map(process_state), + AdviceInjector::FalconSigToStack => { + advice_provider.push_signature(process_state, SignatureKind::RpoFalcon512) + }, + } } } From 596e14f4389e62b98f017678459f8509401df7ec Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Thu, 21 Nov 2024 08:47:16 -0500 Subject: [PATCH 18/39] refactor: remove default methods from `AdviceProvider` trait --- processor/src/host/advice/mod.rs | 1230 +++++++++++------------- processor/src/operations/crypto_ops.rs | 10 +- processor/src/operations/sys_ops.rs | 49 +- 3 files changed, 604 insertions(+), 685 deletions(-) diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index f6f548b07c..331714f9bf 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -171,727 +171,635 @@ pub trait AdviceProvider: Sized { /// It is not checked whether a Merkle tree for either of the specified roots can be found in /// this advice provider. fn merge_roots(&mut self, lhs: Word, rhs: Word) -> Result; +} - // PROVIDED METHODS - // -------------------------------------------------------------------------------------------- - - // DEFAULT ADVICE MAP INJECTORS - // -------------------------------------------------------------------------------------------- - - /// Reads words from memory at the specified range and inserts them into the advice map under - /// the key `KEY` located at the top of the stack. - /// - /// Inputs: - /// Operand stack: [KEY, start_addr, end_addr, ...] - /// Advice map: {...} - /// - /// Outputs: - /// Operand stack: [KEY, start_addr, end_addr, ...] - /// Advice map: {KEY: values} - /// - /// Where `values` are the elements located in memory[start_addr..end_addr]. - /// - /// # Errors - /// Returns an error: - /// - `start_addr` is greater than or equal to 2^32. - /// - `end_addr` is greater than or equal to 2^32. - /// - `start_addr` > `end_addr`. - fn insert_mem_values_into_adv_map( - &mut self, - process: ProcessState, - ) -> Result<(), ExecutionError> { - let (start_addr, end_addr) = get_mem_addr_range(process, 4, 5)?; - let ctx = process.ctx(); - - let mut values = Vec::with_capacity(((end_addr - start_addr) as usize) * WORD_SIZE); - for addr in start_addr..end_addr { - let mem_value = process.get_mem_value(ctx, addr).unwrap_or(EMPTY_WORD); - values.extend_from_slice(&mem_value); - } +// NATIVE EVENTS +// =============================================================================================== - let key = process.get_stack_word(0); - self.insert_into_map(key, values); +/// Reads words from memory at the specified range and inserts them into the advice map under +/// the key `KEY` located at the top of the stack. +/// +/// Inputs: +/// Operand stack: [KEY, start_addr, end_addr, ...] +/// Advice map: {...} +/// +/// Outputs: +/// Operand stack: [KEY, start_addr, end_addr, ...] +/// Advice map: {KEY: values} +/// +/// Where `values` are the elements located in memory[start_addr..end_addr]. +/// +/// # Errors +/// Returns an error: +/// - `start_addr` is greater than or equal to 2^32. +/// - `end_addr` is greater than or equal to 2^32. +/// - `start_addr` > `end_addr`. +pub fn insert_mem_values_into_adv_map( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let (start_addr, end_addr) = get_mem_addr_range(process, 4, 5)?; + let ctx = process.ctx(); - Ok(()) + let mut values = Vec::with_capacity(((end_addr - start_addr) as usize) * WORD_SIZE); + for addr in start_addr..end_addr { + let mem_value = process.get_mem_value(ctx, addr).unwrap_or(EMPTY_WORD); + values.extend_from_slice(&mem_value); } - /// Reads two word from the operand stack and inserts them into the advice map under the key - /// defined by the hash of these words. - /// - /// Inputs: - /// Operand stack: [B, A, ...] - /// Advice map: {...} - /// - /// Outputs: - /// Operand stack: [B, A, ...] - /// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} - /// - /// Where KEY is computed as hash(A || B, domain), where domain is provided via the immediate - /// value. - fn insert_hdword_into_adv_map( - &mut self, - process: ProcessState, - domain: Felt, - ) -> Result<(), ExecutionError> { - // get the top two words from the stack and hash them to compute the key value - let word0 = process.get_stack_word(0); - let word1 = process.get_stack_word(1); - let key = Rpo256::merge_in_domain(&[word1.into(), word0.into()], domain); - - // build a vector of values from the two word and insert it into the advice map under the - // computed key - let mut values = Vec::with_capacity(2 * WORD_SIZE); - values.extend_from_slice(&word1); - values.extend_from_slice(&word0); - self.insert_into_map(key.into(), values); - - Ok(()) - } + let key = process.get_stack_word(0); + advice_provider.insert_into_map(key, values); - /// Reads three words from the operand stack and inserts the top two words into the advice map - /// under the key defined by applying an RPO permutation to all three words. - /// - /// Inputs: - /// Operand stack: [B, A, C, ...] - /// Advice map: {...} - /// - /// Outputs: - /// Operand stack: [B, A, C, ...] - /// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} - /// - /// Where KEY is computed by extracting the digest elements from hperm([C, A, B]). For example, - /// if C is [0, d, 0, 0], KEY will be set as hash(A || B, d). - fn insert_hperm_into_adv_map(&mut self, process: ProcessState) -> Result<(), ExecutionError> { - // read the state from the stack - let mut state = [ - process.get_stack_item(11), - process.get_stack_item(10), - process.get_stack_item(9), - process.get_stack_item(8), - process.get_stack_item(7), - process.get_stack_item(6), - process.get_stack_item(5), - process.get_stack_item(4), - process.get_stack_item(3), - process.get_stack_item(2), - process.get_stack_item(1), - process.get_stack_item(0), - ]; - - // get the values to be inserted into the advice map from the state - let values = state[Rpo256::RATE_RANGE].to_vec(); - - // apply the permutation to the state and extract the key from it - Rpo256::apply_permutation(&mut state); - let key = RpoDigest::new( - state[Rpo256::DIGEST_RANGE] - .try_into() - .expect("failed to extract digest from state"), - ); - - self.insert_into_map(key.into(), values); - - Ok(()) - } + Ok(()) +} - /// Creates a new Merkle tree in the advice provider by combining Merkle trees with the - /// specified roots. The root of the new tree is defined as `Hash(LEFT_ROOT, RIGHT_ROOT)`. - /// - /// Inputs: - /// Operand stack: [RIGHT_ROOT, LEFT_ROOT, ...] - /// Merkle store: {RIGHT_ROOT, LEFT_ROOT} - /// - /// Outputs: - /// Operand stack: [RIGHT_ROOT, LEFT_ROOT, ...] - /// Merkle store: {RIGHT_ROOT, LEFT_ROOT, hash(LEFT_ROOT, RIGHT_ROOT)} - /// - /// After the operation, both the original trees and the new tree remains in the advice - /// provider (i.e., the input trees are not removed). - /// - /// It is not checked whether the provided roots exist as Merkle trees in the advide providers. - fn merge_merkle_nodes(&mut self, process: ProcessState) -> Result<(), ExecutionError> { - // fetch the arguments from the stack - let lhs = process.get_stack_word(1); - let rhs = process.get_stack_word(0); +/// Reads two word from the operand stack and inserts them into the advice map under the key +/// defined by the hash of these words. +/// +/// Inputs: +/// Operand stack: [B, A, ...] +/// Advice map: {...} +/// +/// Outputs: +/// Operand stack: [B, A, ...] +/// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} +/// +/// Where KEY is computed as hash(A || B, domain), where domain is provided via the immediate +/// value. +pub fn insert_hdword_into_adv_map( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, + domain: Felt, +) -> Result<(), ExecutionError> { + // get the top two words from the stack and hash them to compute the key value + let word0 = process.get_stack_word(0); + let word1 = process.get_stack_word(1); + let key = Rpo256::merge_in_domain(&[word1.into(), word0.into()], domain); + + // build a vector of values from the two word and insert it into the advice map under the + // computed key + let mut values = Vec::with_capacity(2 * WORD_SIZE); + values.extend_from_slice(&word1); + values.extend_from_slice(&word0); + advice_provider.insert_into_map(key.into(), values); - // perform the merge - self.merge_roots(lhs, rhs)?; + Ok(()) +} - Ok(()) - } +/// Reads three words from the operand stack and inserts the top two words into the advice map +/// under the key defined by applying an RPO permutation to all three words. +/// +/// Inputs: +/// Operand stack: [B, A, C, ...] +/// Advice map: {...} +/// +/// Outputs: +/// Operand stack: [B, A, C, ...] +/// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} +/// +/// Where KEY is computed by extracting the digest elements from hperm([C, A, B]). For example, +/// if C is [0, d, 0, 0], KEY will be set as hash(A || B, d). +pub fn insert_hperm_into_adv_map( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + // read the state from the stack + let mut state = [ + process.get_stack_item(11), + process.get_stack_item(10), + process.get_stack_item(9), + process.get_stack_item(8), + process.get_stack_item(7), + process.get_stack_item(6), + process.get_stack_item(5), + process.get_stack_item(4), + process.get_stack_item(3), + process.get_stack_item(2), + process.get_stack_item(1), + process.get_stack_item(0), + ]; + + // get the values to be inserted into the advice map from the state + let values = state[Rpo256::RATE_RANGE].to_vec(); + + // apply the permutation to the state and extract the key from it + Rpo256::apply_permutation(&mut state); + let key = RpoDigest::new( + state[Rpo256::DIGEST_RANGE] + .try_into() + .expect("failed to extract digest from state"), + ); + + advice_provider.insert_into_map(key.into(), values); - // DEFAULT ADVICE STACK INJECTORS - // -------------------------------------------------------------------------------------------- + Ok(()) +} - /// Pushes a node of the Merkle tree specified by the values on the top of the operand stack - /// onto the advice stack. - /// - /// Inputs: - /// Operand stack: [depth, index, TREE_ROOT, ...] - /// Advice stack: [...] - /// Merkle store: {TREE_ROOT<-NODE} - /// - /// Outputs: - /// Operand stack: [depth, index, TREE_ROOT, ...] - /// Advice stack: [NODE, ...] - /// Merkle store: {TREE_ROOT<-NODE} - /// - /// # Errors - /// Returns an error if: - /// - Merkle tree for the specified root cannot be found in the advice provider. - /// - The specified depth is either zero or greater than the depth of the Merkle tree identified - /// by the specified root. - /// - Value of the node at the specified depth and index is not known to the advice provider. - fn copy_merkle_node_to_adv_stack( - &mut self, - process: ProcessState, - ) -> Result<(), ExecutionError> { - let depth = process.get_stack_item(0); - let index = process.get_stack_item(1); - let root = [ - process.get_stack_item(5), - process.get_stack_item(4), - process.get_stack_item(3), - process.get_stack_item(2), - ]; - - let node = self.get_tree_node(root, &depth, &index)?; - - self.push_stack(AdviceSource::Value(node[3]))?; - self.push_stack(AdviceSource::Value(node[2]))?; - self.push_stack(AdviceSource::Value(node[1]))?; - self.push_stack(AdviceSource::Value(node[0]))?; - - Ok(()) - } +/// Creates a new Merkle tree in the advice provider by combining Merkle trees with the +/// specified roots. The root of the new tree is defined as `Hash(LEFT_ROOT, RIGHT_ROOT)`. +/// +/// Inputs: +/// Operand stack: [RIGHT_ROOT, LEFT_ROOT, ...] +/// Merkle store: {RIGHT_ROOT, LEFT_ROOT} +/// +/// Outputs: +/// Operand stack: [RIGHT_ROOT, LEFT_ROOT, ...] +/// Merkle store: {RIGHT_ROOT, LEFT_ROOT, hash(LEFT_ROOT, RIGHT_ROOT)} +/// +/// After the operation, both the original trees and the new tree remains in the advice +/// provider (i.e., the input trees are not removed). +/// +/// It is not checked whether the provided roots exist as Merkle trees in the advide providers. +pub fn merge_merkle_nodes( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + // fetch the arguments from the stack + let lhs = process.get_stack_word(1); + let rhs = process.get_stack_word(0); - /// Pushes a list of field elements onto the advice stack. The list is looked up in the advice - /// map using the specified word from the operand stack as the key. If `include_len` is set to - /// true, the number of elements in the value is also pushed onto the advice stack. - /// - /// Inputs: - /// Operand stack: [..., KEY, ...] - /// Advice stack: [...] - /// Advice map: {KEY: values} - /// - /// Outputs: - /// Operand stack: [..., KEY, ...] - /// Advice stack: [values_len?, values, ...] - /// Advice map: {KEY: values} - /// - /// The `key_offset` value specifies the location of the `KEY` on the stack. For example, - /// offset value of 0 indicates that the top word on the stack should be used as the key, the - /// offset value of 4, indicates that the second word on the stack should be used as the key - /// etc. - /// - /// The valid values of `key_offset` are 0 through 12 (inclusive). - /// - /// # Errors - /// Returns an error if the required key was not found in the key-value map or if stack offset - /// is greater than 12. - fn copy_map_value_to_adv_stack( - &mut self, - process: ProcessState, - include_len: bool, - ) -> Result<(), ExecutionError> { - let key = [ - process.get_stack_item(3), - process.get_stack_item(2), - process.get_stack_item(1), - process.get_stack_item(0), - ]; - self.push_stack(AdviceSource::Map { key, include_len })?; - - Ok(()) - } + // perform the merge + advice_provider.merge_roots(lhs, rhs)?; - /// Pushes the result of [u64] division (both the quotient and the remainder) onto the advice - /// stack. - /// - /// Inputs: - /// Operand stack: [b1, b0, a1, a0, ...] - /// Advice stack: [...] - /// - /// Outputs: - /// Operand stack: [b1, b0, a1, a0, ...] - /// Advice stack: [q0, q1, r0, r1, ...] - /// - /// Where (a0, a1) and (b0, b1) are the 32-bit limbs of the dividend and the divisor - /// respectively (with a0 representing the 32 lest significant bits and a1 representing the - /// 32 most significant bits). Similarly, (q0, q1) and (r0, r1) represent the quotient and - /// the remainder respectively. - /// - /// # Errors - /// Returns an error if the divisor is ZERO. - fn push_u64_div_result(&mut self, process: ProcessState) -> Result<(), ExecutionError> { - let divisor_hi = process.get_stack_item(0).as_int(); - let divisor_lo = process.get_stack_item(1).as_int(); - let divisor = (divisor_hi << 32) + divisor_lo; - - if divisor == 0 { - return Err(ExecutionError::DivideByZero(process.clk())); - } + Ok(()) +} - let dividend_hi = process.get_stack_item(2).as_int(); - let dividend_lo = process.get_stack_item(3).as_int(); - let dividend = (dividend_hi << 32) + dividend_lo; +/// Pushes a node of the Merkle tree specified by the values on the top of the operand stack +/// onto the advice stack. +/// +/// Inputs: +/// Operand stack: [depth, index, TREE_ROOT, ...] +/// Advice stack: [...] +/// Merkle store: {TREE_ROOT<-NODE} +/// +/// Outputs: +/// Operand stack: [depth, index, TREE_ROOT, ...] +/// Advice stack: [NODE, ...] +/// Merkle store: {TREE_ROOT<-NODE} +/// +/// # Errors +/// Returns an error if: +/// - Merkle tree for the specified root cannot be found in the advice provider. +/// - The specified depth is either zero or greater than the depth of the Merkle tree identified by +/// the specified root. +/// - Value of the node at the specified depth and index is not known to the advice provider. +pub fn copy_merkle_node_to_adv_stack( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let depth = process.get_stack_item(0); + let index = process.get_stack_item(1); + let root = [ + process.get_stack_item(5), + process.get_stack_item(4), + process.get_stack_item(3), + process.get_stack_item(2), + ]; + + let node = advice_provider.get_tree_node(root, &depth, &index)?; + + advice_provider.push_stack(AdviceSource::Value(node[3]))?; + advice_provider.push_stack(AdviceSource::Value(node[2]))?; + advice_provider.push_stack(AdviceSource::Value(node[1]))?; + advice_provider.push_stack(AdviceSource::Value(node[0]))?; - let quotient = dividend / divisor; - let remainder = dividend - quotient * divisor; + Ok(()) +} - let (q_hi, q_lo) = u64_to_u32_elements(quotient); - let (r_hi, r_lo) = u64_to_u32_elements(remainder); +/// Pushes a list of field elements onto the advice stack. The list is looked up in the advice +/// map using the specified word from the operand stack as the key. If `include_len` is set to +/// true, the number of elements in the value is also pushed onto the advice stack. +/// +/// Inputs: +/// Operand stack: [..., KEY, ...] +/// Advice stack: [...] +/// Advice map: {KEY: values} +/// +/// Outputs: +/// Operand stack: [..., KEY, ...] +/// Advice stack: [values_len?, values, ...] +/// Advice map: {KEY: values} +/// +/// The `key_offset` value specifies the location of the `KEY` on the stack. For example, +/// offset value of 0 indicates that the top word on the stack should be used as the key, the +/// offset value of 4, indicates that the second word on the stack should be used as the key +/// etc. +/// +/// The valid values of `key_offset` are 0 through 12 (inclusive). +/// +/// # Errors +/// Returns an error if the required key was not found in the key-value map or if stack offset +/// is greater than 12. +pub fn copy_map_value_to_adv_stack( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, + include_len: bool, +) -> Result<(), ExecutionError> { + let key = [ + process.get_stack_item(3), + process.get_stack_item(2), + process.get_stack_item(1), + process.get_stack_item(0), + ]; + advice_provider.push_stack(AdviceSource::Map { key, include_len })?; - self.push_stack(AdviceSource::Value(r_hi))?; - self.push_stack(AdviceSource::Value(r_lo))?; - self.push_stack(AdviceSource::Value(q_hi))?; - self.push_stack(AdviceSource::Value(q_lo))?; + Ok(()) +} - Ok(()) - } +/// Pushes the result of [u64] division (both the quotient and the remainder) onto the advice +/// stack. +/// +/// Inputs: +/// Operand stack: [b1, b0, a1, a0, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [b1, b0, a1, a0, ...] +/// Advice stack: [q0, q1, r0, r1, ...] +/// +/// Where (a0, a1) and (b0, b1) are the 32-bit limbs of the dividend and the divisor +/// respectively (with a0 representing the 32 lest significant bits and a1 representing the +/// 32 most significant bits). Similarly, (q0, q1) and (r0, r1) represent the quotient and +/// the remainder respectively. +/// +/// # Errors +/// Returns an error if the divisor is ZERO. +pub fn push_u64_div_result( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let divisor_hi = process.get_stack_item(0).as_int(); + let divisor_lo = process.get_stack_item(1).as_int(); + let divisor = (divisor_hi << 32) + divisor_lo; - /// Given an element in a quadratic extension field on the top of the stack (i.e., a0, b1), - /// computes its multiplicative inverse and push the result onto the advice stack. - /// - /// Inputs: - /// Operand stack: [a1, a0, ...] - /// Advice stack: [...] - /// - /// Outputs: - /// Operand stack: [a1, a0, ...] - /// Advice stack: [b0, b1...] - /// - /// Where (b0, b1) is the multiplicative inverse of the extension field element (a0, a1) at the - /// top of the stack. - /// - /// # Errors - /// Returns an error if the input is a zero element in the extension field. - fn push_ext2_inv_result(&mut self, process: ProcessState) -> Result<(), ExecutionError> { - let coef0 = process.get_stack_item(1); - let coef1 = process.get_stack_item(0); - - let element = QuadFelt::new(coef0, coef1); - if element == QuadFelt::ZERO { - return Err(ExecutionError::DivideByZero(process.clk())); - } - let result = element.inv().to_base_elements(); + if divisor == 0 { + return Err(ExecutionError::DivideByZero(process.clk())); + } - self.push_stack(AdviceSource::Value(result[1]))?; - self.push_stack(AdviceSource::Value(result[0]))?; + let dividend_hi = process.get_stack_item(2).as_int(); + let dividend_lo = process.get_stack_item(3).as_int(); + let dividend = (dividend_hi << 32) + dividend_lo; - Ok(()) - } + let quotient = dividend / divisor; + let remainder = dividend - quotient * divisor; - /// Given evaluations of a polynomial over some specified domain, interpolates the evaluations - /// into a polynomial in coefficient form and pushes the result into the advice stack. - /// - /// The interpolation is performed using the iNTT algorithm. The evaluations are expected to be - /// in the quadratic extension. - /// - /// Inputs: - /// Operand stack: [output_size, input_size, input_start_ptr, ...] - /// Advice stack: [...] - /// - /// Outputs: - /// Operand stack: [output_size, input_size, input_start_ptr, ...] - /// Advice stack: [coefficients...] - /// - /// - `input_size` is the number of evaluations (each evaluation is 2 base field elements). Must - /// be a power of 2 and greater 1. - /// - `output_size` is the number of coefficients in the interpolated polynomial (each - /// coefficient is 2 base field elements). Must be smaller than or equal to the number of - /// input evaluations. - /// - `input_start_ptr` is the memory address of the first evaluation. - /// - `coefficients` are the coefficients of the interpolated polynomial such that lowest degree - /// coefficients are located at the top of the advice stack. - /// - /// # Errors - /// Returns an error if: - /// - `input_size` less than or equal to 1, or is not a power of 2. - /// - `output_size` is 0 or is greater than the `input_size`. - /// - `input_ptr` is greater than 2^32. - /// - `input_ptr + input_size / 2` is greater than 2^32. - fn push_ext2_intt_result(&mut self, process: ProcessState) -> Result<(), ExecutionError> { - let output_size = process.get_stack_item(0).as_int() as usize; - let input_size = process.get_stack_item(1).as_int() as usize; - let input_start_ptr = process.get_stack_item(2).as_int(); - - if input_size <= 1 { - return Err(Ext2InttError::DomainSizeTooSmall(input_size as u64).into()); - } - if !input_size.is_power_of_two() { - return Err(Ext2InttError::DomainSizeNotPowerOf2(input_size as u64).into()); - } - if input_start_ptr >= u32::MAX as u64 { - return Err(Ext2InttError::InputStartAddressTooBig(input_start_ptr).into()); - } - if input_size > u32::MAX as usize { - return Err(Ext2InttError::InputSizeTooBig(input_size as u64).into()); - } + let (q_hi, q_lo) = u64_to_u32_elements(quotient); + let (r_hi, r_lo) = u64_to_u32_elements(remainder); - let input_end_ptr = input_start_ptr + (input_size / 2) as u64; - if input_end_ptr > u32::MAX as u64 { - return Err(Ext2InttError::InputEndAddressTooBig(input_end_ptr).into()); - } + advice_provider.push_stack(AdviceSource::Value(r_hi))?; + advice_provider.push_stack(AdviceSource::Value(r_lo))?; + advice_provider.push_stack(AdviceSource::Value(q_hi))?; + advice_provider.push_stack(AdviceSource::Value(q_lo))?; - if output_size == 0 { - return Err(Ext2InttError::OutputSizeIsZero.into()); - } - if output_size > input_size { - return Err(Ext2InttError::OutputSizeTooBig(output_size, input_size).into()); - } + Ok(()) +} - let mut poly = Vec::with_capacity(input_size); - for addr in (input_start_ptr as u32)..(input_end_ptr as u32) { - let word = process - .get_mem_value(process.ctx(), addr) - .ok_or(Ext2InttError::UninitializedMemoryAddress(addr))?; +/// Given an element in a quadratic extension field on the top of the stack (i.e., a0, b1), +/// computes its multiplicative inverse and push the result onto the advice stack. +/// +/// Inputs: +/// Operand stack: [a1, a0, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [a1, a0, ...] +/// Advice stack: [b0, b1...] +/// +/// Where (b0, b1) is the multiplicative inverse of the extension field element (a0, a1) at the +/// top of the stack. +/// +/// # Errors +/// Returns an error if the input is a zero element in the extension field. +pub fn push_ext2_inv_result( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let coef0 = process.get_stack_item(1); + let coef1 = process.get_stack_item(0); - poly.push(QuadFelt::new(word[0], word[1])); - poly.push(QuadFelt::new(word[2], word[3])); - } + let element = QuadFelt::new(coef0, coef1); + if element == QuadFelt::ZERO { + return Err(ExecutionError::DivideByZero(process.clk())); + } + let result = element.inv().to_base_elements(); - let twiddles = fft::get_inv_twiddles::(input_size); - fft::interpolate_poly::(&mut poly, &twiddles); + advice_provider.push_stack(AdviceSource::Value(result[1]))?; + advice_provider.push_stack(AdviceSource::Value(result[0]))?; - for element in QuadFelt::slice_as_base_elements(&poly[..output_size]).iter().rev() { - self.push_stack(AdviceSource::Value(*element))?; - } + Ok(()) +} - Ok(()) - } +/// Given evaluations of a polynomial over some specified domain, interpolates the evaluations +/// into a polynomial in coefficient form and pushes the result into the advice stack. +/// +/// The interpolation is performed using the iNTT algorithm. The evaluations are expected to be +/// in the quadratic extension. +/// +/// Inputs: +/// Operand stack: [output_size, input_size, input_start_ptr, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [output_size, input_size, input_start_ptr, ...] +/// Advice stack: [coefficients...] +/// +/// - `input_size` is the number of evaluations (each evaluation is 2 base field elements). Must be +/// a power of 2 and greater 1. +/// - `output_size` is the number of coefficients in the interpolated polynomial (each coefficient +/// is 2 base field elements). Must be smaller than or equal to the number of input evaluations. +/// - `input_start_ptr` is the memory address of the first evaluation. +/// - `coefficients` are the coefficients of the interpolated polynomial such that lowest degree +/// coefficients are located at the top of the advice stack. +/// +/// # Errors +/// Returns an error if: +/// - `input_size` less than or equal to 1, or is not a power of 2. +/// - `output_size` is 0 or is greater than the `input_size`. +/// - `input_ptr` is greater than 2^32. +/// - `input_ptr + input_size / 2` is greater than 2^32. +pub fn push_ext2_intt_result( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let output_size = process.get_stack_item(0).as_int() as usize; + let input_size = process.get_stack_item(1).as_int() as usize; + let input_start_ptr = process.get_stack_item(2).as_int(); - /// Pushes values onto the advice stack which are required for verification of a DSA in Miden - /// VM. - /// - /// Inputs: - /// Operand stack: [PK, MSG, ...] - /// Advice stack: [...] - /// - /// Outputs: - /// Operand stack: [PK, MSG, ...] - /// Advice stack: \[DATA\] - /// - /// Where: - /// - PK is the digest of an expanded public. - /// - MSG is the digest of the message to be signed. - /// - DATA is the needed data for signature verification in the VM. - /// - /// The advice provider is expected to contain the private key associated to the public key PK. - fn push_signature( - &mut self, - process: ProcessState, - kind: SignatureKind, - ) -> Result<(), ExecutionError> { - let pub_key = process.get_stack_word(0); - let msg = process.get_stack_word(1); - let result: Vec = self.get_signature(kind, pub_key, msg)?; - for r in result { - self.push_stack(AdviceSource::Value(r))?; - } - Ok(()) + if input_size <= 1 { + return Err(Ext2InttError::DomainSizeTooSmall(input_size as u64).into()); } - - /// Pushes the number of the leading zeros of the top stack element onto the advice stack. - /// - /// Inputs: - /// Operand stack: [n, ...] - /// Advice stack: [...] - /// - /// Outputs: - /// Operand stack: [n, ...] - /// Advice stack: [leading_zeros, ...] - fn push_leading_zeros(&mut self, process: ProcessState) -> Result<(), ExecutionError> { - push_transformed_stack_top(self, process, |stack_top| Felt::from(stack_top.leading_zeros())) + if !input_size.is_power_of_two() { + return Err(Ext2InttError::DomainSizeNotPowerOf2(input_size as u64).into()); } - - /// Pushes the number of the trailing zeros of the top stack element onto the advice stack. - /// - /// Inputs: - /// Operand stack: [n, ...] - /// Advice stack: [...] - /// - /// Outputs: - /// Operand stack: [n, ...] - /// Advice stack: [trailing_zeros, ...] - fn push_trailing_zeros(&mut self, process: ProcessState) -> Result<(), ExecutionError> { - push_transformed_stack_top(self, process, |stack_top| { - Felt::from(stack_top.trailing_zeros()) - }) + if input_start_ptr >= u32::MAX as u64 { + return Err(Ext2InttError::InputStartAddressTooBig(input_start_ptr).into()); } - - /// Pushes the number of the leading ones of the top stack element onto the advice stack. - /// - /// Inputs: - /// Operand stack: [n, ...] - /// Advice stack: [...] - /// - /// Outputs: - /// Operand stack: [n, ...] - /// Advice stack: [leading_ones, ...] - fn push_leading_ones(&mut self, process: ProcessState) -> Result<(), ExecutionError> { - push_transformed_stack_top(self, process, |stack_top| Felt::from(stack_top.leading_ones())) + if input_size > u32::MAX as usize { + return Err(Ext2InttError::InputSizeTooBig(input_size as u64).into()); } - /// Pushes the number of the trailing ones of the top stack element onto the advice stack. - /// - /// Inputs: - /// Operand stack: [n, ...] - /// Advice stack: [...] - /// - /// Outputs: - /// Operand stack: [n, ...] - /// Advice stack: [trailing_ones, ...] - fn push_trailing_ones(&mut self, process: ProcessState) -> Result<(), ExecutionError> { - push_transformed_stack_top(self, process, |stack_top| Felt::from(stack_top.trailing_ones())) + let input_end_ptr = input_start_ptr + (input_size / 2) as u64; + if input_end_ptr > u32::MAX as u64 { + return Err(Ext2InttError::InputEndAddressTooBig(input_end_ptr).into()); } - /// Pushes the base 2 logarithm of the top stack element, rounded down. - /// Inputs: - /// Operand stack: [n, ...] - /// Advice stack: [...] - /// - /// Outputs: - /// Operand stack: [n, ...] - /// Advice stack: [ilog2(n), ...] - /// - /// # Errors - /// Returns an error if the logarithm argument (top stack element) equals ZERO. - fn push_ilog2(&mut self, process: ProcessState) -> Result<(), ExecutionError> { - let n = process.get_stack_item(0).as_int(); - if n == 0 { - return Err(ExecutionError::LogArgumentZero(process.clk())); - } - let ilog2 = Felt::from(n.ilog2()); - self.push_stack(AdviceSource::Value(ilog2))?; - Ok(()) + if output_size == 0 { + return Err(Ext2InttError::OutputSizeIsZero.into()); } - - // DEFAULT MERKLE STORE INJECTORS - // -------------------------------------------------------------------------------------------- - - /// Updates the node of a Merkle tree specified by the values on the top of the operand stack. - /// Returns the path from the updated node to the new root of the tree to the caller. - /// - /// Inputs: - /// Operand stack: [OLD_NODE, depth, index, OLD_ROOT, NEW_NODE, ...] - /// Advice: [...] - /// Merkle store: {...} - /// - /// Outputs: - /// Operand stack: [OLD_NODE, depth, index, OLD_ROOT, NEW_NODE, ...] - /// Advice stack: [...] - /// Merkle store: {path, ...} - /// Return: \[path\] - fn update_operand_stack_merkle_node( - &mut self, - process: ProcessState, - ) -> Result { - let depth = process.get_stack_item(4); - let index = process.get_stack_item(5); - let old_root = [ - process.get_stack_item(9), - process.get_stack_item(8), - process.get_stack_item(7), - process.get_stack_item(6), - ]; - let new_node = [ - process.get_stack_item(13), - process.get_stack_item(12), - process.get_stack_item(11), - process.get_stack_item(10), - ]; - let (path, _) = self.update_merkle_node(old_root, &depth, &index, new_node)?; - Ok(path) + if output_size > input_size { + return Err(Ext2InttError::OutputSizeTooBig(output_size, input_size).into()); } - // DEFAULT MERKLE STORE EXTRACTORS - // -------------------------------------------------------------------------------------------- + let mut poly = Vec::with_capacity(input_size); + for addr in (input_start_ptr as u32)..(input_end_ptr as u32) { + let word = process + .get_mem_value(process.ctx(), addr) + .ok_or(Ext2InttError::UninitializedMemoryAddress(addr))?; - /// Extracts a Merkle path for the node specified by the values at the top of the operand stack - /// and returns it to the caller. - /// - /// # Errors - /// Returns an error if the Merkle store does not contain the specified Merkle path. - /// - /// Inputs: - /// Operand stack: [WORD, depth, index, ROOT, ...] - /// Advice stack: [...] - /// Advice map: {...} - /// Merkle store: {path, ...} - /// - /// Outputs: - /// Operand stack: [WORD, depth, index, ROOT, ...] - /// Advice stack: [...] - /// Advice map: {...} - /// Merkle store: {path, ...} - /// Return: \[path\] - fn get_operand_stack_merkle_path( - &mut self, - process: ProcessState, - ) -> Result { - let depth = process.get_stack_item(4); - let index = process.get_stack_item(5); - let root = [ - process.get_stack_item(9), - process.get_stack_item(8), - process.get_stack_item(7), - process.get_stack_item(6), - ]; - self.get_merkle_path(root, &depth, &index) + poly.push(QuadFelt::new(word[0], word[1])); + poly.push(QuadFelt::new(word[2], word[3])); } - // DEFAULT SMT INJECTORS - // -------------------------------------------------------------------------------------------- - - /// Pushes onto the advice stack the value associated with the specified key in a Sparse - /// Merkle Tree defined by the specified root. - /// - /// If no value was previously associated with the specified key, [ZERO; 4] is pushed onto - /// the advice stack. - /// - /// Inputs: - /// Operand stack: [KEY, ROOT, ...] - /// Advice stack: [...] - /// - /// Outputs: - /// Operand stack: [KEY, ROOT, ...] - /// Advice stack: [VALUE, ...] - /// - /// # Errors - /// Returns an error if the provided Merkle root doesn't exist on the advice provider. - /// - /// # Panics - /// Will panic as unimplemented if the target depth is `64`. - fn push_smtpeek_result(&mut self, process: ProcessState) -> Result<(), ExecutionError> { - let empty_leaf = EmptySubtreeRoots::entry(SMT_DEPTH, SMT_DEPTH); - // fetch the arguments from the operand stack - let key = process.get_stack_word(0); - let root = process.get_stack_word(1); - - // get the node from the SMT for the specified key; this node can be either a leaf node, - // or a root of an empty subtree at the returned depth - let node = self.get_tree_node(root, &Felt::new(SMT_DEPTH as u64), &key[3])?; - - if node == Word::from(empty_leaf) { - // if the node is a root of an empty subtree, then there is no value associated with - // the specified key - self.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE))?; - } else { - let leaf_preimage = get_smt_leaf_preimage(self, node)?; - - for (key_in_leaf, value_in_leaf) in leaf_preimage { - if key == key_in_leaf { - // Found key - push value associated with key, and return - self.push_stack(AdviceSource::Word(value_in_leaf))?; - - return Ok(()); - } - } + let twiddles = fft::get_inv_twiddles::(input_size); + fft::interpolate_poly::(&mut poly, &twiddles); - // if we can't find any key in the leaf that matches `key`, it means no value is - // associated with `key` - self.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE))?; - } - Ok(()) + for element in QuadFelt::slice_as_base_elements(&poly[..output_size]).iter().rev() { + advice_provider.push_stack(AdviceSource::Value(*element))?; } - // DEFAULT MERKLE STORE EXTRACTORS - // -------------------------------------------------------------------------------------------- - - /// Returns a signature on a message using a public key. - fn get_signature( - &self, - kind: SignatureKind, - pub_key: Word, - msg: Word, - ) -> Result, ExecutionError> { - let pk_sk = self - .get_mapped_values(&pub_key.into()) - .ok_or(ExecutionError::AdviceMapKeyNotFound(pub_key))?; - - match kind { - SignatureKind::RpoFalcon512 => dsa::falcon_sign(pk_sk, msg), - } - } + Ok(()) } -impl AdviceProvider for &mut T -where - T: AdviceProvider, -{ - fn pop_stack(&mut self, process: ProcessState) -> Result { - T::pop_stack(self, process) - } - - fn pop_stack_word(&mut self, process: ProcessState) -> Result { - T::pop_stack_word(self, process) - } - - fn pop_stack_dword(&mut self, process: ProcessState) -> Result<[Word; 2], ExecutionError> { - T::pop_stack_dword(self, process) +/// Pushes values onto the advice stack which are required for verification of a DSA in Miden +/// VM. +/// +/// Inputs: +/// Operand stack: [PK, MSG, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [PK, MSG, ...] +/// Advice stack: \[DATA\] +/// +/// Where: +/// - PK is the digest of an expanded public. +/// - MSG is the digest of the message to be signed. +/// - DATA is the needed data for signature verification in the VM. +/// +/// The advice provider is expected to contain the private key associated to the public key PK. +pub fn push_signature( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, + kind: SignatureKind, +) -> Result<(), ExecutionError> { + let pub_key = process.get_stack_word(0); + let msg = process.get_stack_word(1); + let result: Vec = get_signature(advice_provider, kind, pub_key, msg)?; + for r in result { + advice_provider.push_stack(AdviceSource::Value(r))?; } + Ok(()) +} - fn push_stack(&mut self, source: AdviceSource) -> Result<(), ExecutionError> { - T::push_stack(self, source) - } +/// Pushes the number of the leading zeros of the top stack element onto the advice stack. +/// +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [leading_zeros, ...] +pub fn push_leading_zeros( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + push_transformed_stack_top(advice_provider, process, |stack_top| { + Felt::from(stack_top.leading_zeros()) + }) +} - fn insert_into_map(&mut self, key: Word, values: Vec) { - T::insert_into_map(self, key, values) - } +/// Pushes the number of the trailing zeros of the top stack element onto the advice stack. +/// +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [trailing_zeros, ...] +pub fn push_trailing_zeros( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + push_transformed_stack_top(advice_provider, process, |stack_top| { + Felt::from(stack_top.trailing_zeros()) + }) +} - fn get_signature( - &self, - kind: SignatureKind, - pub_key: Word, - msg: Word, - ) -> Result, ExecutionError> { - T::get_signature(self, kind, pub_key, msg) - } +/// Pushes the number of the leading ones of the top stack element onto the advice stack. +/// +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [leading_ones, ...] +pub fn push_leading_ones( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + push_transformed_stack_top(advice_provider, process, |stack_top| { + Felt::from(stack_top.leading_ones()) + }) +} - fn get_mapped_values(&self, key: &RpoDigest) -> Option<&[Felt]> { - T::get_mapped_values(self, key) - } +/// Pushes the number of the trailing ones of the top stack element onto the advice stack. +/// +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [trailing_ones, ...] +pub fn push_trailing_ones( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + push_transformed_stack_top(advice_provider, process, |stack_top| { + Felt::from(stack_top.trailing_ones()) + }) +} - fn get_tree_node( - &self, - root: Word, - depth: &Felt, - index: &Felt, - ) -> Result { - T::get_tree_node(self, root, depth, index) +/// Pushes the base 2 logarithm of the top stack element, rounded down. +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [ilog2(n), ...] +/// +/// # Errors +/// Returns an error if the logarithm argument (top stack element) equals ZERO. +pub fn push_ilog2( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let n = process.get_stack_item(0).as_int(); + if n == 0 { + return Err(ExecutionError::LogArgumentZero(process.clk())); } + let ilog2 = Felt::from(n.ilog2()); + advice_provider.push_stack(AdviceSource::Value(ilog2))?; + Ok(()) +} - fn get_merkle_path( - &self, - root: Word, - depth: &Felt, - index: &Felt, - ) -> Result { - T::get_merkle_path(self, root, depth, index) - } +/// Updates the node of a Merkle tree specified by the values on the top of the operand stack. +/// Returns the path from the updated node to the new root of the tree to the caller. +/// +/// Inputs: +/// Operand stack: [OLD_NODE, depth, index, OLD_ROOT, NEW_NODE, ...] +/// Advice: [...] +/// Merkle store: {...} +/// +/// Outputs: +/// Operand stack: [OLD_NODE, depth, index, OLD_ROOT, NEW_NODE, ...] +/// Advice stack: [...] +/// Merkle store: {path, ...} +/// Return: \[path\] +pub fn update_operand_stack_merkle_node( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result { + let depth = process.get_stack_item(4); + let index = process.get_stack_item(5); + let old_root = [ + process.get_stack_item(9), + process.get_stack_item(8), + process.get_stack_item(7), + process.get_stack_item(6), + ]; + let new_node = [ + process.get_stack_item(13), + process.get_stack_item(12), + process.get_stack_item(11), + process.get_stack_item(10), + ]; + let (path, _) = advice_provider.update_merkle_node(old_root, &depth, &index, new_node)?; + Ok(path) +} - fn get_leaf_depth( - &self, - root: Word, - tree_depth: &Felt, - index: &Felt, - ) -> Result { - T::get_leaf_depth(self, root, tree_depth, index) - } +/// Pushes onto the advice stack the value associated with the specified key in a Sparse +/// Merkle Tree defined by the specified root. +/// +/// If no value was previously associated with the specified key, [ZERO; 4] is pushed onto +/// the advice stack. +/// +/// Inputs: +/// Operand stack: [KEY, ROOT, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [KEY, ROOT, ...] +/// Advice stack: [VALUE, ...] +/// +/// # Errors +/// Returns an error if the provided Merkle root doesn't exist on the advice provider. +/// +/// # Panics +/// Will panic as unimplemented if the target depth is `64`. +pub fn push_smtpeek_result( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let empty_leaf = EmptySubtreeRoots::entry(SMT_DEPTH, SMT_DEPTH); + // fetch the arguments from the operand stack + let key = process.get_stack_word(0); + let root = process.get_stack_word(1); + + // get the node from the SMT for the specified key; this node can be either a leaf node, + // or a root of an empty subtree at the returned depth + let node = advice_provider.get_tree_node(root, &Felt::new(SMT_DEPTH as u64), &key[3])?; + + if node == Word::from(empty_leaf) { + // if the node is a root of an empty subtree, then there is no value associated with + // the specified key + advice_provider.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE))?; + } else { + let leaf_preimage = get_smt_leaf_preimage(advice_provider, node)?; + + for (key_in_leaf, value_in_leaf) in leaf_preimage { + if key == key_in_leaf { + // Found key - push value associated with key, and return + advice_provider.push_stack(AdviceSource::Word(value_in_leaf))?; + + return Ok(()); + } + } - fn update_merkle_node( - &mut self, - root: Word, - depth: &Felt, - index: &Felt, - value: Word, - ) -> Result<(MerklePath, Word), ExecutionError> { - T::update_merkle_node(self, root, depth, index, value) + // if we can't find any key in the leaf that matches `key`, it means no value is + // associated with `key` + advice_provider.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE))?; } + Ok(()) +} - fn merge_roots(&mut self, lhs: Word, rhs: Word) -> Result { - T::merge_roots(self, lhs, rhs) +/// Returns a signature on a message using a public key. +pub fn get_signature( + advice_provider: &impl AdviceProvider, + kind: SignatureKind, + pub_key: Word, + msg: Word, +) -> Result, ExecutionError> { + let pk_sk = advice_provider + .get_mapped_values(&pub_key.into()) + .ok_or(ExecutionError::AdviceMapKeyNotFound(pub_key))?; + + match kind { + SignatureKind::RpoFalcon512 => dsa::falcon_sign(pk_sk, msg), } } diff --git a/processor/src/operations/crypto_ops.rs b/processor/src/operations/crypto_ops.rs index acbecd545a..cb5b9e90c1 100644 --- a/processor/src/operations/crypto_ops.rs +++ b/processor/src/operations/crypto_ops.rs @@ -1,5 +1,7 @@ use super::{ExecutionError, Operation, Process}; -use crate::{crypto::MerklePath, AdviceProvider, Host}; +use crate::{ + crypto::MerklePath, host::advice::update_operand_stack_merkle_node, AdviceProvider, Host, +}; // CRYPTOGRAPHIC OPERATIONS // ================================================================================================ @@ -70,13 +72,13 @@ impl Process { ) -> 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 depth = self.stack.get(4); let index = self.stack.get(5); let root = [self.stack.get(9), self.stack.get(8), self.stack.get(7), self.stack.get(6)]; // get a Merkle path from the advice provider for the specified root and node index. // the path is expected to be of the specified depth. - let path: MerklePath = - host.advice_provider_mut().get_operand_stack_merkle_path(self.into())?; + let path = host.advice_provider_mut().get_merkle_path(root, &depth, &index)?; // use hasher to compute the Merkle root of the path let (addr, computed_root) = self.chiplets.build_merkle_root(node, &path, index); @@ -148,7 +150,7 @@ impl Process { // specified depth. if the new node is the root of a tree, this instruction will append the // whole sub-tree to this node. let path: MerklePath = - host.advice_provider_mut().update_operand_stack_merkle_node(self.into())?; + update_operand_stack_merkle_node(host.advice_provider_mut(), self.into())?; assert_eq!(path.len(), depth.as_int() as usize); diff --git a/processor/src/operations/sys_ops.rs b/processor/src/operations/sys_ops.rs index f4e70b5f7e..f67a031480 100644 --- a/processor/src/operations/sys_ops.rs +++ b/processor/src/operations/sys_ops.rs @@ -7,7 +7,16 @@ use super::{ }, ExecutionError, Process, }; -use crate::{AdviceProvider, Host, ProcessState}; +use crate::{ + host::advice::{ + copy_map_value_to_adv_stack, copy_merkle_node_to_adv_stack, insert_hdword_into_adv_map, + insert_hperm_into_adv_map, insert_mem_values_into_adv_map, merge_merkle_nodes, + push_ext2_intt_result, push_ext2_inv_result, push_ilog2, push_leading_ones, + push_leading_zeros, push_signature, push_smtpeek_result, push_trailing_ones, + push_trailing_zeros, push_u64_div_result, update_operand_stack_merkle_node, + }, + Host, ProcessState, +}; // SYSTEM OPERATIONS // ================================================================================================ @@ -139,43 +148,43 @@ impl Process { let advice_provider = host.advice_provider_mut(); let process_state: ProcessState = self.into(); match advice_injector { - AdviceInjector::MerkleNodeMerge => advice_provider.merge_merkle_nodes(process_state), + AdviceInjector::MerkleNodeMerge => merge_merkle_nodes(advice_provider, process_state), AdviceInjector::MerkleNodeToStack => { - advice_provider.copy_merkle_node_to_adv_stack(process_state) + copy_merkle_node_to_adv_stack(advice_provider, process_state) }, AdviceInjector::MapValueToStack => { - advice_provider.copy_map_value_to_adv_stack(process_state, false) + copy_map_value_to_adv_stack(advice_provider, process_state, false) }, AdviceInjector::MapValueToStackN => { - advice_provider.copy_map_value_to_adv_stack(process_state, true) + copy_map_value_to_adv_stack(advice_provider, process_state, true) }, AdviceInjector::UpdateMerkleNode => { - let _ = advice_provider.update_operand_stack_merkle_node(process_state)?; + let _ = update_operand_stack_merkle_node(advice_provider, process_state)?; Ok(()) }, - AdviceInjector::U64Div => advice_provider.push_u64_div_result(process_state), - AdviceInjector::Ext2Inv => advice_provider.push_ext2_inv_result(process_state), - AdviceInjector::Ext2Intt => advice_provider.push_ext2_intt_result(process_state), - AdviceInjector::SmtPeek => advice_provider.push_smtpeek_result(process_state), - AdviceInjector::U32Clz => advice_provider.push_leading_zeros(process_state), - AdviceInjector::U32Ctz => advice_provider.push_trailing_zeros(process_state), - AdviceInjector::U32Clo => advice_provider.push_leading_ones(process_state), - AdviceInjector::U32Cto => advice_provider.push_trailing_ones(process_state), - AdviceInjector::ILog2 => advice_provider.push_ilog2(process_state), + AdviceInjector::U64Div => push_u64_div_result(advice_provider, process_state), + AdviceInjector::Ext2Inv => push_ext2_inv_result(advice_provider, process_state), + AdviceInjector::Ext2Intt => push_ext2_intt_result(advice_provider, process_state), + AdviceInjector::SmtPeek => push_smtpeek_result(advice_provider, process_state), + AdviceInjector::U32Clz => push_leading_zeros(advice_provider, process_state), + AdviceInjector::U32Ctz => push_trailing_zeros(advice_provider, process_state), + AdviceInjector::U32Clo => push_leading_ones(advice_provider, process_state), + AdviceInjector::U32Cto => push_trailing_ones(advice_provider, process_state), + AdviceInjector::ILog2 => push_ilog2(advice_provider, process_state), AdviceInjector::MemToMap => { - advice_provider.insert_mem_values_into_adv_map(process_state) + insert_mem_values_into_adv_map(advice_provider, process_state) }, AdviceInjector::HdwordToMap => { - advice_provider.insert_hdword_into_adv_map(process_state, ZERO) + insert_hdword_into_adv_map(advice_provider, process_state, ZERO) }, AdviceInjector::HdwordToMapWithDomain => { let domain = self.stack.get(8); - advice_provider.insert_hdword_into_adv_map(process_state, domain) + insert_hdword_into_adv_map(advice_provider, process_state, domain) }, - AdviceInjector::HpermToMap => advice_provider.insert_hperm_into_adv_map(process_state), + AdviceInjector::HpermToMap => insert_hperm_into_adv_map(advice_provider, process_state), AdviceInjector::FalconSigToStack => { - advice_provider.push_signature(process_state, SignatureKind::RpoFalcon512) + push_signature(advice_provider, process_state, SignatureKind::RpoFalcon512) }, } } From 9716730cde1c0a8ebd59387f1d83b28aabc634d7 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Fri, 22 Nov 2024 08:58:55 -0500 Subject: [PATCH 19/39] refactor: remove unused `AdviceInjector::UpdateMerkleNode` --- core/src/operations/decorators/advice.rs | 21 -------------- processor/src/host/advice/mod.rs | 35 ------------------------ processor/src/operations/crypto_ops.rs | 9 +++--- processor/src/operations/sys_ops.rs | 6 +--- 4 files changed, 5 insertions(+), 66 deletions(-) diff --git a/core/src/operations/decorators/advice.rs b/core/src/operations/decorators/advice.rs index 5ce7dceadb..7197486c64 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/operations/decorators/advice.rs @@ -11,7 +11,6 @@ pub use constants::*; mod constants { pub const EVENT_MERKLE_NODE_MERGE: u32 = 276124218; pub const EVENT_MERKLE_NODE_TO_STACK: u32 = 361943238; - pub const EVENT_UPDATE_MERKLE_NODE: u32 = 483702215; pub const EVENT_MAP_VALUE_TO_STACK: u32 = 574478993; pub const EVENT_MAP_VALUE_TO_STACK_N: u32 = 630847990; pub const EVENT_U64_DIV: u32 = 678156251; @@ -73,21 +72,6 @@ pub enum AdviceInjector { /// Merkle store: {TREE_ROOT<-NODE} MerkleNodeToStack, - /// Updates the node of a Merkle tree specified by the values at the top of the operand stack. - /// Returns the path from the updated node to the new root of the tree to the caller. - /// - /// Inputs: - /// Operand stack: [OLD_NODE, depth, index, OLD_ROOT, NEW_NODE, ...] - /// Advice: [...] - /// Merkle store: {...} - /// - /// Outputs: - /// Operand stack: [OLD_NODE, depth, index, OLD_ROOT, NEW_NODE, ...] - /// Advice stack: [...] - /// Merkle store: {path, ...} - /// Return: \[path\] - UpdateMerkleNode, - /// Pushes a list of field elements onto the advice stack. The list is looked up in the advice /// map using the specified word from the operand stack as the key. /// @@ -322,7 +306,6 @@ impl AdviceInjector { match self { AdviceInjector::MerkleNodeMerge => EVENT_MERKLE_NODE_MERGE, AdviceInjector::MerkleNodeToStack => EVENT_MERKLE_NODE_TO_STACK, - AdviceInjector::UpdateMerkleNode => EVENT_UPDATE_MERKLE_NODE, AdviceInjector::MapValueToStack => EVENT_MAP_VALUE_TO_STACK, AdviceInjector::MapValueToStackN => EVENT_MAP_VALUE_TO_STACK_N, AdviceInjector::U64Div => EVENT_U64_DIV, @@ -348,7 +331,6 @@ impl AdviceInjector { match event_id { EVENT_MERKLE_NODE_MERGE => Some(AdviceInjector::MerkleNodeMerge), EVENT_MERKLE_NODE_TO_STACK => Some(AdviceInjector::MerkleNodeToStack), - EVENT_UPDATE_MERKLE_NODE => Some(AdviceInjector::UpdateMerkleNode), EVENT_MAP_VALUE_TO_STACK => Some(AdviceInjector::MapValueToStack), EVENT_MAP_VALUE_TO_STACK_N => Some(AdviceInjector::MapValueToStackN), EVENT_U64_DIV => Some(AdviceInjector::U64Div), @@ -381,9 +363,6 @@ impl fmt::Display for AdviceInjector { match self { Self::MerkleNodeMerge => write!(f, "merkle_node_merge"), Self::MerkleNodeToStack => write!(f, "merkle_node_to_stack"), - Self::UpdateMerkleNode => { - write!(f, "update_merkle_node") - }, Self::MapValueToStack => write!(f, "map_value_to_stack"), Self::MapValueToStackN => write!(f, "map_value_to_stack_with_len"), Self::U64Div => write!(f, "div_u64"), diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index 331714f9bf..c3ed79db63 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -697,41 +697,6 @@ pub fn push_ilog2( Ok(()) } -/// Updates the node of a Merkle tree specified by the values on the top of the operand stack. -/// Returns the path from the updated node to the new root of the tree to the caller. -/// -/// Inputs: -/// Operand stack: [OLD_NODE, depth, index, OLD_ROOT, NEW_NODE, ...] -/// Advice: [...] -/// Merkle store: {...} -/// -/// Outputs: -/// Operand stack: [OLD_NODE, depth, index, OLD_ROOT, NEW_NODE, ...] -/// Advice stack: [...] -/// Merkle store: {path, ...} -/// Return: \[path\] -pub fn update_operand_stack_merkle_node( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result { - let depth = process.get_stack_item(4); - let index = process.get_stack_item(5); - let old_root = [ - process.get_stack_item(9), - process.get_stack_item(8), - process.get_stack_item(7), - process.get_stack_item(6), - ]; - let new_node = [ - process.get_stack_item(13), - process.get_stack_item(12), - process.get_stack_item(11), - process.get_stack_item(10), - ]; - let (path, _) = advice_provider.update_merkle_node(old_root, &depth, &index, new_node)?; - Ok(path) -} - /// Pushes onto the advice stack the value associated with the specified key in a Sparse /// Merkle Tree defined by the specified root. /// diff --git a/processor/src/operations/crypto_ops.rs b/processor/src/operations/crypto_ops.rs index cb5b9e90c1..4850cd3105 100644 --- a/processor/src/operations/crypto_ops.rs +++ b/processor/src/operations/crypto_ops.rs @@ -1,7 +1,5 @@ use super::{ExecutionError, Operation, Process}; -use crate::{ - crypto::MerklePath, host::advice::update_operand_stack_merkle_node, AdviceProvider, Host, -}; +use crate::{AdviceProvider, Host}; // CRYPTOGRAPHIC OPERATIONS // ================================================================================================ @@ -149,8 +147,9 @@ impl Process { // get a Merkle path to it. the length of the returned path is expected to match the // specified depth. if the new node is the root of a tree, this instruction will append the // whole sub-tree to this node. - let path: MerklePath = - update_operand_stack_merkle_node(host.advice_provider_mut(), self.into())?; + let (path, _) = host + .advice_provider_mut() + .update_merkle_node(old_root, &depth, &index, new_node)?; assert_eq!(path.len(), depth.as_int() as usize); diff --git a/processor/src/operations/sys_ops.rs b/processor/src/operations/sys_ops.rs index f67a031480..ac3a119762 100644 --- a/processor/src/operations/sys_ops.rs +++ b/processor/src/operations/sys_ops.rs @@ -13,7 +13,7 @@ use crate::{ insert_hperm_into_adv_map, insert_mem_values_into_adv_map, merge_merkle_nodes, push_ext2_intt_result, push_ext2_inv_result, push_ilog2, push_leading_ones, push_leading_zeros, push_signature, push_smtpeek_result, push_trailing_ones, - push_trailing_zeros, push_u64_div_result, update_operand_stack_merkle_node, + push_trailing_zeros, push_u64_div_result, }, Host, ProcessState, }; @@ -158,10 +158,6 @@ impl Process { AdviceInjector::MapValueToStackN => { copy_map_value_to_adv_stack(advice_provider, process_state, true) }, - AdviceInjector::UpdateMerkleNode => { - let _ = update_operand_stack_merkle_node(advice_provider, process_state)?; - Ok(()) - }, AdviceInjector::U64Div => push_u64_div_result(advice_provider, process_state), AdviceInjector::Ext2Inv => push_ext2_inv_result(advice_provider, process_state), AdviceInjector::Ext2Intt => push_ext2_intt_result(advice_provider, process_state), From b82f2f1f2770825af4985c398d18eafa8265ebd8 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Fri, 22 Nov 2024 09:30:29 -0500 Subject: [PATCH 20/39] refactor: move system event handlers to new `sys_event_handlers` module, and rename AdviceInjector --- assembly/src/assembler/basic_block_builder.rs | 9 +- assembly/src/assembler/instruction/adv_ops.rs | 10 +- .../src/assembler/instruction/crypto_ops.rs | 6 +- .../src/assembler/instruction/ext2_ops.rs | 6 +- .../src/assembler/instruction/field_ops.rs | 4 +- assembly/src/assembler/instruction/mod.rs | 4 +- assembly/src/assembler/instruction/u32_ops.rs | 19 +- assembly/src/ast/instruction/advice.rs | 16 +- assembly/src/ast/instruction/mod.rs | 4 +- assembly/src/ast/instruction/print.rs | 2 +- assembly/src/ast/mod.rs | 2 +- assembly/src/ast/tests.rs | 8 +- assembly/src/ast/visit.rs | 35 +- assembly/src/parser/grammar.lalrpop | 26 +- core/src/lib.rs | 6 +- core/src/operations/decorators/mod.rs | 3 - core/src/operations/mod.rs | 3 +- .../decorators/advice.rs => sys_events.rs} | 95 +-- .../user_docs/assembly/code_organization.md | 2 - docs/src/user_docs/assembly/io_operations.md | 6 +- processor/src/host/advice/inputs.rs | 10 +- processor/src/host/advice/mod.rs | 685 +--------------- processor/src/host/advice/providers.rs | 5 +- processor/src/lib.rs | 5 +- .../advice => operations/sys_ops}/dsa.rs | 13 +- .../operations/{sys_ops.rs => sys_ops/mod.rs} | 66 +- .../operations/sys_ops/sys_event_handlers.rs | 732 ++++++++++++++++++ 27 files changed, 886 insertions(+), 896 deletions(-) rename core/src/{operations/decorators/advice.rs => sys_events.rs} (82%) rename processor/src/{host/advice => operations/sys_ops}/dsa.rs (94%) rename processor/src/operations/{sys_ops.rs => sys_ops/mod.rs} (77%) create mode 100644 processor/src/operations/sys_ops/sys_event_handlers.rs diff --git a/assembly/src/assembler/basic_block_builder.rs b/assembly/src/assembler/basic_block_builder.rs index 85f886f97f..4d924b58bd 100644 --- a/assembly/src/assembler/basic_block_builder.rs +++ b/assembly/src/assembler/basic_block_builder.rs @@ -2,7 +2,8 @@ use alloc::{borrow::Borrow, string::ToString, vec::Vec}; use vm_core::{ mast::{DecoratorId, MastNodeId}, - AdviceInjector, AssemblyOp, Decorator, Operation, + sys_events::SystemEvent, + AssemblyOp, Decorator, Operation, }; use super::{mast_forest_builder::MastForestBuilder, BodyWrapper, DecoratorList, ProcedureContext}; @@ -94,10 +95,10 @@ impl BasicBlockBuilder<'_> { self.ops.resize(new_len, op); } - /// Converts the advice injector into its corresponding event ID, and adds an `Emit` operation + /// Converts the system event into its corresponding event ID, and adds an `Emit` operation /// to the list of basic block operations. - pub fn push_advice_injector(&mut self, injector: AdviceInjector) { - self.push_op(Operation::Emit(injector.into_event_id())) + pub fn push_system_event(&mut self, sys_event: SystemEvent) { + self.push_op(Operation::Emit(sys_event.into_event_id())) } } diff --git a/assembly/src/assembler/instruction/adv_ops.rs b/assembly/src/assembler/instruction/adv_ops.rs index 049ace4e41..399913946b 100644 --- a/assembly/src/assembler/instruction/adv_ops.rs +++ b/assembly/src/assembler/instruction/adv_ops.rs @@ -1,7 +1,7 @@ use vm_core::Operation; use super::{validate_param, BasicBlockBuilder}; -use crate::{ast::AdviceInjectorNode, AssemblyError, ADVICE_READ_LIMIT}; +use crate::{AssemblyError, ADVICE_READ_LIMIT}; // NON-DETERMINISTIC (ADVICE) INPUTS // ================================================================================================ @@ -18,11 +18,3 @@ pub fn adv_push(block_builder: &mut BasicBlockBuilder, n: u8) -> Result<(), Asse block_builder.push_op_many(Operation::AdvPop, n as usize); Ok(()) } - -// ADVICE INJECTORS -// ================================================================================================ - -/// Appends advice injector decorator to the span. -pub fn adv_inject(block_builder: &mut BasicBlockBuilder, injector: &AdviceInjectorNode) { - block_builder.push_advice_injector(injector.into()) -} diff --git a/assembly/src/assembler/instruction/crypto_ops.rs b/assembly/src/assembler/instruction/crypto_ops.rs index a89015a06a..7efed9ee9a 100644 --- a/assembly/src/assembler/instruction/crypto_ops.rs +++ b/assembly/src/assembler/instruction/crypto_ops.rs @@ -1,4 +1,4 @@ -use vm_core::{AdviceInjector, Felt, Operation::*}; +use vm_core::{sys_events::SystemEvent, Felt, Operation::*}; use super::BasicBlockBuilder; use crate::AssemblyError; @@ -169,7 +169,7 @@ pub(super) fn mtree_merge(block_builder: &mut BasicBlockBuilder) { // invoke the advice provider function to merge 2 Merkle trees defined by the roots on the top // of the operand stack - block_builder.push_advice_injector(AdviceInjector::MerkleNodeMerge); + block_builder.push_system_event(SystemEvent::MerkleNodeMerge); // perform the `hmerge`, updating the operand stack hmerge(block_builder) @@ -202,7 +202,7 @@ fn read_mtree_node(block_builder: &mut BasicBlockBuilder) { // new node value post the tree root: [d, i, R, V_new] // // pops the value of the node we are looking for from the advice stack - block_builder.push_advice_injector(AdviceInjector::MerkleNodeToStack); + block_builder.push_system_event(SystemEvent::MerkleNodeToStack); // pops the old node value from advice the stack => MPVERIFY: [V_old, d, i, R, ...] // MRUPDATE: [V_old, d, i, R, V_new, ...] diff --git a/assembly/src/assembler/instruction/ext2_ops.rs b/assembly/src/assembler/instruction/ext2_ops.rs index b2729553e4..b3d2cfc766 100644 --- a/assembly/src/assembler/instruction/ext2_ops.rs +++ b/assembly/src/assembler/instruction/ext2_ops.rs @@ -1,4 +1,4 @@ -use vm_core::{AdviceInjector::Ext2Inv, Operation::*}; +use vm_core::{sys_events::SystemEvent::Ext2Inv, Operation::*}; use super::BasicBlockBuilder; use crate::AssemblyError; @@ -54,7 +54,7 @@ pub fn ext2_mul(block_builder: &mut BasicBlockBuilder) { /// /// This operation takes 11 VM cycles. pub fn ext2_div(block_builder: &mut BasicBlockBuilder) { - block_builder.push_advice_injector(Ext2Inv); + block_builder.push_system_event(Ext2Inv); #[rustfmt::skip] let ops = [ AdvPop, // [b0', b1, b0, a1, a0, ...] @@ -114,7 +114,7 @@ pub fn ext2_neg(block_builder: &mut BasicBlockBuilder) { /// /// This operation takes 8 VM cycles. pub fn ext2_inv(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(Ext2Inv); + block_builder.push_system_event(Ext2Inv); #[rustfmt::skip] let ops = [ AdvPop, // [a0', a1, a0, ...] diff --git a/assembly/src/assembler/instruction/field_ops.rs b/assembly/src/assembler/instruction/field_ops.rs index 7033a92eff..e4797c441d 100644 --- a/assembly/src/assembler/instruction/field_ops.rs +++ b/assembly/src/assembler/instruction/field_ops.rs @@ -1,4 +1,4 @@ -use vm_core::{AdviceInjector, FieldElement, Operation::*}; +use vm_core::{sys_events::SystemEvent, FieldElement, Operation::*}; use super::{validate_param, BasicBlockBuilder}; use crate::{ @@ -260,7 +260,7 @@ fn perform_exp_for_small_power(span_builder: &mut BasicBlockBuilder, pow: u64) { /// # Errors /// Returns an error if the logarithm argument (top stack element) equals ZERO. pub fn ilog2(block_builder: &mut BasicBlockBuilder) { - block_builder.push_advice_injector(AdviceInjector::ILog2); + block_builder.push_system_event(SystemEvent::ILog2); block_builder.push_op(AdvPop); // [ilog2, n, ...] // compute the power-of-two for the value given in the advice tape (17 cycles) diff --git a/assembly/src/assembler/instruction/mod.rs b/assembly/src/assembler/instruction/mod.rs index 76de7147ce..34b45bc156 100644 --- a/assembly/src/assembler/instruction/mod.rs +++ b/assembly/src/assembler/instruction/mod.rs @@ -361,7 +361,9 @@ impl Assembler { false, )?, - Instruction::AdvInject(injector) => adv_ops::adv_inject(block_builder, injector), + Instruction::SysEvent(system_event) => { + block_builder.push_system_event(system_event.into()) + }, // ----- cryptographic instructions --------------------------------------------------- Instruction::Hash => crypto_ops::hash(block_builder), diff --git a/assembly/src/assembler/instruction/u32_ops.rs b/assembly/src/assembler/instruction/u32_ops.rs index 23801be09a..42159babff 100644 --- a/assembly/src/assembler/instruction/u32_ops.rs +++ b/assembly/src/assembler/instruction/u32_ops.rs @@ -1,5 +1,6 @@ use vm_core::{ - AdviceInjector, Felt, + sys_events::SystemEvent, + Felt, Operation::{self, *}, }; @@ -299,7 +300,7 @@ pub fn u32popcnt(span_builder: &mut BasicBlockBuilder) { /// /// This operation takes 42 VM cycles. pub fn u32clz(block_builder: &mut BasicBlockBuilder) { - block_builder.push_advice_injector(AdviceInjector::U32Clz); + block_builder.push_system_event(SystemEvent::U32Clz); block_builder.push_op(AdvPop); // [clz, n, ...] verify_clz(block_builder); @@ -311,7 +312,7 @@ pub fn u32clz(block_builder: &mut BasicBlockBuilder) { /// /// This operation takes 34 VM cycles. pub fn u32ctz(block_builder: &mut BasicBlockBuilder) { - block_builder.push_advice_injector(AdviceInjector::U32Ctz); + block_builder.push_system_event(SystemEvent::U32Ctz); block_builder.push_op(AdvPop); // [ctz, n, ...] verify_ctz(block_builder); @@ -323,7 +324,7 @@ pub fn u32ctz(block_builder: &mut BasicBlockBuilder) { /// /// This operation takes 41 VM cycles. pub fn u32clo(block_builder: &mut BasicBlockBuilder) { - block_builder.push_advice_injector(AdviceInjector::U32Clo); + block_builder.push_system_event(SystemEvent::U32Clo); block_builder.push_op(AdvPop); // [clo, n, ...] verify_clo(block_builder); @@ -335,7 +336,7 @@ pub fn u32clo(block_builder: &mut BasicBlockBuilder) { /// /// This operation takes 33 VM cycles. pub fn u32cto(block_builder: &mut BasicBlockBuilder) { - block_builder.push_advice_injector(AdviceInjector::U32Cto); + block_builder.push_system_event(SystemEvent::U32Cto); block_builder.push_op(AdvPop); // [cto, n, ...] verify_cto(block_builder); @@ -415,7 +416,7 @@ fn prepare_bitwise( } /// Appends relevant operations to the span block for the correctness check of the `U32Clz` -/// injector. +/// system event. /// The idea is to compare the actual value with a bitmask consisting of `clz` leading ones to /// check that every bit in `clz` leading bits is zero and `1` additional one to check that /// `clz + 1`'th leading bit is one: @@ -496,7 +497,7 @@ fn verify_clz(block_builder: &mut BasicBlockBuilder) { } /// Appends relevant operations to the span block for the correctness check of the `U32Clo` -/// injector. +/// system event. /// The idea is to compare the actual value with a bitmask consisting of `clo` leading ones to /// check that every bit in `clo` leading bits is one and `1` additional one to check that /// `clo + 1`'th leading bit is zero: @@ -571,7 +572,7 @@ fn verify_clo(block_builder: &mut BasicBlockBuilder) { } /// Appends relevant operations to the span block for the correctness check of the `U32Ctz` -/// injector. +/// system event. /// The idea is to compare the actual value with a bitmask consisting of `ctz` trailing ones to /// check that every bit in `ctz` trailing bits is zero and `1` additional one to check that /// `ctz + 1`'th trailing bit is one: @@ -645,7 +646,7 @@ fn verify_ctz(block_builder: &mut BasicBlockBuilder) { } /// Appends relevant operations to the span block for the correctness check of the `U32Cto` -/// injector. +/// system event. /// The idea is to compare the actual value with a bitmask consisting of `cto` trailing ones to /// check that every bit in `cto` trailing bits is one and `1` additional one to check that /// `cto + 1`'th trailing bit is zero: diff --git a/assembly/src/ast/instruction/advice.rs b/assembly/src/ast/instruction/advice.rs index b8b7b0d4b5..f6bfe807d0 100644 --- a/assembly/src/ast/instruction/advice.rs +++ b/assembly/src/ast/instruction/advice.rs @@ -1,8 +1,8 @@ use core::fmt; -use vm_core::AdviceInjector; +use vm_core::sys_events::SystemEvent; -// ADVICE INJECTOR NODE +// SYSTEM EVENT NODE // ================================================================================================ /// Instructions which inject data into the advice provider. @@ -11,7 +11,7 @@ use vm_core::AdviceInjector; /// - Push new data onto the advice stack. /// - Insert new data into the advice map. #[derive(Clone, PartialEq, Eq, Debug)] -pub enum AdviceInjectorNode { +pub enum SystemEventNode { PushU64Div, PushExt2intt, PushSmtPeek, @@ -25,9 +25,9 @@ pub enum AdviceInjectorNode { PushSignature { kind: SignatureKind }, } -impl From<&AdviceInjectorNode> for AdviceInjector { - fn from(value: &AdviceInjectorNode) -> Self { - use AdviceInjectorNode::*; +impl From<&SystemEventNode> for SystemEvent { + fn from(value: &SystemEventNode) -> Self { + use SystemEventNode::*; match value { PushU64Div => Self::U64Div, PushExt2intt => Self::Ext2Intt, @@ -46,13 +46,13 @@ impl From<&AdviceInjectorNode> for AdviceInjector { } } -impl crate::prettier::PrettyPrint for AdviceInjectorNode { +impl crate::prettier::PrettyPrint for SystemEventNode { fn render(&self) -> crate::prettier::Document { crate::prettier::display(self) } } -impl fmt::Display for AdviceInjectorNode { +impl fmt::Display for SystemEventNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::PushU64Div => write!(f, "push_u64div"), diff --git a/assembly/src/ast/instruction/mod.rs b/assembly/src/ast/instruction/mod.rs index a88441d89f..b42d976d8d 100644 --- a/assembly/src/ast/instruction/mod.rs +++ b/assembly/src/ast/instruction/mod.rs @@ -4,7 +4,7 @@ mod print; use alloc::vec::Vec; -pub use self::{advice::AdviceInjectorNode, debug::DebugOptions}; +pub use self::{advice::SystemEventNode, debug::DebugOptions}; use crate::{ ast::{immediate::*, InvocationTarget}, Felt, Word, @@ -239,7 +239,7 @@ pub enum Instruction { AdvPush(ImmU8), AdvLoadW, - AdvInject(AdviceInjectorNode), + SysEvent(SystemEventNode), // ----- cryptographic operations ------------------------------------------------------------ Hash, diff --git a/assembly/src/ast/instruction/print.rs b/assembly/src/ast/instruction/print.rs index b7ca11c37c..07cecbab09 100644 --- a/assembly/src/ast/instruction/print.rs +++ b/assembly/src/ast/instruction/print.rs @@ -248,7 +248,7 @@ impl PrettyPrint for Instruction { Self::AdvPush(value) => inst_with_imm("adv_push", value), Self::AdvLoadW => const_text("adv_loadw"), - Self::AdvInject(injector) => inst_with_imm("adv", injector), + Self::SysEvent(sys_event) => inst_with_imm("adv", sys_event), // ----- cryptographic operations ----------------------------------------------------- Self::Hash => const_text("hash"), diff --git a/assembly/src/ast/mod.rs b/assembly/src/ast/mod.rs index a606d9e906..19d8c50e51 100644 --- a/assembly/src/ast/mod.rs +++ b/assembly/src/ast/mod.rs @@ -27,7 +27,7 @@ pub use self::{ ident::{CaseKindError, Ident, IdentError}, immediate::{ErrorCode, ImmFelt, ImmU16, ImmU32, ImmU8, Immediate}, imports::Import, - instruction::{advice::SignatureKind, AdviceInjectorNode, DebugOptions, Instruction}, + instruction::{advice::SignatureKind, DebugOptions, Instruction, SystemEventNode}, invocation_target::{InvocationTarget, Invoke, InvokeKind}, module::{Module, ModuleKind}, op::Op, diff --git a/assembly/src/ast/tests.rs b/assembly/src/ast/tests.rs index e2c81b08d7..dd9e910297 100644 --- a/assembly/src/ast/tests.rs +++ b/assembly/src/ast/tests.rs @@ -467,14 +467,14 @@ fn test_ast_parsing_adv_ops() -> Result<(), Report> { #[test] fn test_ast_parsing_adv_injection() -> Result<(), Report> { - use super::AdviceInjectorNode::*; + use super::SystemEventNode::*; let context = TestContext::new(); let source = source_file!(&context, "begin adv.push_u64div adv.push_mapval adv.insert_mem end"); let forms = module!(begin!( - inst!(AdvInject(PushU64Div)), - inst!(AdvInject(PushMapVal)), - inst!(AdvInject(InsertMem)) + inst!(SysEvent(PushU64Div)), + inst!(SysEvent(PushMapVal)), + inst!(SysEvent(InsertMem)) )); assert_eq!(context.parse_forms(source)?, forms); Ok(()) diff --git a/assembly/src/ast/visit.rs b/assembly/src/ast/visit.rs index 9a9afd8d2f..083a4d4fe6 100644 --- a/assembly/src/ast/visit.rs +++ b/assembly/src/ast/visit.rs @@ -98,8 +98,8 @@ pub trait Visit { fn visit_inst(&mut self, inst: &Span) -> ControlFlow { visit_inst(self, inst) } - fn visit_advice_injector(&mut self, injector: Span<&AdviceInjectorNode>) -> ControlFlow { - visit_advice_injector(self, injector) + fn visit_system_event(&mut self, sys_event: Span<&SystemEventNode>) -> ControlFlow { + visit_system_event(self, sys_event) } fn visit_debug_options(&mut self, options: Span<&DebugOptions>) -> ControlFlow { visit_debug_options(self, options) @@ -167,8 +167,8 @@ where fn visit_inst(&mut self, inst: &Span) -> ControlFlow { (**self).visit_inst(inst) } - fn visit_advice_injector(&mut self, injector: Span<&AdviceInjectorNode>) -> ControlFlow { - (**self).visit_advice_injector(injector) + fn visit_system_event(&mut self, sys_event: Span<&SystemEventNode>) -> ControlFlow { + (**self).visit_system_event(sys_event) } fn visit_debug_options(&mut self, options: Span<&DebugOptions>) -> ControlFlow { (**self).visit_debug_options(options) @@ -315,7 +315,7 @@ where | MemStoreWImm(ref imm) | Emit(ref imm) | Trace(ref imm) => visitor.visit_immediate_u32(imm), - AdvInject(ref injector) => visitor.visit_advice_injector(Span::new(span, injector)), + SysEvent(ref sys_event) => visitor.visit_system_event(Span::new(span, sys_event)), Exec(ref target) => visitor.visit_exec(target), Call(ref target) => visitor.visit_call(target), SysCall(ref target) => visitor.visit_syscall(target), @@ -346,10 +346,7 @@ where } } -pub fn visit_advice_injector( - _visitor: &mut V, - _node: Span<&AdviceInjectorNode>, -) -> ControlFlow +pub fn visit_system_event(_visitor: &mut V, _node: Span<&SystemEventNode>) -> ControlFlow where V: ?Sized + Visit, { @@ -513,11 +510,8 @@ pub trait VisitMut { fn visit_mut_inst(&mut self, inst: &mut Span) -> ControlFlow { visit_mut_inst(self, inst) } - fn visit_mut_advice_injector( - &mut self, - injector: Span<&mut AdviceInjectorNode>, - ) -> ControlFlow { - visit_mut_advice_injector(self, injector) + fn visit_mut_system_event(&mut self, sys_event: Span<&mut SystemEventNode>) -> ControlFlow { + visit_mut_system_event(self, sys_event) } fn visit_mut_debug_options(&mut self, options: Span<&mut DebugOptions>) -> ControlFlow { visit_mut_debug_options(self, options) @@ -585,11 +579,8 @@ where fn visit_mut_inst(&mut self, inst: &mut Span) -> ControlFlow { (**self).visit_mut_inst(inst) } - fn visit_mut_advice_injector( - &mut self, - injector: Span<&mut AdviceInjectorNode>, - ) -> ControlFlow { - (**self).visit_mut_advice_injector(injector) + fn visit_mut_system_event(&mut self, sys_event: Span<&mut SystemEventNode>) -> ControlFlow { + (**self).visit_mut_system_event(sys_event) } fn visit_mut_debug_options(&mut self, options: Span<&mut DebugOptions>) -> ControlFlow { (**self).visit_mut_debug_options(options) @@ -749,7 +740,7 @@ where | MemStoreWImm(ref mut imm) | Emit(ref mut imm) | Trace(ref mut imm) => visitor.visit_mut_immediate_u32(imm), - AdvInject(ref mut injector) => visitor.visit_mut_advice_injector(Span::new(span, injector)), + SysEvent(ref mut sys_event) => visitor.visit_mut_system_event(Span::new(span, sys_event)), Exec(ref mut target) => visitor.visit_mut_exec(target), Call(ref mut target) => visitor.visit_mut_call(target), SysCall(ref mut target) => visitor.visit_mut_syscall(target), @@ -780,9 +771,9 @@ where } } -pub fn visit_mut_advice_injector( +pub fn visit_mut_system_event( _visitor: &mut V, - _node: Span<&mut AdviceInjectorNode>, + _node: Span<&mut SystemEventNode>, ) -> ControlFlow where V: ?Sized + VisitMut, diff --git a/assembly/src/parser/grammar.lalrpop b/assembly/src/parser/grammar.lalrpop index f0b6d882f0..378ae1ec50 100644 --- a/assembly/src/parser/grammar.lalrpop +++ b/assembly/src/parser/grammar.lalrpop @@ -598,7 +598,7 @@ MacroInst: SmallOpsVec = { #[inline] Inst: Instruction = { - AdviceInjector, + SystemEvent, Call, Debug, InstWithBitSizeImmediate, @@ -665,18 +665,18 @@ Inst: Instruction = { } #[inline] -AdviceInjector: Instruction = { - "adv" "." "insert_hdword" => Instruction::AdvInject(AdviceInjectorNode::InsertHdword), - "adv" "." "insert_hdword_d" => Instruction::AdvInject(AdviceInjectorNode::InsertHdwordWithDomain), - "adv" "." "insert_hperm" => Instruction::AdvInject(AdviceInjectorNode::InsertHperm), - "adv" "." "insert_mem" => Instruction::AdvInject(AdviceInjectorNode::InsertMem), - "adv" "." "push_ext2intt" => Instruction::AdvInject(AdviceInjectorNode::PushExt2intt), - "adv" "." "push_mapval" => Instruction::AdvInject(AdviceInjectorNode::PushMapVal), - "adv" "." "push_mapvaln" => Instruction::AdvInject(AdviceInjectorNode::PushMapValN), - "adv" "." "push_mtnode" => Instruction::AdvInject(AdviceInjectorNode::PushMtNode), - "adv" "." "push_sig" "." => Instruction::AdvInject(AdviceInjectorNode::PushSignature { kind }), - "adv" "." "push_smtpeek" => Instruction::AdvInject(AdviceInjectorNode::PushSmtPeek), - "adv" "." "push_u64div" => Instruction::AdvInject(AdviceInjectorNode::PushU64Div), +SystemEvent: Instruction = { + "adv" "." "insert_hdword" => Instruction::SysEvent(SystemEventNode::InsertHdword), + "adv" "." "insert_hdword_d" => Instruction::SysEvent(SystemEventNode::InsertHdwordWithDomain), + "adv" "." "insert_hperm" => Instruction::SysEvent(SystemEventNode::InsertHperm), + "adv" "." "insert_mem" => Instruction::SysEvent(SystemEventNode::InsertMem), + "adv" "." "push_ext2intt" => Instruction::SysEvent(SystemEventNode::PushExt2intt), + "adv" "." "push_mapval" => Instruction::SysEvent(SystemEventNode::PushMapVal), + "adv" "." "push_mapvaln" => Instruction::SysEvent(SystemEventNode::PushMapValN), + "adv" "." "push_mtnode" => Instruction::SysEvent(SystemEventNode::PushMtNode), + "adv" "." "push_sig" "." => Instruction::SysEvent(SystemEventNode::PushSignature { kind }), + "adv" "." "push_smtpeek" => Instruction::SysEvent(SystemEventNode::PushSmtPeek), + "adv" "." "push_u64div" => Instruction::SysEvent(SystemEventNode::PushU64Div), } #[inline] diff --git a/core/src/lib.rs b/core/src/lib.rs index 355c07395c..650c726573 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -116,13 +116,15 @@ pub mod prettier { mod operations; pub use operations::{ - opcode_constants::*, AdviceInjector, AssemblyOp, DebugOptions, Decorator, DecoratorIterator, - DecoratorList, Operation, SignatureKind, + opcode_constants::*, AssemblyOp, DebugOptions, Decorator, DecoratorIterator, DecoratorList, + Operation, SignatureKind, }; pub mod stack; pub use stack::{StackInputs, StackOutputs}; +pub mod sys_events; + mod advice; pub use advice::map::AdviceMap; diff --git a/core/src/operations/decorators/mod.rs b/core/src/operations/decorators/mod.rs index 9a12789e15..72c409a48b 100644 --- a/core/src/operations/decorators/mod.rs +++ b/core/src/operations/decorators/mod.rs @@ -4,9 +4,6 @@ use core::fmt; use miden_crypto::hash::blake::Blake3_256; use num_traits::ToBytes; -mod advice; -pub use advice::AdviceInjector; - mod assembly_op; pub use assembly_op::AssemblyOp; diff --git a/core/src/operations/mod.rs b/core/src/operations/mod.rs index 7a2eb6e3c0..725c377256 100644 --- a/core/src/operations/mod.rs +++ b/core/src/operations/mod.rs @@ -3,8 +3,7 @@ use core::fmt; use super::Felt; mod decorators; pub use decorators::{ - AdviceInjector, AssemblyOp, DebugOptions, Decorator, DecoratorIterator, DecoratorList, - SignatureKind, + AssemblyOp, DebugOptions, Decorator, DecoratorIterator, DecoratorList, SignatureKind, }; // OPERATIONS OP CODES // ================================================================================================ diff --git a/core/src/operations/decorators/advice.rs b/core/src/sys_events.rs similarity index 82% rename from core/src/operations/decorators/advice.rs rename to core/src/sys_events.rs index 7197486c64..0a4c8df2a7 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/sys_events.rs @@ -1,12 +1,13 @@ use core::fmt; -use super::SignatureKind; -// ADVICE INJECTORS +// SYSTEM EVENTS // ================================================================================================ -// Randomly generated constant values for the VM's native events. All values were sampled +// Randomly generated constant values for the VM's system events. All values were sampled // between 0 and 2^32. pub use constants::*; + +use super::SignatureKind; #[rustfmt::skip] mod constants { pub const EVENT_MERKLE_NODE_MERGE: u32 = 276124218; @@ -38,8 +39,8 @@ mod constants { /// All actions, except for `MerkleNodeMerge`, `Ext2Inv` and `UpdateMerkleNode` can be invoked /// directly from Miden assembly via dedicated instructions. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum AdviceInjector { - // MERKLE STORE INJECTORS +pub enum SystemEvent { + // MERKLE STORE EVENTS // -------------------------------------------------------------------------------------------- /// Creates a new Merkle tree in the advice provider by combining Merkle trees with the /// specified roots. The root of the new tree is defined as `Hash(LEFT_ROOT, RIGHT_ROOT)`. @@ -56,7 +57,7 @@ pub enum AdviceInjector { /// provider (i.e., the input trees are not removed). MerkleNodeMerge, - // ADVICE STACK INJECTORS + // ADVICE STACK SYSTEM EVENTS // -------------------------------------------------------------------------------------------- /// Pushes a node of the Merkle tree specified by the values on the top of the operand stack /// onto the advice stack. @@ -226,7 +227,7 @@ pub enum AdviceInjector { /// Advice stack: [ilog2(n), ...] ILog2, - // ADVICE MAP INJECTORS + // ADVICE MAP SYSTEM EVENTS // -------------------------------------------------------------------------------------------- /// Reads words from memory at the specified range and inserts them into the advice map under /// the key `KEY` located at the top of the stack. @@ -301,64 +302,64 @@ pub enum AdviceInjector { FalconSigToStack, } -impl AdviceInjector { +impl SystemEvent { pub fn into_event_id(self) -> u32 { match self { - AdviceInjector::MerkleNodeMerge => EVENT_MERKLE_NODE_MERGE, - AdviceInjector::MerkleNodeToStack => EVENT_MERKLE_NODE_TO_STACK, - AdviceInjector::MapValueToStack => EVENT_MAP_VALUE_TO_STACK, - AdviceInjector::MapValueToStackN => EVENT_MAP_VALUE_TO_STACK_N, - AdviceInjector::U64Div => EVENT_U64_DIV, - AdviceInjector::Ext2Inv => EVENT_EXT2_INV, - AdviceInjector::Ext2Intt => EVENT_EXT2_INTT, - AdviceInjector::SmtPeek => EVENT_SMT_PEEK, - AdviceInjector::U32Clz => EVENT_U32_CLZ, - AdviceInjector::U32Ctz => EVENT_U32_CTZ, - AdviceInjector::U32Clo => EVENT_U32_CLO, - AdviceInjector::U32Cto => EVENT_U32_CTO, - AdviceInjector::ILog2 => EVENT_ILOG2, - AdviceInjector::MemToMap => EVENT_MEM_TO_MAP, - AdviceInjector::HdwordToMap => EVENT_HDWORD_TO_MAP, - AdviceInjector::HdwordToMapWithDomain => EVENT_HDWORD_TO_MAP_WITH_DOMAIN, - AdviceInjector::HpermToMap => EVENT_HPERM_TO_MAP, - AdviceInjector::FalconSigToStack => EVENT_FALCON_SIG_TO_STACK, + SystemEvent::MerkleNodeMerge => EVENT_MERKLE_NODE_MERGE, + SystemEvent::MerkleNodeToStack => EVENT_MERKLE_NODE_TO_STACK, + SystemEvent::MapValueToStack => EVENT_MAP_VALUE_TO_STACK, + SystemEvent::MapValueToStackN => EVENT_MAP_VALUE_TO_STACK_N, + SystemEvent::U64Div => EVENT_U64_DIV, + SystemEvent::Ext2Inv => EVENT_EXT2_INV, + SystemEvent::Ext2Intt => EVENT_EXT2_INTT, + SystemEvent::SmtPeek => EVENT_SMT_PEEK, + SystemEvent::U32Clz => EVENT_U32_CLZ, + SystemEvent::U32Ctz => EVENT_U32_CTZ, + SystemEvent::U32Clo => EVENT_U32_CLO, + SystemEvent::U32Cto => EVENT_U32_CTO, + SystemEvent::ILog2 => EVENT_ILOG2, + SystemEvent::MemToMap => EVENT_MEM_TO_MAP, + SystemEvent::HdwordToMap => EVENT_HDWORD_TO_MAP, + SystemEvent::HdwordToMapWithDomain => EVENT_HDWORD_TO_MAP_WITH_DOMAIN, + SystemEvent::HpermToMap => EVENT_HPERM_TO_MAP, + SystemEvent::FalconSigToStack => EVENT_FALCON_SIG_TO_STACK, } } - /// Returns an advice injector corresponding to the specified event ID, or `None` if the event + /// Returns a system event corresponding to the specified event ID, or `None` if the event /// ID is not recognized. pub fn from_event_id(event_id: u32) -> Option { match event_id { - EVENT_MERKLE_NODE_MERGE => Some(AdviceInjector::MerkleNodeMerge), - EVENT_MERKLE_NODE_TO_STACK => Some(AdviceInjector::MerkleNodeToStack), - EVENT_MAP_VALUE_TO_STACK => Some(AdviceInjector::MapValueToStack), - EVENT_MAP_VALUE_TO_STACK_N => Some(AdviceInjector::MapValueToStackN), - EVENT_U64_DIV => Some(AdviceInjector::U64Div), - EVENT_EXT2_INV => Some(AdviceInjector::Ext2Inv), - EVENT_EXT2_INTT => Some(AdviceInjector::Ext2Intt), - EVENT_SMT_PEEK => Some(AdviceInjector::SmtPeek), - EVENT_U32_CLZ => Some(AdviceInjector::U32Clz), - EVENT_U32_CTZ => Some(AdviceInjector::U32Ctz), - EVENT_U32_CLO => Some(AdviceInjector::U32Clo), - EVENT_U32_CTO => Some(AdviceInjector::U32Cto), - EVENT_ILOG2 => Some(AdviceInjector::ILog2), - EVENT_MEM_TO_MAP => Some(AdviceInjector::MemToMap), - EVENT_HDWORD_TO_MAP => Some(AdviceInjector::HdwordToMap), - EVENT_HDWORD_TO_MAP_WITH_DOMAIN => Some(AdviceInjector::HdwordToMapWithDomain), - EVENT_HPERM_TO_MAP => Some(AdviceInjector::HpermToMap), - EVENT_FALCON_SIG_TO_STACK => Some(AdviceInjector::FalconSigToStack), + EVENT_MERKLE_NODE_MERGE => Some(SystemEvent::MerkleNodeMerge), + EVENT_MERKLE_NODE_TO_STACK => Some(SystemEvent::MerkleNodeToStack), + EVENT_MAP_VALUE_TO_STACK => Some(SystemEvent::MapValueToStack), + EVENT_MAP_VALUE_TO_STACK_N => Some(SystemEvent::MapValueToStackN), + EVENT_U64_DIV => Some(SystemEvent::U64Div), + EVENT_EXT2_INV => Some(SystemEvent::Ext2Inv), + EVENT_EXT2_INTT => Some(SystemEvent::Ext2Intt), + EVENT_SMT_PEEK => Some(SystemEvent::SmtPeek), + EVENT_U32_CLZ => Some(SystemEvent::U32Clz), + EVENT_U32_CTZ => Some(SystemEvent::U32Ctz), + EVENT_U32_CLO => Some(SystemEvent::U32Clo), + EVENT_U32_CTO => Some(SystemEvent::U32Cto), + EVENT_ILOG2 => Some(SystemEvent::ILog2), + EVENT_MEM_TO_MAP => Some(SystemEvent::MemToMap), + EVENT_HDWORD_TO_MAP => Some(SystemEvent::HdwordToMap), + EVENT_HDWORD_TO_MAP_WITH_DOMAIN => Some(SystemEvent::HdwordToMapWithDomain), + EVENT_HPERM_TO_MAP => Some(SystemEvent::HpermToMap), + EVENT_FALCON_SIG_TO_STACK => Some(SystemEvent::FalconSigToStack), _ => None, } } } -impl crate::prettier::PrettyPrint for AdviceInjector { +impl crate::prettier::PrettyPrint for SystemEvent { fn render(&self) -> crate::prettier::Document { crate::prettier::display(self) } } -impl fmt::Display for AdviceInjector { +impl fmt::Display for SystemEvent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::MerkleNodeMerge => write!(f, "merkle_node_merge"), diff --git a/docs/src/user_docs/assembly/code_organization.md b/docs/src/user_docs/assembly/code_organization.md index 4fabdd2d71..19db520437 100644 --- a/docs/src/user_docs/assembly/code_organization.md +++ b/docs/src/user_docs/assembly/code_organization.md @@ -43,8 +43,6 @@ begin end ``` -Finally, a procedure cannot contain *solely* any number of [advice injectors](./io_operations.md#nondeterministic-inputs), `emit`, `debug` and `trace` instructions. In other words, it must contain at least one instruction which is not in the aforementioned list. - #### Dynamic procedure invocation It is also possible to invoke procedures dynamically - i.e., without specifying target procedure labels at compile time. A procedure can only call itself using dynamic invocation. There are two instructions, `dynexec` and `dyncall`, which can be used to execute dynamically-specified code targets. Both instructions expect the [MAST root](../../design/programs.md) of the target to be stored in memory, and the memory address of the MAST root to be on the top of the stack. The difference between `dynexec` and `dyncall` corresponds to the difference between `exec` and `call`, see the documentation on [procedure invocation semantics](./execution_contexts.md#procedure-invocation-semantics) for more details. diff --git a/docs/src/user_docs/assembly/io_operations.md b/docs/src/user_docs/assembly/io_operations.md index 23e31bd181..78501df52e 100644 --- a/docs/src/user_docs/assembly/io_operations.md +++ b/docs/src/user_docs/assembly/io_operations.md @@ -3,7 +3,7 @@ Miden assembly provides a set of instructions for moving data between the operan * **Program code**: values to be moved onto the operand stack can be hard-coded in a program's source code. * **Environment**: values can be moved onto the operand stack from environment variables. These include current clock cycle, current stack depth, and a few others. -* **Advice provider**: values can be moved onto the operand stack from the advice provider by popping them from the advice stack (see more about the advice provider [here](../../intro/overview.md#nondeterministic-inputs)). The VM can also inject new data into the advice provider via *advice injector* instructions. +* **Advice provider**: values can be moved onto the operand stack from the advice provider by popping them from the advice stack (see more about the advice provider [here](../../intro/overview.md#nondeterministic-inputs)). The VM can also inject new data into the advice provider via *system event* instructions. * **Memory**: values can be moved between the stack and random-access memory. The memory is word-addressable, meaning, four elements are located at each address, and we can read and write elements to/from memory in batches of four. Memory can be accessed via absolute memory references (i.e., via memory addresses) as well as via local procedure references (i.e., local index). The latter approach ensures that a procedure does not access locals of another procedure. ### Constant inputs @@ -43,9 +43,9 @@ As mentioned above, nondeterministic inputs are provided to the VM via the advic > **Note**: The opcodes above always push data onto the operand stack so that the first element is placed deepest in the stack. For example, if the data on the stack is `a,b,c,d` and you use the opcode `adv_push.4`, the data will be `d,c,b,a` on your stack. This is also the behavior of the other opcodes. -The second category injects new data into the advice provider. These operations are called *advice injectors* and they affect only the advice provider state. That is, the state of all other VM components (e.g., stack, memory) are unaffected. Executing advice injectors does not consume any VM cycles (i.e., these instructions are executed in $0$ cycles). +The second category injects new data into the advice provider. These operations are called *system events* and they affect only the advice provider state. That is, the state of all other VM components (e.g., stack, memory) are unaffected. Handling system events does not consume any VM cycles (i.e., these instructions are executed in $0$ cycles). -Advice injectors fall into two categories: (1) injectors which push new data onto the advice stack, and (2) injectors which insert new data into the advice map. +System events fall into two categories: (1) events which push new data onto the advice stack, and (2) events which insert new data into the advice map. | Instruction | Stack_input | Stack_output | Notes | | -------------------------------------------- | -------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/processor/src/host/advice/inputs.rs b/processor/src/host/advice/inputs.rs index 2941d4a9df..8cd83bb15c 100644 --- a/processor/src/host/advice/inputs.rs +++ b/processor/src/host/advice/inputs.rs @@ -1,13 +1,15 @@ use alloc::vec::Vec; use vm_core::{ - crypto::hash::RpoDigest, + crypto::{ + hash::RpoDigest, + merkle::{InnerNodeInfo, MerkleStore}, + }, + errors::InputError, utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, - AdviceMap, + AdviceMap, Felt, }; -use super::{Felt, InnerNodeInfo, InputError, MerkleStore}; - // ADVICE INPUTS // ================================================================================================ diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index c3ed79db63..31e8816ff5 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -1,20 +1,11 @@ use alloc::vec::Vec; use vm_core::{ - crypto::{ - hash::{Rpo256, RpoDigest}, - merkle::{ - EmptySubtreeRoots, InnerNodeInfo, MerklePath, MerkleStore, NodeIndex, Smt, StoreNode, - SMT_DEPTH, - }, - }, - FieldElement, SignatureKind, EMPTY_WORD, WORD_SIZE, ZERO, + crypto::{hash::RpoDigest, merkle::MerklePath}, + Felt, }; -use winter_prover::math::fft; -use crate::{ExecutionError, Ext2InttError, Felt, InputError, ProcessState, QuadFelt, Word}; - -mod dsa; +use crate::{ExecutionError, ProcessState, Word}; mod inputs; pub use inputs::AdviceInputs; @@ -172,673 +163,3 @@ pub trait AdviceProvider: Sized { /// this advice provider. fn merge_roots(&mut self, lhs: Word, rhs: Word) -> Result; } - -// NATIVE EVENTS -// =============================================================================================== - -/// Reads words from memory at the specified range and inserts them into the advice map under -/// the key `KEY` located at the top of the stack. -/// -/// Inputs: -/// Operand stack: [KEY, start_addr, end_addr, ...] -/// Advice map: {...} -/// -/// Outputs: -/// Operand stack: [KEY, start_addr, end_addr, ...] -/// Advice map: {KEY: values} -/// -/// Where `values` are the elements located in memory[start_addr..end_addr]. -/// -/// # Errors -/// Returns an error: -/// - `start_addr` is greater than or equal to 2^32. -/// - `end_addr` is greater than or equal to 2^32. -/// - `start_addr` > `end_addr`. -pub fn insert_mem_values_into_adv_map( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result<(), ExecutionError> { - let (start_addr, end_addr) = get_mem_addr_range(process, 4, 5)?; - let ctx = process.ctx(); - - let mut values = Vec::with_capacity(((end_addr - start_addr) as usize) * WORD_SIZE); - for addr in start_addr..end_addr { - let mem_value = process.get_mem_value(ctx, addr).unwrap_or(EMPTY_WORD); - values.extend_from_slice(&mem_value); - } - - let key = process.get_stack_word(0); - advice_provider.insert_into_map(key, values); - - Ok(()) -} - -/// Reads two word from the operand stack and inserts them into the advice map under the key -/// defined by the hash of these words. -/// -/// Inputs: -/// Operand stack: [B, A, ...] -/// Advice map: {...} -/// -/// Outputs: -/// Operand stack: [B, A, ...] -/// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} -/// -/// Where KEY is computed as hash(A || B, domain), where domain is provided via the immediate -/// value. -pub fn insert_hdword_into_adv_map( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, - domain: Felt, -) -> Result<(), ExecutionError> { - // get the top two words from the stack and hash them to compute the key value - let word0 = process.get_stack_word(0); - let word1 = process.get_stack_word(1); - let key = Rpo256::merge_in_domain(&[word1.into(), word0.into()], domain); - - // build a vector of values from the two word and insert it into the advice map under the - // computed key - let mut values = Vec::with_capacity(2 * WORD_SIZE); - values.extend_from_slice(&word1); - values.extend_from_slice(&word0); - advice_provider.insert_into_map(key.into(), values); - - Ok(()) -} - -/// Reads three words from the operand stack and inserts the top two words into the advice map -/// under the key defined by applying an RPO permutation to all three words. -/// -/// Inputs: -/// Operand stack: [B, A, C, ...] -/// Advice map: {...} -/// -/// Outputs: -/// Operand stack: [B, A, C, ...] -/// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} -/// -/// Where KEY is computed by extracting the digest elements from hperm([C, A, B]). For example, -/// if C is [0, d, 0, 0], KEY will be set as hash(A || B, d). -pub fn insert_hperm_into_adv_map( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result<(), ExecutionError> { - // read the state from the stack - let mut state = [ - process.get_stack_item(11), - process.get_stack_item(10), - process.get_stack_item(9), - process.get_stack_item(8), - process.get_stack_item(7), - process.get_stack_item(6), - process.get_stack_item(5), - process.get_stack_item(4), - process.get_stack_item(3), - process.get_stack_item(2), - process.get_stack_item(1), - process.get_stack_item(0), - ]; - - // get the values to be inserted into the advice map from the state - let values = state[Rpo256::RATE_RANGE].to_vec(); - - // apply the permutation to the state and extract the key from it - Rpo256::apply_permutation(&mut state); - let key = RpoDigest::new( - state[Rpo256::DIGEST_RANGE] - .try_into() - .expect("failed to extract digest from state"), - ); - - advice_provider.insert_into_map(key.into(), values); - - Ok(()) -} - -/// Creates a new Merkle tree in the advice provider by combining Merkle trees with the -/// specified roots. The root of the new tree is defined as `Hash(LEFT_ROOT, RIGHT_ROOT)`. -/// -/// Inputs: -/// Operand stack: [RIGHT_ROOT, LEFT_ROOT, ...] -/// Merkle store: {RIGHT_ROOT, LEFT_ROOT} -/// -/// Outputs: -/// Operand stack: [RIGHT_ROOT, LEFT_ROOT, ...] -/// Merkle store: {RIGHT_ROOT, LEFT_ROOT, hash(LEFT_ROOT, RIGHT_ROOT)} -/// -/// After the operation, both the original trees and the new tree remains in the advice -/// provider (i.e., the input trees are not removed). -/// -/// It is not checked whether the provided roots exist as Merkle trees in the advide providers. -pub fn merge_merkle_nodes( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result<(), ExecutionError> { - // fetch the arguments from the stack - let lhs = process.get_stack_word(1); - let rhs = process.get_stack_word(0); - - // perform the merge - advice_provider.merge_roots(lhs, rhs)?; - - Ok(()) -} - -/// Pushes a node of the Merkle tree specified by the values on the top of the operand stack -/// onto the advice stack. -/// -/// Inputs: -/// Operand stack: [depth, index, TREE_ROOT, ...] -/// Advice stack: [...] -/// Merkle store: {TREE_ROOT<-NODE} -/// -/// Outputs: -/// Operand stack: [depth, index, TREE_ROOT, ...] -/// Advice stack: [NODE, ...] -/// Merkle store: {TREE_ROOT<-NODE} -/// -/// # Errors -/// Returns an error if: -/// - Merkle tree for the specified root cannot be found in the advice provider. -/// - The specified depth is either zero or greater than the depth of the Merkle tree identified by -/// the specified root. -/// - Value of the node at the specified depth and index is not known to the advice provider. -pub fn copy_merkle_node_to_adv_stack( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result<(), ExecutionError> { - let depth = process.get_stack_item(0); - let index = process.get_stack_item(1); - let root = [ - process.get_stack_item(5), - process.get_stack_item(4), - process.get_stack_item(3), - process.get_stack_item(2), - ]; - - let node = advice_provider.get_tree_node(root, &depth, &index)?; - - advice_provider.push_stack(AdviceSource::Value(node[3]))?; - advice_provider.push_stack(AdviceSource::Value(node[2]))?; - advice_provider.push_stack(AdviceSource::Value(node[1]))?; - advice_provider.push_stack(AdviceSource::Value(node[0]))?; - - Ok(()) -} - -/// Pushes a list of field elements onto the advice stack. The list is looked up in the advice -/// map using the specified word from the operand stack as the key. If `include_len` is set to -/// true, the number of elements in the value is also pushed onto the advice stack. -/// -/// Inputs: -/// Operand stack: [..., KEY, ...] -/// Advice stack: [...] -/// Advice map: {KEY: values} -/// -/// Outputs: -/// Operand stack: [..., KEY, ...] -/// Advice stack: [values_len?, values, ...] -/// Advice map: {KEY: values} -/// -/// The `key_offset` value specifies the location of the `KEY` on the stack. For example, -/// offset value of 0 indicates that the top word on the stack should be used as the key, the -/// offset value of 4, indicates that the second word on the stack should be used as the key -/// etc. -/// -/// The valid values of `key_offset` are 0 through 12 (inclusive). -/// -/// # Errors -/// Returns an error if the required key was not found in the key-value map or if stack offset -/// is greater than 12. -pub fn copy_map_value_to_adv_stack( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, - include_len: bool, -) -> Result<(), ExecutionError> { - let key = [ - process.get_stack_item(3), - process.get_stack_item(2), - process.get_stack_item(1), - process.get_stack_item(0), - ]; - advice_provider.push_stack(AdviceSource::Map { key, include_len })?; - - Ok(()) -} - -/// Pushes the result of [u64] division (both the quotient and the remainder) onto the advice -/// stack. -/// -/// Inputs: -/// Operand stack: [b1, b0, a1, a0, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [b1, b0, a1, a0, ...] -/// Advice stack: [q0, q1, r0, r1, ...] -/// -/// Where (a0, a1) and (b0, b1) are the 32-bit limbs of the dividend and the divisor -/// respectively (with a0 representing the 32 lest significant bits and a1 representing the -/// 32 most significant bits). Similarly, (q0, q1) and (r0, r1) represent the quotient and -/// the remainder respectively. -/// -/// # Errors -/// Returns an error if the divisor is ZERO. -pub fn push_u64_div_result( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result<(), ExecutionError> { - let divisor_hi = process.get_stack_item(0).as_int(); - let divisor_lo = process.get_stack_item(1).as_int(); - let divisor = (divisor_hi << 32) + divisor_lo; - - if divisor == 0 { - return Err(ExecutionError::DivideByZero(process.clk())); - } - - let dividend_hi = process.get_stack_item(2).as_int(); - let dividend_lo = process.get_stack_item(3).as_int(); - let dividend = (dividend_hi << 32) + dividend_lo; - - let quotient = dividend / divisor; - let remainder = dividend - quotient * divisor; - - let (q_hi, q_lo) = u64_to_u32_elements(quotient); - let (r_hi, r_lo) = u64_to_u32_elements(remainder); - - advice_provider.push_stack(AdviceSource::Value(r_hi))?; - advice_provider.push_stack(AdviceSource::Value(r_lo))?; - advice_provider.push_stack(AdviceSource::Value(q_hi))?; - advice_provider.push_stack(AdviceSource::Value(q_lo))?; - - Ok(()) -} - -/// Given an element in a quadratic extension field on the top of the stack (i.e., a0, b1), -/// computes its multiplicative inverse and push the result onto the advice stack. -/// -/// Inputs: -/// Operand stack: [a1, a0, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [a1, a0, ...] -/// Advice stack: [b0, b1...] -/// -/// Where (b0, b1) is the multiplicative inverse of the extension field element (a0, a1) at the -/// top of the stack. -/// -/// # Errors -/// Returns an error if the input is a zero element in the extension field. -pub fn push_ext2_inv_result( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result<(), ExecutionError> { - let coef0 = process.get_stack_item(1); - let coef1 = process.get_stack_item(0); - - let element = QuadFelt::new(coef0, coef1); - if element == QuadFelt::ZERO { - return Err(ExecutionError::DivideByZero(process.clk())); - } - let result = element.inv().to_base_elements(); - - advice_provider.push_stack(AdviceSource::Value(result[1]))?; - advice_provider.push_stack(AdviceSource::Value(result[0]))?; - - Ok(()) -} - -/// Given evaluations of a polynomial over some specified domain, interpolates the evaluations -/// into a polynomial in coefficient form and pushes the result into the advice stack. -/// -/// The interpolation is performed using the iNTT algorithm. The evaluations are expected to be -/// in the quadratic extension. -/// -/// Inputs: -/// Operand stack: [output_size, input_size, input_start_ptr, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [output_size, input_size, input_start_ptr, ...] -/// Advice stack: [coefficients...] -/// -/// - `input_size` is the number of evaluations (each evaluation is 2 base field elements). Must be -/// a power of 2 and greater 1. -/// - `output_size` is the number of coefficients in the interpolated polynomial (each coefficient -/// is 2 base field elements). Must be smaller than or equal to the number of input evaluations. -/// - `input_start_ptr` is the memory address of the first evaluation. -/// - `coefficients` are the coefficients of the interpolated polynomial such that lowest degree -/// coefficients are located at the top of the advice stack. -/// -/// # Errors -/// Returns an error if: -/// - `input_size` less than or equal to 1, or is not a power of 2. -/// - `output_size` is 0 or is greater than the `input_size`. -/// - `input_ptr` is greater than 2^32. -/// - `input_ptr + input_size / 2` is greater than 2^32. -pub fn push_ext2_intt_result( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result<(), ExecutionError> { - let output_size = process.get_stack_item(0).as_int() as usize; - let input_size = process.get_stack_item(1).as_int() as usize; - let input_start_ptr = process.get_stack_item(2).as_int(); - - if input_size <= 1 { - return Err(Ext2InttError::DomainSizeTooSmall(input_size as u64).into()); - } - if !input_size.is_power_of_two() { - return Err(Ext2InttError::DomainSizeNotPowerOf2(input_size as u64).into()); - } - if input_start_ptr >= u32::MAX as u64 { - return Err(Ext2InttError::InputStartAddressTooBig(input_start_ptr).into()); - } - if input_size > u32::MAX as usize { - return Err(Ext2InttError::InputSizeTooBig(input_size as u64).into()); - } - - let input_end_ptr = input_start_ptr + (input_size / 2) as u64; - if input_end_ptr > u32::MAX as u64 { - return Err(Ext2InttError::InputEndAddressTooBig(input_end_ptr).into()); - } - - if output_size == 0 { - return Err(Ext2InttError::OutputSizeIsZero.into()); - } - if output_size > input_size { - return Err(Ext2InttError::OutputSizeTooBig(output_size, input_size).into()); - } - - let mut poly = Vec::with_capacity(input_size); - for addr in (input_start_ptr as u32)..(input_end_ptr as u32) { - let word = process - .get_mem_value(process.ctx(), addr) - .ok_or(Ext2InttError::UninitializedMemoryAddress(addr))?; - - poly.push(QuadFelt::new(word[0], word[1])); - poly.push(QuadFelt::new(word[2], word[3])); - } - - let twiddles = fft::get_inv_twiddles::(input_size); - fft::interpolate_poly::(&mut poly, &twiddles); - - for element in QuadFelt::slice_as_base_elements(&poly[..output_size]).iter().rev() { - advice_provider.push_stack(AdviceSource::Value(*element))?; - } - - Ok(()) -} - -/// Pushes values onto the advice stack which are required for verification of a DSA in Miden -/// VM. -/// -/// Inputs: -/// Operand stack: [PK, MSG, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [PK, MSG, ...] -/// Advice stack: \[DATA\] -/// -/// Where: -/// - PK is the digest of an expanded public. -/// - MSG is the digest of the message to be signed. -/// - DATA is the needed data for signature verification in the VM. -/// -/// The advice provider is expected to contain the private key associated to the public key PK. -pub fn push_signature( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, - kind: SignatureKind, -) -> Result<(), ExecutionError> { - let pub_key = process.get_stack_word(0); - let msg = process.get_stack_word(1); - let result: Vec = get_signature(advice_provider, kind, pub_key, msg)?; - for r in result { - advice_provider.push_stack(AdviceSource::Value(r))?; - } - Ok(()) -} - -/// Pushes the number of the leading zeros of the top stack element onto the advice stack. -/// -/// Inputs: -/// Operand stack: [n, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [n, ...] -/// Advice stack: [leading_zeros, ...] -pub fn push_leading_zeros( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result<(), ExecutionError> { - push_transformed_stack_top(advice_provider, process, |stack_top| { - Felt::from(stack_top.leading_zeros()) - }) -} - -/// Pushes the number of the trailing zeros of the top stack element onto the advice stack. -/// -/// Inputs: -/// Operand stack: [n, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [n, ...] -/// Advice stack: [trailing_zeros, ...] -pub fn push_trailing_zeros( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result<(), ExecutionError> { - push_transformed_stack_top(advice_provider, process, |stack_top| { - Felt::from(stack_top.trailing_zeros()) - }) -} - -/// Pushes the number of the leading ones of the top stack element onto the advice stack. -/// -/// Inputs: -/// Operand stack: [n, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [n, ...] -/// Advice stack: [leading_ones, ...] -pub fn push_leading_ones( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result<(), ExecutionError> { - push_transformed_stack_top(advice_provider, process, |stack_top| { - Felt::from(stack_top.leading_ones()) - }) -} - -/// Pushes the number of the trailing ones of the top stack element onto the advice stack. -/// -/// Inputs: -/// Operand stack: [n, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [n, ...] -/// Advice stack: [trailing_ones, ...] -pub fn push_trailing_ones( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result<(), ExecutionError> { - push_transformed_stack_top(advice_provider, process, |stack_top| { - Felt::from(stack_top.trailing_ones()) - }) -} - -/// Pushes the base 2 logarithm of the top stack element, rounded down. -/// Inputs: -/// Operand stack: [n, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [n, ...] -/// Advice stack: [ilog2(n), ...] -/// -/// # Errors -/// Returns an error if the logarithm argument (top stack element) equals ZERO. -pub fn push_ilog2( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result<(), ExecutionError> { - let n = process.get_stack_item(0).as_int(); - if n == 0 { - return Err(ExecutionError::LogArgumentZero(process.clk())); - } - let ilog2 = Felt::from(n.ilog2()); - advice_provider.push_stack(AdviceSource::Value(ilog2))?; - Ok(()) -} - -/// Pushes onto the advice stack the value associated with the specified key in a Sparse -/// Merkle Tree defined by the specified root. -/// -/// If no value was previously associated with the specified key, [ZERO; 4] is pushed onto -/// the advice stack. -/// -/// Inputs: -/// Operand stack: [KEY, ROOT, ...] -/// Advice stack: [...] -/// -/// Outputs: -/// Operand stack: [KEY, ROOT, ...] -/// Advice stack: [VALUE, ...] -/// -/// # Errors -/// Returns an error if the provided Merkle root doesn't exist on the advice provider. -/// -/// # Panics -/// Will panic as unimplemented if the target depth is `64`. -pub fn push_smtpeek_result( - advice_provider: &mut impl AdviceProvider, - process: ProcessState, -) -> Result<(), ExecutionError> { - let empty_leaf = EmptySubtreeRoots::entry(SMT_DEPTH, SMT_DEPTH); - // fetch the arguments from the operand stack - let key = process.get_stack_word(0); - let root = process.get_stack_word(1); - - // get the node from the SMT for the specified key; this node can be either a leaf node, - // or a root of an empty subtree at the returned depth - let node = advice_provider.get_tree_node(root, &Felt::new(SMT_DEPTH as u64), &key[3])?; - - if node == Word::from(empty_leaf) { - // if the node is a root of an empty subtree, then there is no value associated with - // the specified key - advice_provider.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE))?; - } else { - let leaf_preimage = get_smt_leaf_preimage(advice_provider, node)?; - - for (key_in_leaf, value_in_leaf) in leaf_preimage { - if key == key_in_leaf { - // Found key - push value associated with key, and return - advice_provider.push_stack(AdviceSource::Word(value_in_leaf))?; - - return Ok(()); - } - } - - // if we can't find any key in the leaf that matches `key`, it means no value is - // associated with `key` - advice_provider.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE))?; - } - Ok(()) -} - -/// Returns a signature on a message using a public key. -pub fn get_signature( - advice_provider: &impl AdviceProvider, - kind: SignatureKind, - pub_key: Word, - msg: Word, -) -> Result, ExecutionError> { - let pk_sk = advice_provider - .get_mapped_values(&pub_key.into()) - .ok_or(ExecutionError::AdviceMapKeyNotFound(pub_key))?; - - match kind { - SignatureKind::RpoFalcon512 => dsa::falcon_sign(pk_sk, msg), - } -} - -// HELPER METHODS -// -------------------------------------------------------------------------------------------- - -/// Reads (start_addr, end_addr) tuple from the specified elements of the operand stack ( -/// without modifying the state of the stack), and verifies that memory range is valid. -fn get_mem_addr_range( - process: ProcessState, - start_idx: usize, - end_idx: usize, -) -> Result<(u32, u32), ExecutionError> { - let start_addr = process.get_stack_item(start_idx).as_int(); - let end_addr = process.get_stack_item(end_idx).as_int(); - - if start_addr > u32::MAX as u64 { - return Err(ExecutionError::MemoryAddressOutOfBounds(start_addr)); - } - if end_addr > u32::MAX as u64 { - return Err(ExecutionError::MemoryAddressOutOfBounds(end_addr)); - } - - if start_addr > end_addr { - return Err(ExecutionError::InvalidMemoryRange { start_addr, end_addr }); - } - - Ok((start_addr as u32, end_addr as u32)) -} - -fn u64_to_u32_elements(value: u64) -> (Felt, Felt) { - let hi = Felt::from((value >> 32) as u32); - let lo = Felt::from(value as u32); - (hi, lo) -} - -/// Gets the top stack element, applies a provided function to it and pushes it to the advice -/// provider. -fn push_transformed_stack_top( - advice_provider: &mut A, - process: ProcessState, - f: impl FnOnce(u32) -> Felt, -) -> Result<(), ExecutionError> { - let stack_top = process.get_stack_item(0); - let stack_top: u32 = stack_top - .as_int() - .try_into() - .map_err(|_| ExecutionError::NotU32Value(stack_top, ZERO))?; - let transformed_stack_top = f(stack_top); - advice_provider.push_stack(AdviceSource::Value(transformed_stack_top))?; - Ok(()) -} - -fn get_smt_leaf_preimage( - advice_provider: &A, - node: Word, -) -> Result, ExecutionError> { - let node_bytes = RpoDigest::from(node); - - let kv_pairs = advice_provider - .get_mapped_values(&node_bytes) - .ok_or(ExecutionError::SmtNodeNotFound(node))?; - - if kv_pairs.len() % WORD_SIZE * 2 != 0 { - return Err(ExecutionError::SmtNodePreImageNotValid(node, kv_pairs.len())); - } - - Ok(kv_pairs - .chunks_exact(WORD_SIZE * 2) - .map(|kv_chunk| { - let key = [kv_chunk[0], kv_chunk[1], kv_chunk[2], kv_chunk[3]]; - let value = [kv_chunk[4], kv_chunk[5], kv_chunk[6], kv_chunk[7]]; - - (key, value) - }) - .collect()) -} diff --git a/processor/src/host/advice/providers.rs b/processor/src/host/advice/providers.rs index 9a1717c9e6..6edcb85ad1 100644 --- a/processor/src/host/advice/providers.rs +++ b/processor/src/host/advice/providers.rs @@ -1,8 +1,9 @@ use alloc::{collections::BTreeMap, vec::Vec}; +use vm_core::crypto::merkle::{MerkleStore, NodeIndex, StoreNode}; + use super::{ - AdviceInputs, AdviceProvider, AdviceSource, ExecutionError, Felt, MerklePath, MerkleStore, - NodeIndex, RpoDigest, StoreNode, Word, + AdviceInputs, AdviceProvider, AdviceSource, ExecutionError, Felt, MerklePath, RpoDigest, Word, }; use crate::{ utils::collections::{KvMap, RecordingMap}, diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 76a07dd5ca..cf0a80bc9c 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -18,9 +18,10 @@ pub use vm_core::{ crypto::merkle::SMT_DEPTH, errors::InputError, mast::{MastForest, MastNode, MastNodeId}, + sys_events::SystemEvent, utils::{collections::KvMap, DeserializationError}, - AdviceInjector, AssemblyOp, Felt, Kernel, Operation, Program, ProgramInfo, QuadExtension, - StackInputs, StackOutputs, Word, EMPTY_WORD, ONE, ZERO, + AssemblyOp, Felt, Kernel, Operation, Program, ProgramInfo, QuadExtension, StackInputs, + StackOutputs, Word, EMPTY_WORD, ONE, ZERO, }; use vm_core::{ mast::{ diff --git a/processor/src/host/advice/dsa.rs b/processor/src/operations/sys_ops/dsa.rs similarity index 94% rename from processor/src/host/advice/dsa.rs rename to processor/src/operations/sys_ops/dsa.rs index 0311999c10..5c7fa1c813 100644 --- a/processor/src/host/advice/dsa.rs +++ b/processor/src/operations/sys_ops/dsa.rs @@ -1,6 +1,12 @@ use alloc::vec::Vec; -use super::{ExecutionError, Felt, Word}; +use vm_core::{ + crypto::dsa::rpo_falcon512::{Polynomial, SecretKey}, + utils::Deserializable, + Felt, Word, +}; + +use crate::ExecutionError; /// Gets as input a vector containing a secret key, and a word representing a message and outputs a /// vector of values to be pushed onto the advice stack. @@ -18,11 +24,6 @@ use super::{ExecutionError, Felt, Word}; /// - The signature generation failed. #[cfg(feature = "std")] pub fn falcon_sign(sk: &[Felt], msg: Word) -> Result, ExecutionError> { - use vm_core::{ - crypto::dsa::rpo_falcon512::{Polynomial, SecretKey}, - utils::Deserializable, - }; - // Create the corresponding secret key let mut sk_bytes = Vec::with_capacity(sk.len()); for element in sk { diff --git a/processor/src/operations/sys_ops.rs b/processor/src/operations/sys_ops/mod.rs similarity index 77% rename from processor/src/operations/sys_ops.rs rename to processor/src/operations/sys_ops/mod.rs index ac3a119762..d29a23cb54 100644 --- a/processor/src/operations/sys_ops.rs +++ b/processor/src/operations/sys_ops/mod.rs @@ -1,4 +1,4 @@ -use vm_core::{AdviceInjector, Felt, Operation, SignatureKind, ZERO}; +use vm_core::{sys_events::SystemEvent, Felt, Operation}; use super::{ super::{ @@ -7,16 +7,9 @@ use super::{ }, ExecutionError, Process, }; -use crate::{ - host::advice::{ - copy_map_value_to_adv_stack, copy_merkle_node_to_adv_stack, insert_hdword_into_adv_map, - insert_hperm_into_adv_map, insert_mem_values_into_adv_map, merge_merkle_nodes, - push_ext2_intt_result, push_ext2_inv_result, push_ilog2, push_leading_ones, - push_leading_zeros, push_signature, push_smtpeek_result, push_trailing_ones, - push_trailing_zeros, push_u64_div_result, - }, - Host, ProcessState, -}; +use crate::Host; +mod dsa; +mod sys_event_handlers; // SYSTEM OPERATIONS // ================================================================================================ @@ -132,58 +125,13 @@ impl Process { self.stack.copy_state(0); self.decoder.set_user_op_helpers(Operation::Emit(event_id), &[event_id.into()]); - // If it's a native event, handle it directly. Otherwise, forward it to the host. - if let Some(advice_injector) = AdviceInjector::from_event_id(event_id) { - self.handle_advice_injector(advice_injector, host) + // If it's a system event, handle it directly. Otherwise, forward it to the host. + if let Some(system_event) = SystemEvent::from_event_id(event_id) { + self.handle_sytem_event(system_event, host) } else { host.on_event(self.into(), event_id) } } - - fn handle_advice_injector( - &self, - advice_injector: AdviceInjector, - host: &mut impl Host, - ) -> Result<(), ExecutionError> { - let advice_provider = host.advice_provider_mut(); - let process_state: ProcessState = self.into(); - match advice_injector { - AdviceInjector::MerkleNodeMerge => merge_merkle_nodes(advice_provider, process_state), - AdviceInjector::MerkleNodeToStack => { - copy_merkle_node_to_adv_stack(advice_provider, process_state) - }, - AdviceInjector::MapValueToStack => { - copy_map_value_to_adv_stack(advice_provider, process_state, false) - }, - AdviceInjector::MapValueToStackN => { - copy_map_value_to_adv_stack(advice_provider, process_state, true) - }, - AdviceInjector::U64Div => push_u64_div_result(advice_provider, process_state), - AdviceInjector::Ext2Inv => push_ext2_inv_result(advice_provider, process_state), - AdviceInjector::Ext2Intt => push_ext2_intt_result(advice_provider, process_state), - AdviceInjector::SmtPeek => push_smtpeek_result(advice_provider, process_state), - AdviceInjector::U32Clz => push_leading_zeros(advice_provider, process_state), - AdviceInjector::U32Ctz => push_trailing_zeros(advice_provider, process_state), - AdviceInjector::U32Clo => push_leading_ones(advice_provider, process_state), - AdviceInjector::U32Cto => push_trailing_ones(advice_provider, process_state), - AdviceInjector::ILog2 => push_ilog2(advice_provider, process_state), - - AdviceInjector::MemToMap => { - insert_mem_values_into_adv_map(advice_provider, process_state) - }, - AdviceInjector::HdwordToMap => { - insert_hdword_into_adv_map(advice_provider, process_state, ZERO) - }, - AdviceInjector::HdwordToMapWithDomain => { - let domain = self.stack.get(8); - insert_hdword_into_adv_map(advice_provider, process_state, domain) - }, - AdviceInjector::HpermToMap => insert_hperm_into_adv_map(advice_provider, process_state), - AdviceInjector::FalconSigToStack => { - push_signature(advice_provider, process_state, SignatureKind::RpoFalcon512) - }, - } - } } // TESTS diff --git a/processor/src/operations/sys_ops/sys_event_handlers.rs b/processor/src/operations/sys_ops/sys_event_handlers.rs new file mode 100644 index 0000000000..13e4a796be --- /dev/null +++ b/processor/src/operations/sys_ops/sys_event_handlers.rs @@ -0,0 +1,732 @@ +use alloc::vec::Vec; + +use vm_core::{ + crypto::{ + hash::{Rpo256, RpoDigest}, + merkle::{EmptySubtreeRoots, Smt, SMT_DEPTH}, + }, + sys_events::SystemEvent, + Felt, FieldElement, SignatureKind, Word, EMPTY_WORD, WORD_SIZE, ZERO, +}; +use winter_prover::math::fft; + +use super::dsa; +use crate::{ + AdviceProvider, AdviceSource, ExecutionError, Ext2InttError, Host, Process, ProcessState, + QuadFelt, +}; + +/// The offset of the domain value on the stack in the `hdword_to_map_with_domain` system event. +const HDWORD_TO_MAP_WITH_DOMAIN_DOMAIN_OFFSET: usize = 8; + +impl Process { + pub(super) fn handle_sytem_event( + &self, + system_event: SystemEvent, + host: &mut impl Host, + ) -> Result<(), ExecutionError> { + let advice_provider = host.advice_provider_mut(); + let process_state: ProcessState = self.into(); + match system_event { + SystemEvent::MerkleNodeMerge => merge_merkle_nodes(advice_provider, process_state), + SystemEvent::MerkleNodeToStack => { + copy_merkle_node_to_adv_stack(advice_provider, process_state) + }, + SystemEvent::MapValueToStack => { + copy_map_value_to_adv_stack(advice_provider, process_state, false) + }, + SystemEvent::MapValueToStackN => { + copy_map_value_to_adv_stack(advice_provider, process_state, true) + }, + SystemEvent::U64Div => push_u64_div_result(advice_provider, process_state), + SystemEvent::Ext2Inv => push_ext2_inv_result(advice_provider, process_state), + SystemEvent::Ext2Intt => push_ext2_intt_result(advice_provider, process_state), + SystemEvent::SmtPeek => push_smtpeek_result(advice_provider, process_state), + SystemEvent::U32Clz => push_leading_zeros(advice_provider, process_state), + SystemEvent::U32Ctz => push_trailing_zeros(advice_provider, process_state), + SystemEvent::U32Clo => push_leading_ones(advice_provider, process_state), + SystemEvent::U32Cto => push_trailing_ones(advice_provider, process_state), + SystemEvent::ILog2 => push_ilog2(advice_provider, process_state), + + SystemEvent::MemToMap => insert_mem_values_into_adv_map(advice_provider, process_state), + SystemEvent::HdwordToMap => { + insert_hdword_into_adv_map(advice_provider, process_state, ZERO) + }, + SystemEvent::HdwordToMapWithDomain => { + let domain = self.stack.get(HDWORD_TO_MAP_WITH_DOMAIN_DOMAIN_OFFSET); + insert_hdword_into_adv_map(advice_provider, process_state, domain) + }, + SystemEvent::HpermToMap => insert_hperm_into_adv_map(advice_provider, process_state), + SystemEvent::FalconSigToStack => { + push_signature(advice_provider, process_state, SignatureKind::RpoFalcon512) + }, + } + } +} + +/// Reads words from memory at the specified range and inserts them into the advice map under +/// the key `KEY` located at the top of the stack. +/// +/// Inputs: +/// Operand stack: [KEY, start_addr, end_addr, ...] +/// Advice map: {...} +/// +/// Outputs: +/// Operand stack: [KEY, start_addr, end_addr, ...] +/// Advice map: {KEY: values} +/// +/// Where `values` are the elements located in memory[start_addr..end_addr]. +/// +/// # Errors +/// Returns an error: +/// - `start_addr` is greater than or equal to 2^32. +/// - `end_addr` is greater than or equal to 2^32. +/// - `start_addr` > `end_addr`. +pub fn insert_mem_values_into_adv_map( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let (start_addr, end_addr) = get_mem_addr_range(process, 4, 5)?; + let ctx = process.ctx(); + + let mut values = Vec::with_capacity(((end_addr - start_addr) as usize) * WORD_SIZE); + for addr in start_addr..end_addr { + let mem_value = process.get_mem_value(ctx, addr).unwrap_or(EMPTY_WORD); + values.extend_from_slice(&mem_value); + } + + let key = process.get_stack_word(0); + advice_provider.insert_into_map(key, values); + + Ok(()) +} + +/// Reads two word from the operand stack and inserts them into the advice map under the key +/// defined by the hash of these words. +/// +/// Inputs: +/// Operand stack: [B, A, ...] +/// Advice map: {...} +/// +/// Outputs: +/// Operand stack: [B, A, ...] +/// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} +/// +/// Where KEY is computed as hash(A || B, domain), where domain is provided via the immediate +/// value. +pub fn insert_hdword_into_adv_map( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, + domain: Felt, +) -> Result<(), ExecutionError> { + // get the top two words from the stack and hash them to compute the key value + let word0 = process.get_stack_word(0); + let word1 = process.get_stack_word(1); + let key = Rpo256::merge_in_domain(&[word1.into(), word0.into()], domain); + + // build a vector of values from the two word and insert it into the advice map under the + // computed key + let mut values = Vec::with_capacity(2 * WORD_SIZE); + values.extend_from_slice(&word1); + values.extend_from_slice(&word0); + advice_provider.insert_into_map(key.into(), values); + + Ok(()) +} + +/// Reads three words from the operand stack and inserts the top two words into the advice map +/// under the key defined by applying an RPO permutation to all three words. +/// +/// Inputs: +/// Operand stack: [B, A, C, ...] +/// Advice map: {...} +/// +/// Outputs: +/// Operand stack: [B, A, C, ...] +/// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} +/// +/// Where KEY is computed by extracting the digest elements from hperm([C, A, B]). For example, +/// if C is [0, d, 0, 0], KEY will be set as hash(A || B, d). +pub fn insert_hperm_into_adv_map( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + // read the state from the stack + let mut state = [ + process.get_stack_item(11), + process.get_stack_item(10), + process.get_stack_item(9), + process.get_stack_item(8), + process.get_stack_item(7), + process.get_stack_item(6), + process.get_stack_item(5), + process.get_stack_item(4), + process.get_stack_item(3), + process.get_stack_item(2), + process.get_stack_item(1), + process.get_stack_item(0), + ]; + + // get the values to be inserted into the advice map from the state + let values = state[Rpo256::RATE_RANGE].to_vec(); + + // apply the permutation to the state and extract the key from it + Rpo256::apply_permutation(&mut state); + let key = RpoDigest::new( + state[Rpo256::DIGEST_RANGE] + .try_into() + .expect("failed to extract digest from state"), + ); + + advice_provider.insert_into_map(key.into(), values); + + Ok(()) +} + +/// Creates a new Merkle tree in the advice provider by combining Merkle trees with the +/// specified roots. The root of the new tree is defined as `Hash(LEFT_ROOT, RIGHT_ROOT)`. +/// +/// Inputs: +/// Operand stack: [RIGHT_ROOT, LEFT_ROOT, ...] +/// Merkle store: {RIGHT_ROOT, LEFT_ROOT} +/// +/// Outputs: +/// Operand stack: [RIGHT_ROOT, LEFT_ROOT, ...] +/// Merkle store: {RIGHT_ROOT, LEFT_ROOT, hash(LEFT_ROOT, RIGHT_ROOT)} +/// +/// After the operation, both the original trees and the new tree remains in the advice +/// provider (i.e., the input trees are not removed). +/// +/// It is not checked whether the provided roots exist as Merkle trees in the advide providers. +pub fn merge_merkle_nodes( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + // fetch the arguments from the stack + let lhs = process.get_stack_word(1); + let rhs = process.get_stack_word(0); + + // perform the merge + advice_provider.merge_roots(lhs, rhs)?; + + Ok(()) +} + +/// Pushes a node of the Merkle tree specified by the values on the top of the operand stack +/// onto the advice stack. +/// +/// Inputs: +/// Operand stack: [depth, index, TREE_ROOT, ...] +/// Advice stack: [...] +/// Merkle store: {TREE_ROOT<-NODE} +/// +/// Outputs: +/// Operand stack: [depth, index, TREE_ROOT, ...] +/// Advice stack: [NODE, ...] +/// Merkle store: {TREE_ROOT<-NODE} +/// +/// # Errors +/// Returns an error if: +/// - Merkle tree for the specified root cannot be found in the advice provider. +/// - The specified depth is either zero or greater than the depth of the Merkle tree identified by +/// the specified root. +/// - Value of the node at the specified depth and index is not known to the advice provider. +pub fn copy_merkle_node_to_adv_stack( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let depth = process.get_stack_item(0); + let index = process.get_stack_item(1); + let root = [ + process.get_stack_item(5), + process.get_stack_item(4), + process.get_stack_item(3), + process.get_stack_item(2), + ]; + + let node = advice_provider.get_tree_node(root, &depth, &index)?; + + advice_provider.push_stack(AdviceSource::Value(node[3]))?; + advice_provider.push_stack(AdviceSource::Value(node[2]))?; + advice_provider.push_stack(AdviceSource::Value(node[1]))?; + advice_provider.push_stack(AdviceSource::Value(node[0]))?; + + Ok(()) +} + +/// Pushes a list of field elements onto the advice stack. The list is looked up in the advice +/// map using the specified word from the operand stack as the key. If `include_len` is set to +/// true, the number of elements in the value is also pushed onto the advice stack. +/// +/// Inputs: +/// Operand stack: [..., KEY, ...] +/// Advice stack: [...] +/// Advice map: {KEY: values} +/// +/// Outputs: +/// Operand stack: [..., KEY, ...] +/// Advice stack: [values_len?, values, ...] +/// Advice map: {KEY: values} +/// +/// The `key_offset` value specifies the location of the `KEY` on the stack. For example, +/// offset value of 0 indicates that the top word on the stack should be used as the key, the +/// offset value of 4, indicates that the second word on the stack should be used as the key +/// etc. +/// +/// The valid values of `key_offset` are 0 through 12 (inclusive). +/// +/// # Errors +/// Returns an error if the required key was not found in the key-value map or if stack offset +/// is greater than 12. +pub fn copy_map_value_to_adv_stack( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, + include_len: bool, +) -> Result<(), ExecutionError> { + let key = [ + process.get_stack_item(3), + process.get_stack_item(2), + process.get_stack_item(1), + process.get_stack_item(0), + ]; + advice_provider.push_stack(AdviceSource::Map { key, include_len })?; + + Ok(()) +} + +/// Pushes the result of [u64] division (both the quotient and the remainder) onto the advice +/// stack. +/// +/// Inputs: +/// Operand stack: [b1, b0, a1, a0, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [b1, b0, a1, a0, ...] +/// Advice stack: [q0, q1, r0, r1, ...] +/// +/// Where (a0, a1) and (b0, b1) are the 32-bit limbs of the dividend and the divisor +/// respectively (with a0 representing the 32 lest significant bits and a1 representing the +/// 32 most significant bits). Similarly, (q0, q1) and (r0, r1) represent the quotient and +/// the remainder respectively. +/// +/// # Errors +/// Returns an error if the divisor is ZERO. +pub fn push_u64_div_result( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let divisor_hi = process.get_stack_item(0).as_int(); + let divisor_lo = process.get_stack_item(1).as_int(); + let divisor = (divisor_hi << 32) + divisor_lo; + + if divisor == 0 { + return Err(ExecutionError::DivideByZero(process.clk())); + } + + let dividend_hi = process.get_stack_item(2).as_int(); + let dividend_lo = process.get_stack_item(3).as_int(); + let dividend = (dividend_hi << 32) + dividend_lo; + + let quotient = dividend / divisor; + let remainder = dividend - quotient * divisor; + + let (q_hi, q_lo) = u64_to_u32_elements(quotient); + let (r_hi, r_lo) = u64_to_u32_elements(remainder); + + advice_provider.push_stack(AdviceSource::Value(r_hi))?; + advice_provider.push_stack(AdviceSource::Value(r_lo))?; + advice_provider.push_stack(AdviceSource::Value(q_hi))?; + advice_provider.push_stack(AdviceSource::Value(q_lo))?; + + Ok(()) +} + +/// Given an element in a quadratic extension field on the top of the stack (i.e., a0, b1), +/// computes its multiplicative inverse and push the result onto the advice stack. +/// +/// Inputs: +/// Operand stack: [a1, a0, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [a1, a0, ...] +/// Advice stack: [b0, b1...] +/// +/// Where (b0, b1) is the multiplicative inverse of the extension field element (a0, a1) at the +/// top of the stack. +/// +/// # Errors +/// Returns an error if the input is a zero element in the extension field. +pub fn push_ext2_inv_result( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let coef0 = process.get_stack_item(1); + let coef1 = process.get_stack_item(0); + + let element = QuadFelt::new(coef0, coef1); + if element == QuadFelt::ZERO { + return Err(ExecutionError::DivideByZero(process.clk())); + } + let result = element.inv().to_base_elements(); + + advice_provider.push_stack(AdviceSource::Value(result[1]))?; + advice_provider.push_stack(AdviceSource::Value(result[0]))?; + + Ok(()) +} + +/// Given evaluations of a polynomial over some specified domain, interpolates the evaluations +/// into a polynomial in coefficient form and pushes the result into the advice stack. +/// +/// The interpolation is performed using the iNTT algorithm. The evaluations are expected to be +/// in the quadratic extension. +/// +/// Inputs: +/// Operand stack: [output_size, input_size, input_start_ptr, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [output_size, input_size, input_start_ptr, ...] +/// Advice stack: [coefficients...] +/// +/// - `input_size` is the number of evaluations (each evaluation is 2 base field elements). Must be +/// a power of 2 and greater 1. +/// - `output_size` is the number of coefficients in the interpolated polynomial (each coefficient +/// is 2 base field elements). Must be smaller than or equal to the number of input evaluations. +/// - `input_start_ptr` is the memory address of the first evaluation. +/// - `coefficients` are the coefficients of the interpolated polynomial such that lowest degree +/// coefficients are located at the top of the advice stack. +/// +/// # Errors +/// Returns an error if: +/// - `input_size` less than or equal to 1, or is not a power of 2. +/// - `output_size` is 0 or is greater than the `input_size`. +/// - `input_ptr` is greater than 2^32. +/// - `input_ptr + input_size / 2` is greater than 2^32. +pub fn push_ext2_intt_result( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let output_size = process.get_stack_item(0).as_int() as usize; + let input_size = process.get_stack_item(1).as_int() as usize; + let input_start_ptr = process.get_stack_item(2).as_int(); + + if input_size <= 1 { + return Err(Ext2InttError::DomainSizeTooSmall(input_size as u64).into()); + } + if !input_size.is_power_of_two() { + return Err(Ext2InttError::DomainSizeNotPowerOf2(input_size as u64).into()); + } + if input_start_ptr >= u32::MAX as u64 { + return Err(Ext2InttError::InputStartAddressTooBig(input_start_ptr).into()); + } + if input_size > u32::MAX as usize { + return Err(Ext2InttError::InputSizeTooBig(input_size as u64).into()); + } + + let input_end_ptr = input_start_ptr + (input_size / 2) as u64; + if input_end_ptr > u32::MAX as u64 { + return Err(Ext2InttError::InputEndAddressTooBig(input_end_ptr).into()); + } + + if output_size == 0 { + return Err(Ext2InttError::OutputSizeIsZero.into()); + } + if output_size > input_size { + return Err(Ext2InttError::OutputSizeTooBig(output_size, input_size).into()); + } + + let mut poly = Vec::with_capacity(input_size); + for addr in (input_start_ptr as u32)..(input_end_ptr as u32) { + let word = process + .get_mem_value(process.ctx(), addr) + .ok_or(Ext2InttError::UninitializedMemoryAddress(addr))?; + + poly.push(QuadFelt::new(word[0], word[1])); + poly.push(QuadFelt::new(word[2], word[3])); + } + + let twiddles = fft::get_inv_twiddles::(input_size); + fft::interpolate_poly::(&mut poly, &twiddles); + + for element in QuadFelt::slice_as_base_elements(&poly[..output_size]).iter().rev() { + advice_provider.push_stack(AdviceSource::Value(*element))?; + } + + Ok(()) +} + +/// Pushes values onto the advice stack which are required for verification of a DSA in Miden +/// VM. +/// +/// Inputs: +/// Operand stack: [PK, MSG, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [PK, MSG, ...] +/// Advice stack: \[DATA\] +/// +/// Where: +/// - PK is the digest of an expanded public. +/// - MSG is the digest of the message to be signed. +/// - DATA is the needed data for signature verification in the VM. +/// +/// The advice provider is expected to contain the private key associated to the public key PK. +pub fn push_signature( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, + kind: SignatureKind, +) -> Result<(), ExecutionError> { + let pub_key = process.get_stack_word(0); + let msg = process.get_stack_word(1); + let result: Vec = get_signature(advice_provider, kind, pub_key, msg)?; + for r in result { + advice_provider.push_stack(AdviceSource::Value(r))?; + } + Ok(()) +} + +/// Pushes the number of the leading zeros of the top stack element onto the advice stack. +/// +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [leading_zeros, ...] +pub fn push_leading_zeros( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + push_transformed_stack_top(advice_provider, process, |stack_top| { + Felt::from(stack_top.leading_zeros()) + }) +} + +/// Pushes the number of the trailing zeros of the top stack element onto the advice stack. +/// +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [trailing_zeros, ...] +pub fn push_trailing_zeros( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + push_transformed_stack_top(advice_provider, process, |stack_top| { + Felt::from(stack_top.trailing_zeros()) + }) +} + +/// Pushes the number of the leading ones of the top stack element onto the advice stack. +/// +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [leading_ones, ...] +pub fn push_leading_ones( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + push_transformed_stack_top(advice_provider, process, |stack_top| { + Felt::from(stack_top.leading_ones()) + }) +} + +/// Pushes the number of the trailing ones of the top stack element onto the advice stack. +/// +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [trailing_ones, ...] +pub fn push_trailing_ones( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + push_transformed_stack_top(advice_provider, process, |stack_top| { + Felt::from(stack_top.trailing_ones()) + }) +} + +/// Pushes the base 2 logarithm of the top stack element, rounded down. +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [ilog2(n), ...] +/// +/// # Errors +/// Returns an error if the logarithm argument (top stack element) equals ZERO. +pub fn push_ilog2( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let n = process.get_stack_item(0).as_int(); + if n == 0 { + return Err(ExecutionError::LogArgumentZero(process.clk())); + } + let ilog2 = Felt::from(n.ilog2()); + advice_provider.push_stack(AdviceSource::Value(ilog2))?; + Ok(()) +} + +/// Pushes onto the advice stack the value associated with the specified key in a Sparse +/// Merkle Tree defined by the specified root. +/// +/// If no value was previously associated with the specified key, [ZERO; 4] is pushed onto +/// the advice stack. +/// +/// Inputs: +/// Operand stack: [KEY, ROOT, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [KEY, ROOT, ...] +/// Advice stack: [VALUE, ...] +/// +/// # Errors +/// Returns an error if the provided Merkle root doesn't exist on the advice provider. +/// +/// # Panics +/// Will panic as unimplemented if the target depth is `64`. +pub fn push_smtpeek_result( + advice_provider: &mut impl AdviceProvider, + process: ProcessState, +) -> Result<(), ExecutionError> { + let empty_leaf = EmptySubtreeRoots::entry(SMT_DEPTH, SMT_DEPTH); + // fetch the arguments from the operand stack + let key = process.get_stack_word(0); + let root = process.get_stack_word(1); + + // get the node from the SMT for the specified key; this node can be either a leaf node, + // or a root of an empty subtree at the returned depth + let node = advice_provider.get_tree_node(root, &Felt::new(SMT_DEPTH as u64), &key[3])?; + + if node == Word::from(empty_leaf) { + // if the node is a root of an empty subtree, then there is no value associated with + // the specified key + advice_provider.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE))?; + } else { + let leaf_preimage = get_smt_leaf_preimage(advice_provider, node)?; + + for (key_in_leaf, value_in_leaf) in leaf_preimage { + if key == key_in_leaf { + // Found key - push value associated with key, and return + advice_provider.push_stack(AdviceSource::Word(value_in_leaf))?; + + return Ok(()); + } + } + + // if we can't find any key in the leaf that matches `key`, it means no value is + // associated with `key` + advice_provider.push_stack(AdviceSource::Word(Smt::EMPTY_VALUE))?; + } + Ok(()) +} + +/// Returns a signature on a message using a public key. +pub fn get_signature( + advice_provider: &impl AdviceProvider, + kind: SignatureKind, + pub_key: Word, + msg: Word, +) -> Result, ExecutionError> { + let pk_sk = advice_provider + .get_mapped_values(&pub_key.into()) + .ok_or(ExecutionError::AdviceMapKeyNotFound(pub_key))?; + + match kind { + SignatureKind::RpoFalcon512 => dsa::falcon_sign(pk_sk, msg), + } +} + +// HELPER METHODS +// -------------------------------------------------------------------------------------------- + +/// Reads (start_addr, end_addr) tuple from the specified elements of the operand stack ( +/// without modifying the state of the stack), and verifies that memory range is valid. +fn get_mem_addr_range( + process: ProcessState, + start_idx: usize, + end_idx: usize, +) -> Result<(u32, u32), ExecutionError> { + let start_addr = process.get_stack_item(start_idx).as_int(); + let end_addr = process.get_stack_item(end_idx).as_int(); + + if start_addr > u32::MAX as u64 { + return Err(ExecutionError::MemoryAddressOutOfBounds(start_addr)); + } + if end_addr > u32::MAX as u64 { + return Err(ExecutionError::MemoryAddressOutOfBounds(end_addr)); + } + + if start_addr > end_addr { + return Err(ExecutionError::InvalidMemoryRange { start_addr, end_addr }); + } + + Ok((start_addr as u32, end_addr as u32)) +} + +fn u64_to_u32_elements(value: u64) -> (Felt, Felt) { + let hi = Felt::from((value >> 32) as u32); + let lo = Felt::from(value as u32); + (hi, lo) +} + +/// Gets the top stack element, applies a provided function to it and pushes it to the advice +/// provider. +fn push_transformed_stack_top( + advice_provider: &mut A, + process: ProcessState, + f: impl FnOnce(u32) -> Felt, +) -> Result<(), ExecutionError> { + let stack_top = process.get_stack_item(0); + let stack_top: u32 = stack_top + .as_int() + .try_into() + .map_err(|_| ExecutionError::NotU32Value(stack_top, ZERO))?; + let transformed_stack_top = f(stack_top); + advice_provider.push_stack(AdviceSource::Value(transformed_stack_top))?; + Ok(()) +} + +fn get_smt_leaf_preimage( + advice_provider: &A, + node: Word, +) -> Result, ExecutionError> { + let node_bytes = RpoDigest::from(node); + + let kv_pairs = advice_provider + .get_mapped_values(&node_bytes) + .ok_or(ExecutionError::SmtNodeNotFound(node))?; + + if kv_pairs.len() % WORD_SIZE * 2 != 0 { + return Err(ExecutionError::SmtNodePreImageNotValid(node, kv_pairs.len())); + } + + Ok(kv_pairs + .chunks_exact(WORD_SIZE * 2) + .map(|kv_chunk| { + let key = [kv_chunk[0], kv_chunk[1], kv_chunk[2], kv_chunk[3]]; + let value = [kv_chunk[4], kv_chunk[5], kv_chunk[6], kv_chunk[7]]; + + (key, value) + }) + .collect()) +} From 0868ae873fec19ed5474ed862ea017d344ccbbd0 Mon Sep 17 00:00:00 2001 From: yasonk Date: Sat, 23 Nov 2024 13:22:31 -0800 Subject: [PATCH 21/39] feat: serialize decorator data at the end (#1531) --- CHANGELOG.md | 1 + core/src/mast/mod.rs | 17 +- core/src/mast/node/basic_block_node/mod.rs | 6 + core/src/mast/serialization/basic_blocks.rs | 51 +--- core/src/mast/serialization/decorator.rs | 35 +-- core/src/mast/serialization/info.rs | 57 ++--- core/src/mast/serialization/mod.rs | 261 +++++++++++++------- 7 files changed, 243 insertions(+), 185 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51771a5666..11e2f90514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [BREAKING] `Process` no longer takes ownership of the `Host` (#1571) - [BREAKING] `ProcessState` was converted from a trait to a struct (#1571) - [BREAKING] `Host` and `AdviceProvider` traits simplified (#1572) +- [BREAKING] `MastForest` serialization/deserialization will store/read decorator data at the end of the binary (#1531) #### Enhancements - Added `miden_core::mast::MastForest::advice_map` to load it into the advice provider before the `MastForest` execution (#1574). diff --git a/core/src/mast/mod.rs b/core/src/mast/mod.rs index 8e32a9c9d7..142c50b48d 100644 --- a/core/src/mast/mod.rs +++ b/core/src/mast/mod.rs @@ -69,8 +69,6 @@ impl MastForest { impl MastForest { /// The maximum number of nodes that can be stored in a single MAST forest. const MAX_NODES: usize = (1 << 30) - 1; - /// The maximum number of decorators that can be stored in a single MAST forest. - const MAX_DECORATORS: usize = Self::MAX_NODES; /// Adds a decorator to the forest, and returns the associated [`DecoratorId`]. pub fn add_decorator(&mut self, decorator: Decorator) -> Result { @@ -539,6 +537,21 @@ impl MastNodeId { Self::from_u32_with_node_count(value, mast_forest.nodes.len()) } + /// Returns a new [`MastNodeId`] with the provided `node_id`, or an error if `node_id` is + /// greater than the number of nodes in the [`MastForest`] for which this ID is being + /// constructed. + pub fn from_usize_safe( + node_id: usize, + mast_forest: &MastForest, + ) -> Result { + let node_id: u32 = node_id.try_into().map_err(|_| { + DeserializationError::InvalidValue(format!( + "node id '{node_id}' does not fit into a u32" + )) + })?; + MastNodeId::from_u32_safe(node_id, mast_forest) + } + /// Returns a new [`MastNodeId`] from the given `value` without checking its validity. pub(crate) fn new_unchecked(value: u32) -> Self { Self(value) diff --git a/core/src/mast/node/basic_block_node/mod.rs b/core/src/mast/node/basic_block_node/mod.rs index 726decbc72..9a545f66c2 100644 --- a/core/src/mast/node/basic_block_node/mod.rs +++ b/core/src/mast/node/basic_block_node/mod.rs @@ -224,6 +224,12 @@ impl BasicBlockNode { decorator_ids.into_iter().map(|decorator_id| (after_last_op_idx, decorator_id)), ); } + + /// Used to initialize decorators for the [`BasicBlockNode`]. Replaces the existing decorators + /// with the given ['DecoratorList']. + pub fn set_decorators(&mut self, decorator_list: DecoratorList) { + self.decorators = decorator_list; + } } // PRETTY PRINTING diff --git a/core/src/mast/serialization/basic_blocks.rs b/core/src/mast/serialization/basic_blocks.rs index 86cf884717..1e784d5824 100644 --- a/core/src/mast/serialization/basic_blocks.rs +++ b/core/src/mast/serialization/basic_blocks.rs @@ -2,11 +2,8 @@ use alloc::vec::Vec; use winter_utils::{ByteReader, DeserializationError, Serializable, SliceReader}; -use super::{DecoratorDataOffset, NodeDataOffset}; -use crate::{ - mast::{BasicBlockNode, DecoratorId, MastForest}, - DecoratorList, Operation, -}; +use super::NodeDataOffset; +use crate::{mast::BasicBlockNode, Operation}; // BASIC BLOCK DATA BUILDER // ================================================================================================ @@ -26,24 +23,15 @@ impl BasicBlockDataBuilder { /// Mutators impl BasicBlockDataBuilder { - /// Encodes a [`BasicBlockNode`] into the serialized [`crate::mast::MastForest`] data field. - pub fn encode_basic_block( - &mut self, - basic_block: &BasicBlockNode, - ) -> (NodeDataOffset, Option) { + /// Encodes a [`BasicBlockNode`]'s operations into the serialized [`crate::mast::MastForest`] + /// data field. The decorators are not encoded because are stored separately + pub fn encode_basic_block(&mut self, basic_block: &BasicBlockNode) -> NodeDataOffset { let ops_offset = self.node_data.len() as NodeDataOffset; let operations: Vec = basic_block.operations().copied().collect(); operations.write_into(&mut self.node_data); - if basic_block.decorators().is_empty() { - (ops_offset, None) - } else { - let decorator_data_offset = self.node_data.len() as DecoratorDataOffset; - basic_block.decorators().write_into(&mut self.node_data); - - (ops_offset, Some(decorator_data_offset)) - } + ops_offset } /// Returns the serialized [`crate::mast::MastForest`] node data field. @@ -68,35 +56,14 @@ impl<'a> BasicBlockDataDecoder<'a> { /// Decoding methods impl BasicBlockDataDecoder<'_> { - pub fn decode_operations_and_decorators( + pub fn decode_operations( &self, ops_offset: NodeDataOffset, - decorator_list_offset: NodeDataOffset, - mast_forest: &MastForest, - ) -> Result<(Vec, DecoratorList), DeserializationError> { + ) -> Result, DeserializationError> { // Read ops let mut ops_data_reader = SliceReader::new(&self.node_data[ops_offset as usize..]); let operations: Vec = ops_data_reader.read()?; - // read decorators only if there are some - let decorators = if decorator_list_offset == MastForest::MAX_DECORATORS as u32 { - Vec::new() - } else { - let mut decorators_data_reader = - SliceReader::new(&self.node_data[decorator_list_offset as usize..]); - - let num_decorators: usize = decorators_data_reader.read()?; - (0..num_decorators) - .map(|_| { - let decorator_loc: usize = decorators_data_reader.read()?; - let decorator_id = - DecoratorId::from_u32_safe(decorators_data_reader.read()?, mast_forest)?; - - Ok((decorator_loc, decorator_id)) - }) - .collect::>()? - }; - - Ok((operations, decorators)) + Ok(operations) } } diff --git a/core/src/mast/serialization/decorator.rs b/core/src/mast/serialization/decorator.rs index f4e2f31a06..2e7ef6c4c1 100644 --- a/core/src/mast/serialization/decorator.rs +++ b/core/src/mast/serialization/decorator.rs @@ -26,13 +26,9 @@ pub struct DecoratorInfo { impl DecoratorInfo { pub fn from_decorator( decorator: &Decorator, - data_builder: &mut DecoratorDataBuilder, - string_table_builder: &mut StringTableBuilder, + decorator_data_offset: DecoratorDataOffset, ) -> Self { let variant = EncodedDecoratorVariant::from(decorator); - let decorator_data_offset = - data_builder.encode_decorator_data(decorator, string_table_builder).unwrap_or(0); - Self { variant, decorator_data_offset } } @@ -210,6 +206,8 @@ impl Deserializable for EncodedDecoratorVariant { #[derive(Debug, Default)] pub struct DecoratorDataBuilder { decorator_data: Vec, + decorator_infos: Vec, + string_table_builder: StringTableBuilder, } /// Constructors @@ -221,13 +219,15 @@ impl DecoratorDataBuilder { /// Mutators impl DecoratorDataBuilder { + pub fn add_decorator(&mut self, decorator: &Decorator) { + let decorator_data_offset = self.encode_decorator_data(decorator).unwrap_or(0); + self.decorator_infos + .push(DecoratorInfo::from_decorator(decorator, decorator_data_offset)); + } + /// If a decorator has extra data to store, encode it in internal data buffer, and return the /// offset of the newly added data. If not, return `None`. - pub fn encode_decorator_data( - &mut self, - decorator: &Decorator, - string_table_builder: &mut StringTableBuilder, - ) -> Option { + pub fn encode_decorator_data(&mut self, decorator: &Decorator) -> Option { let data_offset = self.decorator_data.len() as DecoratorDataOffset; match decorator { @@ -239,7 +239,7 @@ impl DecoratorDataBuilder { let loc = assembly_op.location(); self.decorator_data.write_bool(loc.is_some()); if let Some(loc) = loc { - let str_offset = string_table_builder.add_string(loc.path.as_ref()); + let str_offset = self.string_table_builder.add_string(loc.path.as_ref()); self.decorator_data.write_usize(str_offset); self.decorator_data.write_u32(loc.start.to_u32()); self.decorator_data.write_u32(loc.end.to_u32()); @@ -247,13 +247,14 @@ impl DecoratorDataBuilder { // context name { - let str_offset = string_table_builder.add_string(assembly_op.context_name()); + let str_offset = + self.string_table_builder.add_string(assembly_op.context_name()); self.decorator_data.write_usize(str_offset); } // op { - let str_index_in_table = string_table_builder.add_string(assembly_op.op()); + let str_index_in_table = self.string_table_builder.add_string(assembly_op.op()); self.decorator_data.write_usize(str_index_in_table); } @@ -288,7 +289,11 @@ impl DecoratorDataBuilder { } /// Returns the serialized [`crate::mast::MastForest`] decorator data field. - pub fn finalize(self) -> Vec { - self.decorator_data + pub fn finalize(self) -> (Vec, Vec, StringTable) { + ( + self.decorator_data, + self.decorator_infos, + self.string_table_builder.into_table(), + ) } } diff --git a/core/src/mast/serialization/info.rs b/core/src/mast/serialization/info.rs index fa9efe3371..51bbabc150 100644 --- a/core/src/mast/serialization/info.rs +++ b/core/src/mast/serialization/info.rs @@ -1,10 +1,10 @@ +use alloc::vec::Vec; + use miden_crypto::hash::rpo::RpoDigest; use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use super::{basic_blocks::BasicBlockDataDecoder, NodeDataOffset}; -use crate::mast::{ - BasicBlockNode, CallNode, JoinNode, LoopNode, MastForest, MastNode, MastNodeId, SplitNode, -}; +use crate::mast::{BasicBlockNode, CallNode, JoinNode, LoopNode, MastNode, MastNodeId, SplitNode}; // MAST NODE INFO // ================================================================================================ @@ -21,46 +21,32 @@ pub struct MastNodeInfo { } impl MastNodeInfo { - /// Constructs a new [`MastNodeInfo`] from a [`MastNode`], along with an `ops_offset` and - /// `decorator_list_offset` in the case of [`BasicBlockNode`]. + /// Constructs a new [`MastNodeInfo`] from a [`MastNode`], along with an `ops_offset` /// - /// If the represented [`MastNode`] is a [`BasicBlockNode`] that has an empty decorator list, - /// use `MastForest::MAX_DECORATORS` for the value of `decorator_list_offset`. For non-basic - /// block nodes, `ops_offset` and `decorator_list_offset` are ignored, and should be set to 0. - pub fn new( - mast_node: &MastNode, - ops_offset: NodeDataOffset, - decorator_list_offset: NodeDataOffset, - ) -> Self { + /// For non-basic block nodes, `ops_offset` is ignored, and should be set to 0. + pub fn new(mast_node: &MastNode, ops_offset: NodeDataOffset) -> Self { if !matches!(mast_node, &MastNode::Block(_)) { debug_assert_eq!(ops_offset, 0); - debug_assert_eq!(decorator_list_offset, 0); } - let ty = MastNodeType::new(mast_node, ops_offset, decorator_list_offset); + let ty = MastNodeType::new(mast_node, ops_offset); Self { ty, digest: mast_node.digest() } } - /// Attempts to convert this [`MastNodeInfo`] into a [`MastNode`] for the given `mast_forest`. + /// Attempts to convert this [`MastNodeInfo`] into a [`MastNode`]. /// /// The `node_count` is the total expected number of nodes in the [`MastForest`] **after /// deserialization**. pub fn try_into_mast_node( self, - mast_forest: &MastForest, node_count: usize, basic_block_data_decoder: &BasicBlockDataDecoder, ) -> Result { match self.ty { - MastNodeType::Block { ops_offset, decorator_list_offset } => { - let (operations, decorators) = basic_block_data_decoder - .decode_operations_and_decorators( - ops_offset, - decorator_list_offset, - mast_forest, - )?; - let block = BasicBlockNode::new_unsafe(operations, decorators, self.digest); + MastNodeType::Block { ops_offset } => { + let operations = basic_block_data_decoder.decode_operations(ops_offset)?; + let block = BasicBlockNode::new_unsafe(operations, Vec::new(), self.digest); Ok(MastNode::Block(block)) }, MastNodeType::Join { left_child_id, right_child_id } => { @@ -151,8 +137,6 @@ pub enum MastNodeType { Block { // offset of operations in node data ops_offset: u32, - // offset of DecoratorList in node data - decorator_list_offset: u32, } = BLOCK, Call { callee_id: u32, @@ -168,18 +152,11 @@ pub enum MastNodeType { /// Constructors impl MastNodeType { /// Constructs a new [`MastNodeType`] from a [`MastNode`]. - /// - /// If the represented [`MastNode`] is a [`BasicBlockNode`] that has an empty decorator list, - /// use `MastForest::MAX_DECORATORS` for the value of `decorator_list_offset`. - pub fn new( - mast_node: &MastNode, - ops_offset: NodeDataOffset, - decorator_list_offset: NodeDataOffset, - ) -> Self { + pub fn new(mast_node: &MastNode, ops_offset: NodeDataOffset) -> Self { use MastNode::*; match mast_node { - Block(_block_node) => Self::Block { decorator_list_offset, ops_offset }, + Block(_block_node) => Self::Block { ops_offset }, Join(join_node) => Self::Join { left_child_id: join_node.first().0, right_child_id: join_node.second().0, @@ -223,9 +200,7 @@ impl Serializable for MastNodeType { else_branch_id: else_branch, } => Self::encode_u32_pair(if_branch, else_branch), MastNodeType::Loop { body_id: body } => Self::encode_u32_payload(body), - MastNodeType::Block { ops_offset, decorator_list_offset } => { - Self::encode_u32_pair(ops_offset, decorator_list_offset) - }, + MastNodeType::Block { ops_offset } => Self::encode_u32_payload(ops_offset), MastNodeType::Call { callee_id } => Self::encode_u32_payload(callee_id), MastNodeType::SysCall { callee_id } => Self::encode_u32_payload(callee_id), MastNodeType::Dyn => 0, @@ -300,8 +275,8 @@ impl Deserializable for MastNodeType { Ok(Self::Loop { body_id }) }, BLOCK => { - let (ops_offset, decorator_list_offset) = Self::decode_u32_pair(payload); - Ok(Self::Block { ops_offset, decorator_list_offset }) + let ops_offset = Self::decode_u32_payload(payload)?; + Ok(Self::Block { ops_offset }) }, CALL => { let callee_id = Self::decode_u32_payload(payload)?; diff --git a/core/src/mast/serialization/mod.rs b/core/src/mast/serialization/mod.rs index 7a8119f7cd..3ea52f8206 100644 --- a/core/src/mast/serialization/mod.rs +++ b/core/src/mast/serialization/mod.rs @@ -4,30 +4,41 @@ //! - MAGIC //! - VERSION //! -//! (lengths) -//! - decorators length (`usize`) +//! (sections metadata) //! - nodes length (`usize`) +//! - decorator data section offset (`usize`) (not implemented, see issue #1580) +//! - decorators length (`usize`) //! -//! (procedure roots) +//! (procedure roots section) //! - procedure roots (`Vec`) //! -//! (raw data) +//! (basic block data section) +//! - basic block data +//! +//! (node info section) +//! - MAST node infos (`Vec`) +//! +//! (advice map section) +//! - Advice map (AdviceMap) +//! +//! (decorator data section) //! - Decorator data -//! - Node data //! - String table //! -//! (info structs) +//! (decorator info section) //! - decorator infos (`Vec`) -//! - MAST node infos (`Vec`) //! -//! (before enter and after exit decorators) +//! (basic block decorator lists section) +//! - basic block decorator lists (`Vec<(MastNodeId, Vec<(usize, DecoratorId)>)>`) +//! +//! (before enter and after exit decorators section) //! - before enter decorators (`Vec<(MastNodeId, Vec)>`) //! - after exit decorators (`Vec<(MastNodeId, Vec)>`) use alloc::vec::Vec; use decorator::{DecoratorDataBuilder, DecoratorInfo}; -use string_table::{StringTable, StringTableBuilder}; +use string_table::StringTable; use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use super::{DecoratorId, MastForest, MastNode, MastNodeId}; @@ -41,6 +52,8 @@ use info::MastNodeInfo; mod basic_blocks; use basic_blocks::{BasicBlockDataBuilder, BasicBlockDataDecoder}; +use crate::DecoratorList; + mod string_table; #[cfg(test)] @@ -80,38 +93,25 @@ const VERSION: [u8; 3] = [0, 0, 0]; impl Serializable for MastForest { fn write_into(&self, target: &mut W) { let mut basic_block_data_builder = BasicBlockDataBuilder::new(); - let mut decorator_data_builder = DecoratorDataBuilder::new(); - let mut string_table_builder = StringTableBuilder::default(); // Set up "before enter" and "after exit" decorators by `MastNodeId` let mut before_enter_decorators: Vec<(usize, Vec)> = Vec::new(); let mut after_exit_decorators: Vec<(usize, Vec)> = Vec::new(); + let mut basic_block_decorators: Vec<(usize, Vec<(usize, DecoratorId)>)> = Vec::new(); + // magic & version target.write_bytes(MAGIC); target.write_bytes(&VERSION); // decorator & node counts - target.write_usize(self.decorators.len()); target.write_usize(self.nodes.len()); + target.write_usize(self.decorators.len()); // roots let roots: Vec = self.roots.iter().map(u32::from).collect(); roots.write_into(target); - // decorators - let decorator_infos: Vec = self - .decorators - .iter() - .map(|decorator| { - DecoratorInfo::from_decorator( - decorator, - &mut decorator_data_builder, - &mut string_table_builder, - ) - }) - .collect(); - // Prepare MAST node infos, but don't store them yet. We store them at the end to make // deserialization more efficient. let mast_node_infos: Vec = self @@ -126,40 +126,49 @@ impl Serializable for MastForest { after_exit_decorators.push((mast_node_id, mast_node.after_exit().to_vec())); } - let (ops_offset, decorator_data_offset) = if let MastNode::Block(basic_block) = - mast_node - { - let (ops_offset, decorator_data_offset) = - basic_block_data_builder.encode_basic_block(basic_block); + let ops_offset = if let MastNode::Block(basic_block) = mast_node { + let ops_offset = basic_block_data_builder.encode_basic_block(basic_block); + + basic_block_decorators.push((mast_node_id, basic_block.decorators().clone())); - (ops_offset, decorator_data_offset.unwrap_or(MastForest::MAX_DECORATORS as u32)) + ops_offset } else { - (0, 0) + 0 }; - MastNodeInfo::new(mast_node, ops_offset, decorator_data_offset) + MastNodeInfo::new(mast_node, ops_offset) }) .collect(); - let decorator_data = decorator_data_builder.finalize(); - let node_data = basic_block_data_builder.finalize(); - let string_table = string_table_builder.into_table(); + let basic_block_data = basic_block_data_builder.finalize(); + basic_block_data.write_into(target); - // Write 3 data buffers - decorator_data.write_into(target); - node_data.write_into(target); - string_table.write_into(target); + // Write node infos + for mast_node_info in mast_node_infos { + mast_node_info.write_into(target); + } self.advice_map.write_into(target); - // Write decorator and node infos + // write all decorator data below + + let mut decorator_data_builder = DecoratorDataBuilder::new(); + for decorator in &self.decorators { + decorator_data_builder.add_decorator(decorator) + } + + let (decorator_data, decorator_infos, string_table) = decorator_data_builder.finalize(); + + // decorator data buffers + decorator_data.write_into(target); + string_table.write_into(target); + + // Write decorator infos for decorator_info in decorator_infos { decorator_info.write_into(target); } - for mast_node_info in mast_node_infos { - mast_node_info.write_into(target); - } + basic_block_decorators.write_into(target); // Write "before enter" and "after exit" decorators before_enter_decorators.write_into(target); @@ -169,35 +178,34 @@ impl Serializable for MastForest { impl Deserializable for MastForest { fn read_from(source: &mut R) -> Result { - let magic: [u8; 5] = source.read_array()?; - if magic != *MAGIC { - return Err(DeserializationError::InvalidValue(format!( - "Invalid magic bytes. Expected '{:?}', got '{:?}'", - *MAGIC, magic - ))); - } + read_and_validate_magic(source)?; + read_and_validate_version(source)?; - let version: [u8; 3] = source.read_array()?; - if version != VERSION { - return Err(DeserializationError::InvalidValue(format!( - "Unsupported version. Got '{version:?}', but only '{VERSION:?}' is supported", - ))); - } - - let decorator_count = source.read_usize()?; + // Reading sections metadata let node_count = source.read_usize()?; + let decorator_count = source.read_usize()?; + + // Reading procedure roots let roots: Vec = Deserializable::read_from(source)?; + + // Reading nodes + let basic_block_data: Vec = Deserializable::read_from(source)?; + let mast_node_infos: Vec = node_infos_iter(source, node_count) + .collect::, DeserializationError>>()?; + + let advice_map = AdviceMap::read_from(source)?; + + // Reading Decorators let decorator_data: Vec = Deserializable::read_from(source)?; - let node_data: Vec = Deserializable::read_from(source)?; let string_table: StringTable = Deserializable::read_from(source)?; - let advice_map = AdviceMap::read_from(source)?; + let decorator_infos = decorator_infos_iter(source, decorator_count); + // Constructing MastForest let mut mast_forest = { let mut mast_forest = MastForest::new(); - // decorators - for _ in 0..decorator_count { - let decorator_info = DecoratorInfo::read_from(source)?; + for decorator_info in decorator_infos { + let decorator_info = decorator_info?; let decorator = decorator_info.try_into_decorator(&string_table, &decorator_data)?; @@ -209,15 +217,10 @@ impl Deserializable for MastForest { } // nodes - let basic_block_data_decoder = BasicBlockDataDecoder::new(&node_data); - for _ in 0..node_count { - let mast_node_info = MastNodeInfo::read_from(source)?; - - let node = mast_node_info.try_into_mast_node( - &mast_forest, - node_count, - &basic_block_data_decoder, - )?; + let basic_block_data_decoder = BasicBlockDataDecoder::new(&basic_block_data); + for mast_node_info in mast_node_infos { + let node = + mast_node_info.try_into_mast_node(node_count, &basic_block_data_decoder)?; mast_forest.add_node(node).map_err(|e| { DeserializationError::InvalidValue(format!( @@ -238,28 +241,35 @@ impl Deserializable for MastForest { mast_forest }; + let basic_block_decorators: Vec<(usize, DecoratorList)> = + read_block_decorators(source, &mast_forest)?; + for (node_id, decorator_list) in basic_block_decorators { + let node_id = MastNodeId::from_usize_safe(node_id, &mast_forest)?; + + match &mut mast_forest[node_id] { + MastNode::Block(basic_block) => { + basic_block.set_decorators(decorator_list); + }, + other => { + return Err(DeserializationError::InvalidValue(format!( + "expected mast node with id {node_id} to be a basic block, found {other:?}" + ))) + }, + } + } + // read "before enter" and "after exit" decorators, and update the corresponding nodes let before_enter_decorators: Vec<(usize, Vec)> = read_before_after_decorators(source, &mast_forest)?; for (node_id, decorator_ids) in before_enter_decorators { - let node_id: u32 = node_id.try_into().map_err(|_| { - DeserializationError::InvalidValue(format!( - "Invalid node id '{node_id}' while deserializing" - )) - })?; - let node_id = MastNodeId::from_u32_safe(node_id, &mast_forest)?; + let node_id = MastNodeId::from_usize_safe(node_id, &mast_forest)?; mast_forest.set_before_enter(node_id, decorator_ids); } let after_exit_decorators: Vec<(usize, Vec)> = read_before_after_decorators(source, &mast_forest)?; for (node_id, decorator_ids) in after_exit_decorators { - let node_id: u32 = node_id.try_into().map_err(|_| { - DeserializationError::InvalidValue(format!( - "Invalid node id '{node_id}' while deserializing" - )) - })?; - let node_id = MastNodeId::from_u32_safe(node_id, &mast_forest)?; + let node_id = MastNodeId::from_usize_safe(node_id, &mast_forest)?; mast_forest.set_after_exit(node_id, decorator_ids); } @@ -267,6 +277,87 @@ impl Deserializable for MastForest { } } +fn read_and_validate_magic(source: &mut R) -> Result<[u8; 5], DeserializationError> { + let magic: [u8; 5] = source.read_array()?; + if magic != *MAGIC { + return Err(DeserializationError::InvalidValue(format!( + "Invalid magic bytes. Expected '{:?}', got '{:?}'", + *MAGIC, magic + ))); + } + Ok(magic) +} + +fn read_and_validate_version( + source: &mut R, +) -> Result<[u8; 3], DeserializationError> { + let version: [u8; 3] = source.read_array()?; + if version != VERSION { + return Err(DeserializationError::InvalidValue(format!( + "Unsupported version. Got '{version:?}', but only '{VERSION:?}' is supported", + ))); + } + Ok(version) +} + +fn read_block_decorators( + source: &mut R, + mast_forest: &MastForest, +) -> Result, DeserializationError> { + let vec_len: usize = source.read()?; + let mut out_vec: Vec<_> = Vec::with_capacity(vec_len); + + for _ in 0..vec_len { + let node_id: usize = source.read()?; + + let decorator_vec_len: usize = source.read()?; + let mut inner_vec: Vec<(usize, DecoratorId)> = Vec::with_capacity(decorator_vec_len); + for _ in 0..decorator_vec_len { + let op_id: usize = source.read()?; + let decorator_id = DecoratorId::from_u32_safe(source.read()?, mast_forest)?; + inner_vec.push((op_id, decorator_id)); + } + + out_vec.push((node_id, inner_vec)); + } + + Ok(out_vec) +} + +fn decorator_infos_iter<'a, R>( + source: &'a mut R, + decorator_count: usize, +) -> impl Iterator> + 'a +where + R: ByteReader + 'a, +{ + let mut remaining = decorator_count; + core::iter::from_fn(move || { + if remaining == 0 { + return None; + } + remaining -= 1; + Some(DecoratorInfo::read_from(source)) + }) +} + +fn node_infos_iter<'a, R>( + source: &'a mut R, + node_count: usize, +) -> impl Iterator> + 'a +where + R: ByteReader + 'a, +{ + let mut remaining = node_count; + core::iter::from_fn(move || { + if remaining == 0 { + return None; + } + remaining -= 1; + Some(MastNodeInfo::read_from(source)) + }) +} + /// Reads the `before_enter_decorators` and `after_exit_decorators` of the serialized `MastForest` /// format. /// From 960dd7d799ceea7237ffdfd2af86d54f51a96952 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 27 Nov 2024 00:36:47 +0100 Subject: [PATCH 22/39] chore: update winterfell dependence to v0.11 (#1586) --- CHANGELOG.md | 1 + Cargo.lock | 207 +++++++++++------- air/Cargo.toml | 6 +- core/Cargo.toml | 8 +- miden/Cargo.toml | 2 +- miden/tests/integration/flow_control/mod.rs | 4 +- .../operations/decorators/advice.rs | 6 +- .../tests/integration/operations/field_ops.rs | 83 ++++--- .../integration/operations/io_ops/adv_ops.rs | 14 +- .../integration/operations/io_ops/mem_ops.rs | 14 +- miden/tests/integration/operations/sys_ops.rs | 44 ++-- .../operations/u32_ops/arithmetic_ops.rs | 22 +- .../operations/u32_ops/bitwise_ops.rs | 39 +++- .../operations/u32_ops/conversion_ops.rs | 40 +++- .../integration/operations/u32_ops/mod.rs | 14 +- processor/Cargo.toml | 6 +- processor/src/errors.rs | 2 +- prover/Cargo.toml | 4 +- prover/src/lib.rs | 24 +- stdlib/Cargo.toml | 6 +- stdlib/tests/crypto/falcon.rs | 14 +- stdlib/tests/crypto/rpo.rs | 13 +- stdlib/tests/math/u64_mod.rs | 20 +- test-utils/Cargo.toml | 4 +- test-utils/src/lib.rs | 12 - verifier/Cargo.toml | 2 +- 26 files changed, 380 insertions(+), 231 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11e2f90514..dcf5dce84a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [BREAKING] `ProcessState` was converted from a trait to a struct (#1571) - [BREAKING] `Host` and `AdviceProvider` traits simplified (#1572) - [BREAKING] `MastForest` serialization/deserialization will store/read decorator data at the end of the binary (#1531) +- [BREAKING] Updated Winterfell dependency to v0.11 (#1586). #### Enhancements - Added `miden_core::mast::MastForest::advice_map` to load it into the advice provider before the `MastForest` execution (#1574). diff --git a/Cargo.lock b/Cargo.lock index 0ecc546d00..834e4bd629 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,12 +214,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "serde", ] @@ -243,9 +243,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.34" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "jobserver", "libc", @@ -287,9 +287,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -297,9 +297,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -321,9 +321,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "clipboard-win" @@ -375,9 +375,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -574,9 +574,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fixedbitset" @@ -745,9 +745,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -826,9 +826,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" [[package]] name = "jobserver" @@ -892,9 +892,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.161" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libm" @@ -1045,16 +1045,16 @@ dependencies = [ "num-traits", "parking_lot", "proptest", - "winter-math", + "winter-math 0.11.0", "winter-rand-utils", - "winter-utils", + "winter-utils 0.11.0", ] [[package]] name = "miden-crypto" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50a68deed96cde1f51eb623f75828e320f699e0d798f11592f8958ba8b512c3" +checksum = "a74af93a5640786def85a08a1fc25e02d8681ed0449ffb4898f4832a5fa08958" dependencies = [ "blake3", "cc", @@ -1064,9 +1064,10 @@ dependencies = [ "rand", "rand_core", "sha3", + "thiserror 2.0.3", "winter-crypto", - "winter-math", - "winter-utils", + "winter-math 0.11.0", + "winter-utils 0.11.0", ] [[package]] @@ -1086,7 +1087,7 @@ checksum = "271d375ea8bfdb0995f30d27d5eccc1468326e502e6ad6bdb0f9ee2d91ade50c" dependencies = [ "metal", "once_cell", - "winter-math", + "winter-math 0.10.2", ] [[package]] @@ -1143,7 +1144,7 @@ dependencies = [ "tracing", "winter-fri", "winter-prover", - "winter-utils", + "winter-utils 0.11.0", ] [[package]] @@ -1168,6 +1169,7 @@ dependencies = [ "criterion", "miden-air", "miden-assembly", + "miden-core", "miden-processor", "miden-test-utils", "num", @@ -1578,9 +1580,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -1696,7 +1698,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1707,7 +1709,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -1722,9 +1724,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1769,9 +1771,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.39" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -1867,18 +1869,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -1887,9 +1889,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -2023,9 +2025,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "2.0.87" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -2040,9 +2042,9 @@ checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" [[package]] name = "tempfile" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", @@ -2133,18 +2135,38 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] name = "thiserror-impl" -version = "1.0.68" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", @@ -2254,7 +2276,7 @@ checksum = "ee40835db14ddd1e3ba414292272eddde9dad04d3d4b65509656414d1c42592f" dependencies = [ "ansi_term", "smallvec", - "thiserror", + "thiserror 1.0.69", "tracing", "tracing-subscriber", ] @@ -2318,9 +2340,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-linebreak" @@ -2740,54 +2762,63 @@ dependencies = [ [[package]] name = "winter-air" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bec0b06b741543f43e3a6677b95b200d4cad2daab76e6721e14345345bfd0e" +checksum = "3a8fdb702503625f54dcaf9222aa2c7a0b2e868b3eb84b90d1837d68034bf999" dependencies = [ "libm", "winter-crypto", "winter-fri", - "winter-math", - "winter-utils", + "winter-math 0.11.0", + "winter-utils 0.11.0", ] [[package]] name = "winter-crypto" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "163da45f1d4d65cac361b8df4835a6daa95b3399154e16eb0305c178c6f6c1f4" +checksum = "67c57748fd2da77742be601f03eda639ff6046879738fd1faae86e80018263cb" dependencies = [ "blake3", "sha3", - "winter-math", - "winter-utils", + "winter-math 0.11.0", + "winter-utils 0.11.0", ] [[package]] name = "winter-fri" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7b394670d68979a4cc21a37a95ef8ef350cf84be9256c53effe3052df50d26" +checksum = "3f9f999bc248c6254138b627035cb6d2c319580eb37dadcab6672298dbf00e41" dependencies = [ "winter-crypto", - "winter-math", - "winter-utils", + "winter-math 0.11.0", + "winter-utils 0.11.0", +] + +[[package]] +name = "winter-math" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82479f94efc0b5374a93e2074ba46ef404384fb1ea6e35a847febec53096509b" +dependencies = [ + "winter-utils 0.10.2", ] [[package]] name = "winter-math" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a8ba832121679e79b004b0003018c85873956d742a39c348c247f680fe15e00" +checksum = "6020c17839fa107ce4a7cc178e407ebbc24adfac1980f4fa2111198e052700ab" dependencies = [ - "winter-utils", + "winter-utils 0.11.0", ] [[package]] name = "winter-maybe-async" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be43529f43f70306437d2c2c9f9e2b3a4d39b42e86702d8d7577f2357ea32fa6" +checksum = "91ce144fde121b98523bb8a6c15a311773e1d534d33c1cb47f5580bba9cff8e7" dependencies = [ "quote", "syn", @@ -2795,49 +2826,55 @@ dependencies = [ [[package]] name = "winter-prover" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f55f0153d26691caaf969066a13a824bcf3c98719d71b0f569bf8dc40a06fb9" +checksum = "b6c2f3cf80955f084fd614c86883195331116b6c96dc88532d43d6836bd7adee" dependencies = [ "tracing", "winter-air", "winter-crypto", "winter-fri", - "winter-math", + "winter-math 0.11.0", "winter-maybe-async", - "winter-utils", + "winter-utils 0.11.0", ] [[package]] name = "winter-rand-utils" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a7616d11fcc26552dada45c803a884ac97c253218835b83a2c63e1c2a988639" +checksum = "226e4c455f6eb72f64ac6eeb7642df25e21ff2280a4f6b09db75392ad6b390ef" dependencies = [ "rand", - "winter-utils", + "winter-utils 0.11.0", ] [[package]] name = "winter-utils" -version = "0.10.1" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f948e71ffd482aa13d0ec3349047f81ecdb89f3b3287577973dcbf092a25fb4" + +[[package]] +name = "winter-utils" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b116c8ade0172506f8bda32dc674cf6b230adc8516e5138a0173ae69158a4f" +checksum = "1507ef312ea5569d54c2c7446a18b82143eb2a2e21f5c3ec7cfbe8200c03bd7c" dependencies = [ "rayon", ] [[package]] name = "winter-verifier" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1648768f96f5e6321a48a5bff5cc3101d2e51b23a6a095c6c9c9e133ecb61" +checksum = "56d949eab6c26328733477ec56ca054fdc69c0c1b8267fa03d8b618ffe2193c4" dependencies = [ "winter-air", "winter-crypto", "winter-fri", - "winter-math", - "winter-utils", + "winter-math 0.11.0", + "winter-utils 0.11.0", ] [[package]] diff --git a/air/Cargo.toml b/air/Cargo.toml index ad70f6b999..811503a0a4 100644 --- a/air/Cargo.toml +++ b/air/Cargo.toml @@ -33,10 +33,10 @@ testing = [] [dependencies] thiserror = { package = "miden-thiserror", version = "1.0", default-features = false } vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false } -winter-air = { package = "winter-air", version = "0.10", default-features = false } -winter-prover = { package = "winter-prover", version = "0.10", default-features = false } +winter-air = { package = "winter-air", version = "0.11", default-features = false } +winter-prover = { package = "winter-prover", version = "0.11", default-features = false } [dev-dependencies] criterion = "0.5" proptest = "1.5" -rand-utils = { package = "winter-rand-utils", version = "0.10" } +rand-utils = { package = "winter-rand-utils", version = "0.11" } diff --git a/core/Cargo.toml b/core/Cargo.toml index 106f228858..8643a81ba9 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -32,9 +32,9 @@ std = [ [dependencies] lock_api = { version = "0.4", features = ["arc_lock"] } -math = { package = "winter-math", version = "0.10", default-features = false } +math = { package = "winter-math", version = "0.11", default-features = false } memchr = { version = "2.7", default-features = false } -miden-crypto = { version = "0.12", default-features = false } +miden-crypto = { version = "0.13", default-features = false } miden-formatting = { version = "0.1", default-features = false } miette = { package = "miden-miette", version = "7.1", default-features = false, optional = true, features = [ "fancy-no-syscall", @@ -44,12 +44,12 @@ num-derive = { version = "0.4", default-features = false } num-traits = { version = "0.2", default-features = false } parking_lot = { version = "0.12", optional = true } thiserror = { package = "miden-thiserror", version = "1.0", default-features = false } -winter-utils = { package = "winter-utils", version = "0.10", default-features = false } +winter-utils = { package = "winter-utils", version = "0.11", default-features = false } [dev-dependencies] loom = "0.7" proptest = "1.5" -rand-utils = { package = "winter-rand-utils", version = "0.10" } +rand-utils = { package = "winter-rand-utils", version = "0.11" } [target.'cfg(loom)'.dependencies] loom = "0.7" diff --git a/miden/Cargo.toml b/miden/Cargo.toml index ce41d362bd..501cbd9080 100644 --- a/miden/Cargo.toml +++ b/miden/Cargo.toml @@ -82,5 +82,5 @@ num-bigint = "0.4" predicates = "3.1" test-utils = { package = "miden-test-utils", path = "../test-utils" } vm-core = { package = "miden-core", path = "../core", version = "0.11" } -winter-fri = { package = "winter-fri", version = "0.10" } +winter-fri = { package = "winter-fri", version = "0.11" } rand_chacha = "0.3" diff --git a/miden/tests/integration/flow_control/mod.rs b/miden/tests/integration/flow_control/mod.rs index d5bb8b880b..ccbbc1c552 100644 --- a/miden/tests/integration/flow_control/mod.rs +++ b/miden/tests/integration/flow_control/mod.rs @@ -5,7 +5,7 @@ use miden_vm::Module; use processor::ExecutionError; use prover::Digest; use stdlib::StdLibrary; -use test_utils::{build_test, expect_exec_error, push_inputs, StackInputs, Test}; +use test_utils::{build_test, expect_exec_error_matches, push_inputs, StackInputs, Test}; // SIMPLE FLOW CONTROL TESTS // ================================================================================================ @@ -141,7 +141,7 @@ fn local_fn_call() { end"; let build_test = build_test!(source, &[1, 2]); - expect_exec_error!(build_test, ExecutionError::InvalidStackDepthOnReturn(17)); + expect_exec_error_matches!(build_test, ExecutionError::InvalidStackDepthOnReturn(17)); let inputs = (1_u64..18).collect::>(); diff --git a/miden/tests/integration/operations/decorators/advice.rs b/miden/tests/integration/operations/decorators/advice.rs index 3e3c6a6bda..b48c9af7b2 100644 --- a/miden/tests/integration/operations/decorators/advice.rs +++ b/miden/tests/integration/operations/decorators/advice.rs @@ -4,7 +4,7 @@ use rand_chacha::rand_core::SeedableRng; use test_utils::{ build_test, crypto::{rpo_falcon512::SecretKey, MerkleStore, RpoDigest}, - expect_exec_error, + expect_exec_error_matches, rand::{rand_array, rand_value}, serde::Serializable, Felt, TRUNCATE_STACK_PROC, @@ -406,7 +406,7 @@ fn advice_push_sig_rpo_falcon_512_bad_key_value() { let test = build_test!(ADVICE_PUSH_SIG, &op_stack, &advice_stack, store, advice_map.into_iter()); - expect_exec_error!(test, ExecutionError::MalformedSignatureKey("RPO Falcon512")); + expect_exec_error_matches!(test, ExecutionError::MalformedSignatureKey("RPO Falcon512")); } #[test] @@ -445,5 +445,5 @@ fn advice_push_sig_rpo_falcon_512_bad_key_length() { let test = build_test!(ADVICE_PUSH_SIG, &op_stack, &advice_stack, store, advice_map.into_iter()); - expect_exec_error!(test, ExecutionError::MalformedSignatureKey("RPO Falcon512")); + expect_exec_error_matches!(test, ExecutionError::MalformedSignatureKey("RPO Falcon512")); } diff --git a/miden/tests/integration/operations/field_ops.rs b/miden/tests/integration/operations/field_ops.rs index 51310b295c..71ed369a85 100644 --- a/miden/tests/integration/operations/field_ops.rs +++ b/miden/tests/integration/operations/field_ops.rs @@ -1,7 +1,7 @@ use assembly::regex; -use processor::ExecutionError; +use processor::{ExecutionError, RowIndex}; use test_utils::{ - assert_assembler_diagnostic, assert_diagnostic_lines, build_op_test, expect_exec_error, + assert_assembler_diagnostic, assert_diagnostic_lines, build_op_test, expect_exec_error_matches, prop_randw, proptest::prelude::*, rand::rand_value, Felt, FieldElement, StarkField, ONE, WORD_SIZE, }; @@ -213,7 +213,7 @@ fn div_fail() { // --- test divide by zero -------------------------------------------------------------------- let test = build_op_test!(asm_op, &[1, 0]); - expect_exec_error!(test, ExecutionError::DivideByZero(2.into())); + expect_exec_error_matches!(test, ExecutionError::DivideByZero(row_idx) if row_idx == RowIndex::from(2)); } #[test] @@ -279,7 +279,7 @@ fn inv_fail() { // --- test no inv on 0 ----------------------------------------------------------------------- let test = build_op_test!(asm_op, &[0]); - expect_exec_error!(test, ExecutionError::DivideByZero(2.into())); + expect_exec_error_matches!(test, ExecutionError::DivideByZero(row_idx) if row_idx == RowIndex::from(2)); let asm_op = "inv.1"; @@ -318,13 +318,11 @@ fn pow2_fail() { value += (u32::MAX as u64) + 1; let test = build_op_test!(asm_op, &[value]); - expect_exec_error!( + + expect_exec_error_matches!( test, - ExecutionError::FailedAssertion { - clk: 17.into(), - err_code: 0, - err_msg: None, - } + ExecutionError::FailedAssertion{clk, err_code, err_msg } + if clk == RowIndex::from(17) && err_code == 0 && err_msg.is_none() ); } @@ -353,13 +351,10 @@ fn exp_bits_length_fail() { let test = build_op_test!(build_asm_op(9), &[base, pow]); - expect_exec_error!( + expect_exec_error_matches!( test, - ExecutionError::FailedAssertion { - clk: 19.into(), - err_code: 0, - err_msg: None - } + ExecutionError::FailedAssertion{clk, err_code, err_msg } + if clk == RowIndex::from(19) && err_code == 0 && err_msg.is_none() ); //---------------------- exp containing more than 64 bits ------------------------------------- @@ -407,7 +402,10 @@ fn ilog2_fail() { let asm_op = "ilog2"; let test = build_op_test!(asm_op, &[0]); - expect_exec_error!(test, ExecutionError::LogArgumentZero(2.into())); + expect_exec_error_matches!( + test, + ExecutionError::LogArgumentZero(row_idx) if row_idx == RowIndex::from(2) + ); } // FIELD OPS BOOLEAN - MANUAL TESTS @@ -430,7 +428,11 @@ fn not_fail() { // --- test value > 1 -------------------------------------------------------------------- let test = build_op_test!(asm_op, &[2]); - expect_exec_error!(test, ExecutionError::NotBinaryValue(Felt::new(2))); + + expect_exec_error_matches!( + test, + ExecutionError::NotBinaryValue(value) if value == Felt::new(2_u64) + ); } #[test] @@ -456,13 +458,22 @@ fn and_fail() { // --- test value > 1 -------------------------------------------------------------------- let test = build_op_test!(asm_op, &[2, 3]); - expect_exec_error!(test, ExecutionError::NotBinaryValue(Felt::new(3))); + expect_exec_error_matches!( + test, + ExecutionError::NotBinaryValue(value) if value == Felt::new(3_u64) + ); let test = build_op_test!(asm_op, &[2, 0]); - expect_exec_error!(test, ExecutionError::NotBinaryValue(Felt::new(2))); + expect_exec_error_matches!( + test, + ExecutionError::NotBinaryValue(value) if value == Felt::new(2_u64) + ); let test = build_op_test!(asm_op, &[0, 2]); - expect_exec_error!(test, ExecutionError::NotBinaryValue(Felt::new(2))); + expect_exec_error_matches!( + test, + ExecutionError::NotBinaryValue(value) if value == Felt::new(2_u64) + ); } #[test] @@ -489,14 +500,23 @@ fn or_fail() { // --- test value > 1 -------------------------------------------------------------------- let expected_value = Felt::new(3); let test = build_op_test!(asm_op, &[2, 3]); - expect_exec_error!(test, ExecutionError::NotBinaryValue(expected_value)); + expect_exec_error_matches!( + test, + ExecutionError::NotBinaryValue(value) if value == expected_value + ); let expected_value = Felt::new(2); let test = build_op_test!(asm_op, &[2, 0]); - expect_exec_error!(test, ExecutionError::NotBinaryValue(expected_value)); + expect_exec_error_matches!( + test, + ExecutionError::NotBinaryValue(value) if value == expected_value + ); let test = build_op_test!(asm_op, &[0, 2]); - expect_exec_error!(test, ExecutionError::NotBinaryValue(expected_value)); + expect_exec_error_matches!( + test, + ExecutionError::NotBinaryValue(value) if value == expected_value + ); } #[test] @@ -523,13 +543,22 @@ fn xor_fail() { let expected_value = Felt::new(2); // --- test value > 1 -------------------------------------------------------------------- let test = build_op_test!(asm_op, &[2, 3]); - expect_exec_error!(test, ExecutionError::NotBinaryValue(expected_value)); + expect_exec_error_matches!( + test, + ExecutionError::NotBinaryValue(value) if value == expected_value + ); let test = build_op_test!(asm_op, &[2, 0]); - expect_exec_error!(test, ExecutionError::NotBinaryValue(expected_value)); + expect_exec_error_matches!( + test, + ExecutionError::NotBinaryValue(value) if value == expected_value + ); let test = build_op_test!(asm_op, &[0, 2]); - expect_exec_error!(test, ExecutionError::NotBinaryValue(expected_value)); + expect_exec_error_matches!( + test, + ExecutionError::NotBinaryValue(value) if value == expected_value + ); } // FIELD OPS COMPARISON - MANUAL TESTS diff --git a/miden/tests/integration/operations/io_ops/adv_ops.rs b/miden/tests/integration/operations/io_ops/adv_ops.rs index e172f8aaf7..5bb3147702 100644 --- a/miden/tests/integration/operations/io_ops/adv_ops.rs +++ b/miden/tests/integration/operations/io_ops/adv_ops.rs @@ -1,5 +1,5 @@ -use processor::{ExecutionError, ExecutionError::AdviceStackReadFailed}; -use test_utils::expect_exec_error; +use processor::{ExecutionError, RowIndex}; +use test_utils::expect_exec_error_matches; use vm_core::{chiplets::hasher::apply_permutation, utils::ToElements, Felt}; use super::{build_op_test, build_test, TRUNCATE_STACK_PROC}; @@ -32,7 +32,10 @@ fn adv_push() { fn adv_push_invalid() { // attempting to read from empty advice stack should throw an error let test = build_op_test!("adv_push.1"); - expect_exec_error!(test, ExecutionError::AdviceStackReadFailed(2.into())); + expect_exec_error_matches!( + test, + ExecutionError::AdviceStackReadFailed(row_idx) if row_idx == RowIndex::from(2) + ); } // OVERWRITING VALUES ON THE STACK (LOAD) @@ -53,7 +56,10 @@ fn adv_loadw() { fn adv_loadw_invalid() { // attempting to read from empty advice stack should throw an error let test = build_op_test!("adv_loadw", &[0, 0, 0, 0]); - expect_exec_error!(test, AdviceStackReadFailed(2.into())); + expect_exec_error_matches!( + test, + ExecutionError::AdviceStackReadFailed(row_idx) if row_idx == RowIndex::from(2) + ); } // MOVING ELEMENTS TO MEMORY VIA THE STACK (PIPE) diff --git a/miden/tests/integration/operations/io_ops/mem_ops.rs b/miden/tests/integration/operations/io_ops/mem_ops.rs index 81542a86d9..a206e6cd4f 100644 --- a/miden/tests/integration/operations/io_ops/mem_ops.rs +++ b/miden/tests/integration/operations/io_ops/mem_ops.rs @@ -1,5 +1,7 @@ +use processor::ContextId; use prover::ExecutionError; -use test_utils::expect_exec_error; +use test_utils::expect_exec_error_matches; +use vm_core::FieldElement; use super::{apply_permutation, build_op_test, build_test, Felt, ToElements, TRUNCATE_STACK_PROC}; @@ -243,12 +245,10 @@ fn mem_reads_same_clock_cycle() { let asm_op = "begin rcomb_base end"; let test = build_test!(asm_op); - expect_exec_error!( + + expect_exec_error_matches!( test, - ExecutionError::DuplicateMemoryAccess { - ctx: 0_u32.into(), - addr: 0, - clk: 1_u32.into() - } + ExecutionError::DuplicateMemoryAccess{ctx, addr, clk } + if ctx == ContextId::from(0) && addr == 0 && clk == Felt::ONE ); } diff --git a/miden/tests/integration/operations/sys_ops.rs b/miden/tests/integration/operations/sys_ops.rs index d775a7f01f..2c5faaa095 100644 --- a/miden/tests/integration/operations/sys_ops.rs +++ b/miden/tests/integration/operations/sys_ops.rs @@ -1,5 +1,5 @@ -use processor::ExecutionError; -use test_utils::{build_op_test, expect_exec_error}; +use processor::{ExecutionError, RowIndex}; +use test_utils::{build_op_test, expect_exec_error_matches}; // SYSTEM OPS ASSERTIONS - MANUAL TESTS // ================================================================================================ @@ -21,13 +21,11 @@ fn assert_with_code() { // triggered assertion captures both the VM cycle and error code let test = build_op_test!(asm_op, &[0]); - expect_exec_error!( + + expect_exec_error_matches!( test, - ExecutionError::FailedAssertion { - clk: 2.into(), - err_code: 123, - err_msg: None, - } + ExecutionError::FailedAssertion{ clk, err_code, err_msg } + if clk == RowIndex::from(2) && err_code == 123_u32 && err_msg.is_none() ); } @@ -36,13 +34,11 @@ fn assert_fail() { let asm_op = "assert"; let test = build_op_test!(asm_op, &[2]); - expect_exec_error!( + + expect_exec_error_matches!( test, - ExecutionError::FailedAssertion { - clk: 2.into(), - err_code: 0, - err_msg: None, - } + ExecutionError::FailedAssertion{ clk, err_code, err_msg } + if clk == RowIndex::from(2) && err_code == 0 && err_msg.is_none() ); } @@ -62,23 +58,19 @@ fn assert_eq_fail() { let asm_op = "assert_eq"; let test = build_op_test!(asm_op, &[2, 1]); - expect_exec_error!( + + expect_exec_error_matches!( test, - ExecutionError::FailedAssertion { - clk: 3.into(), - err_code: 0, - err_msg: None, - } + ExecutionError::FailedAssertion{ clk, err_code, err_msg } + if clk == RowIndex::from(3) && err_code == 0_u32 && err_msg.is_none() ); let test = build_op_test!(asm_op, &[1, 4]); - expect_exec_error!( + + expect_exec_error_matches!( test, - ExecutionError::FailedAssertion { - clk: 3.into(), - err_code: 0, - err_msg: None, - } + ExecutionError::FailedAssertion{ clk, err_code, err_msg } + if clk == RowIndex::from(3) && err_code == 0_u32 && err_msg.is_none() ); } diff --git a/miden/tests/integration/operations/u32_ops/arithmetic_ops.rs b/miden/tests/integration/operations/u32_ops/arithmetic_ops.rs index 4c9ba2f5a9..1dc4da2312 100644 --- a/miden/tests/integration/operations/u32_ops/arithmetic_ops.rs +++ b/miden/tests/integration/operations/u32_ops/arithmetic_ops.rs @@ -1,6 +1,6 @@ -use processor::ExecutionError; +use processor::{ExecutionError, RowIndex}; use test_utils::{ - build_op_test, expect_exec_error, proptest::prelude::*, rand::rand_value, U32_BOUND, + build_op_test, expect_exec_error_matches, proptest::prelude::*, rand::rand_value, U32_BOUND, }; // U32 OPERATIONS TESTS - MANUAL - ARITHMETIC OPERATIONS @@ -440,7 +440,11 @@ fn u32div_fail() { // should fail if b == 0. let test = build_op_test!(asm_op, &[1, 0]); - expect_exec_error!(test, ExecutionError::DivideByZero(2.into())); + + expect_exec_error_matches!( + test, + ExecutionError::DivideByZero(value) if value == RowIndex::from(2) + ); } #[test] @@ -478,7 +482,11 @@ fn u32mod_fail() { // should fail if b == 0 let test = build_op_test!(asm_op, &[1, 0]); - expect_exec_error!(test, ExecutionError::DivideByZero(2.into())); + + expect_exec_error_matches!( + test, + ExecutionError::DivideByZero(value) if value == RowIndex::from(2) + ); } #[test] @@ -521,7 +529,11 @@ fn u32divmod_fail() { // should fail if b == 0. let test = build_op_test!(asm_op, &[1, 0]); - expect_exec_error!(test, ExecutionError::DivideByZero(2.into())); + + expect_exec_error_matches!( + test, + ExecutionError::DivideByZero(value) if value == RowIndex::from(2) + ); } // U32 OPERATIONS TESTS - RANDOMIZED - ARITHMETIC OPERATIONS diff --git a/miden/tests/integration/operations/u32_ops/bitwise_ops.rs b/miden/tests/integration/operations/u32_ops/bitwise_ops.rs index 258fa77eb2..d6b437f4c9 100644 --- a/miden/tests/integration/operations/u32_ops/bitwise_ops.rs +++ b/miden/tests/integration/operations/u32_ops/bitwise_ops.rs @@ -1,6 +1,7 @@ use processor::{math::Felt, ExecutionError}; use test_utils::{ - build_op_test, expect_exec_error, proptest::prelude::*, rand::rand_value, U32_BOUND, ZERO, + build_op_test, expect_exec_error_matches, proptest::prelude::*, rand::rand_value, U32_BOUND, + ZERO, }; use super::test_input_out_of_bounds; @@ -77,10 +78,18 @@ fn u32and_fail() { let asm_op = "u32and"; let test = build_op_test!(asm_op, &[U32_BOUND, 0]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(U32_BOUND), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(U32_BOUND) && err_code == ZERO + ); let test = build_op_test!(asm_op, &[0, U32_BOUND]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(U32_BOUND), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(U32_BOUND) && err_code == ZERO + ); } #[test] @@ -152,10 +161,18 @@ fn u32or_fail() { let asm_op = "u32or"; let test = build_op_test!(asm_op, &[U32_BOUND, 0]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(U32_BOUND), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(U32_BOUND) && err_code == ZERO + ); let test = build_op_test!(asm_op, &[0, U32_BOUND]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(U32_BOUND), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(U32_BOUND) && err_code == ZERO + ); } #[test] @@ -226,10 +243,18 @@ fn u32xor_fail() { let asm_op = "u32xor"; let test = build_op_test!(asm_op, &[U32_BOUND, 0]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(U32_BOUND), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(U32_BOUND) && err_code == ZERO + ); let test = build_op_test!(asm_op, &[0, U32_BOUND]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(U32_BOUND), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(U32_BOUND) && err_code == ZERO + ); } #[test] diff --git a/miden/tests/integration/operations/u32_ops/conversion_ops.rs b/miden/tests/integration/operations/u32_ops/conversion_ops.rs index 2c675d07e5..cebe0ba655 100644 --- a/miden/tests/integration/operations/u32_ops/conversion_ops.rs +++ b/miden/tests/integration/operations/u32_ops/conversion_ops.rs @@ -1,7 +1,7 @@ use processor::ExecutionError; use test_utils::{ - build_op_test, expect_exec_error, proptest::prelude::*, rand::rand_value, Felt, StarkField, - U32_BOUND, WORD_SIZE, ZERO, + build_op_test, expect_exec_error_matches, proptest::prelude::*, rand::rand_value, Felt, + StarkField, U32_BOUND, WORD_SIZE, ZERO, }; use super::{prop_randw, test_inputs_out_of_bounds}; @@ -100,11 +100,19 @@ fn u32assert_fail() { // --- test when a = 2^32 --------------------------------------------------------------------- let test = build_op_test!(asm_op, &[equal]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(equal), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(equal) && err_code == ZERO + ); // --- test when a > 2^32 --------------------------------------------------------------------- let test = build_op_test!(asm_op, &[larger]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(larger), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(larger) && err_code == ZERO + ); } #[test] @@ -131,19 +139,31 @@ fn u32assert2_fail() { let value_a = (1_u64 << 32) + 1; let value_b = value_a + 2; let test = build_op_test!(asm_op, &[value_a, value_b]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(value_b), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(value_b) && err_code == ZERO + ); // -------- Case 2: a > 2^32 and b < 2^32 --------------------------------------------------- let value_a = (1_u64 << 32) + 1; let value_b = 1_u64; let test = build_op_test!(asm_op, &[value_a, value_b]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(value_a), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(value_a) && err_code == ZERO + ); // --------- Case 3: a < 2^32 and b > 2^32 -------------------------------------------------- let value_b = (1_u64 << 32) + 1; let value_a = 1_u64; let test = build_op_test!(asm_op, &[value_a, value_b]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(value_b), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(value_b) && err_code == ZERO + ); } #[test] @@ -165,7 +185,11 @@ fn u32assertw_fail() { // --- all elements out of range -------------------------------------------------------------- let test = build_op_test!(asm_op, &[U32_BOUND; WORD_SIZE]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(U32_BOUND), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(U32_BOUND) && err_code == ZERO + ); } #[test] diff --git a/miden/tests/integration/operations/u32_ops/mod.rs b/miden/tests/integration/operations/u32_ops/mod.rs index 582052ac91..048c5a2fd7 100644 --- a/miden/tests/integration/operations/u32_ops/mod.rs +++ b/miden/tests/integration/operations/u32_ops/mod.rs @@ -1,5 +1,5 @@ use processor::ExecutionError; -use test_utils::{build_op_test, expect_exec_error, prop_randw, Felt, U32_BOUND, ZERO}; +use test_utils::{build_op_test, expect_exec_error_matches, prop_randw, Felt, U32_BOUND, ZERO}; mod arithmetic_ops; mod bitwise_ops; @@ -13,7 +13,11 @@ mod conversion_ops; /// ensure that it fails when the input is >= 2^32. pub fn test_input_out_of_bounds(asm_op: &str) { let test = build_op_test!(asm_op, &[U32_BOUND]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(U32_BOUND), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(U32_BOUND) && err_code == ZERO + ); } /// This helper function tests a provided u32 assembly operation, which takes multiple inputs, to @@ -27,6 +31,10 @@ pub fn test_inputs_out_of_bounds(asm_op: &str, input_count: usize) { i_inputs[i] = U32_BOUND; let test = build_op_test!(asm_op, &i_inputs); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(U32_BOUND), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(U32_BOUND) && err_code == ZERO + ); } } diff --git a/processor/Cargo.toml b/processor/Cargo.toml index 3f8061c300..cd52ae38a9 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -27,11 +27,11 @@ testing = ["miden-air/testing"] miden-air = { package = "miden-air", path = "../air", version = "0.11", default-features = false } tracing = { version = "0.1", default-features = false, features = ["attributes"] } vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false } -winter-prover = { package = "winter-prover", version = "0.10", default-features = false } +winter-prover = { package = "winter-prover", version = "0.11", default-features = false } [dev-dependencies] assembly = { package = "miden-assembly", path = "../assembly", version = "0.11", default-features = false } logtest = { version = "2.0", default-features = false } test-utils = { package = "miden-test-utils", path = "../test-utils" } -winter-fri = { package = "winter-fri", version = "0.10" } -winter-utils = { package = "winter-utils", version = "0.10" } +winter-fri = { package = "winter-fri", version = "0.11" } +winter-utils = { package = "winter-utils", version = "0.11" } diff --git a/processor/src/errors.rs b/processor/src/errors.rs index ae1f548ecb..b5c77f86e9 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -21,7 +21,7 @@ use crate::ContextId; // EXECUTION ERROR // ================================================================================================ -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug)] pub enum ExecutionError { AdviceMapKeyNotFound(Word), AdviceMapKeyAlreadyPresent(Word), diff --git a/prover/Cargo.toml b/prover/Cargo.toml index 64aa4add07..c080274751 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -24,8 +24,8 @@ std = ["air/std", "processor/std", "winter-prover/std"] air = { package = "miden-air", path = "../air", version = "0.11", default-features = false } processor = { package = "miden-processor", path = "../processor", version = "0.11", default-features = false } tracing = { version = "0.1", default-features = false, features = ["attributes"] } -winter-maybe-async = { package = "winter-maybe-async", version = "0.10", default-features = false } -winter-prover = { package = "winter-prover", version = "0.10", default-features = false } +winter-maybe-async = { package = "winter-maybe-async", version = "0.11", default-features = false } +winter-prover = { package = "winter-prover", version = "0.11", default-features = false } [target.'cfg(all(target_arch = "aarch64", target_os = "macos"))'.dependencies] elsa = { version = "1.9", optional = true } diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 09ab1de7e1..25fb837dbd 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -22,9 +22,9 @@ use processor::{ use tracing::instrument; use winter_maybe_async::{maybe_async, maybe_await}; use winter_prover::{ - matrix::ColMatrix, ConstraintCompositionCoefficients, DefaultConstraintEvaluator, - DefaultTraceLde, ProofOptions as WinterProofOptions, Prover, StarkDomain, TraceInfo, - TracePolyTable, + matrix::ColMatrix, CompositionPoly, CompositionPolyTrace, ConstraintCompositionCoefficients, + DefaultConstraintCommitment, DefaultConstraintEvaluator, DefaultTraceLde, + ProofOptions as WinterProofOptions, Prover, StarkDomain, TraceInfo, TracePolyTable, }; #[cfg(feature = "std")] use {std::time::Instant, winter_prover::Trace}; @@ -190,6 +190,8 @@ where type TraceLde> = DefaultTraceLde; type ConstraintEvaluator<'a, E: FieldElement> = DefaultConstraintEvaluator<'a, ProcessorAir, E>; + type ConstraintCommitment> = + DefaultConstraintCommitment; fn options(&self) -> &WinterProofOptions { &self.options @@ -240,4 +242,20 @@ where ) -> ColMatrix { trace.build_aux_trace(aux_rand_elements.rand_elements()).unwrap() } + + #[maybe_async] + fn build_constraint_commitment>( + &self, + composition_poly_trace: CompositionPolyTrace, + num_constraint_composition_columns: usize, + domain: &StarkDomain, + partition_options: PartitionOptions, + ) -> (Self::ConstraintCommitment, CompositionPoly) { + DefaultConstraintCommitment::new( + composition_poly_trace, + num_constraint_composition_columns, + domain, + partition_options, + ) + } } diff --git a/stdlib/Cargo.toml b/stdlib/Cargo.toml index 07e24b43dc..5d35ff006e 100644 --- a/stdlib/Cargo.toml +++ b/stdlib/Cargo.toml @@ -48,9 +48,9 @@ serde_json = "1.0" sha2 = "0.10" sha3 = "0.10" test-utils = { package = "miden-test-utils", path = "../test-utils" } -winter-air = { package = "winter-air", version = "0.10" } -winter-fri = { package = "winter-fri", version = "0.10" } - +vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false } +winter-air = { package = "winter-air", version = "0.11" } +winter-fri = { package = "winter-fri", version = "0.11" } [build-dependencies] assembly = { package = "miden-assembly", path = "../assembly", version = "0.11" } diff --git a/stdlib/tests/crypto/falcon.rs b/stdlib/tests/crypto/falcon.rs index eb27962348..5c41c90052 100644 --- a/stdlib/tests/crypto/falcon.rs +++ b/stdlib/tests/crypto/falcon.rs @@ -1,7 +1,7 @@ use std::vec; use assembly::{utils::Serializable, Assembler}; -use miden_air::{Felt, ProvingOptions}; +use miden_air::{Felt, ProvingOptions, RowIndex}; use miden_stdlib::StdLibrary; use processor::{ crypto::RpoRandomCoin, AdviceInputs, DefaultHost, Digest, ExecutionError, MemAdviceProvider, @@ -13,7 +13,7 @@ use test_utils::{ rpo_falcon512::{Polynomial, SecretKey}, MerkleStore, Rpo256, }, - expect_exec_error, + expect_exec_error_matches, rand::{rand_value, rand_vector}, FieldElement, QuadFelt, Word, WORD_SIZE, }; @@ -170,13 +170,11 @@ fn test_falcon512_probabilistic_product_failure() { let stack_init = vec![h_hash_copy[0], h_hash_copy[1], h_hash_copy[2], h_hash_copy[3]]; let test = build_test!(PROBABILISTIC_PRODUCT_SOURCE, &stack_init, &advice_stack); - expect_exec_error!( + + expect_exec_error_matches!( test, - ExecutionError::FailedAssertion { - clk: 17490.into(), - err_code: 0, - err_msg: None, - } + ExecutionError::FailedAssertion{ clk, err_code, err_msg } + if clk == RowIndex::from(17490) && err_code == 0 && err_msg.is_none() ); } diff --git a/stdlib/tests/crypto/rpo.rs b/stdlib/tests/crypto/rpo.rs index 48721c4e6a..f3879ce22a 100644 --- a/stdlib/tests/crypto/rpo.rs +++ b/stdlib/tests/crypto/rpo.rs @@ -1,5 +1,6 @@ +use miden_air::RowIndex; use processor::ExecutionError; -use test_utils::{build_expected_hash, build_expected_perm, expect_exec_error}; +use test_utils::{build_expected_hash, build_expected_perm, expect_exec_error_matches}; #[test] fn test_invalid_end_addr() { @@ -15,13 +16,11 @@ fn test_invalid_end_addr() { end "; let test = build_test!(empty_range, &[]); - expect_exec_error!( + + expect_exec_error_matches!( test, - ExecutionError::FailedAssertion { - clk: 18.into(), - err_code: 0, - err_msg: None, - } + ExecutionError::FailedAssertion{ clk, err_code, err_msg } + if clk == RowIndex::from(18) && err_code == 0 && err_msg.is_none() ); } diff --git a/stdlib/tests/math/u64_mod.rs b/stdlib/tests/math/u64_mod.rs index 55cf8206fd..cd621ce6ea 100644 --- a/stdlib/tests/math/u64_mod.rs +++ b/stdlib/tests/math/u64_mod.rs @@ -2,7 +2,7 @@ use core::cmp; use processor::ExecutionError; use test_utils::{ - expect_exec_error, proptest::prelude::*, rand::rand_value, Felt, U32_BOUND, ZERO, + expect_exec_error_matches, proptest::prelude::*, rand::rand_value, Felt, U32_BOUND, ZERO, }; // ADDITION @@ -520,7 +520,11 @@ fn checked_and_fail() { end"; let test = build_test!(source, &[a0, a1, b0, b1]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(a0), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(a0) && err_code == ZERO + ); } #[test] @@ -558,7 +562,11 @@ fn checked_or_fail() { end"; let test = build_test!(source, &[a0, a1, b0, b1]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(a0), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(a0) && err_code == ZERO + ); } #[test] @@ -596,7 +604,11 @@ fn checked_xor_fail() { end"; let test = build_test!(source, &[a0, a1, b0, b1]); - expect_exec_error!(test, ExecutionError::NotU32Value(Felt::new(a0), ZERO)); + + expect_exec_error_matches!( + test, + ExecutionError::NotU32Value(value, err_code) if value == Felt::new(a0) && err_code == ZERO + ); } #[test] diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index 5f02885ef9..5646e622f1 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -35,7 +35,7 @@ prover = { package = "miden-prover", path = "../prover", version = "0.11", defau test-case = "3.2" verifier = { package = "miden-verifier", path = "../verifier", version = "0.11", default-features = false } vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false } -winter-prover = { package = "winter-prover", version = "0.10", default-features = false } +winter-prover = { package = "winter-prover", version = "0.11", default-features = false } [target.'cfg(target_family = "wasm")'.dependencies] pretty_assertions = { version = "1.4", default-features = false, features = ["alloc"] } @@ -43,4 +43,4 @@ pretty_assertions = { version = "1.4", default-features = false, features = ["al [target.'cfg(not(target_family = "wasm"))'.dependencies] pretty_assertions = "1.4" proptest = "1.4" -rand-utils = { package = "winter-rand-utils", version = "0.10" } +rand-utils = { package = "winter-rand-utils", version = "0.11" } diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index c5b3f4ebc5..2d048db53f 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -116,18 +116,6 @@ macro_rules! expect_exec_error_matches { }; } -/// Asserts that running the given execution test will result in the expected error. -#[cfg(all(feature = "std", not(target_family = "wasm")))] -#[macro_export] -macro_rules! expect_exec_error { - ($test:expr, $expected:expr) => { - match $test.execute() { - Ok(_) => panic!("expected execution to fail @ {}:{}", file!(), line!()), - Err(error) => $crate::assert_eq!(error, $expected), - } - }; -} - /// Like [assembly::assert_diagnostic], but matches each non-empty line of the rendered output to a /// corresponding pattern. /// diff --git a/verifier/Cargo.toml b/verifier/Cargo.toml index a72dc4e5d2..a91742fd3c 100644 --- a/verifier/Cargo.toml +++ b/verifier/Cargo.toml @@ -25,4 +25,4 @@ std = ["air/std", "vm-core/std", "winter-verifier/std"] air = { package = "miden-air", path = "../air", version = "0.11", default-features = false } tracing = { version = "0.1", default-features = false, features = ["attributes"] } vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false } -winter-verifier = { package = "winter-verifier", version = "0.10", default-features = false } +winter-verifier = { package = "winter-verifier", version = "0.11", default-features = false } From 60a32af1bd8eddc2d8cfcf957ec3345157c81463 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Nov 2024 18:12:55 -0800 Subject: [PATCH 23/39] fix: update metal prover to work with latest miden-gpu --- Cargo.lock | 55 ++--- prover/Cargo.toml | 2 +- prover/src/gpu/metal/mod.rs | 373 +++++++++++++++++++++------------- prover/src/gpu/metal/tests.rs | 23 ++- 4 files changed, 272 insertions(+), 181 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 834e4bd629..7421c9a8d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1045,9 +1045,9 @@ dependencies = [ "num-traits", "parking_lot", "proptest", - "winter-math 0.11.0", + "winter-math", "winter-rand-utils", - "winter-utils 0.11.0", + "winter-utils", ] [[package]] @@ -1066,8 +1066,8 @@ dependencies = [ "sha3", "thiserror 2.0.3", "winter-crypto", - "winter-math 0.11.0", - "winter-utils 0.11.0", + "winter-math", + "winter-utils", ] [[package]] @@ -1081,13 +1081,13 @@ dependencies = [ [[package]] name = "miden-gpu" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271d375ea8bfdb0995f30d27d5eccc1468326e502e6ad6bdb0f9ee2d91ade50c" +checksum = "04742d5184bd76e7b92afcd9ca36b7c587a555d5fc4b27bb8f9d64ac57151f9c" dependencies = [ "metal", "once_cell", - "winter-math 0.10.2", + "winter-math", ] [[package]] @@ -1144,7 +1144,7 @@ dependencies = [ "tracing", "winter-fri", "winter-prover", - "winter-utils 0.11.0", + "winter-utils", ] [[package]] @@ -2769,8 +2769,8 @@ dependencies = [ "libm", "winter-crypto", "winter-fri", - "winter-math 0.11.0", - "winter-utils 0.11.0", + "winter-math", + "winter-utils", ] [[package]] @@ -2781,8 +2781,8 @@ checksum = "67c57748fd2da77742be601f03eda639ff6046879738fd1faae86e80018263cb" dependencies = [ "blake3", "sha3", - "winter-math 0.11.0", - "winter-utils 0.11.0", + "winter-math", + "winter-utils", ] [[package]] @@ -2792,17 +2792,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9f999bc248c6254138b627035cb6d2c319580eb37dadcab6672298dbf00e41" dependencies = [ "winter-crypto", - "winter-math 0.11.0", - "winter-utils 0.11.0", -] - -[[package]] -name = "winter-math" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82479f94efc0b5374a93e2074ba46ef404384fb1ea6e35a847febec53096509b" -dependencies = [ - "winter-utils 0.10.2", + "winter-math", + "winter-utils", ] [[package]] @@ -2811,7 +2802,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6020c17839fa107ce4a7cc178e407ebbc24adfac1980f4fa2111198e052700ab" dependencies = [ - "winter-utils 0.11.0", + "winter-utils", ] [[package]] @@ -2834,9 +2825,9 @@ dependencies = [ "winter-air", "winter-crypto", "winter-fri", - "winter-math 0.11.0", + "winter-math", "winter-maybe-async", - "winter-utils 0.11.0", + "winter-utils", ] [[package]] @@ -2846,15 +2837,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "226e4c455f6eb72f64ac6eeb7642df25e21ff2280a4f6b09db75392ad6b390ef" dependencies = [ "rand", - "winter-utils 0.11.0", + "winter-utils", ] -[[package]] -name = "winter-utils" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f948e71ffd482aa13d0ec3349047f81ecdb89f3b3287577973dcbf092a25fb4" - [[package]] name = "winter-utils" version = "0.11.0" @@ -2873,8 +2858,8 @@ dependencies = [ "winter-air", "winter-crypto", "winter-fri", - "winter-math 0.11.0", - "winter-utils 0.11.0", + "winter-math", + "winter-utils", ] [[package]] diff --git a/prover/Cargo.toml b/prover/Cargo.toml index c080274751..2dc9f783a5 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -29,5 +29,5 @@ winter-prover = { package = "winter-prover", version = "0.11", default-features [target.'cfg(all(target_arch = "aarch64", target_os = "macos"))'.dependencies] elsa = { version = "1.9", optional = true } -miden-gpu = { version = "0.3", optional = true } +miden-gpu = { version = "0.4", optional = true } pollster = { version = "0.4", optional = true } diff --git a/prover/src/gpu/metal/mod.rs b/prover/src/gpu/metal/mod.rs index 3aa6ae9dca..c01b13ee85 100644 --- a/prover/src/gpu/metal/mod.rs +++ b/prover/src/gpu/metal/mod.rs @@ -65,59 +65,6 @@ where phantom_data: PhantomData, } } - - fn build_aligned_segment( - polys: &ColMatrix, - poly_offset: usize, - offsets: &[Felt], - twiddles: &[Felt], - ) -> Segment - where - E: FieldElement, - { - let poly_size = polys.num_rows(); - let domain_size = offsets.len(); - assert!(domain_size.is_power_of_two()); - assert!(domain_size > poly_size); - assert_eq!(poly_size, twiddles.len() * 2); - assert!(poly_offset < polys.num_base_cols()); - - // allocate memory for the segment - let data = if polys.num_base_cols() - poly_offset >= N { - // if we will fill the entire segment, we allocate uninitialized memory - unsafe { page_aligned_uninit_vector(domain_size) } - } else { - // but if some columns in the segment will remain unfilled, we allocate memory - // initialized to zeros to make sure we don't end up with memory with - // undefined values - vec![[E::BaseField::ZERO; N]; domain_size] - }; - - Segment::new_with_buffer(data, polys, poly_offset, offsets, twiddles) - } - - fn build_aligned_segments( - polys: &ColMatrix, - twiddles: &[Felt], - offsets: &[Felt], - ) -> Vec> - where - E: FieldElement, - { - assert!(N > 0, "batch size N must be greater than zero"); - debug_assert_eq!(polys.num_rows(), twiddles.len() * 2); - debug_assert_eq!(offsets.len() % polys.num_rows(), 0); - - let num_segments = if polys.num_base_cols() % N == 0 { - polys.num_base_cols() / N - } else { - polys.num_base_cols() / N + 1 - }; - - (0..num_segments) - .map(|i| Self::build_aligned_segment(polys, i * N, offsets, twiddles)) - .collect() - } } impl Prover for MetalExecutionProver @@ -135,6 +82,7 @@ where type TraceLde> = MetalTraceLde; type ConstraintEvaluator<'a, E: FieldElement> = DefaultConstraintEvaluator<'a, ProcessorAir, E>; + type ConstraintCommitment> = MetalConstraintCommitment; fn get_pub_inputs(&self, trace: &ExecutionTrace) -> PublicInputs { self.execution_prover.get_pub_inputs(trace) @@ -172,85 +120,19 @@ where .new_evaluator(air, aux_rand_elements, composition_coefficients) } - /// Evaluates constraint composition polynomial over the LDE domain and builds a commitment - /// to these evaluations. - /// - /// The evaluation is done by evaluating each composition polynomial column over the LDE - /// domain. - /// - /// The commitment is computed by hashing each row in the evaluation matrix, and then building - /// a Merkle tree from the resulting hashes. - /// - /// The composition polynomial columns are evaluated on the CPU. Afterwards the commitment - /// is computed on the GPU. - /// - /// ```text - /// ───────────────────────────────────────────────────── - /// ┌───┐ ┌───┐ - /// CPU: ... ─┤fft├─┤fft├─┐ ┌─ ... - /// └───┘ └───┘ │ │ - /// ╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴┼╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴┼╴╴╴╴╴╴ - /// │ ┌──────────┐ ┌──────────┐ │ - /// GPU: └─┤ hash ├─┤ hash ├─┘ - /// └──────────┘ └──────────┘ - /// ────┼────────┼────────┼────────┼────────┼────────┼─── - /// t=n t=n+1 t=n+2 t=n+3 t=n+4 t=n+5 - /// ``` fn build_constraint_commitment>( &self, composition_poly_trace: CompositionPolyTrace, - num_trace_poly_columns: usize, - domain: &StarkDomain, - ) -> ( - ConstraintCommitment>, - CompositionPoly, - ) { - // evaluate composition polynomial columns over the LDE domain - let now = Instant::now(); - let composition_poly = - CompositionPoly::new(composition_poly_trace, domain, num_trace_poly_columns); - let blowup = domain.trace_to_lde_blowup(); - let offsets = - get_evaluation_offsets::(composition_poly.column_len(), blowup, domain.offset()); - let segments = Self::build_aligned_segments( - composition_poly.data(), - domain.trace_twiddles(), - &offsets, - ); - event!( - Level::INFO, - "Evaluated {} composition polynomial columns over LDE domain (2^{} elements) in {} ms", - composition_poly.num_columns(), - offsets.len().ilog2(), - now.elapsed().as_millis() - ); - - // build constraint evaluation commitment - let now = Instant::now(); - let lde_domain_size = domain.lde_domain_size(); - let num_base_columns = - composition_poly.num_columns() * ::EXTENSION_DEGREE; - - let mut row_hasher = RowHasher::new(lde_domain_size, num_base_columns, self.metal_hash_fn); - for segment in segments.iter() { - row_hasher.update(segment); - } - let row_hashes = block_on(row_hasher.finish()); - let tree_nodes = build_merkle_tree(&row_hashes, self.metal_hash_fn); - - // aggregate segments at the same time as the GPU generates the merkle tree nodes - let composed_evaluations = RowMatrix::::from_segments(segments, num_base_columns); - let nodes = block_on(tree_nodes).into_iter().map(|dig| H::Digest::from(&dig)).collect(); - let leaves = row_hashes.into_iter().map(|dig| H::Digest::from(&dig)).collect(); - let commitment = MerkleTree::::from_raw_parts(nodes, leaves).unwrap(); - let constraint_commitment = ConstraintCommitment::new(composed_evaluations, commitment); - event!( - Level::INFO, - "Computed constraint evaluation commitment on the GPU (Merkle tree of depth {}) in {} ms", - lde_domain_size.ilog2(), - now.elapsed().as_millis() - ); - (constraint_commitment, composition_poly) + num_constraint_composition_columns: usize, + domain: &StarkDomain, + _partition_options: PartitionOptions, + ) -> (Self::ConstraintCommitment, CompositionPoly) { + MetalConstraintCommitment::new( + composition_poly_trace, + num_constraint_composition_columns, + domain, + self.metal_hash_fn, + ) } } @@ -279,11 +161,11 @@ pub struct MetalTraceLde, H: Hasher> { metal_hash_fn: HashFn, } -impl< - E: FieldElement, - H: Hasher + ElementHasher, - D: Digest + for<'a> From<&'a [Felt; DIGEST_SIZE]>, - > MetalTraceLde +impl MetalTraceLde +where + E: FieldElement, + H: Hasher + ElementHasher, + D: Digest + for<'a> From<&'a [Felt; DIGEST_SIZE]>, { /// Takes the main trace segment columns as input, interpolates them into polynomials in /// coefficient form, evaluates the polynomials over the LDE domain, commits to the @@ -505,15 +387,16 @@ where /// ────┼────────┼────────┼────────┼────────┼────────┼──── /// t=n t=n+1 t=n+2 t=n+3 t=n+4 t=n+5 /// ``` -fn build_trace_commitment< - E: FieldElement, - H: Hasher + ElementHasher, - D: Digest + for<'a> From<&'a [Felt; DIGEST_SIZE]>, ->( +fn build_trace_commitment( trace: &ColMatrix, domain: &StarkDomain, hash_fn: HashFn, -) -> (RowMatrix, MerkleTree, ColMatrix) { +) -> (RowMatrix, MerkleTree, ColMatrix) +where + E: FieldElement, + H: Hasher + ElementHasher, + D: Digest + for<'a> From<&'a [Felt; DIGEST_SIZE]>, +{ // interpolate the execution trace let now = Instant::now(); let inv_twiddles = fft::get_inv_twiddles::(trace.num_rows()); @@ -556,6 +439,163 @@ fn build_trace_commitment< (trace_lde, trace_tree, trace_polys) } +// CONSTRAINT COMMITMENT (METAL) +// ================================================================================================ + +pub struct MetalConstraintCommitment> { + evaluations: RowMatrix, + vector_commitment: MerkleTree, +} + +impl MetalConstraintCommitment +where + E: FieldElement, + H: Hasher + ElementHasher, + D: Digest + for<'a> From<&'a [Felt; DIGEST_SIZE]>, +{ + /// Creates a new constraint evaluation commitment from the provided composition polynomial + /// evaluations and the corresponding vector commitment. + pub fn new( + composition_poly_trace: CompositionPolyTrace, + num_constraint_composition_columns: usize, + domain: &StarkDomain, + metal_hash_fn: HashFn, + ) -> (Self, CompositionPoly) { + // extend the main execution trace and build a commitment to the extended trace + let (evaluations, commitment, composition_poly) = build_constraint_commitment::( + composition_poly_trace, + num_constraint_composition_columns, + domain, + metal_hash_fn, + ); + + assert_eq!( + evaluations.num_rows(), + commitment.domain_len(), + "number of rows in constraint evaluation matrix must be the same as the size \ + of the vector commitment domain" + ); + + let commitment = Self { + evaluations, + vector_commitment: commitment, + }; + + (commitment, composition_poly) + } +} + +impl ConstraintCommitment for MetalConstraintCommitment +where + E: FieldElement, + H: ElementHasher + core::marker::Sync, +{ + type HashFn = H; + type VC = MerkleTree; + + /// Returns the commitment. + fn commitment(&self) -> H::Digest { + self.vector_commitment.commitment() + } + + /// Returns constraint evaluations at the specified positions along with a batch opening proof + /// against the vector commitment. + fn query(self, positions: &[usize]) -> Queries { + // build batch opening proof to the leaves specified by positions + let opening_proof = self + .vector_commitment + .open_many(positions) + .expect("failed to generate a batch opening proof for constraint queries"); + + // determine a set of evaluations corresponding to each position + let mut evaluations = Vec::new(); + for &position in positions { + let row = self.evaluations.row(position).to_vec(); + evaluations.push(row); + } + + Queries::new::>(opening_proof.1, evaluations) + } +} + +/// Evaluates constraint composition polynomial over the LDE domain and builds a commitment +/// to these evaluations. +/// +/// The evaluation is done by evaluating each composition polynomial column over the LDE +/// domain. +/// +/// The commitment is computed by hashing each row in the evaluation matrix, and then building +/// a Merkle tree from the resulting hashes. +/// +/// The composition polynomial columns are evaluated on the CPU. Afterwards the commitment +/// is computed on the GPU. +/// +/// ```text +/// ───────────────────────────────────────────────────── +/// ┌───┐ ┌───┐ +/// CPU: ... ─┤fft├─┤fft├─┐ ┌─ ... +/// └───┘ └───┘ │ │ +/// ╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴┼╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴┼╴╴╴╴╴╴ +/// │ ┌──────────┐ ┌──────────┐ │ +/// GPU: └─┤ hash ├─┤ hash ├─┘ +/// └──────────┘ └──────────┘ +/// ────┼────────┼────────┼────────┼────────┼────────┼─── +/// t=n t=n+1 t=n+2 t=n+3 t=n+4 t=n+5 +/// ``` +fn build_constraint_commitment( + composition_poly_trace: CompositionPolyTrace, + num_constraint_composition_columns: usize, + domain: &StarkDomain, + hash_fn: HashFn, +) -> (RowMatrix, MerkleTree, CompositionPoly) +where + E: FieldElement, + H: Hasher + ElementHasher, + D: Digest + for<'a> From<&'a [Felt; DIGEST_SIZE]>, +{ + // evaluate composition polynomial columns over the LDE domain + let now = Instant::now(); + let composition_poly = + CompositionPoly::new(composition_poly_trace, domain, num_constraint_composition_columns); + let blowup = domain.trace_to_lde_blowup(); + let offsets = + get_evaluation_offsets::(composition_poly.column_len(), blowup, domain.offset()); + let segments = + build_aligned_segments(composition_poly.data(), domain.trace_twiddles(), &offsets); + event!( + Level::INFO, + "Evaluated {} composition polynomial columns over LDE domain (2^{} elements) in {} ms", + composition_poly.num_columns(), + offsets.len().ilog2(), + now.elapsed().as_millis() + ); + + // build constraint evaluation commitment + let now = Instant::now(); + let lde_domain_size = domain.lde_domain_size(); + let num_base_columns = composition_poly.num_columns() * ::EXTENSION_DEGREE; + + let mut row_hasher = RowHasher::new(lde_domain_size, num_base_columns, hash_fn); + for segment in segments.iter() { + row_hasher.update(segment); + } + let row_hashes = block_on(row_hasher.finish()); + let tree_nodes = build_merkle_tree(&row_hashes, hash_fn); + + // aggregate segments at the same time as the GPU generates the merkle tree nodes + let composed_evaluations = RowMatrix::::from_segments(segments, num_base_columns); + let nodes = block_on(tree_nodes).into_iter().map(|dig| H::Digest::from(&dig)).collect(); + let leaves = row_hashes.into_iter().map(|dig| H::Digest::from(&dig)).collect(); + let commitment = MerkleTree::::from_raw_parts(nodes, leaves).unwrap(); + event!( + Level::INFO, + "Computed constraint evaluation commitment on the GPU (Merkle tree of depth {}) in {} ms", + lde_domain_size.ilog2(), + now.elapsed().as_millis() + ); + (composed_evaluations, commitment, composition_poly) +} + // SEGMENT GENERATOR // ================================================================================================ @@ -675,3 +715,56 @@ where self.0.gen_next_segment() } } + +fn build_aligned_segments( + polys: &ColMatrix, + twiddles: &[Felt], + offsets: &[Felt], +) -> Vec> +where + E: FieldElement, +{ + assert!(N > 0, "batch size N must be greater than zero"); + debug_assert_eq!(polys.num_rows(), twiddles.len() * 2); + debug_assert_eq!(offsets.len() % polys.num_rows(), 0); + + let num_segments = if polys.num_base_cols() % N == 0 { + polys.num_base_cols() / N + } else { + polys.num_base_cols() / N + 1 + }; + + (0..num_segments) + .map(|i| build_aligned_segment(polys, i * N, offsets, twiddles)) + .collect() +} + +fn build_aligned_segment( + polys: &ColMatrix, + poly_offset: usize, + offsets: &[Felt], + twiddles: &[Felt], +) -> Segment +where + E: FieldElement, +{ + let poly_size = polys.num_rows(); + let domain_size = offsets.len(); + assert!(domain_size.is_power_of_two()); + assert!(domain_size > poly_size); + assert_eq!(poly_size, twiddles.len() * 2); + assert!(poly_offset < polys.num_base_cols()); + + // allocate memory for the segment + let data = if polys.num_base_cols() - poly_offset >= N { + // if we will fill the entire segment, we allocate uninitialized memory + unsafe { page_aligned_uninit_vector(domain_size) } + } else { + // but if some columns in the segment will remain unfilled, we allocate memory + // initialized to zeros to make sure we don't end up with memory with + // undefined values + vec![[E::BaseField::ZERO; N]; domain_size] + }; + + Segment::new_with_buffer(data, polys, poly_offset, offsets, twiddles) +} diff --git a/prover/src/gpu/metal/tests.rs b/prover/src/gpu/metal/tests.rs index 533169d49f..2d59755308 100644 --- a/prover/src/gpu/metal/tests.rs +++ b/prover/src/gpu/metal/tests.rs @@ -7,7 +7,10 @@ use processor::{ math::fft, StackInputs, StackOutputs, }; -use winter_prover::{crypto::Digest, math::fields::CubeExtension, CompositionPolyTrace, TraceLde}; +use winter_prover::{ + crypto::Digest, math::fields::CubeExtension, CompositionPolyTrace, ConstraintCommitment, + TraceLde, +}; use crate::*; @@ -176,9 +179,14 @@ where CompositionPolyTrace::new(values.clone()), 2, &domain, + PartitionOptions::default(), + ); + let (commitment_gpu, composition_poly_gpu) = gpu_prover.build_constraint_commitment( + CompositionPolyTrace::new(values), + 2, + &domain, + PartitionOptions::default(), ); - let (commitment_gpu, composition_poly_gpu) = - gpu_prover.build_constraint_commitment(CompositionPolyTrace::new(values), 2, &domain); assert_eq!(commitment_cpu.commitment(), commitment_gpu.commitment()); assert_ne!(0, composition_poly_cpu.data().num_base_cols() % RATE); @@ -204,9 +212,14 @@ where CompositionPolyTrace::new(values.clone()), 8, &domain, + PartitionOptions::default(), + ); + let (commitment_gpu, composition_poly_gpu) = gpu_prover.build_constraint_commitment( + CompositionPolyTrace::new(values), + 8, + &domain, + PartitionOptions::default(), ); - let (commitment_gpu, composition_poly_gpu) = - gpu_prover.build_constraint_commitment(CompositionPolyTrace::new(values), 8, &domain); assert_eq!(commitment_cpu.commitment(), commitment_gpu.commitment()); assert_eq!(0, composition_poly_cpu.data().num_base_cols() % RATE); From 18cb244e9a27b91869f54dc819c7739a1d280e72 Mon Sep 17 00:00:00 2001 From: Varun Doshi <61531351+varun-doshi@users.noreply.github.com> Date: Fri, 29 Nov 2024 12:34:46 +0530 Subject: [PATCH 24/39] fix: VerifyCmd Flag Collision (#1513) --- CHANGELOG.md | 9 +++++---- miden/src/cli/verify.rs | 38 ++++++++++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcf5dce84a..90bbc4eac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog #### Changes -- [BREAKING] `Process` no longer takes ownership of the `Host` (#1571) -- [BREAKING] `ProcessState` was converted from a trait to a struct (#1571) -- [BREAKING] `Host` and `AdviceProvider` traits simplified (#1572) -- [BREAKING] `MastForest` serialization/deserialization will store/read decorator data at the end of the binary (#1531) +- [BREAKING] `Process` no longer takes ownership of the `Host` (#1571). +- [BREAKING] `ProcessState` was converted from a trait to a struct (#1571). +- [BREAKING] `Host` and `AdviceProvider` traits simplified (#1572). +- [BREAKING] `MastForest` serialization/deserialization will store/read decorator data at the end of the binary (#1531). - [BREAKING] Updated Winterfell dependency to v0.11 (#1586). +- [BREAKING] resolved flag collision in `--verify` command and added functionality for optional input/output files (#1513). #### Enhancements - Added `miden_core::mast::MastForest::advice_map` to load it into the advice provider before the `MastForest` execution (#1574). diff --git a/miden/src/cli/verify.rs b/miden/src/cli/verify.rs index 71e259ab4d..1130b82d45 100644 --- a/miden/src/cli/verify.rs +++ b/miden/src/cli/verify.rs @@ -1,6 +1,9 @@ -use std::{path::PathBuf, time::Instant}; +use std::{ + path::{Path, PathBuf}, + time::Instant, +}; -use assembly::diagnostics::{IntoDiagnostic, Report, WrapErr}; +use assembly::diagnostics::{IntoDiagnostic, Report, Result, WrapErr}; use clap::Parser; use miden_vm::{Kernel, ProgramInfo}; @@ -19,12 +22,14 @@ pub struct VerifyCmd { #[clap(short = 'p', long = "proof", value_parser)] proof_file: PathBuf, /// Program hash (hex) - #[clap(short = 'h', long = "program-hash")] + #[clap(short = 'x', long = "program-hash")] program_hash: String, } impl VerifyCmd { pub fn execute(&self) -> Result<(), Report> { + let (input_file, output_file) = self.infer_defaults().unwrap(); + println!("==============================================================================="); println!("Verifying proof: {}", self.proof_file.display()); println!("-------------------------------------------------------------------------------"); @@ -33,17 +38,17 @@ impl VerifyCmd { let program_hash = ProgramHash::read(&self.program_hash).map_err(Report::msg)?; // load input data from file - let input_data = InputFile::read(&self.input_file, &self.proof_file)?; + let input_data = InputFile::read(&Some(input_file), self.proof_file.as_ref())?; // fetch the stack inputs from the arguments let stack_inputs = input_data.parse_stack_inputs().map_err(Report::msg)?; // load outputs data from file let outputs_data = - OutputFile::read(&self.output_file, &self.proof_file).map_err(Report::msg)?; + OutputFile::read(&Some(output_file), self.proof_file.as_ref()).map_err(Report::msg)?; // load proof from file - let proof = ProofFile::read(&Some(self.proof_file.clone()), &self.proof_file) + let proof = ProofFile::read(&Some(self.proof_file.clone()), self.proof_file.as_ref()) .map_err(Report::msg)?; let now = Instant::now(); @@ -62,4 +67,25 @@ impl VerifyCmd { Ok(()) } + + fn infer_defaults(&self) -> Result<(PathBuf, PathBuf), Report> { + let proof_file = if Path::new(&self.proof_file.as_os_str()).try_exists().is_err() { + return Err(Report::msg("Proof file does not exist")); + } else { + self.proof_file.clone() + }; + + let input_file = self.input_file.clone().unwrap_or_else(|| { + let mut input_path = proof_file.clone(); + input_path.set_extension("inputs"); + input_path + }); + let output_file = self.output_file.clone().unwrap_or_else(|| { + let mut output_path = proof_file.clone(); + output_path.set_extension("outputs"); + output_path + }); + + Ok((input_file.to_path_buf(), output_file.to_path_buf())) + } } From 3896669cfa073274da7e7905fbad39ad287f5556 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Tue, 26 Nov 2024 11:21:22 -0500 Subject: [PATCH 25/39] refactor: rename `examples/` folder to `masm-examples/` --- docs/src/intro/usage.md | 10 +++++----- docs/src/tools/debugger.md | 6 +++--- miden/README.md | 8 ++++---- miden/masm-examples/README.md | 3 +++ miden/{examples => masm-examples}/debug/debug.inputs | 0 miden/{examples => masm-examples}/debug/debug.masm | 0 miden/{examples => masm-examples}/fib/fib.inputs | 0 miden/{examples => masm-examples}/fib/fib.masm | 0 .../hashing/blake3/blake3.inputs | 0 .../hashing/blake3/blake3.masm | 0 .../hashing/sha256/sha256.inputs | 0 .../hashing/sha256/sha256.masm | 0 .../merkle_store/merkle_store.inputs | 0 .../merkle_store/merkle_store.masm | 0 miden/{examples => masm-examples}/nprime/nprime.inputs | 0 miden/{examples => masm-examples}/nprime/nprime.masm | 0 miden/tests/integration/cli/cli_test.rs | 4 ++-- 17 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 miden/masm-examples/README.md rename miden/{examples => masm-examples}/debug/debug.inputs (100%) rename miden/{examples => masm-examples}/debug/debug.masm (100%) rename miden/{examples => masm-examples}/fib/fib.inputs (100%) rename miden/{examples => masm-examples}/fib/fib.masm (100%) rename miden/{examples => masm-examples}/hashing/blake3/blake3.inputs (100%) rename miden/{examples => masm-examples}/hashing/blake3/blake3.masm (100%) rename miden/{examples => masm-examples}/hashing/sha256/sha256.inputs (100%) rename miden/{examples => masm-examples}/hashing/sha256/sha256.masm (100%) rename miden/{examples => masm-examples}/merkle_store/merkle_store.inputs (100%) rename miden/{examples => masm-examples}/merkle_store/merkle_store.masm (100%) rename miden/{examples => masm-examples}/nprime/nprime.inputs (100%) rename miden/{examples => masm-examples}/nprime/nprime.masm (100%) diff --git a/docs/src/intro/usage.md b/docs/src/intro/usage.md index 67137c8593..44423c16e9 100644 --- a/docs/src/intro/usage.md +++ b/docs/src/intro/usage.md @@ -139,10 +139,10 @@ After a program finishes executing, the elements that remain on the stack become ## Fibonacci example -In the `miden/examples/fib` directory, we provide a very simple Fibonacci calculator example. This example computes the 1001st term of the Fibonacci sequence. You can execute this example on Miden VM like so: +In the `miden/masm-examples/fib` directory, we provide a very simple Fibonacci calculator example. This example computes the 1001st term of the Fibonacci sequence. You can execute this example on Miden VM like so: ```shell -./target/optimized/miden run -a miden/examples/fib/fib.masm -n 1 +./target/optimized/miden run -a miden/masm-examples/fib/fib.masm ``` ### Capturing Output @@ -152,17 +152,17 @@ This will run the example code to completion and will output the top element rem If you want the output of the program in a file, you can use the `--output` or `-o` flag and specify the path to the output file. For example: ```shell -./target/optimized/miden run -a miden/examples/fib/fib.masm -o fib.out +./target/optimized/miden run -a miden/masm-examples/fib/fib.masm -o fib.out ``` This will dump the output of the program into the `fib.out` file. The output file will contain the state of the stack at the end of the program execution. ### Running with debug instruction enabled -Inside `miden/examples/fib/fib.masm`, insert `debug.stack` instruction anywhere between `begin` and `end`. Then run: +Inside `miden/masm-examples/fib/fib.masm`, insert `debug.stack` instruction anywhere between `begin` and `end`. Then run: ```shell -./target/optimized/miden run -a miden/examples/fib/fib.masm -n 1 --debug +./target/optimized/miden run -a miden/masm-examples/fib/fib.masm -n 1 --debug ``` You should see output similar to "Stack state before step ..." diff --git a/docs/src/tools/debugger.md b/docs/src/tools/debugger.md index 83af1e15e6..5dc5edb069 100644 --- a/docs/src/tools/debugger.md +++ b/docs/src/tools/debugger.md @@ -20,7 +20,7 @@ The Miden debugger supports the following commands: In order to start debugging, the user should provide a `MASM` program: ```shell -cargo run --features executable -- debug --assembly miden/examples/nprime/nprime.masm +cargo run --features executable -- debug --assembly miden/masm-examples/nprime/nprime.masm ``` The expected output is: @@ -29,11 +29,11 @@ The expected output is: ============================================================ Debug program ============================================================ -Reading program file `miden/examples/nprime/nprime.masm` +Reading program file `miden/masm-examples/nprime/nprime.masm` Compiling program... done (16 ms) Debugging program with hash 11dbbddff27e26e48be3198133df8cbed6c5875d0fb 606c9f037c7893fde4118... -Reading input file `miden/examples/nprime/nprime.inputs` +Reading input file `miden/masm-examples/nprime/nprime.inputs` Welcome! Enter `h` for help. >> ``` diff --git a/miden/README.md b/miden/README.md index f3266c8bf4..4a4db7e1f5 100644 --- a/miden/README.md +++ b/miden/README.md @@ -32,7 +32,7 @@ Having a small number elements to describe public inputs and outputs of a progra ## Usage -Miden crate exposes several functions which can be used to execute programs, generate proofs of their correct execution, and verify the generated proofs. How to do this is explained below, but you can also take a look at working examples [here](examples/) and find instructions for running them via CLI [here](#fibonacci-example). +Miden crate exposes several functions which can be used to execute programs, generate proofs of their correct execution, and verify the generated proofs. How to do this is explained below, but you can also take a look at working examples [here](masm-examples/) and find instructions for running them via CLI [here](#fibonacci-example). ### Executing programs @@ -174,7 +174,7 @@ dup.1 // stack state: 2 1 2 add // stack state: 3 2 ``` -Notice that except for the first 2 operations which initialize the stack, the sequence of `swap dup.1 add` operations repeats over and over. In fact, we can repeat these operations an arbitrary number of times to compute an arbitrary Fibonacci number. In Rust, it would look like this (this is actually a simplified version of the example in [fibonacci.rs](src/examples/fibonacci.rs)): +Notice that except for the first 2 operations which initialize the stack, the sequence of `swap dup.1 add` operations repeats over and over. In fact, we can repeat these operations an arbitrary number of times to compute an arbitrary Fibonacci number. In Rust, it would look like this: ```rust use miden_vm::{Assembler, DefaultHost, Program, ProvingOptions, StackInputs}; @@ -291,10 +291,10 @@ For example: ### Fibonacci example -In the `miden/examples/fib` directory, we provide a very simple Fibonacci calculator example. This example computes the 1000th term of the Fibonacci sequence. You can execute this example on Miden VM like so: +In the `miden/masm-examples/fib` directory, we provide a very simple Fibonacci calculator example. This example computes the 1000th term of the Fibonacci sequence. You can execute this example on Miden VM like so: ```shell -./target/optimized/miden run -a miden/examples/fib/fib.masm -n 1 +./target/optimized/miden run -a miden/masm-examples/fib/fib.masm -n 1 ``` This will run the example code to completion and will output the top element remaining on the stack. diff --git a/miden/masm-examples/README.md b/miden/masm-examples/README.md new file mode 100644 index 0000000000..7dcaab22e5 --- /dev/null +++ b/miden/masm-examples/README.md @@ -0,0 +1,3 @@ +This directory contains a set of example MASM programs, along with files that describe the inputs to the stack and advice provider. + +Those files are also used for benchmarking. diff --git a/miden/examples/debug/debug.inputs b/miden/masm-examples/debug/debug.inputs similarity index 100% rename from miden/examples/debug/debug.inputs rename to miden/masm-examples/debug/debug.inputs diff --git a/miden/examples/debug/debug.masm b/miden/masm-examples/debug/debug.masm similarity index 100% rename from miden/examples/debug/debug.masm rename to miden/masm-examples/debug/debug.masm diff --git a/miden/examples/fib/fib.inputs b/miden/masm-examples/fib/fib.inputs similarity index 100% rename from miden/examples/fib/fib.inputs rename to miden/masm-examples/fib/fib.inputs diff --git a/miden/examples/fib/fib.masm b/miden/masm-examples/fib/fib.masm similarity index 100% rename from miden/examples/fib/fib.masm rename to miden/masm-examples/fib/fib.masm diff --git a/miden/examples/hashing/blake3/blake3.inputs b/miden/masm-examples/hashing/blake3/blake3.inputs similarity index 100% rename from miden/examples/hashing/blake3/blake3.inputs rename to miden/masm-examples/hashing/blake3/blake3.inputs diff --git a/miden/examples/hashing/blake3/blake3.masm b/miden/masm-examples/hashing/blake3/blake3.masm similarity index 100% rename from miden/examples/hashing/blake3/blake3.masm rename to miden/masm-examples/hashing/blake3/blake3.masm diff --git a/miden/examples/hashing/sha256/sha256.inputs b/miden/masm-examples/hashing/sha256/sha256.inputs similarity index 100% rename from miden/examples/hashing/sha256/sha256.inputs rename to miden/masm-examples/hashing/sha256/sha256.inputs diff --git a/miden/examples/hashing/sha256/sha256.masm b/miden/masm-examples/hashing/sha256/sha256.masm similarity index 100% rename from miden/examples/hashing/sha256/sha256.masm rename to miden/masm-examples/hashing/sha256/sha256.masm diff --git a/miden/examples/merkle_store/merkle_store.inputs b/miden/masm-examples/merkle_store/merkle_store.inputs similarity index 100% rename from miden/examples/merkle_store/merkle_store.inputs rename to miden/masm-examples/merkle_store/merkle_store.inputs diff --git a/miden/examples/merkle_store/merkle_store.masm b/miden/masm-examples/merkle_store/merkle_store.masm similarity index 100% rename from miden/examples/merkle_store/merkle_store.masm rename to miden/masm-examples/merkle_store/merkle_store.masm diff --git a/miden/examples/nprime/nprime.inputs b/miden/masm-examples/nprime/nprime.inputs similarity index 100% rename from miden/examples/nprime/nprime.inputs rename to miden/masm-examples/nprime/nprime.inputs diff --git a/miden/examples/nprime/nprime.masm b/miden/masm-examples/nprime/nprime.masm similarity index 100% rename from miden/examples/nprime/nprime.masm rename to miden/masm-examples/nprime/nprime.masm diff --git a/miden/tests/integration/cli/cli_test.rs b/miden/tests/integration/cli/cli_test.rs index ccadae4d0f..8d657bd7aa 100644 --- a/miden/tests/integration/cli/cli_test.rs +++ b/miden/tests/integration/cli/cli_test.rs @@ -8,7 +8,7 @@ extern crate escargot; fn cli_run() -> Result<(), Box> { let bin_under_test = escargot::CargoBuild::new() .bin("miden") - .features("executable") + .features("executable internal") .current_release() .current_target() .run() @@ -21,7 +21,7 @@ fn cli_run() -> Result<(), Box> { cmd.arg("run") .arg("-a") - .arg("./examples/fib/fib.masm") + .arg("./masm-examples/fib/fib.masm") .arg("-n") .arg("1") .arg("-m") From 0d5865e8dc7190e2199816ae572d8bd108583718 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Tue, 26 Nov 2024 14:01:03 -0500 Subject: [PATCH 26/39] refactor: move blake3 example to `masm-examples` directory --- .../hashing/blake3_1to1/blake3_1to1.inputs | 4 + .../hashing/blake3_1to1/blake3_1to1.masm | 28 ++++++ .../blake3_2to1.inputs} | 0 .../blake3_2to1.masm} | 0 miden/src/cli/prove.rs | 2 + miden/src/cli/run.rs | 2 + miden/src/examples/blake3.rs | 95 ------------------- miden/src/examples/mod.rs | 9 -- 8 files changed, 36 insertions(+), 104 deletions(-) create mode 100644 miden/masm-examples/hashing/blake3_1to1/blake3_1to1.inputs create mode 100644 miden/masm-examples/hashing/blake3_1to1/blake3_1to1.masm rename miden/masm-examples/hashing/{blake3/blake3.inputs => blake3_2to1/blake3_2to1.inputs} (100%) rename miden/masm-examples/hashing/{blake3/blake3.masm => blake3_2to1/blake3_2to1.masm} (100%) delete mode 100644 miden/src/examples/blake3.rs diff --git a/miden/masm-examples/hashing/blake3_1to1/blake3_1to1.inputs b/miden/masm-examples/hashing/blake3_1to1/blake3_1to1.inputs new file mode 100644 index 0000000000..426e94ead0 --- /dev/null +++ b/miden/masm-examples/hashing/blake3_1to1/blake3_1to1.inputs @@ -0,0 +1,4 @@ +{ + "operand_stack": ["4294967295", "4294967295", "4294967295", "4294967295", "4294967295", "4294967295", "4294967295", "4294967295"], + "advice_stack": ["100"] +} diff --git a/miden/masm-examples/hashing/blake3_1to1/blake3_1to1.masm b/miden/masm-examples/hashing/blake3_1to1/blake3_1to1.masm new file mode 100644 index 0000000000..562882f015 --- /dev/null +++ b/miden/masm-examples/hashing/blake3_1to1/blake3_1to1.masm @@ -0,0 +1,28 @@ +use.std::crypto::hashes::blake3 +use.std::sys + +begin + # Push the number of iterations on the stack, and assess if we should loop + adv_push.1 dup neq.0 + # => [0 or 1, num_iters_left, HASH_INPUTS_1, HASH_INPUTS_2] + + while.true + # Move loop counter down + movdn.8 + # => [HASH_INPUTS_1, HASH_INPUTS_2, num_iters_left] + + # Execute blake3 hash function + exec.blake3::hash_1to1 + # => [HASH_INPUTS_1', HASH_INPUTS_2', num_iters_left] + + # Decrement counter, and check if we loop again + movup.8 sub.1 dup neq.0 + # => [0 or 1, num_iters_left - 1, HASH_INPUTS_1', HASH_INPUTS_2'] + end + + # Drop counter + drop + + # Truncate stack to make constraints happy + exec.sys::truncate_stack +end diff --git a/miden/masm-examples/hashing/blake3/blake3.inputs b/miden/masm-examples/hashing/blake3_2to1/blake3_2to1.inputs similarity index 100% rename from miden/masm-examples/hashing/blake3/blake3.inputs rename to miden/masm-examples/hashing/blake3_2to1/blake3_2to1.inputs diff --git a/miden/masm-examples/hashing/blake3/blake3.masm b/miden/masm-examples/hashing/blake3_2to1/blake3_2to1.masm similarity index 100% rename from miden/masm-examples/hashing/blake3/blake3.masm rename to miden/masm-examples/hashing/blake3_2to1/blake3_2to1.masm diff --git a/miden/src/cli/prove.rs b/miden/src/cli/prove.rs index 92e41356c7..a5a782d2d9 100644 --- a/miden/src/cli/prove.rs +++ b/miden/src/cli/prove.rs @@ -4,6 +4,7 @@ use assembly::diagnostics::{IntoDiagnostic, Report, WrapErr}; use clap::Parser; use miden_vm::ProvingOptions; use processor::{DefaultHost, ExecutionOptions, ExecutionOptionsError, Program}; +use stdlib::StdLibrary; use super::data::{instrument, Debug, InputFile, Libraries, OutputFile, ProgramFile, ProofFile}; @@ -97,6 +98,7 @@ impl ProveCmd { // fetch the stack and program inputs from the arguments let stack_inputs = input_data.parse_stack_inputs().map_err(Report::msg)?; let mut host = DefaultHost::new(input_data.parse_advice_provider().map_err(Report::msg)?); + host.load_mast_forest(StdLibrary::default().mast_forest().clone()).unwrap(); let proving_options = self.get_proof_options().map_err(|err| Report::msg(format!("{err}")))?; diff --git a/miden/src/cli/run.rs b/miden/src/cli/run.rs index d726494d38..0c7c968a23 100644 --- a/miden/src/cli/run.rs +++ b/miden/src/cli/run.rs @@ -3,6 +3,7 @@ use std::{path::PathBuf, time::Instant}; use assembly::diagnostics::{IntoDiagnostic, Report, WrapErr}; use clap::Parser; use processor::{DefaultHost, ExecutionOptions, ExecutionTrace}; +use stdlib::StdLibrary; use super::data::{instrument, InputFile, Libraries, OutputFile, ProgramFile}; @@ -127,6 +128,7 @@ fn run_program(params: &RunCmd) -> Result<(ExecutionTrace, [u8; 32]), Report> { // fetch the stack and program inputs from the arguments let stack_inputs = input_data.parse_stack_inputs().map_err(Report::msg)?; let mut host = DefaultHost::new(input_data.parse_advice_provider().map_err(Report::msg)?); + host.load_mast_forest(StdLibrary::default().mast_forest().clone()).unwrap(); let program_hash: [u8; 32] = program.hash().into(); diff --git a/miden/src/examples/blake3.rs b/miden/src/examples/blake3.rs deleted file mode 100644 index 4f6fac6577..0000000000 --- a/miden/src/examples/blake3.rs +++ /dev/null @@ -1,95 +0,0 @@ -use miden_vm::{Assembler, DefaultHost, MemAdviceProvider, Program, StackInputs}; -use stdlib::StdLibrary; -use vm_core::{utils::group_slice_elements, Felt}; - -use super::Example; - -// CONSTANTS -// ================================================================================================ - -const INITIAL_HASH_VALUE: [u32; 8] = [u32::MAX; 8]; - -// EXAMPLE BUILDER -// ================================================================================================ - -pub fn get_example(n: usize) -> Example> { - // generate the program and expected results - let program = generate_blake3_program(n); - let expected_result = compute_hash_chain(n); - println!( - "Generated a program to compute {}-th iteration of BLAKE3 1-to-1 hash; expected result: {:?}", - n, expected_result - ); - - let mut host = DefaultHost::default(); - host.load_mast_forest(StdLibrary::default().mast_forest().clone()).unwrap(); - - let stack_inputs = - StackInputs::try_from_ints(INITIAL_HASH_VALUE.iter().map(|&v| v as u64)).unwrap(); - - Example { - program, - stack_inputs, - host, - expected_result, - num_outputs: 8, - } -} - -/// Generates a program to compute the `n`-th hash of blake3 1-to-1 hash chain -fn generate_blake3_program(n: usize) -> Program { - let program = format!( - " - use.std::crypto::hashes::blake3 - use.std::sys - - begin - repeat.{} - exec.blake3::hash_1to1 - end - exec.sys::truncate_stack - end", - n - ); - - Assembler::default() - .with_library(StdLibrary::default()) - .unwrap() - .assemble_program(program) - .unwrap() -} - -/// Computes the `n`-th hash of blake3 1-to-1 hash chain -fn compute_hash_chain(n: usize) -> Vec { - let mut bytes: [u8; 32] = INITIAL_HASH_VALUE - .iter() - .flat_map(|v| v.to_le_bytes()) - .collect::>() - .try_into() - .unwrap(); - - for _ in 0..n { - let hasher = blake3::hash(&bytes); - bytes = *hasher.as_bytes(); - } - - group_slice_elements::(&bytes) - .iter() - .map(|&bytes| Felt::from(u32::from_le_bytes(bytes))) - .collect::>() -} - -// EXAMPLE TESTER -// ================================================================================================ - -#[test] -fn test_blake3_example() { - let example = get_example(2); - super::test_example(example, false); -} - -#[test] -fn test_blake3_example_fail() { - let example = get_example(2); - super::test_example(example, true); -} diff --git a/miden/src/examples/mod.rs b/miden/src/examples/mod.rs index ffd37b7a9d..2fb8ccf067 100644 --- a/miden/src/examples/mod.rs +++ b/miden/src/examples/mod.rs @@ -5,7 +5,6 @@ use clap::Parser; use miden_vm::{ExecutionProof, Host, Program, ProgramInfo, ProvingOptions, StackInputs}; use processor::{ExecutionOptions, ExecutionOptionsError, Felt, ONE, ZERO}; -pub mod blake3; pub mod fibonacci; // EXAMPLE @@ -65,13 +64,6 @@ pub enum ExampleType { #[clap(short = 'n', default_value = "1024")] sequence_length: usize, }, - - /// Compute a chain of the BLAKE3 1-to-1 hashes - Blake3 { - /// Length of the hash chain - #[clap(short = 'n', default_value = "32")] - chain_length: usize, - }, } impl ExampleOptions { @@ -110,7 +102,6 @@ impl ExampleOptions { // instantiate and prepare the example let example = match self.example { ExampleType::Fib { sequence_length } => fibonacci::get_example(sequence_length), - ExampleType::Blake3 { chain_length } => blake3::get_example(chain_length), }; let Example { From bbaa9ee5484fbce99ae0909128d49480b6534323 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Tue, 26 Nov 2024 14:08:24 -0500 Subject: [PATCH 27/39] cleanup: remove `miden example` command line option It is redundant with `miden prove`, and runs code that is baked in to the CLI binary. This is not very useful to users, since they won't know the code that the example runs. It is better to just have people use `miden run` or `miden prove` on our example MASM programs. --- miden/src/examples/fibonacci.rs | 85 -------------- miden/src/examples/mod.rs | 198 -------------------------------- miden/src/main.rs | 3 - 3 files changed, 286 deletions(-) delete mode 100644 miden/src/examples/fibonacci.rs delete mode 100644 miden/src/examples/mod.rs diff --git a/miden/src/examples/fibonacci.rs b/miden/src/examples/fibonacci.rs deleted file mode 100644 index 9b9515921f..0000000000 --- a/miden/src/examples/fibonacci.rs +++ /dev/null @@ -1,85 +0,0 @@ -use miden_vm::{math::Felt, Assembler, DefaultHost, MemAdviceProvider, Program, StackInputs}; - -use super::{Example, ONE, ZERO}; - -// EXAMPLE BUILDER -// ================================================================================================ - -pub fn get_example(n: usize) -> Example> { - // generate the program and expected results - let program = generate_fibonacci_program(n); - let expected_result = vec![compute_fibonacci(n)]; - println!( - "Generated a program to compute {}-th Fibonacci term; expected result: {}", - n, expected_result[0] - ); - - Example { - program, - stack_inputs: StackInputs::try_from_ints([0, 1]).unwrap(), - host: DefaultHost::default(), - expected_result, - num_outputs: 1, - } -} - -/// Generates a program to compute the `n`-th term of Fibonacci sequence -fn generate_fibonacci_program(n: usize) -> Program { - // the program is a simple repetition of 4 stack operations: - // the first operation moves the 2nd stack item to the top, - // the second operation duplicates the top 2 stack items, - // the third operation removes the top item from the stack - // the last operation pops top 2 stack items, adds them, and pushes - // the result back onto the stack - let program = format!( - "begin - repeat.{} - swap dup.1 add - end - end", - n - 1 - ); - - Assembler::default().assemble_program(program).unwrap() -} - -/// Computes the `n`-th term of Fibonacci sequence -fn compute_fibonacci(n: usize) -> Felt { - let mut t0 = ZERO; - let mut t1 = ONE; - - for _ in 0..n { - t1 = t0 + t1; - core::mem::swap(&mut t0, &mut t1); - } - t0 -} - -// EXAMPLE TESTER -// ================================================================================================ - -#[cfg(test)] -mod tests { - use prover::ProvingOptions; - - use super::*; - use crate::examples::{test_example, test_example_with_options}; - - #[test] - fn test_fib_example() { - let example = get_example(16); - test_example(example, false); - } - - #[test] - fn test_fib_example_fail() { - let example = get_example(16); - test_example(example, true); - } - - #[test] - fn test_fib_example_rpo() { - let example = get_example(16); - test_example_with_options(example, false, ProvingOptions::with_96_bit_security(true)); - } -} diff --git a/miden/src/examples/mod.rs b/miden/src/examples/mod.rs deleted file mode 100644 index 2fb8ccf067..0000000000 --- a/miden/src/examples/mod.rs +++ /dev/null @@ -1,198 +0,0 @@ -use std::time::Instant; - -use assembly::diagnostics::{IntoDiagnostic, Report}; -use clap::Parser; -use miden_vm::{ExecutionProof, Host, Program, ProgramInfo, ProvingOptions, StackInputs}; -use processor::{ExecutionOptions, ExecutionOptionsError, Felt, ONE, ZERO}; - -pub mod fibonacci; - -// EXAMPLE -// ================================================================================================ - -pub struct Example -where - H: Host, -{ - pub program: Program, - pub stack_inputs: StackInputs, - pub host: H, - pub num_outputs: usize, - pub expected_result: Vec, -} - -// EXAMPLE OPTIONS -// ================================================================================================ - -#[derive(Debug, Clone, Parser)] -#[clap(about = "Run an example miden program")] -pub struct ExampleOptions { - #[clap(subcommand)] - pub example: ExampleType, - - /// Number of cycles the program is expected to consume - #[clap(short = 'e', long = "exp-cycles", default_value = "64")] - expected_cycles: u32, - - /// Maximum number of cycles a program is allowed to consume - #[clap(short = 'm', long = "max-cycles", default_value = "4294967295")] - max_cycles: u32, - - /// Enable generation of proofs suitable for recursive verification - #[clap(short = 'r', long = "recursive")] - recursive: bool, - - /// Specifies if the RPX Hash should be used. Conflicts with the recursive flag - #[clap(long = "rpx", conflicts_with("recursive"))] - rpx: bool, - - /// Security level for execution proofs generated by the VM - #[clap(short = 's', long = "security", default_value = "96bits")] - security: String, - - /// Enable tracing to monitor execution of the VM - #[clap(short = 't', long = "tracing")] - tracing: bool, -} - -#[derive(Debug, Clone, Parser)] -//#[clap(about = "available examples")] -pub enum ExampleType { - /// Compute a Fibonacci sequence of the specified length - Fib { - /// Length of Fibonacci sequence - #[clap(short = 'n', default_value = "1024")] - sequence_length: usize, - }, -} - -impl ExampleOptions { - pub fn get_proof_options(&self) -> Result { - let exec_options = ExecutionOptions::new( - Some(self.max_cycles), - self.expected_cycles, - self.tracing, - false, - )?; - Ok(match self.security.as_str() { - "96bits" => { - if self.rpx { - ProvingOptions::with_96_bit_security_rpx() - } else { - ProvingOptions::with_96_bit_security(self.recursive) - } - }, - "128bits" => { - if self.rpx { - ProvingOptions::with_128_bit_security_rpx() - } else { - ProvingOptions::with_128_bit_security(self.recursive) - } - }, - other => panic!("{} is not a valid security level", other), - } - .with_execution_options(exec_options)) - } - - pub fn execute(&self) -> Result<(), Report> { - println!("============================================================"); - - let proof_options = self.get_proof_options().into_diagnostic()?; - - // instantiate and prepare the example - let example = match self.example { - ExampleType::Fib { sequence_length } => fibonacci::get_example(sequence_length), - }; - - let Example { - program, - stack_inputs, - mut host, - num_outputs, - expected_result, - .. - } = example; - println!("--------------------------------"); - - // execute the program and generate the proof of execution - let now = Instant::now(); - let (stack_outputs, proof) = - miden_vm::prove(&program, stack_inputs.clone(), &mut host, proof_options).unwrap(); - println!("--------------------------------"); - - println!( - "Executed program in {} ms", - //hex::encode(program.hash()), // TODO: include into message - now.elapsed().as_millis() - ); - println!("Stack outputs: {:?}", stack_outputs.stack_truncated(num_outputs)); - assert_eq!( - expected_result, - stack_outputs.stack_truncated(num_outputs), - "Program result was computed incorrectly" - ); - - // serialize the proof to see how big it is - let proof_bytes = proof.to_bytes(); - println!("Execution proof size: {} KB", proof_bytes.len() / 1024); - println!("Execution proof security: {} bits", proof.security_level()); - println!("--------------------------------"); - - // verify that executing a program with a given hash and given inputs - // results in the expected output - let proof = ExecutionProof::from_bytes(&proof_bytes).unwrap(); - let now = Instant::now(); - let program_info = ProgramInfo::from(program); - - match miden_vm::verify(program_info, stack_inputs, stack_outputs, proof) { - Ok(_) => println!("Execution verified in {} ms", now.elapsed().as_millis()), - Err(err) => println!("Failed to verify execution: {}", err), - } - - Ok(()) - } -} - -// TESTS -// ================================================================================================ - -#[cfg(test)] -pub fn test_example_with_options(example: Example, fail: bool, options: ProvingOptions) -where - H: Host, -{ - let Example { - program, - stack_inputs, - mut host, - num_outputs, - expected_result, - } = example; - - let (mut outputs, proof) = - miden_vm::prove(&program, stack_inputs.clone(), &mut host, options).unwrap(); - - assert_eq!( - expected_result, - outputs.stack_truncated(num_outputs), - "Program result was computed incorrectly" - ); - - let kernel = miden_vm::Kernel::default(); - let program_info = ProgramInfo::new(program.hash(), kernel); - - if fail { - outputs.stack_mut()[0] += ONE; - assert!(miden_vm::verify(program_info, stack_inputs, outputs, proof).is_err()) - } else { - assert!(miden_vm::verify(program_info, stack_inputs, outputs, proof).is_ok()); - } -} - -#[cfg(test)] -pub fn test_example(example: Example, fail: bool) -where - H: Host, -{ - test_example_with_options(example, fail, ProvingOptions::default()); -} diff --git a/miden/src/main.rs b/miden/src/main.rs index 22fb81a155..85d15d73a7 100644 --- a/miden/src/main.rs +++ b/miden/src/main.rs @@ -7,7 +7,6 @@ use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::{prelude::*, EnvFilter}; mod cli; -mod examples; mod repl; mod tools; @@ -26,7 +25,6 @@ pub enum Actions { Compile(cli::CompileCmd), Bundle(cli::BundleCmd), Debug(cli::DebugCmd), - Example(examples::ExampleOptions), Prove(cli::ProveCmd), Run(cli::RunCmd), Verify(cli::VerifyCmd), @@ -42,7 +40,6 @@ impl Cli { Actions::Compile(compile) => compile.execute(), Actions::Bundle(compile) => compile.execute(), Actions::Debug(debug) => debug.execute(), - Actions::Example(example) => example.execute(), Actions::Prove(prove) => prove.execute(), Actions::Run(run) => run.execute(), Actions::Verify(verify) => verify.execute(), From 071229578bdc4685b47832692b4451e15ff12451 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Tue, 26 Nov 2024 14:30:51 -0500 Subject: [PATCH 28/39] refactor: move `InputFile` to an "internal" module of `lib.rs` The goal is to be able to reuse this file format for all targets (integration tests, benchmarks, and cli) --- Makefile | 4 +- miden/Cargo.toml | 5 +- miden/src/cli/data.rs | 358 +----------------------------------- miden/src/cli/debug/mod.rs | 3 +- miden/src/cli/mod.rs | 1 - miden/src/cli/prove.rs | 5 +- miden/src/cli/run.rs | 4 +- miden/src/cli/verify.rs | 4 +- miden/src/internal.rs | 362 +++++++++++++++++++++++++++++++++++++ miden/src/lib.rs | 6 + miden/src/tools/mod.rs | 4 +- 11 files changed, 390 insertions(+), 366 deletions(-) create mode 100644 miden/src/internal.rs diff --git a/Makefile b/Makefile index 7a60dc155a..92636ec556 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DEBUG_ASSERTIONS=RUSTFLAGS="-C debug-assertions" FEATURES_CONCURRENT_EXEC=--features concurrent,executable FEATURES_LOG_TREE=--features concurrent,executable,tracing-forest FEATURES_METAL_EXEC=--features concurrent,executable,metal -ALL_FEATURES_BUT_ASYNC=--features concurrent,executable,metal,testing,with-debug-info +ALL_FEATURES_BUT_ASYNC=--features concurrent,executable,metal,testing,with-debug-info,internal # -- linting -------------------------------------------------------------------------------------- @@ -116,4 +116,4 @@ exec-info: ## Builds an executable with log tree enabled .PHONY: bench bench: ## Runs benchmarks - cargo bench --profile optimized + cargo bench --profile optimized --features internal diff --git a/miden/Cargo.toml b/miden/Cargo.toml index 501cbd9080..442316b278 100644 --- a/miden/Cargo.toml +++ b/miden/Cargo.toml @@ -41,9 +41,10 @@ path = "tests/integration/main.rs" concurrent = ["prover/concurrent", "std"] default = ["std"] executable = [ + "std", + "internal", "dep:hex", "hex?/std", - "std", "dep:serde", "serde?/std", "dep:serde_derive", @@ -55,6 +56,8 @@ executable = [ ] metal = ["prover/metal", "std"] std = ["assembly/std", "processor/std", "prover/std", "verifier/std"] +# For internal use, not meant to be used by users +internal = [] [dependencies] assembly = { package = "miden-assembly", path = "../assembly", version = "0.11", default-features = false } diff --git a/miden/src/cli/data.rs b/miden/src/cli/data.rs index f49adb1034..0c648c8937 100644 --- a/miden/src/cli/data.rs +++ b/miden/src/cli/data.rs @@ -1,5 +1,4 @@ use std::{ - collections::{BTreeMap, HashMap}, fs, io::Write, path::{Path, PathBuf}, @@ -8,23 +7,14 @@ use std::{ use assembly::{ ast::{Module, ModuleKind}, - diagnostics::{IntoDiagnostic, Report, WrapErr}, + diagnostics::{Report, WrapErr}, Assembler, Library, LibraryNamespace, }; -use miden_vm::{ - crypto::{MerkleStore, MerkleTree, NodeIndex, PartialMerkleTree, RpoDigest, SimpleSmt}, - math::Felt, - utils::{Deserializable, SliceReader}, - AdviceInputs, Digest, ExecutionProof, MemAdviceProvider, Program, StackInputs, StackOutputs, - Word, -}; +use miden_vm::{utils::SliceReader, Digest, ExecutionProof, Program, StackOutputs}; +use prover::utils::Deserializable; use serde_derive::{Deserialize, Serialize}; use stdlib::StdLibrary; -pub use tracing::{event, instrument, Level}; - -// CONSTANTS -// ================================================================================================ -const SIMPLE_SMT_DEPTH: u8 = u64::BITS as u8; +use tracing::instrument; // HELPERS // ================================================================================================ @@ -51,271 +41,6 @@ impl From for Debug { } } -// MERKLE DATA -// ================================================================================================ - -/// Struct used to deserialize merkle data from input file. Merkle data can be represented as a -/// merkle tree or a Sparse Merkle Tree. -#[allow(clippy::enum_variant_names)] -#[derive(Deserialize, Debug)] -pub enum MerkleData { - /// String representation of a merkle tree. The merkle tree is represented as a vector of - /// 32 byte hex strings where each string represents a leaf in the tree. - #[serde(rename = "merkle_tree")] - MerkleTree(Vec), - /// String representation of a Sparse Merkle Tree. The Sparse Merkle Tree is represented as a - /// vector of tuples where each tuple consists of a u64 node index and a 32 byte hex string - /// representing the value of the node. - #[serde(rename = "sparse_merkle_tree")] - SparseMerkleTree(Vec<(u64, String)>), - /// String representation of a Partial Merkle Tree. The Partial Merkle Tree is represented as a - /// vector of tuples where each tuple consists of a leaf index tuple (depth, index) and a 32 - /// byte hex string representing the value of the leaf. - #[serde(rename = "partial_merkle_tree")] - PartialMerkleTree(Vec<((u8, u64), String)>), -} - -// INPUT FILE -// ================================================================================================ - -// TODO consider using final types instead of string representations. -/// Input file struct that is used to deserialize input data from file. It consists of four -/// components: -/// - operand_stack -/// - advice_stack -/// - advice_map -/// - merkle_store -#[derive(Deserialize, Debug)] -pub struct InputFile { - /// String representation of the initial operand stack, composed of chained field elements. - pub operand_stack: Vec, - /// Optional string representation of the initial advice stack, composed of chained field - /// elements. - pub advice_stack: Option>, - /// Optional map of 32 byte hex strings to vectors of u64s representing the initial advice map. - pub advice_map: Option>>, - /// Optional vector of merkle data which will be loaded into the initial merkle store. Merkle - /// data is represented as 32 byte hex strings and node indexes are represented as u64s. - pub merkle_store: Option>, -} - -/// Helper methods to interact with the input file -impl InputFile { - #[instrument(name = "read_input_file", skip_all)] - pub fn read(inputs_path: &Option, program_path: &Path) -> Result { - // if file not specified explicitly and corresponding file with same name as program_path - // with '.inputs' extension does't exist, set operand_stack to empty vector - if !inputs_path.is_some() && !program_path.with_extension("inputs").exists() { - return Ok(Self { - operand_stack: Vec::new(), - advice_stack: Some(Vec::new()), - advice_map: Some(HashMap::new()), - merkle_store: None, - }); - } - - // If inputs_path has been provided then use this as path. Alternatively we will - // replace the program_path extension with `.inputs` and use this as a default. - let path = match inputs_path { - Some(path) => path.clone(), - None => program_path.with_extension("inputs"), - }; - - // read input file to string - let inputs_file = fs::read_to_string(&path) - .into_diagnostic() - .wrap_err_with(|| format!("Failed to open input file `{}`", path.display()))?; - - // deserialize input data - let inputs: InputFile = serde_json::from_str(&inputs_file) - .into_diagnostic() - .wrap_err("Failed to deserialize input data")?; - - Ok(inputs) - } - - /// Parse advice provider data from the input file. - pub fn parse_advice_provider(&self) -> Result { - let mut advice_inputs = AdviceInputs::default(); - - let stack = self - .parse_advice_stack() - .map_err(|e| format!("failed to parse advice provider: {e}"))?; - advice_inputs = advice_inputs.with_stack_values(stack).map_err(|e| e.to_string())?; - - if let Some(map) = self - .parse_advice_map() - .map_err(|e| format!("failed to parse advice provider: {e}"))? - { - advice_inputs = advice_inputs.with_map(map); - } - - if let Some(merkle_store) = self - .parse_merkle_store() - .map_err(|e| format!("failed to parse advice provider: {e}"))? - { - advice_inputs = advice_inputs.with_merkle_store(merkle_store); - } - - Ok(MemAdviceProvider::from(advice_inputs)) - } - - /// Parse advice stack data from the input file. - fn parse_advice_stack(&self) -> Result, String> { - self.advice_stack - .as_deref() - .unwrap_or(&[]) - .iter() - .map(|v| { - v.parse::() - .map_err(|e| format!("failed to parse advice stack value '{v}': {e}")) - }) - .collect::, _>>() - } - - /// Parse advice map data from the input file. - fn parse_advice_map(&self) -> Result>>, String> { - let advice_map = match &self.advice_map { - Some(advice_map) => advice_map, - None => return Ok(None), - }; - - let map = advice_map - .iter() - .map(|(k, v)| { - // Convert key to RpoDigest - let key = RpoDigest::try_from(k) - .map_err(|e| format!("failed to decode advice map key '{k}': {e}"))?; - - // convert values to Felt - let values = v - .iter() - .map(|v| { - Felt::try_from(*v).map_err(|e| { - format!("failed to convert advice map value '{v}' to Felt: {e}") - }) - }) - .collect::, _>>()?; - Ok((key, values)) - }) - .collect::>, String>>()?; - - Ok(Some(map)) - } - - /// Parse merkle store data from the input file. - fn parse_merkle_store(&self) -> Result, String> { - let merkle_data = match &self.merkle_store { - Some(merkle_data) => merkle_data, - None => return Ok(None), - }; - - let mut merkle_store = MerkleStore::default(); - for data in merkle_data { - match data { - MerkleData::MerkleTree(data) => { - let leaves = Self::parse_merkle_tree(data)?; - let tree = MerkleTree::new(leaves) - .map_err(|e| format!("failed to parse a Merkle tree: {e}"))?; - merkle_store.extend(tree.inner_nodes()); - event!( - Level::TRACE, - "Added Merkle tree with root {} to the Merkle store", - tree.root() - ); - }, - MerkleData::SparseMerkleTree(data) => { - let entries = Self::parse_sparse_merkle_tree(data)?; - let tree = SimpleSmt::::with_leaves(entries) - .map_err(|e| format!("failed to parse a Sparse Merkle Tree: {e}"))?; - merkle_store.extend(tree.inner_nodes()); - event!( - Level::TRACE, - "Added Sparse Merkle tree with root {} to the Merkle store", - tree.root() - ); - }, - MerkleData::PartialMerkleTree(data) => { - let entries = Self::parse_partial_merkle_tree(data)?; - let tree = PartialMerkleTree::with_leaves(entries) - .map_err(|e| format!("failed to parse a Partial Merkle Tree: {e}"))?; - merkle_store.extend(tree.inner_nodes()); - event!( - Level::TRACE, - "Added Partial Merkle tree with root {} to the Merkle store", - tree.root() - ); - }, - } - } - - Ok(Some(merkle_store)) - } - - /// Parse and return merkle tree leaves. - fn parse_merkle_tree(tree: &[String]) -> Result, String> { - tree.iter() - .map(|v| { - let leaf = Self::parse_word(v)?; - Ok(leaf) - }) - .collect() - } - - /// Parse and return Sparse Merkle Tree entries. - fn parse_sparse_merkle_tree(tree: &[(u64, String)]) -> Result, String> { - tree.iter() - .map(|(index, v)| { - let leaf = Self::parse_word(v)?; - Ok((*index, leaf)) - }) - .collect() - } - - /// Parse and return Partial Merkle Tree entries. - fn parse_partial_merkle_tree( - tree: &[((u8, u64), String)], - ) -> Result, String> { - tree.iter() - .map(|((depth, index), v)| { - let node_index = NodeIndex::new(*depth, *index).map_err(|e| { - format!( - "failed to create node index with depth {depth} and index {index} - {e}" - ) - })?; - let leaf = Self::parse_word(v)?; - Ok((node_index, RpoDigest::new(leaf))) - }) - .collect() - } - - /// Parse a `Word` from a hex string. - pub fn parse_word(word_hex: &str) -> Result { - let word_value = &word_hex[2..]; - let mut word_data = [0u8; 32]; - hex::decode_to_slice(word_value, &mut word_data) - .map_err(|e| format!("failed to decode `Word` from hex {word_hex} - {e}"))?; - let mut word = Word::default(); - for (i, value) in word_data.chunks(8).enumerate() { - word[i] = Felt::try_from(value).map_err(|e| { - format!("failed to convert `Word` data {word_hex} (element {i}) to Felt - {e}") - })?; - } - Ok(word) - } - - /// Parse and return the stack inputs for the program. - pub fn parse_stack_inputs(&self) -> Result { - let stack_inputs = self - .operand_stack - .iter() - .map(|v| v.parse::().map_err(|e| e.to_string())) - .collect::, _>>()?; - - StackInputs::try_from_ints(stack_inputs).map_err(|e| e.to_string()) - } -} - // OUTPUT FILE // ================================================================================================ @@ -549,80 +274,7 @@ impl Libraries { // ================================================================================================ #[cfg(test)] mod test { - use super::{Debug, InputFile}; - - #[test] - fn test_merkle_data_parsing() { - let program_with_pmt = " - { - \"operand_stack\": [\"1\"], - \"merkle_store\": [ - { - \"partial_merkle_tree\": [ - [ - [2, 0], - \"0x1400000000000000000000000000000000000000000000000000000000000000\" - ], - [ - [2, 1], - \"0x1500000000000000000000000000000000000000000000000000000000000000\" - ], - [ - [1, 1], - \"0x0b00000000000000000000000000000000000000000000000000000000000000\" - ] - ] - } - ] - }"; - let inputs: InputFile = serde_json::from_str(program_with_pmt).unwrap(); - let merkle_store = inputs.parse_merkle_store().unwrap(); - assert!(merkle_store.is_some()); - - let program_with_smt = " - { - \"operand_stack\": [\"1\"], - \"merkle_store\": [ - { - \"sparse_merkle_tree\": [ - [ - 0, - \"0x1400000000000000000000000000000000000000000000000000000000000000\" - ], - [ - 1, - \"0x1500000000000000000000000000000000000000000000000000000000000000\" - ], - [ - 3, - \"0x1700000000000000000000000000000000000000000000000000000000000000\" - ] - ] - } - ] - }"; - let inputs: InputFile = serde_json::from_str(program_with_smt).unwrap(); - let merkle_store = inputs.parse_merkle_store().unwrap(); - assert!(merkle_store.is_some()); - - let program_with_merkle_tree = " - { - \"operand_stack\": [\"1\"], - \"merkle_store\": [ - { - \"merkle_tree\": [ - \"0x1400000000000000000000000000000000000000000000000000000000000000\", - \"0x1500000000000000000000000000000000000000000000000000000000000000\", - \"0x1600000000000000000000000000000000000000000000000000000000000000\", - \"0x1700000000000000000000000000000000000000000000000000000000000000\" - ] - } - ] - }"; - let inputs: InputFile = serde_json::from_str(program_with_merkle_tree).unwrap(); - let merkle_store = inputs.parse_merkle_store().unwrap(); - assert!(merkle_store.is_some()); - } + use super::*; #[test] fn test_debug_from_true() { diff --git a/miden/src/cli/debug/mod.rs b/miden/src/cli/debug/mod.rs index 3670692001..48b2f24a42 100644 --- a/miden/src/cli/debug/mod.rs +++ b/miden/src/cli/debug/mod.rs @@ -2,9 +2,10 @@ use std::{path::PathBuf, sync::Arc}; use assembly::diagnostics::Report; use clap::Parser; +use miden_vm::internal::InputFile; use rustyline::{error::ReadlineError, Config, DefaultEditor, EditMode}; -use super::data::{Debug, InputFile, Libraries, ProgramFile}; +use super::data::{Debug, Libraries, ProgramFile}; mod command; use command::DebugCommand; diff --git a/miden/src/cli/mod.rs b/miden/src/cli/mod.rs index af0b6b2d32..e61a9d83bd 100644 --- a/miden/src/cli/mod.rs +++ b/miden/src/cli/mod.rs @@ -9,7 +9,6 @@ mod verify; pub use bundle::BundleCmd; pub use compile::CompileCmd; -pub use data::InputFile; pub use debug::DebugCmd; pub use prove::ProveCmd; pub use repl::ReplCmd; diff --git a/miden/src/cli/prove.rs b/miden/src/cli/prove.rs index a5a782d2d9..1a0d5a59ea 100644 --- a/miden/src/cli/prove.rs +++ b/miden/src/cli/prove.rs @@ -2,11 +2,12 @@ use std::{path::PathBuf, time::Instant}; use assembly::diagnostics::{IntoDiagnostic, Report, WrapErr}; use clap::Parser; -use miden_vm::ProvingOptions; +use miden_vm::{internal::InputFile, ProvingOptions}; use processor::{DefaultHost, ExecutionOptions, ExecutionOptionsError, Program}; use stdlib::StdLibrary; +use tracing::instrument; -use super::data::{instrument, Debug, InputFile, Libraries, OutputFile, ProgramFile, ProofFile}; +use super::data::{Debug, Libraries, OutputFile, ProgramFile, ProofFile}; #[derive(Debug, Clone, Parser)] #[clap(about = "Prove a miden program")] diff --git a/miden/src/cli/run.rs b/miden/src/cli/run.rs index 0c7c968a23..663d393b46 100644 --- a/miden/src/cli/run.rs +++ b/miden/src/cli/run.rs @@ -2,10 +2,12 @@ use std::{path::PathBuf, time::Instant}; use assembly::diagnostics::{IntoDiagnostic, Report, WrapErr}; use clap::Parser; +use miden_vm::internal::InputFile; use processor::{DefaultHost, ExecutionOptions, ExecutionTrace}; use stdlib::StdLibrary; +use tracing::instrument; -use super::data::{instrument, InputFile, Libraries, OutputFile, ProgramFile}; +use super::data::{Libraries, OutputFile, ProgramFile}; #[derive(Debug, Clone, Parser)] #[clap(about = "Run a miden program")] diff --git a/miden/src/cli/verify.rs b/miden/src/cli/verify.rs index 1130b82d45..e885682d3b 100644 --- a/miden/src/cli/verify.rs +++ b/miden/src/cli/verify.rs @@ -5,9 +5,9 @@ use std::{ use assembly::diagnostics::{IntoDiagnostic, Report, Result, WrapErr}; use clap::Parser; -use miden_vm::{Kernel, ProgramInfo}; +use miden_vm::{internal::InputFile, Kernel, ProgramInfo}; -use super::data::{InputFile, OutputFile, ProgramHash, ProofFile}; +use super::data::{OutputFile, ProgramHash, ProofFile}; #[derive(Debug, Clone, Parser)] #[clap(about = "Verify a miden program")] diff --git a/miden/src/internal.rs b/miden/src/internal.rs new file mode 100644 index 0000000000..7b2d506ade --- /dev/null +++ b/miden/src/internal.rs @@ -0,0 +1,362 @@ +use std::{ + collections::{BTreeMap, HashMap}, + fs, + path::{Path, PathBuf}, +}; + +use assembly::diagnostics::{IntoDiagnostic, Report, WrapErr}; +use serde_derive::Deserialize; +pub use tracing::{event, instrument, Level}; +use vm_core::Felt; + +use crate::{ + crypto::{MerkleStore, MerkleTree, NodeIndex, PartialMerkleTree, RpoDigest, SimpleSmt}, + AdviceInputs, MemAdviceProvider, StackInputs, Word, +}; + +// CONSTANTS +// ================================================================================================ +const SIMPLE_SMT_DEPTH: u8 = u64::BITS as u8; + +// MERKLE DATA +// ================================================================================================ + +/// Struct used to deserialize merkle data from input file. Merkle data can be represented as a +/// merkle tree or a Sparse Merkle Tree. +#[allow(clippy::enum_variant_names)] +#[derive(Deserialize, Debug)] +pub enum MerkleData { + /// String representation of a merkle tree. The merkle tree is represented as a vector of + /// 32 byte hex strings where each string represents a leaf in the tree. + #[serde(rename = "merkle_tree")] + MerkleTree(Vec), + /// String representation of a Sparse Merkle Tree. The Sparse Merkle Tree is represented as a + /// vector of tuples where each tuple consists of a u64 node index and a 32 byte hex string + /// representing the value of the node. + #[serde(rename = "sparse_merkle_tree")] + SparseMerkleTree(Vec<(u64, String)>), + /// String representation of a Partial Merkle Tree. The Partial Merkle Tree is represented as a + /// vector of tuples where each tuple consists of a leaf index tuple (depth, index) and a 32 + /// byte hex string representing the value of the leaf. + #[serde(rename = "partial_merkle_tree")] + PartialMerkleTree(Vec<((u8, u64), String)>), +} + +// INPUT FILE +// ================================================================================================ + +// TODO consider using final types instead of string representations. +/// Input file struct that is used to deserialize input data from file. It consists of four +/// components: +/// - operand_stack +/// - advice_stack +/// - advice_map +/// - merkle_store +#[derive(Deserialize, Debug)] +pub struct InputFile { + /// String representation of the initial operand stack, composed of chained field elements. + pub operand_stack: Vec, + /// Optional string representation of the initial advice stack, composed of chained field + /// elements. + pub advice_stack: Option>, + /// Optional map of 32 byte hex strings to vectors of u64s representing the initial advice map. + pub advice_map: Option>>, + /// Optional vector of merkle data which will be loaded into the initial merkle store. Merkle + /// data is represented as 32 byte hex strings and node indexes are represented as u64s. + pub merkle_store: Option>, +} + +/// Helper methods to interact with the input file +impl InputFile { + #[instrument(name = "read_input_file", skip_all)] + pub fn read(inputs_path: &Option, program_path: &Path) -> Result { + // if file not specified explicitly and corresponding file with same name as program_path + // with '.inputs' extension does't exist, set operand_stack to empty vector + if !inputs_path.is_some() && !program_path.with_extension("inputs").exists() { + return Ok(Self { + operand_stack: Vec::new(), + advice_stack: Some(Vec::new()), + advice_map: Some(HashMap::new()), + merkle_store: None, + }); + } + + // If inputs_path has been provided then use this as path. Alternatively we will + // replace the program_path extension with `.inputs` and use this as a default. + let path = match inputs_path { + Some(path) => path.clone(), + None => program_path.with_extension("inputs"), + }; + + // read input file to string + let inputs_file = fs::read_to_string(&path) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to open input file `{}`", path.display()))?; + + // deserialize input data + let inputs: InputFile = serde_json::from_str(&inputs_file) + .into_diagnostic() + .wrap_err("Failed to deserialize input data")?; + + Ok(inputs) + } + + /// Parse advice provider data from the input file. + pub fn parse_advice_provider(&self) -> Result { + let mut advice_inputs = AdviceInputs::default(); + + let stack = self + .parse_advice_stack() + .map_err(|e| format!("failed to parse advice provider: {e}"))?; + advice_inputs = advice_inputs.with_stack_values(stack).map_err(|e| e.to_string())?; + + if let Some(map) = self + .parse_advice_map() + .map_err(|e| format!("failed to parse advice provider: {e}"))? + { + advice_inputs = advice_inputs.with_map(map); + } + + if let Some(merkle_store) = self + .parse_merkle_store() + .map_err(|e| format!("failed to parse advice provider: {e}"))? + { + advice_inputs = advice_inputs.with_merkle_store(merkle_store); + } + + Ok(MemAdviceProvider::from(advice_inputs)) + } + + /// Parse advice stack data from the input file. + fn parse_advice_stack(&self) -> Result, String> { + self.advice_stack + .as_deref() + .unwrap_or(&[]) + .iter() + .map(|v| { + v.parse::() + .map_err(|e| format!("failed to parse advice stack value '{v}': {e}")) + }) + .collect::, _>>() + } + + /// Parse advice map data from the input file. + fn parse_advice_map(&self) -> Result>>, String> { + let advice_map = match &self.advice_map { + Some(advice_map) => advice_map, + None => return Ok(None), + }; + + let map = advice_map + .iter() + .map(|(k, v)| { + // Convert key to RpoDigest + let key = RpoDigest::try_from(k) + .map_err(|e| format!("failed to decode advice map key '{k}': {e}"))?; + + // convert values to Felt + let values = v + .iter() + .map(|v| { + Felt::try_from(*v).map_err(|e| { + format!("failed to convert advice map value '{v}' to Felt: {e}") + }) + }) + .collect::, _>>()?; + Ok((key, values)) + }) + .collect::>, String>>()?; + + Ok(Some(map)) + } + + /// Parse merkle store data from the input file. + fn parse_merkle_store(&self) -> Result, String> { + let merkle_data = match &self.merkle_store { + Some(merkle_data) => merkle_data, + None => return Ok(None), + }; + + let mut merkle_store = MerkleStore::default(); + for data in merkle_data { + match data { + MerkleData::MerkleTree(data) => { + let leaves = Self::parse_merkle_tree(data)?; + let tree = MerkleTree::new(leaves) + .map_err(|e| format!("failed to parse a Merkle tree: {e}"))?; + merkle_store.extend(tree.inner_nodes()); + event!( + Level::TRACE, + "Added Merkle tree with root {} to the Merkle store", + tree.root() + ); + }, + MerkleData::SparseMerkleTree(data) => { + let entries = Self::parse_sparse_merkle_tree(data)?; + let tree = SimpleSmt::::with_leaves(entries) + .map_err(|e| format!("failed to parse a Sparse Merkle Tree: {e}"))?; + merkle_store.extend(tree.inner_nodes()); + event!( + Level::TRACE, + "Added Sparse Merkle tree with root {} to the Merkle store", + tree.root() + ); + }, + MerkleData::PartialMerkleTree(data) => { + let entries = Self::parse_partial_merkle_tree(data)?; + let tree = PartialMerkleTree::with_leaves(entries) + .map_err(|e| format!("failed to parse a Partial Merkle Tree: {e}"))?; + merkle_store.extend(tree.inner_nodes()); + event!( + Level::TRACE, + "Added Partial Merkle tree with root {} to the Merkle store", + tree.root() + ); + }, + } + } + + Ok(Some(merkle_store)) + } + + /// Parse and return merkle tree leaves. + fn parse_merkle_tree(tree: &[String]) -> Result, String> { + tree.iter() + .map(|v| { + let leaf = Self::parse_word(v)?; + Ok(leaf) + }) + .collect() + } + + /// Parse and return Sparse Merkle Tree entries. + fn parse_sparse_merkle_tree(tree: &[(u64, String)]) -> Result, String> { + tree.iter() + .map(|(index, v)| { + let leaf = Self::parse_word(v)?; + Ok((*index, leaf)) + }) + .collect() + } + + /// Parse and return Partial Merkle Tree entries. + fn parse_partial_merkle_tree( + tree: &[((u8, u64), String)], + ) -> Result, String> { + tree.iter() + .map(|((depth, index), v)| { + let node_index = NodeIndex::new(*depth, *index).map_err(|e| { + format!( + "failed to create node index with depth {depth} and index {index} - {e}" + ) + })?; + let leaf = Self::parse_word(v)?; + Ok((node_index, RpoDigest::new(leaf))) + }) + .collect() + } + + /// Parse a `Word` from a hex string. + pub fn parse_word(word_hex: &str) -> Result { + let word_value = &word_hex[2..]; + let mut word_data = [0u8; 32]; + hex::decode_to_slice(word_value, &mut word_data) + .map_err(|e| format!("failed to decode `Word` from hex {word_hex} - {e}"))?; + let mut word = Word::default(); + for (i, value) in word_data.chunks(8).enumerate() { + word[i] = Felt::try_from(value).map_err(|e| { + format!("failed to convert `Word` data {word_hex} (element {i}) to Felt - {e}") + })?; + } + Ok(word) + } + + /// Parse and return the stack inputs for the program. + pub fn parse_stack_inputs(&self) -> Result { + let stack_inputs = self + .operand_stack + .iter() + .map(|v| v.parse::().map_err(|e| e.to_string())) + .collect::, _>>()?; + + StackInputs::try_from_ints(stack_inputs).map_err(|e| e.to_string()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_merkle_data_parsing() { + let program_with_pmt = " + { + \"operand_stack\": [\"1\"], + \"merkle_store\": [ + { + \"partial_merkle_tree\": [ + [ + [2, 0], + \"0x1400000000000000000000000000000000000000000000000000000000000000\" + ], + [ + [2, 1], + \"0x1500000000000000000000000000000000000000000000000000000000000000\" + ], + [ + [1, 1], + \"0x0b00000000000000000000000000000000000000000000000000000000000000\" + ] + ] + } + ] + }"; + let inputs: InputFile = serde_json::from_str(program_with_pmt).unwrap(); + let merkle_store = inputs.parse_merkle_store().unwrap(); + assert!(merkle_store.is_some()); + + let program_with_smt = " + { + \"operand_stack\": [\"1\"], + \"merkle_store\": [ + { + \"sparse_merkle_tree\": [ + [ + 0, + \"0x1400000000000000000000000000000000000000000000000000000000000000\" + ], + [ + 1, + \"0x1500000000000000000000000000000000000000000000000000000000000000\" + ], + [ + 3, + \"0x1700000000000000000000000000000000000000000000000000000000000000\" + ] + ] + } + ] + }"; + let inputs: InputFile = serde_json::from_str(program_with_smt).unwrap(); + let merkle_store = inputs.parse_merkle_store().unwrap(); + assert!(merkle_store.is_some()); + + let program_with_merkle_tree = " + { + \"operand_stack\": [\"1\"], + \"merkle_store\": [ + { + \"merkle_tree\": [ + \"0x1400000000000000000000000000000000000000000000000000000000000000\", + \"0x1500000000000000000000000000000000000000000000000000000000000000\", + \"0x1600000000000000000000000000000000000000000000000000000000000000\", + \"0x1700000000000000000000000000000000000000000000000000000000000000\" + ] + } + ] + }"; + let inputs: InputFile = serde_json::from_str(program_with_merkle_tree).unwrap(); + let merkle_store = inputs.parse_merkle_store().unwrap(); + assert!(merkle_store.is_some()); + } +} diff --git a/miden/src/lib.rs b/miden/src/lib.rs index 53eda18507..61e85e5002 100644 --- a/miden/src/lib.rs +++ b/miden/src/lib.rs @@ -19,3 +19,9 @@ pub use prover::{ ProvingOptions, StackOutputs, Word, }; pub use verifier::{verify, VerificationError}; + +// (private) exports +// ================================================================================================ + +#[cfg(feature = "internal")] +pub mod internal; diff --git a/miden/src/tools/mod.rs b/miden/src/tools/mod.rs index 621e283b9e..bb2f7aecac 100644 --- a/miden/src/tools/mod.rs +++ b/miden/src/tools/mod.rs @@ -3,12 +3,10 @@ use std::{fs, path::PathBuf}; use assembly::diagnostics::{IntoDiagnostic, Report, WrapErr}; use clap::Parser; -use miden_vm::{Assembler, DefaultHost, Host, Operation, StackInputs}; +use miden_vm::{internal::InputFile, Assembler, DefaultHost, Host, Operation, StackInputs}; use processor::{AsmOpInfo, TraceLenSummary}; use stdlib::StdLibrary; -use super::cli::InputFile; - // CLI // ================================================================================================ From e2bccbce482fda057076bc99cca0ee07d57c9471 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Tue, 26 Nov 2024 17:02:23 -0500 Subject: [PATCH 29/39] refactor: have the `program_execution` benchmark run all the example MASM files --- CHANGELOG.md | 1 + Cargo.lock | 1 + miden/Cargo.toml | 6 ++- miden/benches/program_execution.rs | 87 +++++++++++++++++++++--------- 4 files changed, 68 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90bbc4eac7..cf5c12887d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [BREAKING] `MastForest` serialization/deserialization will store/read decorator data at the end of the binary (#1531). - [BREAKING] Updated Winterfell dependency to v0.11 (#1586). - [BREAKING] resolved flag collision in `--verify` command and added functionality for optional input/output files (#1513). +- [BREAKING] Cleanup benchmarks and examples in the `miden-vm` crate (#1587) #### Enhancements - Added `miden_core::mast::MastForest::advice_map` to load it into the advice provider before the `MastForest` execution (#1574). diff --git a/Cargo.lock b/Cargo.lock index 7421c9a8d3..0597bccfcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1257,6 +1257,7 @@ dependencies = [ "tracing", "tracing-forest", "tracing-subscriber", + "walkdir", "winter-fri", ] diff --git a/miden/Cargo.toml b/miden/Cargo.toml index 442316b278..354a1dad5d 100644 --- a/miden/Cargo.toml +++ b/miden/Cargo.toml @@ -18,7 +18,7 @@ name = "miden" path = "src/main.rs" bench = false doctest = false -required-features = ["executable"] +required-features = ["executable", "internal"] [lib] path = "src/lib.rs" @@ -27,6 +27,7 @@ doctest = false [[bench]] name = "program_execution" +required-features = ["internal"] harness = false [[bench]] @@ -57,7 +58,7 @@ executable = [ metal = ["prover/metal", "std"] std = ["assembly/std", "processor/std", "prover/std", "verifier/std"] # For internal use, not meant to be used by users -internal = [] +internal = ["dep:serde", "dep:serde_derive", "dep:serde_json", "dep:hex"] [dependencies] assembly = { package = "miden-assembly", path = "../assembly", version = "0.11", default-features = false } @@ -87,3 +88,4 @@ test-utils = { package = "miden-test-utils", path = "../test-utils" } vm-core = { package = "miden-core", path = "../core", version = "0.11" } winter-fri = { package = "winter-fri", version = "0.11" } rand_chacha = "0.3" +walkdir = "2.5" diff --git a/miden/benches/program_execution.rs b/miden/benches/program_execution.rs index d5126da41f..09296af15e 100644 --- a/miden/benches/program_execution.rs +++ b/miden/benches/program_execution.rs @@ -1,35 +1,72 @@ -use std::time::Duration; - use criterion::{criterion_group, criterion_main, Criterion}; -use miden_vm::{Assembler, DefaultHost, StackInputs}; +use miden_vm::{internal::InputFile, Assembler, DefaultHost, StackInputs}; use processor::{execute, ExecutionOptions}; use stdlib::StdLibrary; +use walkdir::WalkDir; +/// Benchmark the execution of all the masm examples in the `masm-examples` directory. fn program_execution(c: &mut Criterion) { let mut group = c.benchmark_group("program_execution"); - group.measurement_time(Duration::from_secs(10)); - - let stdlib = StdLibrary::default(); - let mut host = DefaultHost::default(); - host.load_mast_forest(stdlib.as_ref().mast_forest().clone()).unwrap(); - - group.bench_function("sha256", |bench| { - let source = " - use.std::crypto::hashes::sha256 - - begin - exec.sha256::hash_2to1 - end"; - let mut assembler = Assembler::default(); - assembler.add_library(&stdlib).expect("failed to load stdlib"); - let program = assembler.assemble_program(source).expect("Failed to compile test source."); - bench.iter(|| { - execute(&program, StackInputs::default(), &mut host, ExecutionOptions::default()) - }); - }); + + let masm_examples_dir = { + let mut miden_dir = std::env::current_dir().unwrap(); + miden_dir.push("masm-examples"); + + miden_dir + }; + + for entry in WalkDir::new(masm_examples_dir) { + match &entry { + Ok(entry) => { + // if it's not a masm file, skip it. + if !entry.file_type().is_file() || entry.path().extension().unwrap() != "masm" { + continue; + } + + // if there's a `.inputs` file associated with this `.masm` file, use it as the + // inputs. + let (mut host, stack_inputs) = match InputFile::read(&None, entry.path()) { + Ok(input_data) => { + let stack_inputs = input_data.parse_stack_inputs().unwrap(); + let host = DefaultHost::new(input_data.parse_advice_provider().unwrap()); + (host, stack_inputs) + }, + Err(_) => (DefaultHost::default(), StackInputs::default()), + }; + host.load_mast_forest(StdLibrary::default().as_ref().mast_forest().clone()) + .unwrap(); + + // the name of the file without the extension + let source = std::fs::read_to_string(entry.path()).unwrap(); + + // Create a benchmark for the masm file + let file_stem = entry.path().file_stem().unwrap().to_string_lossy(); + group.bench_function(file_stem, |bench| { + let mut assembler = Assembler::default(); + assembler.add_library(StdLibrary::default()).expect("failed to load stdlib"); + let program = assembler + .assemble_program(&source) + .expect("Failed to compile test source."); + bench.iter(|| { + execute( + &program, + stack_inputs.clone(), + &mut host, + ExecutionOptions::default(), + ) + }); + }); + }, + // If we can't access the entry, just skip it + Err(err) => { + eprintln!("Failed to access file: {:?} with error {err:?}", entry); + continue; + }, + } + } group.finish(); } -criterion_group!(sha256_group, program_execution); -criterion_main!(sha256_group); +criterion_group!(benchmark, program_execution); +criterion_main!(benchmark); From 70b483c9311f3bbcfb693cb2cd671bcfa5b25f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Laferri=C3=A8re?= Date: Mon, 2 Dec 2024 15:31:56 -0500 Subject: [PATCH 30/39] fix: fix miden-vm Cargo.toml (#1592) --- miden/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miden/Cargo.toml b/miden/Cargo.toml index 354a1dad5d..0c55a8a862 100644 --- a/miden/Cargo.toml +++ b/miden/Cargo.toml @@ -18,7 +18,7 @@ name = "miden" path = "src/main.rs" bench = false doctest = false -required-features = ["executable", "internal"] +required-features = ["executable"] [lib] path = "src/lib.rs" From bf9ef88848804b6e3c641cb1997765bc6d942e40 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 3 Dec 2024 00:28:47 +0100 Subject: [PATCH 31/39] chore: update and sync-up the recursive verifier (#1575) --- CHANGELOG.md | 1 + processor/src/operations/comb_ops.rs | 18 +- stdlib/asm/crypto/stark/constants.masm | 29 ++- stdlib/asm/crypto/stark/deep_queries.masm | 207 +++++------------- stdlib/asm/crypto/stark/ood_frames.masm | 67 +++--- stdlib/asm/crypto/stark/public_inputs.masm | 2 +- stdlib/asm/crypto/stark/random_coin.masm | 85 ++++--- stdlib/asm/crypto/stark/verifier.masm | 28 +-- stdlib/tests/crypto/stark/mod.rs | 11 +- .../stark/verifier_recursive/channel.rs | 137 +++++------- .../crypto/stark/verifier_recursive/mod.rs | 136 ++++++++---- 11 files changed, 335 insertions(+), 386 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf5c12887d..ab9c09580c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Debug instructions can be enabled in the cli `run` command using `--debug` flag (#1502). - Added support for procedure annotation (attribute) syntax to Miden Assembly (#1510). - Make `miden-prover::prove()` method conditionally asynchronous (#1563). +- Update and sync the recursive verifier (#1575). #### Changes diff --git a/processor/src/operations/comb_ops.rs b/processor/src/operations/comb_ops.rs index 03181d37ac..498026f038 100644 --- a/processor/src/operations/comb_ops.rs +++ b/processor/src/operations/comb_ops.rs @@ -14,8 +14,8 @@ impl Process { /// \frac{T_i(x) - T_i(g \cdot z)}{x - g \cdot z} \right)} /// /// The instruction computes the numerators $\alpha_i \cdot (T_i(x) - T_i(z))$ and - /// $\alpha_i \cdot (T_i(x) - T_i(g \cdot z))$ and stores the values in two accumulators $p$ - /// and $r$, respectively. This instruction is specialized to main trace columns i.e. + /// $\alpha_i \cdot (T_i(x) - T_i(g \cdot z))$ and stores the values in two accumulators $r$ + /// and $p$, respectively. This instruction is specialized to main trace columns i.e. /// the values $T_i(x)$ are base field elements. /// /// The instruction is used in the context of STARK proof verification in order to compute @@ -44,9 +44,9 @@ impl Process { /// 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). /// 2. (p0, p1) stands for an extension field element accumulating the values for the quotients - /// 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). + /// 3. (r0, r1) stands for an extension field element accumulating the values for the quotients + /// with common denominator (x - z). /// 4. x_addr is the memory address from which we are loading the Ti's using the MSTREAM /// instruction. /// 5. z_addr is the memory address to the i-th OOD evaluations at z and gz i.e. T_i(z):= @@ -72,7 +72,7 @@ impl Process { // --- compute the updated accumulator values --------------------------------------------- let v0 = self.stack.get(7); let tx = QuadFelt::new(v0, ZERO); - let [p_new, r_new] = [p + alpha * (tx - tz), r + alpha * (tx - tgz)]; + let [p_new, r_new] = [p + alpha * (tx - tgz), r + alpha * (tx - tz)]; // --- rotate the top 8 elements of the stack --------------------------------------------- self.stack.set(0, t0); @@ -258,8 +258,8 @@ mod tests { let a1 = a[1]; let alpha = QuadFelt::new(a0, a1); - let p_new = p + alpha * (tx - tz); - let r_new = r + alpha * (tx - tgz); + let p_new = p + alpha * (tx - tgz); + let r_new = r + alpha * (tx - tz); assert_eq!(p_new.to_base_elements()[1], stack_state[8]); assert_eq!(p_new.to_base_elements()[0], stack_state[9]); @@ -337,8 +337,8 @@ mod tests { let tz: Vec = tz_tgz.iter().step_by(2).map(|e| e.to_owned()).collect(); let tgz: Vec = tz_tgz.iter().skip(1).step_by(2).map(|e| e.to_owned()).collect(); for i in 0..8 { - p += a[i] * (QuadFelt::from(tx[i]) - tz[i]); - r += a[i] * (QuadFelt::from(tx[i]) - tgz[i]); + p += a[i] * (QuadFelt::from(tx[i]) - tgz[i]); + r += a[i] * (QuadFelt::from(tx[i]) - tz[i]); } // prepare the advice stack with the generated data diff --git a/stdlib/asm/crypto/stark/constants.masm b/stdlib/asm/crypto/stark/constants.masm index d062e2007d..cf8784c9d7 100644 --- a/stdlib/asm/crypto/stark/constants.masm +++ b/stdlib/asm/crypto/stark/constants.masm @@ -5,6 +5,8 @@ const.ROOT_UNITY=7277203076849721926 const.DOMAIN_OFFSET=7 const.DOMAIN_OFFSET_INV=2635249152773512046 +const.NUM_CONSTRAINT_COMPOSITION_COEF_MULTIPLIED_BY_TWO_ROUNDED_UP_TO_FOUR=224 +const.NUM_DEEP_COMPOSITION_COEF_MULTIPLIED_BY_TWO_ROUNDED_UP_TO_FOUR=88 # MEMORY POINTERS @@ -17,15 +19,15 @@ const.TRACE_DOMAIN_GENERATOR_PTR=4294799999 const.PUBLIC_INPUTS_PTR=4294800000 # OOD Frames -# (72 + 9 + 8) * 2 * 2 Felt for current and next trace rows and 8 * 2 Felt for constraint composition -# polynomials. Total memory slots required: ((72 + 9 + 8) * 2 * 2 + 8 * 2) / 4 = 93 +# (70 + 7) * 2 * 2 Felt for current and next trace rows and 8 * 2 Felt for constraint composition +# polynomials. Total memory slots required: ((70 + 7) * 2 * 2 + 8 * 2) / 4 = 81 const.OOD_TRACE_PTR=4294900000 -const.OOD_CONSTRAINT_EVALS_PTR=4294900081 +const.OOD_CONSTRAINT_EVALS_PTR=4294900077 # Current trace row -# 72 Felt for main portion of trace, 9 * 2 Felt for auxiliary portion of trace and 8 * 2 Felt for +# 70 Felt for main portion of trace, 7 * 2 Felt for auxiliary portion of trace and 8 * 2 Felt for # constraint composition polynomials. Since we store these with the padding to make each of the -# three portions a multiple of 8, the number of slots required is (80 + 24 + 16) / 4 = 30 +# three portions a multiple of 8, the number of slots required is (72 + 16 + 16) / 4 = 26 const.CURRENT_TRACE_ROW_PTR=4294900100 # Random elements @@ -36,7 +38,7 @@ const.AUX_RAND_ELEM_PTR=4294900150 const.COMPOSITION_COEF_PTR=4294900200 # We need 2 Felt for each trace column and each of the 8 constraint composition columns. We thus need -# (72 + 9 + 8) * 2 Felt i.e. 44 memory slots. +# (70 + 7 + 8) * 2 Felt i.e. 43 memory slots. const.DEEP_RAND_CC_PTR=4294903000 # FRI @@ -80,7 +82,6 @@ const.GRINDING_FACTOR_PTR=4294903308 # RPO capacity initialization words const.ZERO_WORD_PTR=4294903309 -const.ZERO_ZERO_ZERO_ONE_PTR=4294903310 # State of RPO-based random coin const.C_PTR=4294903311 @@ -106,7 +107,7 @@ const.TMP8=4294903322 # | TRACE_DOMAIN_GENERATOR_PTR | 4294799999 | # | PUBLIC_INPUTS_PTR | 4294800000 | # | OOD_TRACE_PTR | 4294900000 | -# | OOD_CONSTRAINT_EVALS_PTR | 4294900081 | +# | OOD_CONSTRAINT_EVALS_PTR | 4294900077 | # | CURRENT_TRACE_ROW_PTR | 4294900100 | # | AUX_RAND_ELEM_PTR | 4294900150 | # | COMPOSITION_COEF_PTR | 4294900200 | @@ -160,6 +161,14 @@ export.domain_offset_inv push.DOMAIN_OFFSET_INV end +export.num_constraint_composition_coef_multiplied_by_two_and_rounded_up_to_4 + push.NUM_CONSTRAINT_COMPOSITION_COEF_MULTIPLIED_BY_TWO_ROUNDED_UP_TO_FOUR +end + +export.num_deep_composition_coef_multiplied_by_two_and_rounded_up_to_4 + push.NUM_DEEP_COMPOSITION_COEF_MULTIPLIED_BY_TWO_ROUNDED_UP_TO_FOUR +end + export.public_inputs_ptr push.PUBLIC_INPUTS_PTR end @@ -238,10 +247,6 @@ export.zero_word push.ZERO_WORD_PTR end -export.zero_zero_zero_one_word - push.ZERO_ZERO_ZERO_ONE_PTR -end - #! Returns the pointer to the capacity word of the random coin. #! #! Note: The random coin is implemented using a hash function, this returns the diff --git a/stdlib/asm/crypto/stark/deep_queries.masm b/stdlib/asm/crypto/stark/deep_queries.masm index ce8ef49e30..0ce9e0e962 100644 --- a/stdlib/asm/crypto/stark/deep_queries.masm +++ b/stdlib/asm/crypto/stark/deep_queries.masm @@ -1,127 +1,5 @@ use.std::crypto::stark::constants - -#! Computes a single step of the random linear combination defining the DEEP composition polynomial -#! that is the input to the FRI protocol. More precisely, the sum in question is: -#! $$ -#! \sum_{i=0}^k{\alpha_i \cdot \left(\frac{T_i(x) - T_i(z)}{x - z} + -#! \frac{T_i(x) - T_i(z \cdot g)}{x - z \cdot g} \right)} -#! $$ -#! -#! and the following instruction computes the denominators $\alpha_i \cdot (T_i(x) - T_i(z))$ and -#! $\alpha_i \cdot (T_i(x) - T_i(z \cdot g))$ and stores the values in two accumulators $r$ and $p$, -#! respectively. This instruction is specialized to main trace columns i.e. the values $T_i(x)$ are -#! base field elements. -#! -#! The stack transition of the instruction can be visualized as follows: -#! -#! +------+------+------+------+------+------+------+------+------+------+------+------+------+------+------+---+ -#! | T7 | T6 | T5 | T4 | T3 | T2 | T1 | T0 | p1 | p0 | r1 | r0 |x_addr|z_addr|a_addr| - | -#! +------+------+------+------+------+------+------+------+------+------+------+------+------+------+------+---+ -#! -#! || -#! \/ -#! -#! +------+------+------+------+------+------+------+------+------+------+------+------+------+--------+--------+---+ -#! | T0 | T7 | T6 | T5 | T4 | T3 | T2 | T1 | p1' | p0' | r1' | r0' |x_addr|z_addr+1|a_addr+1| - | -#! +------+------+------+------+------+------+------+------+------+------+------+------+------+--------+--------+---+ -#! -#! -#! 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). -#! 2- (p0, p1) stands for an extension field element accumulating the values for the quotients with common denominator (x - gz). -#! 3- (r0, r1) stands for an extension field element accumulating the values for the quotients with common denominator (x - z). -#! 4- x_addr is the memory address from which we are loading the Ti's using the MSTREAM instruction. -#! 5- z_addr is the memory address to the i-th OOD evaluation frame 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) -#! 6- a_addr is the memory address of the i-th random element used in batching the trace polynomial quotients. -#! The random elements a := (a0, a1) are stored in memory as [0, 0, a0, a1]. -#! -#! Input: [T7, T6, T5, T4, T3, T2, T1, T0, p1, p0, r1, r0, x_addr, z_addr, a_addr, 0] -#! Output: [T0, T7, T6, T5, T4, T3, T2, T1, p1', p0', r1', r0', x_addr, z_addr+1, a_addr+1, 0] -export.combine_main - - # 1) Shift trace columns values left - movup.7 - #=> [T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr, a_addr, 0] - - # 2) Get a_addr and update it. This is done here before the element becomes inaccessible. - - # Update a_addr - dup.14 add.1 swap.15 - #=> [a_addr, T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr, a_addr', 0] - - # 3) Load i-th OOD frame portion. This assumes that the OOD frame has been serialized with `current` and `next` rows interleaved. - # This also updates the z_addr pointer. - dup.14 add.1 swap.15 - padw movup.4 mem_loadw - #=> [Tgz1, Tgz0, Tz1, Tz0, a_addr, T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr', a_addr', 0] - - # 4) Compute the numerators - - # a) Compute T_i - T_i(z). This equals, in the case of T0, (-Tz1, T0 - Tz0) - dup.5 - movup.4 - #=> [Tz0, T0, Tgz1, Tgz0, Tz1, a_addr, T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr', a_addr', 0] - - sub - #=> [T0 - Tz0, Tgz1, Tgz0, Tz1, a_addr, T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr', a_addr', 0] - - swap.3 neg swap.2 - #=> [Tgz0, Tgz1, -Tz1, T0 - Tz0, a_addr, T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr', a_addr', 0] - - # b) Compute T_i - T_i(gz). This equals, in the case of T0, (-Tgz1, T0 - Tgz0) - dup.5 swap sub - #=> [T0 - Tgz0, Tgz1, -Tz1, T0 - Tz0, a_addr, T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr', a_addr', 0] - - swap - neg - #=> [-Tgz1, T0 - Tgz0, -Tz1, T0 - Tz0, a_addr, T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr', a_addr', 0] - #=> [Δg1, Δg0, Δ1, Δ0, a_addr, T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr', a_addr', 0] - # where Δg1 := -Tgz1, Δg0 := T0 - Tgz0, Δ1 := -Tz1 and Δ0 := T0 - Tz0 - - # 5) Multiply by randomness - - # a) Load randomness from memory - padw - movup.8 mem_loadw drop drop - #=> [a1, a0, Δg1, Δg0, Δ1, Δ0, T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr', a_addr', 0] - - # b) Multiply (Δ0, Δ1) - dup.1 dup.1 - movup.7 movup.7 - #=> [Δ1, Δ0, a1, a0, a1, a0, Δg1, Δg0, T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr', a_addr', 0] - - ext2mul - #=> [prod1, prod0, a1, a0, Δg1, Δg0, T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr', a_addr', 0] - # where (prod0, prod1) := (Δ0, Δ1) * (a0, a1) - - movdn.5 movdn.5 - #=> [a1, a0, Δg1, Δg0, prod1, prod0, T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr', a_addr', 0] - - # c) Multiply (Δg0, Δg1) - ext2mul - #=> [prodg1, prodg0, prod1, prod0, T0, T7, T6, T5, T4, T3, T2, T1, p1, p0, r1, r0, x_addr, z_addr', a_addr', 0] - # where (prodg0, prodg1) := (Δg0, Δg1) * (a0, a1) - - # 6) Accumulate into (p0, p1) and (r0, r1) - movupw.3 - - # a) Accumulate into (r0, r1) - movup.7 movup.7 - #=> [prod1, prod0, p1, p0, r1, r0, prodg1, prodg0, T0, T7, T6, T5, T4, T3, T2, T1, x_addr, z_addr', a_addr', 0] - movup.5 movup.5 ext2add - #=> [r1', r0', p1, p0, prodg1, prodg0, T0, T7, T6, T5, T4, T3, T2, T1, x_addr, z_addr', a_addr', 0] - - # b) Accumulate into (p0, p1) - movdn.5 movdn.5 ext2add - #=> [p1', p0', r1', r0', T0, T7, T6, T5, T4, T3, T2, T1, x_addr, z_addr', a_addr', 0] - - # c) Prepare for next iteration. - movdnw.2 - #=> [T0, T7, T6, T5, T4, T3, T2, T1, p1', p0', r1', r0', x_addr, z_addr', a_addr', 0] -end - #! Computes a single step of the random linear combination defining the DEEP composition polynomial #! that is the input to the FRI protocol. More precisely, the sum in question is: #! $$ @@ -252,7 +130,7 @@ end #! Input: [query_ptr, ...] #! Output: [index, query_ptr, ...] #! -#! Cycles: 198 +#! Cycles: 219 proc.load_query_row # Main trace portion of the query @@ -280,14 +158,31 @@ proc.load_query_row swapw #=>[R, ptr, y, y, y, depth, index, query_ptr, ...] exec.constants::zero_word mem_loadw + add.6 + swap.3 padw padw #=> [Y, Y, 0, 0, 0, 1, ptr, y, y, y] - repeat.9 + repeat.8 adv_pipe hperm end #=> [Y, L, Y, ptr, y, y, y, depth, index, query_ptr, ...] + ## Store the last full word of main segment columns + adv_loadw + dup.12 mem_storew + swapw + dropw + adv_push.1 + adv_push.1 + push.0 + push.0 + ## Store the last 2 main segment columns + dup.12 add.1 mem_storew + + ## Final hperm + hperm + ## Load the leaf value we got using mtree_get exec.constants::tmp3 mem_loadw @@ -301,6 +196,9 @@ proc.load_query_row assert_eq #=> [Y, ptr, y, y, y, depth, index, query_ptr, ...] + ## increment ptr to account for the last two words we loaded from the advice tape + swapw add.2 swapw + # Aux trace part @@ -314,25 +212,19 @@ proc.load_query_row #=> [L, R, ptr, y, y, y, depth, index, query_ptr, ...] ## adv_pipe aux trace portion - push.1.0.0.0 + push.6.0.0.0 swapw.2 adv_pipe hperm - adv_pipe hperm + adv_loadw + dup.12 mem_storew + swapw dropw adv_push.1 adv_push.1 - push.1 - push.0 - - ## Store the 9-th auxiliary column - dup.12 mem_storew + push.0.0 - ## Since combine_aux follows a mem_stream we need to store (i.e. pad with) the all zero word in - ## order to avoid over-stepping into the constraint polynomial columns. - swapw - exec.constants::zero_word mem_loadw - dup.12 add.1 - mem_storew + ## Store the last aux segment column + dup.12 add.1 mem_storew ## Final hperm hperm @@ -439,7 +331,7 @@ end #! The procedure then outputs a stack in the same configuration but with the pointers and accumulators #! updated to [Y`, Y`, Acc`, P`, ...] where: #! -#! 1. P` := [CURRENT_TRACE_ROW_PTR+18, OOD_TRACE_PTR+72, DEEP_RAND_CC_PTR+72, 0]. +#! 1. P` := [CURRENT_TRACE_ROW_PTR+18, OOD_TRACE_PTR+70, DEEP_RAND_CC_PTR+70, 0]. #! 2. [Y`, Y`] is a "garbage" double-word used to later mem_stream auxiliary portion referenced now #! by CURRENT_TRACE_ROW_PTR`. #! 3. Acc` is the accumulator holding the updated numerator values i.e. with terms involving main @@ -450,12 +342,17 @@ end #! #! Cycles: 81 proc.combine_main_trace_columns - repeat.9 + repeat.8 mem_stream repeat.8 - exec.combine_main + rcomb_base end end + + mem_stream + repeat.6 + rcomb_base + end end #! Computes the random linear combination involving the aux trace columns and accumulates @@ -470,7 +367,7 @@ end #! The procedure then outputs a stack in the same configuration but with the pointers and accumulators #! updated to [Y`, Y`, Acc`, P`, ...] where: #! -#! 1. P` := [CURRENT_TRACE_ROW_PTR+6, OOD_TRACE_PTR+9, DEEP_RAND_CC_PTR+9, 0]. +#! 1. P` := [CURRENT_TRACE_ROW_PTR+4, OOD_TRACE_PTR+7, DEEP_RAND_CC_PTR+7, 0]. #! 2. [Y`, Y`] is a "garbage" double-word used to later mem_stream constraint composition polynomial #! trace portion referenced now by CURRENT_TRACE_ROW_PTR`. #! 3. Acc` is the accumulator holding the updated numerator values i.e. with terms involving main @@ -479,19 +376,19 @@ end #! Input: [Y, Y, Acc, P, ...] #! Output: [Y`, Y`, Acc`, P`, ...] #! -#! Cycles: 12 +#! Cycles: 9 proc.combine_aux_trace_columns - # Compute the random linear combination of the first 8 auxiliary trace columns - repeat.2 - mem_stream - repeat.4 - exec.combine_aux - end + # Compute the random linear combination of the first 4 auxiliary trace columns + mem_stream + repeat.4 + exec.combine_aux end - # and the 9th aux column + # Compute the random linear combination of the last 3 auxiliary trace columns mem_stream - exec.combine_aux + repeat.3 + exec.combine_aux + end end #! Computes the random linear combination involving the constraint composition polynomial trace @@ -512,7 +409,7 @@ end #! Input: [Y, Y, Acc, P, ...] #! Output: [Acc`, ...] #! -#! Cycles: 33 +#! Cycles: 25 proc.combine_constraint_poly_columns # Save Acc swapw.2 @@ -570,7 +467,7 @@ end #! #! Input: [query_ptr, ...] #! Output: [...] -#! Cycles: 6 + num_queries * 463 +#! Cycles: 6 + num_queries * 473 export.compute_deep_composition_polynomial_queries exec.constants::fri_com_ptr dup.1 @@ -583,7 +480,7 @@ export.compute_deep_composition_polynomial_queries # Load the (main, aux, constraint)-traces rows associated with the current query and get # the index of the query. # - # Cycles: 200 + # Cycles: 219 exec.load_query_row #=>[index, query_ptr, query_end_ptr, ...] @@ -629,9 +526,9 @@ export.compute_deep_composition_polynomial_queries ## d) Compute the random linear combination ## - ## Cycles: 81 + 12 + 33 = 126 + ## Cycles: 81 + 9 + 25 = 115 exec.combine_main_trace_columns - exec.combine_aux_trace_columns + exec.combine_aux_trace_columns exec.combine_constraint_poly_columns #=> [Acc, Z, x, index, query_ptr, query_end_ptr, ...] diff --git a/stdlib/asm/crypto/stark/ood_frames.masm b/stdlib/asm/crypto/stark/ood_frames.masm index bb3fabc1bc..b162d71b77 100644 --- a/stdlib/asm/crypto/stark/ood_frames.masm +++ b/stdlib/asm/crypto/stark/ood_frames.masm @@ -7,20 +7,20 @@ use.std::crypto::hashes::rpo #! #! Input: [...] #! Output: [OOD_FRAME_HASH, ...] -#! Cycles: 106 +#! Cycles: 100 export.load_evaluation_frame - # We have 72 main trace columns and 9 aux trace columns for a total of 162 base field elements + # We have 70 main trace columns and 7 aux trace columns for a total of 154 base field elements # per row. Since we have two rows, i.e. current and next, the total number of field elements # making up the OOD evaluation frame is: - # 324 = 40 * 8 + 4 + # 324 = 38 * 8 + 4 # The elements are stored from the stack as (a1_1, a1_0, a0_1, a0_0) where a0 is from the # current row and a1 from the next row. exec.constants::ood_trace_ptr - push.1.0.0.0 + push.4.0.0.0 padw padw - repeat.40 + repeat.38 adv_pipe hperm end @@ -29,7 +29,7 @@ export.load_evaluation_frame adv_loadw dup.12 mem_storew swapw - exec.constants::zero_zero_zero_one_word mem_loadw + exec.constants::zero_word mem_loadw hperm dropw @@ -113,40 +113,49 @@ end #! #! Input: [...] #! Output: [res1, res0, ...] -#! Cycles: 118 +#! Cycles: 152 export.compute_Hz - # TODO: remove this + # Load the pointer to the OOD constraint polynomials evaluations exec.constants::ood_constraint_evals_ptr - add.4 - repeat.3 - padw - end + # => [ptr, ...] # Compute `H(z)` ## Load `value_i`'s - dup.12 - sub.4 - mem_loadw - swapw.2 + # Load value_0 + padw dup.4 mem_loadw + # => [0, 0, v0_1, v0_0, ptr, ...] + + # Load value_1 + push.0.0 dup.6 add.1 mem_loadw + # => [0, 0, v1_1, v1_0, v0_1, v0_0, ptr, ...] - dup.12 - sub.3 - mem_loadw - swapw + # Load value_2 + push.0.0 dup.8 add.2 mem_loadw + # => [0, 0, v2_1, v2_0, v1_1, v1_0, v0_1, v0_0, ptr, ...] - dup.12 - sub.2 - mem_loadw + # Load value_3 + push.0.0 dup.10 add.3 mem_loadw + # => [0, 0, v3_1, v3_0, v2_1, v2_0, v1_1, v1_0, v0_1, v0_0, ptr, ...] - movup.12 - sub.1 - padw - movup.4 - mem_loadw + # Load value_4 + push.0.0 dup.12 add.4 mem_loadw + # => [0, 0, v4_1, v4_0, v3_1, v3_0, v2_1, v2_0, v1_1, v1_0, v0_1, v0_0, ptr, ...] + + # Load value_5 + push.0.0 movup.14 movdn.4 dup.4 add.5 mem_loadw + # => [0, 0, v5_1, v5_0, ptr, v4_1, v4_0, v3_1, v3_0, v2_1, v2_0, v1_1, v1_0, v0_1, v0_0, ptr, ...] + + # Load value_6 + push.0.0 dup.6 add.6 mem_loadw + # => [0, 0, v6_1, v6_0, v5_1, v5_0, ptr, v4_1, v4_0, v3_1, v3_0, v2_1, v2_0, v1_1, v1_0, v0_1, v0_0, ptr, ...] + + # Load value_7 + push.0.0 movup.8 add.7 mem_loadw + # => [0, 0, v7_1, v7_0, v6_1, v6_0, v5_1, v5_0, ptr, v4_1, v4_0, v3_1, v3_0, v2_1, v2_0, v1_1, v1_0, v0_1, v0_0, ptr, ...] ## Load z^N where N is the length of the execution trace - padw + push.0.0 exec.constants::z_ptr mem_loadw movup.2 drop movup.2 drop diff --git a/stdlib/asm/crypto/stark/public_inputs.masm b/stdlib/asm/crypto/stark/public_inputs.masm index 4ea8426b3c..a837288d3e 100644 --- a/stdlib/asm/crypto/stark/public_inputs.masm +++ b/stdlib/asm/crypto/stark/public_inputs.masm @@ -28,7 +28,7 @@ export.load end adv_loadw swapw - exec.constants::zero_zero_zero_one_word mem_loadw + exec.constants::zero_word mem_loadw hperm dropw diff --git a/stdlib/asm/crypto/stark/random_coin.masm b/stdlib/asm/crypto/stark/random_coin.masm index 07f431cb22..63fe259c9f 100644 --- a/stdlib/asm/crypto/stark/random_coin.masm +++ b/stdlib/asm/crypto/stark/random_coin.masm @@ -95,11 +95,6 @@ export.init_seed exec.constants::c_ptr mem_storew exec.constants::r1_ptr mem_storew exec.constants::r2_ptr mem_storew - - drop - push.1 - swap.3 - exec.constants::zero_zero_zero_one_word mem_storew dropw #=> [log(trace_length), num_queries, log(blowup), grinding] @@ -164,36 +159,41 @@ export.init_seed exec.constants::trace_domain_generator_ptr mem_store #=> [0, trace_length, num_queries, blowup, grinding] - # clean satck + # clean stack drop #=> [trace_length, num_queries, blowup, grinding] # Construct the proof context - ##trace layout info - push.1208027408 - - ##field modulus bytes (2 field elements) - push.1 - push.4294967295 - + ## trace layout info, which is the concatenation as u8-s of: + ## 1. main segment width + ## 2. num auxiliary segments, which always 1 + ## 3. auxiliary segment width + ## 4. number of auxiliary random values + ## 5. trace length (this is already on the stack) + + ## main segment width is 70 and there are 1 auxiliary segments + ## of width 7 using 16 random extension field elements + push.0x46010710 + ## field modulus bytes (2 field elements) + push.0x01 # lower half of the modulus + push.0xffffffff # upper half of the modulus ## field extension and FRI parameters - push.132103 - + ## field extension degree || FRI folding factor || FRI remainder polynomial max degree + push.0x020407 # Hash proof context # Cycles: 15 swapw - push.1.0.0.0 - #=> [0, 0, 0, 1, B, A, ..] - swapw.2 - swapw - #=> [B, A, 0, 0, 0, 1, ..] + movdn.6 + push.4.0.0.0 + movdnw.2 + # => [B, A, 0, 0, 0, 4, ..] hperm dropw dropw - #=> [C] -end + # => [C] + end #! Reseed the random coin with `DATA` #! @@ -422,7 +422,7 @@ end #! #! Input: [aux_rand_elem_ptr, ...] #! Output: [...] -#! Cycles: 150 +#! Cycles: 159 export.generate_aux_randomness push.16 swap @@ -431,14 +431,14 @@ export.generate_aux_randomness end #! Draw constraint composition random coefficients and save them into memory in the region from -#! `compos_coef_ptr` `compos_coef_ptr + 118 - 1` as `(r1_1, r1_0, r0_1, r0_0)` +#! `compos_coef_ptr` `compos_coef_ptr + 112 - 1` as `(r1_1, r1_0, r0_1, r0_0)` #! #! Input: [compos_coef_ptr, ...] #! Output: [...] -#! Cycles: 1309 +#! Cycles: 1305 export.generate_constraint_composition_coefficients - push.236 + exec.constants::num_constraint_composition_coef_multiplied_by_two_and_rounded_up_to_4 swap exec.generate_random_coefficients #=> [...] @@ -447,16 +447,16 @@ end #! Draw deep composition polynomial random coefficients and save them into memory in the region from #! `deep_rand_coef_ptr` to `deep_rand_coef_ptr + 89 - 1` as `(0, 0, r0_1, r0_0)` #! The number of coefficients is equal to: -#! 1. (72 + 9) * 2 Felt for the main and auxiliary traces. +#! 1. (70 + 7) * 2 Felt for the main and auxiliary traces. #! 2. 8 * 2 Felt for constraint polynomial. -#! Total: 89 tuples of type (Felt, Felt) +#! Total: 85 tuples of type (Felt, Felt) #! #! Input: [deep_rand_coef_ptr, ...] #! Output: [...] -#! Cycles: 1693 +#! Cycles: 1624 export.generate_deep_composition_random_coefficients - - push.92 + # note that 88 is the next number after 85 divisible by 4 + exec.constants::num_deep_composition_coef_multiplied_by_two_and_rounded_up_to_4 swap exec.generate_random_coefficients_pad #=> [...] @@ -665,7 +665,15 @@ export.generate_list_indices exec.get_rate_2 hperm - exec.rpo::squeeze_digest + # Save the new state + exec.constants::r2_ptr mem_storew + dropw + # => [R1, C] + exec.constants::r1_ptr mem_storew + swapw + # => [C, R1] + exec.constants::c_ptr mem_storew + dropw #=> [R1, query_ptr, mask, depth, num_queries, ...] @@ -698,8 +706,17 @@ export.generate_list_indices exec.get_rate_2 hperm - exec.rpo::squeeze_digest - #=> [R1, query_ptr, mask, depth, num_queries, ...] + # Save the new state + exec.constants::r2_ptr mem_storew + dropw + # => [R1, C] + exec.constants::r1_ptr mem_storew + swapw + # => [C, R1] + exec.constants::c_ptr mem_storew + dropw + #=> [R1, query_ptr, mask, depth, num_remaining_iterations, remainder, ...] + movup.7 sub.1 dup movdn.8 push.0 neq end diff --git a/stdlib/asm/crypto/stark/verifier.masm b/stdlib/asm/crypto/stark/verifier.masm index 3b4fc05a03..edcc4adeca 100644 --- a/stdlib/asm/crypto/stark/verifier.masm +++ b/stdlib/asm/crypto/stark/verifier.masm @@ -12,25 +12,25 @@ use.std::crypto::stark::constants #! The following simplifying assumptions are currently made: #! - The blowup is set to 8. #! - The maximal allowed degree of the remainder polynomial is 7. -#! - Only the input and output stacks, assumed of fixed size equal to 16, are handled in regards -#! to public inputs. +#! - The public inputs are composed of the input and output stacks, of fixed size equal to 16. #! - There are two trace segments, main and auxiliary. It is assumed that the main trace segment -#! is 73 columns wide while the auxiliary trace segment is 9 columns wide. +#! is 70 columns wide while the auxiliary trace segment is 7 columns wide. #! - The OOD evaluation frame is composed of two interleaved rows, current and next, each composed -#! of 73 elements representing the main trace portion and 9 elements for the auxiliary trace one. +#! of 70 elements representing the main trace portion and 7 elements for the auxiliary trace one. #! - To boost soundness, the protocol is run on a quadratic extension field and this means that #! the OOD evaluation frame is composed of elements in a quadratic extension field i.e. tuples. -#! Similarly, elements of the auxiliary trace are quadratic extension field elements. +#! Similarly, elements of the auxiliary trace are quadratic extension field elements. The random +#! values for computing random linear combinations are also in this extension field. #! - The following procedure makes use of global memory address beyond 3 * 2^30 and these are #! defined in `constants.masm`. #! -#! Input: [log(trace_length), num_queries, log(blowup), grinding] +#! Input: [log(trace_length), num_queries, log(blowup), grinding, ...] #! Output: [] #! Cycles: #! 1- Remainder codeword size 32: -#! 5000 + num_queries * (40 + num_fri_layers * 76 + 26 + 463) + 83 * num_fri_layers + 10 * log(trace_length) + 1633 +#! 4975 + num_queries * (40 + num_fri_layers * 76 + 26 + 473) + 83 * num_fri_layers + 10 * log(trace_length) + 1633 #! 2- Remainder codeword size 64: -#! 5000 + num_queries * (40 + num_fri_layers * 76 + 26 + 463) + 83 * num_fri_layers + 10 * log(trace_length) + 3109 +#! 4975 + num_queries * (40 + num_fri_layers * 76 + 26 + 473) + 83 * num_fri_layers + 10 * log(trace_length) + 3109 export.verify #============================================================================================== @@ -67,7 +67,7 @@ export.verify # Draw random ExtFelt for the auxiliary trace # - # Cycles: 150 + # Cycles: 160 exec.constants::aux_rand_elem_ptr exec.random_coin::generate_aux_randomness #=> [...] @@ -85,7 +85,7 @@ export.verify # III) Draw constraint composition coefficients #============================================================================================== - # Cycles: 1309 + # Cycles: 1306 exec.constants::composition_coef_ptr exec.random_coin::generate_constraint_composition_coefficients #=> [...] @@ -110,7 +110,7 @@ export.verify # of H over the LDE domain. #============================================================================================== - # Cycles: 106 + # Cycles: 104 exec.ood_frames::load_evaluation_frame #=> [OOD_FRAME_HASH, ...] @@ -126,7 +126,7 @@ export.verify # Compute `H(z)` # - # Cycles: 118 + # Cycles: 152 exec.ood_frames::compute_Hz #=> [res1, res0, ...] @@ -147,7 +147,7 @@ export.verify # DEEP composition polynomial. #============================================ - # Cycles: 1693 + # Cycles: 1625 exec.constants::deep_rand_coef_ptr exec.random_coin::generate_deep_composition_random_coefficients @@ -221,7 +221,7 @@ export.verify # Compute deep compostion polynomial queries # - # Cycles: 14 + num_queries * 463 + # Cycles: 14 + num_queries * 473 #=> [query_ptr, ...] exec.deep_queries::compute_deep_composition_polynomial_queries #=> [query_ptr, ...] diff --git a/stdlib/tests/crypto/stark/mod.rs b/stdlib/tests/crypto/stark/mod.rs index b81a6f4b2b..c72d725158 100644 --- a/stdlib/tests/crypto/stark/mod.rs +++ b/stdlib/tests/crypto/stark/mod.rs @@ -10,7 +10,6 @@ use verifier_recursive::{generate_advice_inputs, VerifierData}; // Note: Changes to MidenVM may cause this test to fail when some of the assumptions documented // in `stdlib/asm/crypto/stark/verifier.masm` are violated. #[test] -#[ignore] fn stark_verifier_e2f4() { // An example MASM program to be verified inside Miden VM. // Note that output stack-overflow is not yet supported because of the way we handle public @@ -25,8 +24,12 @@ fn stark_verifier_e2f4() { stack_inputs[15] = 0; stack_inputs[14] = 1; - let VerifierData { initial_stack, tape, store, advice_map } = - generate_recursive_verifier_data(example_source, stack_inputs).unwrap(); + let VerifierData { + initial_stack, + advice_stack: tape, + store, + advice_map, + } = generate_recursive_verifier_data(example_source, stack_inputs).unwrap(); // Verify inside Miden VM let source = " @@ -53,7 +56,7 @@ pub fn generate_recursive_verifier_data( let mut host = DefaultHost::new(advice_provider); let options = - ProvingOptions::new(43, 8, 12, FieldExtension::Quadratic, 4, 7, HashFunction::Rpo256); + ProvingOptions::new(27, 8, 12, FieldExtension::Quadratic, 4, 7, HashFunction::Rpo256); let (stack_outputs, proof) = prove(&program, stack_inputs.clone(), &mut host, options).unwrap(); diff --git a/stdlib/tests/crypto/stark/verifier_recursive/channel.rs b/stdlib/tests/crypto/stark/verifier_recursive/channel.rs index 0ae32bcdfd..8a92142064 100644 --- a/stdlib/tests/crypto/stark/verifier_recursive/channel.rs +++ b/stdlib/tests/crypto/stark/verifier_recursive/channel.rs @@ -1,14 +1,11 @@ -// VERIFIER CHANNEL -// ================================================================================================ - use alloc::vec::Vec; use miden_air::ProcessorAir; use test_utils::{ - crypto::{BatchMerkleProof, MerklePath, PartialMerkleTree, Rpo256, RpoDigest}, + crypto::{BatchMerkleProof, PartialMerkleTree, Rpo256, RpoDigest}, group_slice_elements, math::{FieldElement, QuadExtension, StarkField}, - Felt, MerkleTreeVC, VerifierError, EMPTY_WORD, + Felt, MerkleTreeVC, VerifierError, }; use winter_air::{ proof::{Proof, Queries, Table, TraceOodFrame}, @@ -170,24 +167,26 @@ impl VerifierChannel { positions: &[usize], ) -> Result<(AdvMap, Vec), VerifierError> { let queries = self.trace_queries.take().expect("already read"); - let mut trees = Vec::new(); - - let proofs: Vec<_> = queries.query_proofs.into_iter().collect(); - let main_queries = queries.main_states.clone(); - let aux_queries = queries.aux_states.clone(); + let proofs = queries.query_proofs; + let main_queries = queries.main_states; + let aux_queries = queries.aux_states; let main_queries_vec: Vec> = main_queries.rows().map(|a| a.to_owned()).collect(); + let aux_queries_vec: Vec> = aux_queries .as_ref() .unwrap() .rows() .map(|a| QuadExt::slice_as_base_elements(a).to_vec()) .collect(); + let (main_trace_pmt, mut main_trace_adv_map) = unbatch_to_partial_mt(positions.to_vec(), main_queries_vec, proofs[0].clone()); let (aux_trace_pmt, mut aux_trace_adv_map) = unbatch_to_partial_mt(positions.to_vec(), aux_queries_vec, proofs[1].clone()); - trees.push(main_trace_pmt); + + let mut trees = vec![main_trace_pmt]; trees.push(aux_trace_pmt); + main_trace_adv_map.append(&mut aux_trace_adv_map); Ok((main_trace_adv_map, trees)) } @@ -202,85 +201,58 @@ impl VerifierChannel { let queries = self.constraint_queries.take().expect("already read"); let proof = queries.query_proofs; - let queries_: Vec> = queries + let queries = queries .evaluations .rows() - .map(|a| a.iter().flat_map(|x| QuadExt::to_base_elements(*x).to_owned()).collect()) + .map(|a| QuadExt::slice_as_base_elements(a).into()) .collect(); let (constraint_pmt, constraint_adv_map) = - unbatch_to_partial_mt(positions.to_vec(), queries_, proof); + unbatch_to_partial_mt(positions.to_vec(), queries, proof); Ok((constraint_adv_map, constraint_pmt)) } - // Get the FRI layer challenges alpha - pub fn fri_layer_commitments(&self) -> Option> { - self.fri_roots.clone() - } - - // Get remainder codeword - pub fn fri_remainder(&self) -> Vec { - self.fri_remainder.clone().unwrap() - } - // - pub fn layer_proofs(&self) -> Vec> { + /// Returns the FRI layers Merkle batch proofs. + pub fn fri_layer_proofs(&self) -> Vec> { self.fri_layer_proofs.clone() } - pub fn unbatch( + /// Returns the unbatched Merkle proofs as well as a global key-value map for all the FRI layer + /// proofs. + pub fn unbatch_fri_layer_proofs( &mut self, positions_: &[usize], domain_size: usize, layer_commitments: Vec, ) -> (Vec, Vec<(RpoDigest, Vec)>) { - let queries = self.fri_layer_queries.clone(); + let all_layers_queries = self.fri_layer_queries.clone(); let mut current_domain_size = domain_size; let mut positions = positions_.to_vec(); - let depth = layer_commitments.len() - 1; + let number_of_folds = layer_commitments.len() - 1; - let mut adv_key_map = Vec::new(); - let mut partial_trees = Vec::new(); - let mut layer_proofs = self.layer_proofs(); - for query in queries.iter().take(depth) { + let mut global_adv_key_map = Vec::new(); + let mut global_partial_merkle_trees = Vec::new(); + let mut layer_proofs = self.fri_layer_proofs(); + for current_layer_queries in all_layers_queries.iter().take(number_of_folds) { let mut folded_positions = fold_positions(&positions, current_domain_size, N); let layer_proof = layer_proofs.remove(0); - let x = group_slice_elements::(query); - let leaves: Vec = x.iter().map(|row| Rpo256::hash_elements(row)).collect(); - let unbatched_proof = layer_proof.into_openings(&leaves, &folded_positions).unwrap(); - assert_eq!(x.len(), unbatched_proof.len()); - - let nodes: Vec<[Felt; 4]> = - leaves.iter().map(|leaf| [leaf[0], leaf[1], leaf[2], leaf[3]]).collect(); - - let paths: Vec = - unbatched_proof.into_iter().map(|list| list.1.into()).collect(); + let queries: Vec<_> = group_slice_elements::(current_layer_queries) + .iter() + .map(|query| QuadExt::slice_as_base_elements(query).to_vec()) + .collect(); - let iter_pos = folded_positions.iter_mut().map(|a| *a as u64); - let nodes_tmp = nodes.clone(); - let iter_nodes = nodes_tmp.iter(); - let iter_paths = paths.into_iter(); - let mut tmp_vec = Vec::new(); - for (p, (node, path)) in iter_pos.zip(iter_nodes.zip(iter_paths)) { - tmp_vec.push((p, RpoDigest::from(*node), path)); - } + let (current_partial_merkle_tree, mut cur_adv_key_map) = + unbatch_to_partial_mt(folded_positions.clone(), queries, layer_proof); - let new_pmt = - PartialMerkleTree::with_paths(tmp_vec).expect("should not fail from paths"); - partial_trees.push(new_pmt); - - nodes.into_iter().zip(x.iter()).for_each(|(a, b)| { - let mut value = QuadExt::slice_as_base_elements(b).to_owned(); - value.extend(EMPTY_WORD); - - adv_key_map.push((a.to_owned().into(), value)); - }); + global_partial_merkle_trees.push(current_partial_merkle_tree); + global_adv_key_map.append(&mut cur_adv_key_map); core::mem::swap(&mut positions, &mut folded_positions); current_domain_size /= N; } - (partial_trees, adv_key_map) + (global_partial_merkle_trees, global_adv_key_map) } } @@ -425,36 +397,41 @@ impl ConstraintQueries { // HELPER FUNCTIONS // ================================================================================================ +/// Takes a set of positions, query values of a trace at these positions and a Merkle batch proof +/// against a committment to this trace, and outputs a partial Merkle tree with individual Merkle +/// paths for each position as well as a key-value map mapping the digests of the query values +/// (i.e. Merkle tree leaves) to their corresponding query values. pub fn unbatch_to_partial_mt( - mut positions: Vec, + positions: Vec, queries: Vec>, proof: BatchMerkleProof, ) -> (PartialMerkleTree, Vec<(RpoDigest, Vec)>) { + // hash the query values in order to get the leaf let leaves: Vec = queries.iter().map(|row| Rpo256::hash_elements(row)).collect(); - let unbatched_proof = proof.into_openings(&leaves, &positions).unwrap(); - let mut adv_key_map = Vec::new(); - let nodes: Vec<[Felt; 4]> = - queries.iter().map(|node| [node[0], node[1], node[2], node[3]]).collect(); - - let paths: Vec = unbatched_proof.into_iter().map(|list| list.1.into()).collect(); - - let iter_pos = positions.iter_mut().map(|a| *a as u64); - let nodes_tmp = nodes.clone(); - let iter_nodes = nodes_tmp.iter(); - let iter_paths = paths.into_iter(); - let mut tmp_vec = vec![]; - for (p, (node, path)) in iter_pos.zip(iter_nodes.zip(iter_paths)) { - tmp_vec.push((p, RpoDigest::from(*node), path)); + // use the computed leaves with the indices in order to unbatch the Merkle proof batch proof + let unbatched_proof = proof + .into_openings(&leaves, &positions) + .expect("failed to unbatch the batched Merkle proof"); + + // construct the partial Merkle tree data + let mut paths_with_leaves = vec![]; + for (position, merkle_proof) in positions.iter().zip(unbatched_proof.iter()) { + paths_with_leaves.push(( + *position as u64, + merkle_proof.0.to_owned(), + merkle_proof.1.to_owned().into(), + )) } - nodes.into_iter().zip(queries.iter()).for_each(|(a, b)| { - let data = b.to_owned(); - adv_key_map.push((a.to_owned().into(), data)); + // construct the advice key map linking leaves to query values + let mut adv_key_map = Vec::new(); + leaves.into_iter().zip(queries.iter()).for_each(|(leaf, query_data)| { + adv_key_map.push((leaf, query_data.to_owned())); }); ( - PartialMerkleTree::with_paths(tmp_vec).expect("should not fail from paths"), + PartialMerkleTree::with_paths(paths_with_leaves).expect("should not fail from paths"), adv_key_map, ) } diff --git a/stdlib/tests/crypto/stark/verifier_recursive/mod.rs b/stdlib/tests/crypto/stark/verifier_recursive/mod.rs index 85a38d0675..2d59f5119e 100644 --- a/stdlib/tests/crypto/stark/verifier_recursive/mod.rs +++ b/stdlib/tests/crypto/stark/verifier_recursive/mod.rs @@ -8,6 +8,7 @@ use test_utils::{ Felt, VerifierError, }; use winter_air::{proof::Proof, Air}; +use winter_fri::VerifierChannel as FriVerifierChannel; mod channel; use channel::VerifierChannel; @@ -18,7 +19,7 @@ pub type QuadExt = QuadExtension; #[derive(Debug, Clone, Eq, PartialEq)] pub struct VerifierData { pub initial_stack: Vec, - pub tape: Vec, + pub advice_stack: Vec, pub store: MerkleStore, pub advice_map: Vec<(RpoDigest, Vec)>, } @@ -27,23 +28,25 @@ pub fn generate_advice_inputs( proof: Proof, pub_inputs: ::PublicInputs, ) -> Result { - //// build a seed for the public coin; the initial seed is the hash of public inputs and proof - //// context, but as the protocol progresses, the coin will be reseeded with the info received - //// from the prover - let mut public_coin_seed = proof.context.to_elements(); - let trace_len: Felt = public_coin_seed[7]; + // we need to provide the following instance specific data through the operand stack let initial_stack = vec![ - public_coin_seed[4].as_int(), - (public_coin_seed[5].as_int() as usize).ilog2() as u64, - public_coin_seed[6].as_int(), - (trace_len.as_int() as usize).ilog2() as u64, + proof.context.options().grinding_factor() as u64, + proof.context.options().blowup_factor().ilog2() as u64, + proof.context.options().num_queries() as u64, + proof.context.trace_info().length().ilog2() as u64, ]; - let mut tape = vec![]; + // build a seed for the public coin; the initial seed is the hash of public inputs and proof + // context, but as the protocol progresses, the coin will be reseeded with the info received + // from the prover + let mut advice_stack = vec![]; + let mut public_coin_seed = proof.context.to_elements(); public_coin_seed.append(&mut pub_inputs.to_elements()); + // add the public inputs, which is nothing but the input and output stacks to the VM, to the + // advice tape let pub_inputs_int: Vec = pub_inputs.to_elements().iter().map(|a| a.as_int()).collect(); - tape.extend_from_slice(&pub_inputs_int[..]); + advice_stack.extend_from_slice(&pub_inputs_int[..]); // create AIR instance for the computation specified in the proof let air = ProcessorAir::new(proof.trace_info().to_owned(), pub_inputs, proof.options().clone()); @@ -51,14 +54,16 @@ pub fn generate_advice_inputs( let mut public_coin: RpoRandomCoin = RpoRandomCoin::new(seed_digest.into()); let mut channel = VerifierChannel::new(&air, proof)?; - // 1 ----- trace commitment ------------------------------------------------------------------- + // 1 ----- main segment trace ----------------------------------------------------------------- let trace_commitments = channel.read_trace_commitments(); - // reseed the coin with the commitment to the main trace segment + // reseed the coin with the commitment to the main segment trace public_coin.reseed(trace_commitments[0]); - tape.extend_from_slice(&digest_to_int_vec(trace_commitments)); + advice_stack.extend_from_slice(&digest_to_int_vec(trace_commitments)); - // process auxiliary trace segments, to build a set of random elements for each segment + // 2 ----- auxiliary segment trace ------------------------------------------------------------ + + // generate the auxiliary random elements let mut aux_trace_rand_elements = vec![]; for commitment in trace_commitments.iter().skip(1) { let rand_elements: Vec = air @@ -67,85 +72,120 @@ pub fn generate_advice_inputs( aux_trace_rand_elements.push(rand_elements); public_coin.reseed(*commitment); } - // build random coefficients for the composition polynomial + + // 3 ----- constraint composition trace ------------------------------------------------------- + + // build random coefficients for the composition polynomial. we don't need them but we have to + // generate them in order to update the random coin let _constraint_coeffs: winter_air::ConstraintCompositionCoefficients = air .get_constraint_composition_coefficients(&mut public_coin) .map_err(|_| VerifierError::RandomCoinError)?; - - // 2 ----- constraint commitment -------------------------------------------------------------- let constraint_commitment = channel.read_constraint_commitment(); - tape.extend_from_slice(&digest_to_int_vec(&[constraint_commitment])); + advice_stack.extend_from_slice(&digest_to_int_vec(&[constraint_commitment])); public_coin.reseed(constraint_commitment); - // 3 ----- OOD frames -------------------------------------------------------------- + // 4 ----- OOD frames -------------------------------------------------------------- + + // generate the the OOD point + let _z: QuadExt = public_coin.draw().unwrap(); + + // read the main and auxiliary segments' OOD frames and add them to advice tape let ood_trace_frame = channel.read_ood_trace_frame(); let _ood_main_trace_frame = ood_trace_frame.main_frame(); let _ood_aux_trace_frame = ood_trace_frame.aux_frame(); - // TODO: fix - tape.extend_from_slice(&to_int_vec(ood_trace_frame.current_row())); - public_coin.reseed(Rpo256::hash_elements(ood_trace_frame.current_row())); + let mut main_and_aux_frame_states = Vec::new(); + for col in 0.._ood_main_trace_frame.current().len() { + main_and_aux_frame_states.push(_ood_main_trace_frame.current()[col]); + main_and_aux_frame_states.push(_ood_main_trace_frame.next()[col]); + } + for col in 0.._ood_aux_trace_frame.as_ref().unwrap().current().len() { + main_and_aux_frame_states.push(_ood_aux_trace_frame.as_ref().unwrap().current()[col]); + main_and_aux_frame_states.push(_ood_aux_trace_frame.as_ref().unwrap().next()[col]); + } + advice_stack.extend_from_slice(&to_int_vec(&main_and_aux_frame_states)); + public_coin.reseed(Rpo256::hash_elements(&main_and_aux_frame_states)); - // read evaluations of composition polynomial columns + // read OOD evaluations of composition polynomial columns let ood_constraint_evaluations = channel.read_ood_constraint_evaluations(); - tape.extend_from_slice(&to_int_vec(&ood_constraint_evaluations)); + advice_stack.extend_from_slice(&to_int_vec(&ood_constraint_evaluations)); public_coin.reseed(Rpo256::hash_elements(&ood_constraint_evaluations)); - // 4 ----- FRI -------------------------------------------------------------------- - let fri_commitments_digests = channel.fri_layer_commitments().unwrap(); - let poly = channel.fri_remainder(); + // 5 ----- FRI ------------------------------------------------------------------------------- + + // read the FRI layer committments as well as remainder polynomial + let fri_commitments_digests = channel.read_fri_layer_commitments(); + let poly = channel.read_remainder().unwrap(); + + // Reed-Solomon encode the remainder polynomial as this is needed for the probabilistic NTT let twiddles = fft::get_twiddles(poly.len()); let fri_remainder = fft::evaluate_poly_with_offset(&poly, &twiddles, Felt::GENERATOR, BLOWUP_FACTOR); + // add the above to the advice tape let fri_commitments: Vec = digest_to_int_vec(&fri_commitments_digests); - tape.extend_from_slice(&fri_commitments); - tape.extend_from_slice(&to_int_vec(&poly)); - tape.extend_from_slice(&to_int_vec(&fri_remainder)); + advice_stack.extend_from_slice(&fri_commitments); + advice_stack.extend_from_slice(&to_int_vec(&poly)); + advice_stack.extend_from_slice(&to_int_vec(&fri_remainder)); + // reseed with FRI layer commitments let _deep_coefficients = air .get_deep_composition_coefficients::(&mut public_coin) .map_err(|_| VerifierError::RandomCoinError)?; - // Reseed with FRI layer commitments let layer_commitments = fri_commitments_digests.clone(); for commitment in layer_commitments.iter() { public_coin.reseed(*commitment); let _alpha: QuadExt = public_coin.draw().expect("failed to draw random indices"); } - // 5 ----- trace and constraint queries ------------------------------------------------------- + // 6 ----- trace and constraint queries ------------------------------------------------------- // read proof-of-work nonce sent by the prover and draw pseudo-random query positions for - // the LDE domain from the public coin. - // This is needed in order to construct Partial Merkle Trees + // the LDE domain from the public coin let pow_nonce = channel.read_pow_nonce(); - let query_positions = public_coin + let mut query_positions = public_coin .draw_integers(air.options().num_queries(), air.lde_domain_size(), pow_nonce) .map_err(|_| VerifierError::RandomCoinError)?; + advice_stack.extend_from_slice(&[pow_nonce]); + query_positions.sort(); + query_positions.dedup(); - // read advice maps and Merkle paths related to trace and constraint composition polynomial - // evaluations - let (mut advice_map, mut partial_trees_traces) = + // read advice maps and Merkle paths of the queries to main/aux and constraint composition + // traces + let (mut main_aux_adv_map, mut partial_trees_traces) = channel.read_queried_trace_states(&query_positions)?; - let (mut adv_map_constraint, partial_tree_constraint) = + let (mut constraint_adv_map, partial_tree_constraint) = channel.read_constraint_evaluations(&query_positions)?; - let domain_size = (air.trace_poly_degree() + 1) * BLOWUP_FACTOR; - let mut ress = channel.unbatch::<4, 3>(&query_positions, domain_size, fri_commitments_digests); + let (mut partial_trees_fri, mut fri_adv_map) = channel.unbatch_fri_layer_proofs::<4>( + &query_positions, + air.lde_domain_size(), + fri_commitments_digests, + ); + // consolidate advice maps - advice_map.append(&mut adv_map_constraint); - advice_map.append(&mut ress.1); - let mut partial_trees_fri = ress.0; + main_aux_adv_map.append(&mut constraint_adv_map); + main_aux_adv_map.append(&mut fri_adv_map); + + // build the full MerkleStore partial_trees_fri.append(&mut partial_trees_traces); partial_trees_fri.push(partial_tree_constraint); let mut store = MerkleStore::new(); for partial_tree in &partial_trees_fri { store.extend(partial_tree.inner_nodes()); } - Ok(VerifierData { initial_stack, tape, store, advice_map }) + + Ok(VerifierData { + initial_stack, + advice_stack, + store, + advice_map: main_aux_adv_map, + }) } -// Helpers +// HELPER FUNCTIONS +// ================================================================================================ + pub fn digest_to_int_vec(digest: &[RpoDigest]) -> Vec { digest .iter() From b7ff6c842b64392738019eb49f2a8c99d6557568 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 4 Dec 2024 16:27:34 +0100 Subject: [PATCH 32/39] Error refactoring and `thiserror` 2.0 (#1588) * feat: Use `thiserror` in `air` crate * feat: Refactor errors in `assembly` * feat: Refactor `errors` in `core` * feat: Refactor errors in `processor` * feat: Refactor errors for `verifier` * chore: Add changelog entry * chore: Add `"thiserror/std"` conditional feature * feat: Add static assertions for errors with `dyn Error` * chore: Test using miette with thiserror 2 * feat: Remove `PartialEq, Eq, Clone` from errors * chore: Use miden-miette 8.0 * chore: Address review comments --- CHANGELOG.md | 1 + Cargo.lock | 38 +-- Cargo.toml | 3 + air/Cargo.toml | 2 +- air/src/errors.rs | 31 +- air/src/options.rs | 5 +- air/src/trace/rows.rs | 48 ++- assembly/Cargo.toml | 6 +- assembly/src/assembler/mast_forest_builder.rs | 34 +- assembly/src/ast/ident.rs | 4 +- assembly/src/errors.rs | 17 +- assembly/src/library/error.rs | 8 +- assembly/src/library/mod.rs | 2 +- assembly/src/library/namespace.rs | 4 +- assembly/src/library/path.rs | 10 +- assembly/src/parser/error.rs | 2 +- core/Cargo.toml | 4 +- core/src/debuginfo/source_manager.rs | 46 ++- core/src/mast/mod.rs | 18 +- processor/Cargo.toml | 3 +- processor/src/errors.rs | 311 ++++++------------ processor/src/host/advice/providers.rs | 17 +- processor/src/lib.rs | 6 +- verifier/Cargo.toml | 3 +- verifier/src/lib.rs | 25 +- 25 files changed, 277 insertions(+), 371 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab9c09580c..93797a7f60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [BREAKING] Updated Winterfell dependency to v0.11 (#1586). - [BREAKING] resolved flag collision in `--verify` command and added functionality for optional input/output files (#1513). - [BREAKING] Cleanup benchmarks and examples in the `miden-vm` crate (#1587) +- [BREAKING] Use `thiserror` 2.0 to derive errors and refactor them (#1588). #### Enhancements - Added `miden_core::mast::MastForest::advice_map` to load it into the advice provider before the `MastForest` execution (#1574). diff --git a/Cargo.lock b/Cargo.lock index 0597bccfcf..dbe6e24e5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1005,8 +1005,8 @@ version = "0.11.0" dependencies = [ "criterion", "miden-core", - "miden-thiserror", "proptest", + "thiserror 2.0.3", "winter-air", "winter-prover", "winter-rand-utils", @@ -1021,11 +1021,11 @@ dependencies = [ "lalrpop-util", "miden-core", "miden-miette", - "miden-thiserror", "pretty_assertions", "regex", "rustc_version 0.4.1", "smallvec", + "thiserror 2.0.3", "tracing", "unicode-width 0.2.0", ] @@ -1040,11 +1040,11 @@ dependencies = [ "miden-crypto", "miden-formatting", "miden-miette", - "miden-thiserror", "num-derive", "num-traits", "parking_lot", "proptest", + "thiserror 2.0.3", "winter-math", "winter-rand-utils", "winter-utils", @@ -1092,9 +1092,9 @@ dependencies = [ [[package]] name = "miden-miette" -version = "7.1.1" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c532250422d933f15b148fb81e4522a5d649c178ab420d0d596c86228da35570" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" dependencies = [ "backtrace", "backtrace-ext", @@ -1103,7 +1103,6 @@ dependencies = [ "indenter", "lazy_static", "miden-miette-derive", - "miden-thiserror", "owo-colors", "regex", "rustc_version 0.2.3", @@ -1117,15 +1116,16 @@ dependencies = [ "syn", "terminal_size", "textwrap", + "thiserror 2.0.3", "trybuild", "unicode-width 0.1.14", ] [[package]] name = "miden-miette-derive" -version = "7.1.0" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cc759f0a2947acae217a2f32f722105cacc57d17d5f93bc16362142943a4edd" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" dependencies = [ "proc-macro2", "quote", @@ -1141,6 +1141,7 @@ dependencies = [ "miden-assembly", "miden-core", "miden-test-utils", + "thiserror 2.0.3", "tracing", "winter-fri", "winter-prover", @@ -1200,32 +1201,13 @@ dependencies = [ "winter-rand-utils", ] -[[package]] -name = "miden-thiserror" -version = "1.0.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183ff8de338956ecfde3a38573241eb7a6f3d44d73866c210e5629c07fa00253" -dependencies = [ - "miden-thiserror-impl", -] - -[[package]] -name = "miden-thiserror-impl" -version = "1.0.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee4176a0f2e7d29d2a8ee7e60b6deb14ce67a20e94c3e2c7275cdb8804e1862" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "miden-verifier" version = "0.11.0" dependencies = [ "miden-air", "miden-core", + "thiserror 2.0.3", "tracing", "winter-verifier", ] diff --git a/Cargo.toml b/Cargo.toml index 30d5cdbdff..c043adbf7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,6 @@ inherits = "release" debug = true debug-assertions = true overflow-checks = true + +[workspace.dependencies] +thiserror = { version = "2.0", default-features = false } diff --git a/air/Cargo.toml b/air/Cargo.toml index 811503a0a4..627cd691bf 100644 --- a/air/Cargo.toml +++ b/air/Cargo.toml @@ -31,7 +31,7 @@ std = ["vm-core/std", "winter-air/std", "thiserror/std"] testing = [] [dependencies] -thiserror = { package = "miden-thiserror", version = "1.0", default-features = false } +thiserror = { workspace = true } vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false } winter-air = { package = "winter-air", version = "0.11", default-features = false } winter-prover = { package = "winter-prover", version = "0.11", default-features = false } diff --git a/air/src/errors.rs b/air/src/errors.rs index af884215c3..03439cad4c 100644 --- a/air/src/errors.rs +++ b/air/src/errors.rs @@ -1,33 +1,12 @@ -use alloc::string::String; -use core::fmt::{Display, Formatter}; - use crate::trace::MIN_TRACE_LEN; -// EXECUTION ERROR +// EXECUTION OPTIONS ERROR // ================================================================================================ -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum ExecutionOptionsError { - ExpectedCyclesTooBig(u32, u32), + #[error("expected number of cycles {expected_cycles} must be smaller than the maximum number of cycles {max_cycles}")] + ExpectedCyclesTooBig { max_cycles: u32, expected_cycles: u32 }, + #[error("maximum number of cycles {0} must be greater than the minimum number of cycles {MIN_TRACE_LEN}")] MaxCycleNumTooSmall(u32), - OtherErrors(String), } - -impl Display for ExecutionOptionsError { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> { - use ExecutionOptionsError::*; - - match self { - ExpectedCyclesTooBig(max, expected) => { - write!(f, "The expected number of cycles must be smaller than the maximum number of cycles: maximum is {max}, but expectd is {expected}") - }, - MaxCycleNumTooSmall(max) => { - write!(f, "The maximum number of cycles must be greater than the minimum number of cycles: minimum is {MIN_TRACE_LEN}, but maximum is {max}") - }, - OtherErrors(error) => write!(f, "{error}"), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ExecutionOptionsError {} diff --git a/air/src/options.rs b/air/src/options.rs index fbbbbb31ce..bd1a5e6e56 100644 --- a/air/src/options.rs +++ b/air/src/options.rs @@ -199,7 +199,10 @@ impl ExecutionOptions { return Err(ExecutionOptionsError::MaxCycleNumTooSmall(expected_cycles)); } if max_cycles < expected_cycles { - return Err(ExecutionOptionsError::ExpectedCyclesTooBig(max_cycles, expected_cycles)); + return Err(ExecutionOptionsError::ExpectedCyclesTooBig { + max_cycles, + expected_cycles, + }); } // Round up the expected number of cycles to the next power of two. If it is smaller than diff --git a/air/src/trace/rows.rs b/air/src/trace/rows.rs index 25f1f9e338..8f3a9a22a2 100644 --- a/air/src/trace/rows.rs +++ b/air/src/trace/rows.rs @@ -1,3 +1,4 @@ +use alloc::boxed::Box; use core::{ fmt::{Display, Formatter}, ops::{Add, AddAssign, Bound, Index, IndexMut, Mul, RangeBounds, Sub, SubAssign}, @@ -7,10 +8,11 @@ use vm_core::Felt; /// Represents the types of errors that can occur when converting from and into [`RowIndex`] and /// using its operations. -#[derive(Debug, thiserror::Error, PartialEq, Eq)] -pub enum RowIndexError { - #[error("value is too large to be converted into RowIndex: {0}")] - InvalidSize(T), +#[derive(Debug, thiserror::Error)] +pub enum RowIndexError { + // This uses Box rather than String because its stack size is 8 bytes smaller. + #[error("value {0} is larger than u32::MAX so it cannot be converted into a RowIndex")] + InvalidSize(Box), } // ROW INDEX @@ -71,25 +73,28 @@ impl From for Felt { /// # Panics /// /// This function will panic if the number represented by the usize is greater than the maximum -/// [`RowIndex`] value, `u32::MAX`. +/// [`RowIndex`] value, [`u32::MAX`]. impl From for RowIndex { fn from(value: usize) -> Self { - let value = u32::try_from(value).map_err(|_| RowIndexError::InvalidSize(value)).unwrap(); + let value = u32::try_from(value) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_usize", value).into())) + .unwrap(); value.into() } } /// Converts a u64 value into a [`RowIndex`]. /// -/// # Panics +/// # Errors /// -/// This function will panic if the number represented by the u64 is greater than the maximum -/// [`RowIndex`] value, `u32::MAX`. +/// This function returns an error if the number represented by the u64 is greater than the +/// maximum [`RowIndex`] value, [`u32::MAX`]. impl TryFrom for RowIndex { - type Error = RowIndexError; + type Error = RowIndexError; fn try_from(value: u64) -> Result { - let value = u32::try_from(value).map_err(|_| RowIndexError::InvalidSize(value))?; + let value = u32::try_from(value) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_u64", value).into()))?; Ok(RowIndex::from(value)) } } @@ -107,7 +112,9 @@ impl From for RowIndex { /// This function will panic if the number represented by the i32 is less than 0. impl From for RowIndex { fn from(value: i32) -> Self { - let value = u32::try_from(value).map_err(|_| RowIndexError::InvalidSize(value)).unwrap(); + let value = u32::try_from(value) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_i32", value).into())) + .unwrap(); RowIndex(value) } } @@ -125,7 +132,9 @@ impl Sub for RowIndex { type Output = RowIndex; fn sub(self, rhs: usize) -> Self::Output { - let rhs = u32::try_from(rhs).map_err(|_| RowIndexError::InvalidSize(rhs)).unwrap(); + let rhs = u32::try_from(rhs) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_usize", rhs).into())) + .unwrap(); RowIndex(self.0 - rhs) } } @@ -164,7 +173,9 @@ impl Add for RowIndex { type Output = RowIndex; fn add(self, rhs: usize) -> Self::Output { - let rhs = u32::try_from(rhs).map_err(|_| RowIndexError::InvalidSize(rhs)).unwrap(); + let rhs = u32::try_from(rhs) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_usize", rhs).into())) + .unwrap(); RowIndex(self.0 + rhs) } } @@ -209,7 +220,10 @@ impl PartialEq for RowIndex { impl PartialEq for RowIndex { fn eq(&self, rhs: &usize) -> bool { - self.0 == u32::try_from(*rhs).map_err(|_| RowIndexError::InvalidSize(*rhs)).unwrap() + self.0 + == u32::try_from(*rhs) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_usize", *rhs).into())) + .unwrap() } } @@ -221,7 +235,9 @@ impl PartialEq for i32 { impl PartialOrd for RowIndex { fn partial_cmp(&self, rhs: &usize) -> Option { - let rhs = u32::try_from(*rhs).map_err(|_| RowIndexError::InvalidSize(*rhs)).unwrap(); + let rhs = u32::try_from(*rhs) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_usize", *rhs).into())) + .unwrap(); self.0.partial_cmp(&rhs) } } diff --git a/assembly/Cargo.toml b/assembly/Cargo.toml index e72f780fb6..86276f8f46 100644 --- a/assembly/Cargo.toml +++ b/assembly/Cargo.toml @@ -19,19 +19,19 @@ doctest = false [features] default = ["std"] -std = ["aho-corasick/std", "miette/fancy", "miette/std", "thiserror/std", "vm-core/std"] +std = ["aho-corasick/std", "miette/fancy", "miette/std", "vm-core/std", "thiserror/std"] testing = ["dep:regex"] [dependencies] aho-corasick = { version = "1.1", default-features = false } lalrpop-util = { version = "0.20", default-features = false } -miette = { package = "miden-miette", version = "7.1", default-features = false, features = [ +miette = { package = "miden-miette", version = "8.0", default-features = false, features = [ "fancy-no-syscall", "derive" ] } regex = { version = "1.10", optional = true, default-features = false, features = ["unicode", "perf"] } smallvec = { version = "1.13", features = ["union", "const_generics", "const_new"] } -thiserror = { package = "miden-thiserror", version = "1.0", default-features = false } +thiserror = { workspace = true } tracing = { version = "0.1", default-features = false, features = ["attributes"] } unicode-width = { version = "0.2", features = ["no_std"] } vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false, features = [ diff --git a/assembly/src/assembler/mast_forest_builder.rs b/assembly/src/assembler/mast_forest_builder.rs index 96e7d51b52..0a17bd07c7 100644 --- a/assembly/src/assembler/mast_forest_builder.rs +++ b/assembly/src/assembler/mast_forest_builder.rs @@ -347,7 +347,9 @@ impl MastForestBuilder { // decorator already exists in the forest; return previously assigned id Ok(*decorator_id) } else { - let new_decorator_id = self.mast_forest.add_decorator(decorator)?; + let new_decorator_id = self.mast_forest.add_decorator(decorator).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new decorator", source) + })?; self.decorator_id_by_fingerprint.insert(decorator_hash, new_decorator_id); Ok(new_decorator_id) @@ -366,7 +368,9 @@ impl MastForestBuilder { // node already exists in the forest; return previously assigned id Ok(*node_id) } else { - let new_node_id = self.mast_forest.add_node(node)?; + let new_node_id = self.mast_forest.add_node(node).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new node", source) + })?; self.node_id_by_fingerprint.insert(node_fingerprint, new_node_id); self.hash_by_node_id.insert(new_node_id, node_fingerprint); @@ -380,7 +384,9 @@ impl MastForestBuilder { operations: Vec, decorators: Option, ) -> Result { - let block = MastNode::new_basic_block(operations, decorators)?; + let block = MastNode::new_basic_block(operations, decorators).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new basic block node", source) + })?; self.ensure_node(block) } @@ -390,7 +396,10 @@ impl MastForestBuilder { left_child: MastNodeId, right_child: MastNodeId, ) -> Result { - let join = MastNode::new_join(left_child, right_child, &self.mast_forest)?; + let join = + MastNode::new_join(left_child, right_child, &self.mast_forest).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new join node", source) + })?; self.ensure_node(join) } @@ -400,25 +409,34 @@ impl MastForestBuilder { if_branch: MastNodeId, else_branch: MastNodeId, ) -> Result { - let split = MastNode::new_split(if_branch, else_branch, &self.mast_forest)?; + let split = + MastNode::new_split(if_branch, else_branch, &self.mast_forest).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new split node", source) + })?; self.ensure_node(split) } /// Adds a loop node to the forest, and returns the [`MastNodeId`] associated with it. pub fn ensure_loop(&mut self, body: MastNodeId) -> Result { - let loop_node = MastNode::new_loop(body, &self.mast_forest)?; + let loop_node = MastNode::new_loop(body, &self.mast_forest).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new loop node", source) + })?; self.ensure_node(loop_node) } /// Adds a call node to the forest, and returns the [`MastNodeId`] associated with it. pub fn ensure_call(&mut self, callee: MastNodeId) -> Result { - let call = MastNode::new_call(callee, &self.mast_forest)?; + let call = MastNode::new_call(callee, &self.mast_forest).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new call node", source) + })?; self.ensure_node(call) } /// Adds a syscall node to the forest, and returns the [`MastNodeId`] associated with it. pub fn ensure_syscall(&mut self, callee: MastNodeId) -> Result { - let syscall = MastNode::new_syscall(callee, &self.mast_forest)?; + let syscall = MastNode::new_syscall(callee, &self.mast_forest).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new syscall node", source) + })?; self.ensure_node(syscall) } diff --git a/assembly/src/ast/ident.rs b/assembly/src/ast/ident.rs index ea2d13a238..399f63a18f 100644 --- a/assembly/src/ast/ident.rs +++ b/assembly/src/ast/ident.rs @@ -8,7 +8,7 @@ use core::{ use crate::{SourceSpan, Span, Spanned}; /// Represents the types of errors that can occur when parsing/validating an [Ident] -#[derive(Debug, thiserror::Error, PartialEq, Eq)] +#[derive(Debug, thiserror::Error)] pub enum IdentError { #[error("invalid identifier: cannot be empty")] Empty, @@ -24,7 +24,7 @@ pub enum IdentError { /// Represents the various types of casing errors that can occur, e.g. using an identifier /// with `SCREAMING_CASE` where one with `snake_case` is expected. -#[derive(Debug, thiserror::Error, PartialEq, Eq)] +#[derive(Debug, thiserror::Error)] pub enum CaseKindError { #[error("only uppercase characters or underscores are allowed, and must start with an alphabetic character")] Screaming, diff --git a/assembly/src/errors.rs b/assembly/src/errors.rs index 4fd06609ef..bee90c05f1 100644 --- a/assembly/src/errors.rs +++ b/assembly/src/errors.rs @@ -4,7 +4,7 @@ use vm_core::mast::MastForestError; use crate::{ ast::QualifiedProcedureName, - diagnostics::{Diagnostic, RelatedError, RelatedLabel, Report, SourceFile}, + diagnostics::{Diagnostic, RelatedError, RelatedLabel, SourceFile}, LibraryNamespace, LibraryPath, SourceSpan, }; @@ -81,13 +81,16 @@ pub enum AssemblyError { }, #[error(transparent)] #[diagnostic(transparent)] - Other(#[from] RelatedError), - #[error(transparent)] - Forest(#[from] MastForestError), + Other(RelatedError), + // Technically MastForestError is the source error here, but since AssemblyError is converted + // into a Report and that doesn't implement core::error::Error, treating MastForestError as a + // source error would effectively swallow it, so we include it in the error message instead. + #[error("{0}: {1}")] + Forest(&'static str, MastForestError), } -impl From for AssemblyError { - fn from(report: Report) -> Self { - Self::Other(RelatedError::new(report)) +impl AssemblyError { + pub(super) fn forest_error(message: &'static str, source: MastForestError) -> Self { + Self::Forest(message, source) } } diff --git a/assembly/src/library/error.rs b/assembly/src/library/error.rs index 3df795ca3a..e5158188e4 100644 --- a/assembly/src/library/error.rs +++ b/assembly/src/library/error.rs @@ -9,8 +9,12 @@ pub enum LibraryError { EmptyKernel, #[error("invalid export in kernel library: {procedure_path}")] InvalidKernelExport { procedure_path: QualifiedProcedureName }, - #[error(transparent)] - Kernel(#[from] KernelError), + // Technically KernelError is the source error here, but since LibraryError is sometimes + // converted into a Report and that doesn't implement core::error::Error, treating + // KernelError as a source error would effectively swallow it, so we include it in the + // error message instead. + #[error("failed to convert library into kernel library: {0}")] + KernelConversion(KernelError), #[error("invalid export: no procedure root for {procedure_path} procedure")] NoProcedureRootForExport { procedure_path: QualifiedProcedureName }, } diff --git a/assembly/src/library/mod.rs b/assembly/src/library/mod.rs index 7e2d35789a..8f42223c22 100644 --- a/assembly/src/library/mod.rs +++ b/assembly/src/library/mod.rs @@ -375,7 +375,7 @@ impl TryFrom for KernelLibrary { kernel_module.add_procedure(proc_path.name.clone(), proc_digest); } - let kernel = Kernel::new(&proc_digests)?; + let kernel = Kernel::new(&proc_digests).map_err(LibraryError::KernelConversion)?; Ok(Self { kernel, diff --git a/assembly/src/library/namespace.rs b/assembly/src/library/namespace.rs index 529718264c..e9a41e191e 100644 --- a/assembly/src/library/namespace.rs +++ b/assembly/src/library/namespace.rs @@ -13,9 +13,9 @@ use crate::{ // ================================================================================================ /// Represents an error when parsing or validating a library namespace -#[derive(Debug, thiserror::Error, Diagnostic, PartialEq, Eq)] +#[derive(Debug, thiserror::Error, Diagnostic)] pub enum LibraryNamespaceError { - #[error("invalid library namespace name: cannot be a empty")] + #[error("invalid library namespace name: cannot be empty")] #[diagnostic()] Empty, #[error("invalid library namespace name: too many characters")] diff --git a/assembly/src/library/path.rs b/assembly/src/library/path.rs index 915bc9aaad..4c8f110072 100644 --- a/assembly/src/library/path.rs +++ b/assembly/src/library/path.rs @@ -18,18 +18,18 @@ use crate::{ }; /// Represents errors that can occur when creating, parsing, or manipulating [LibraryPath]s -#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[derive(Debug, thiserror::Error)] pub enum PathError { #[error("invalid library path: cannot be empty")] Empty, #[error("invalid library path component: cannot be empty")] EmptyComponent, #[error("invalid library path component: {0}")] - InvalidComponent(#[from] crate::ast::IdentError), + InvalidComponent(crate::ast::IdentError), #[error("invalid library path: contains invalid utf8 byte sequences")] InvalidUtf8, #[error(transparent)] - InvalidNamespace(#[from] crate::library::LibraryNamespaceError), + InvalidNamespace(crate::library::LibraryNamespaceError), #[error("cannot join a path with reserved name to other paths")] UnsupportedJoin, } @@ -429,7 +429,9 @@ impl<'a> TryFrom>> for LibraryPath { let ns = match iter.next() { None => return Err(PathError::Empty), Some(LibraryPathComponent::Namespace(ns)) => ns.clone(), - Some(LibraryPathComponent::Normal(ident)) => LibraryNamespace::try_from(ident.clone())?, + Some(LibraryPathComponent::Normal(ident)) => { + LibraryNamespace::try_from(ident.clone()).map_err(PathError::InvalidNamespace)? + }, }; let mut components = Components::default(); for component in iter { diff --git a/assembly/src/parser/error.rs b/assembly/src/parser/error.rs index 4b0fd1bf2b..1f6278b58e 100644 --- a/assembly/src/parser/error.rs +++ b/assembly/src/parser/error.rs @@ -91,7 +91,7 @@ impl fmt::Display for BinErrorKind { // PARSING ERROR // ================================================================================================ -#[derive(Debug, Default, Clone, thiserror::Error, Diagnostic)] +#[derive(Debug, Default, thiserror::Error, Diagnostic)] #[repr(u8)] pub enum ParsingError { #[default] diff --git a/core/Cargo.toml b/core/Cargo.toml index 8643a81ba9..e840b429b5 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -36,14 +36,14 @@ math = { package = "winter-math", version = "0.11", default-features = false } memchr = { version = "2.7", default-features = false } miden-crypto = { version = "0.13", default-features = false } miden-formatting = { version = "0.1", default-features = false } -miette = { package = "miden-miette", version = "7.1", default-features = false, optional = true, features = [ +miette = { package = "miden-miette", version = "8.0", default-features = false, optional = true, features = [ "fancy-no-syscall", "derive" ] } num-derive = { version = "0.4", default-features = false } num-traits = { version = "0.2", default-features = false } parking_lot = { version = "0.12", optional = true } -thiserror = { package = "miden-thiserror", version = "1.0", default-features = false } +thiserror = { workspace = true } winter-utils = { package = "winter-utils", version = "0.11", default-features = false } [dev-dependencies] diff --git a/core/src/debuginfo/source_manager.rs b/core/src/debuginfo/source_manager.rs index e88e96d3dd..5a71fcb08b 100644 --- a/core/src/debuginfo/source_manager.rs +++ b/core/src/debuginfo/source_manager.rs @@ -1,9 +1,11 @@ use alloc::{ + boxed::Box, collections::BTreeMap, string::{String, ToString}, sync::Arc, vec::Vec, }; +use core::error::Error; use super::*; @@ -69,7 +71,6 @@ impl TryFrom for SourceId { /// The set of errors which may be raised by a [SourceManager] #[derive(Debug, thiserror::Error)] -#[non_exhaustive] pub enum SourceManagerError { /// A [SourceId] was provided to a [SourceManager] which was allocated by a different /// [SourceManager] @@ -78,10 +79,26 @@ pub enum SourceManagerError { /// An attempt was made to read content using invalid byte indices #[error("attempted to read content out of bounds")] InvalidBounds, - /// An attempt to load a source file failed due to an I/O error - #[cfg(feature = "std")] - #[error(transparent)] - LoadFailed(#[from] std::io::Error), + /// Custom error variant for implementors of the trait. + #[error("{error_msg}")] + Custom { + error_msg: Box, + // thiserror will return this when calling Error::source on SourceManagerError. + source: Option>, + }, +} + +impl SourceManagerError { + pub fn custom(message: String) -> Self { + Self::Custom { error_msg: message.into(), source: None } + } + + pub fn custom_with_source(message: String, source: impl Error + Send + Sync + 'static) -> Self { + Self::Custom { + error_msg: message.into(), + source: Some(Box::new(source)), + } + } } pub trait SourceManager { @@ -201,7 +218,12 @@ pub trait SourceManagerExt: SourceManager { let name = Arc::from(name.into_owned().into_boxed_str()); let content = std::fs::read_to_string(path) .map(|s| SourceContent::new(Arc::clone(&name), s.into_boxed_str())) - .map_err(SourceManagerError::LoadFailed)?; + .map_err(|source| { + SourceManagerError::custom_with_source( + format!("failed to load filed at `{}`", path.display()), + source, + ) + })?; Ok(self.load_from_raw_parts(name, content)) } @@ -367,3 +389,15 @@ impl SourceManager for DefaultSourceManager { .ok_or(SourceManagerError::InvalidBounds) } } + +#[cfg(test)] +mod error_assertions { + use super::*; + + /// Asserts at compile time that the passed error has Send + Sync + 'static bounds. + fn _assert_error_is_send_sync_static(_: E) {} + + fn _assert_source_manager_error_bounds(err: SourceManagerError) { + _assert_error_is_send_sync_static(err); + } +} diff --git a/core/src/mast/mod.rs b/core/src/mast/mod.rs index 142c50b48d..1d8af06abe 100644 --- a/core/src/mast/mod.rs +++ b/core/src/mast/mod.rs @@ -695,24 +695,18 @@ impl Serializable for DecoratorId { /// Represents the types of errors that can occur when dealing with MAST forest. #[derive(Debug, thiserror::Error, PartialEq)] pub enum MastForestError { - #[error( - "invalid decorator count: MAST forest exceeds the maximum of {} decorators", - u32::MAX - )] + #[error("MAST forest decorator count exceeds the maximum of {} decorators", u32::MAX)] TooManyDecorators, - #[error( - "invalid node count: MAST forest exceeds the maximum of {} nodes", - MastForest::MAX_NODES - )] + #[error("MAST forest node count exceeds the maximum of {} nodes", MastForest::MAX_NODES)] TooManyNodes, - #[error("node id: {0} is greater than or equal to forest length: {1}")] + #[error("node id {0} is greater than or equal to forest length {1}")] NodeIdOverflow(MastNodeId, usize), - #[error("decorator id: {0} is greater than or equal to decorator count: {1}")] + #[error("decorator id {0} is greater than or equal to decorator count {1}")] DecoratorIdOverflow(DecoratorId, usize), #[error("basic block cannot be created from an empty list of operations")] EmptyBasicBlock, - #[error("decorator root of child with node id {0} is missing but required for fingerprint computation")] + #[error("decorator root of child with node id {0} is missing but is required for fingerprint computation")] ChildFingerprintMissing(MastNodeId), - #[error("advice map key already exists when merging forests: {0}")] + #[error("advice map key {0} already exists when merging forests")] AdviceMapKeyCollisionOnMerge(RpoDigest), } diff --git a/processor/Cargo.toml b/processor/Cargo.toml index cd52ae38a9..a2867798d3 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -20,7 +20,7 @@ doctest = false [features] concurrent = ["std", "winter-prover/concurrent"] default = ["std"] -std = ["vm-core/std", "winter-prover/std"] +std = ["vm-core/std", "winter-prover/std", "thiserror/std"] testing = ["miden-air/testing"] [dependencies] @@ -28,6 +28,7 @@ miden-air = { package = "miden-air", path = "../air", version = "0.11", default- tracing = { version = "0.1", default-features = false, features = ["attributes"] } vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false } winter-prover = { package = "winter-prover", version = "0.11", default-features = false } +thiserror = { workspace = true } [dev-dependencies] assembly = { package = "miden-assembly", path = "../assembly", version = "0.11", default-features = false } diff --git a/processor/src/errors.rs b/processor/src/errors.rs index b5c77f86e9..61d7319300 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -1,7 +1,5 @@ -use alloc::string::String; -use core::fmt::{Display, Formatter}; -#[cfg(feature = "std")] -use std::error::Error; +use alloc::{boxed::Box, string::String}; +use core::error::Error; use miden_air::RowIndex; use vm_core::{ @@ -21,223 +19,118 @@ use crate::ContextId; // EXECUTION ERROR // ================================================================================================ -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum ExecutionError { + #[error("value for key {} not present in the advice map", to_hex(Felt::elements_as_bytes(.0)))] AdviceMapKeyNotFound(Word), + #[error("value for key {} already present in the advice map", to_hex(Felt::elements_as_bytes(.0)))] AdviceMapKeyAlreadyPresent(Word), + #[error("advice stack read failed at step {0}")] AdviceStackReadFailed(RowIndex), + #[error("instruction `caller` used outside of kernel context")] CallerNotInSyscall, + #[error("external node with mast root {0} resolved to an external node")] CircularExternalNode(Digest), + #[error("exceeded the allowed number of max cycles {0}")] CycleLimitExceeded(u32), - DecoratorNotFoundInForest { - decorator_id: DecoratorId, - }, + #[error("decorator id {decorator_id} does not exist in MAST forest")] + DecoratorNotFoundInForest { decorator_id: DecoratorId }, + #[error("division by zero at clock cycle {0}")] DivideByZero(RowIndex), - DuplicateMemoryAccess { - ctx: ContextId, - addr: u32, - clk: Felt, - }, + #[error("memory address {addr} in context {ctx} accessed twice in clock cycle {clk}")] + DuplicateMemoryAccess { ctx: ContextId, addr: u32, clk: Felt }, + #[error("failed to execute the dynamic code block provided by the stack with root {hex}; the block could not be found", + hex = to_hex(.0.as_bytes()) + )] DynamicNodeNotFound(Digest), - EventError(String), + #[error("error during processing of event in on_event handler")] + EventError(#[source] Box), + #[error("failed to execute Ext2Intt operation: {0}")] Ext2InttError(Ext2InttError), + #[error("assertion failed at clock cycle {clk} with error code {err_code}{}", + match err_msg { + Some(msg) => format!(": {msg}"), + None => "".into() + } + )] FailedAssertion { clk: RowIndex, err_code: u32, err_msg: Option, }, + #[error("failed to generate signature: {0}")] FailedSignatureGeneration(&'static str), + #[error("Updating FMP register from {0} to {1} failed because {1} is outside of {FMP_MIN}..{FMP_MAX}")] InvalidFmpValue(Felt, Felt), + #[error("FRI domain segment value cannot exceed 3, but was {0}")] InvalidFriDomainSegment(u64), + #[error("degree-respecting projection is inconsistent: expected {0} but was {1}")] InvalidFriLayerFolding(QuadFelt, QuadFelt), - InvalidMemoryRange { - start_addr: u64, - end_addr: u64, - }, + #[error( + "memory range start address cannot exceed end address, but was ({start_addr}, {end_addr})" + )] + InvalidMemoryRange { start_addr: u64, end_addr: u64 }, + #[error("when returning from a call, stack depth must be {MIN_STACK_DEPTH}, but was {0}")] InvalidStackDepthOnReturn(usize), - InvalidTreeDepth { - depth: Felt, - }, - InvalidTreeNodeIndex { - depth: Felt, - value: Felt, - }, + #[error("provided merkle tree {depth} is out of bounds and cannot be represented as an unsigned 8-bit integer")] + InvalidMerkleTreeDepth { depth: Felt }, + #[error( + "provided node index {value} is out of bounds for a merkle tree node at depth {depth}" + )] + InvalidMerkleTreeNodeIndex { depth: Felt, value: Felt }, + #[error("attempted to calculate integer logarithm with zero argument at clock cycle {0}")] LogArgumentZero(RowIndex), + #[error("malformed signature key: {0}")] MalformedSignatureKey(&'static str), - MalformedMastForestInHost { - root_digest: Digest, - }, - MastNodeNotFoundInForest { - node_id: MastNodeId, - }, - MastForestNotFound { - root_digest: Digest, - }, + #[error( + "MAST forest in host indexed by procedure root {root_digest} doesn't contain that root" + )] + MalformedMastForestInHost { root_digest: Digest }, + #[error("node id {node_id} does not exist in MAST forest")] + MastNodeNotFoundInForest { node_id: MastNodeId }, + #[error("no MAST forest contains the procedure with root digest {root_digest}")] + NoMastForestWithProcedure { root_digest: Digest }, + #[error("memory address cannot exceed 2^32 but was {0}")] MemoryAddressOutOfBounds(u64), + #[error("merkle path verification failed for value {value} at index {index} in the Merkle tree with root {root} (error code: {err_code})", + value = to_hex(Felt::elements_as_bytes(value)), + root = to_hex(root.as_bytes()), + )] MerklePathVerificationFailed { value: Word, index: Felt, root: Digest, err_code: u32, }, - MerkleStoreLookupFailed(MerkleError), - MerkleStoreMergeFailed(MerkleError), - MerkleStoreUpdateFailed(MerkleError), + #[error("advice provider Merkle store backend lookup failed")] + MerkleStoreLookupFailed(#[source] MerkleError), + #[error("advice provider Merkle store backend merge failed")] + MerkleStoreMergeFailed(#[source] MerkleError), + #[error("advice provider Merkle store backend update failed")] + MerkleStoreUpdateFailed(#[source] MerkleError), + #[error("an operation expected a binary value, but received {0}")] NotBinaryValue(Felt), + #[error("an operation expected a u32 value, but received {0} (error code: {1})")] NotU32Value(Felt, Felt), + #[error("stack should have at most {MIN_STACK_DEPTH} elements at the end of program execution, but had {} elements", MIN_STACK_DEPTH + .0)] OutputStackOverflow(usize), + #[error("a program has already been executed in this process")] ProgramAlreadyExecuted, - ProverError(ProverError), + #[error("proof generation failed")] + ProverError(#[source] ProverError), + #[error("smt node {node_hex} not found", node_hex = to_hex(Felt::elements_as_bytes(.0)))] SmtNodeNotFound(Word), + #[error("expected pre-image length of node {node_hex} to be a multiple of 8 but was {preimage_len}", + node_hex = to_hex(Felt::elements_as_bytes(.0)), + preimage_len = .1 + )] SmtNodePreImageNotValid(Word, usize), + #[error("syscall failed: procedure with root {hex} was not found in the kernel", + hex = to_hex(.0.as_bytes()) + )] SyscallTargetNotInKernel(Digest), } -impl Display for ExecutionError { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> { - use ExecutionError::*; - - match self { - AdviceMapKeyNotFound(key) => { - let hex = to_hex(Felt::elements_as_bytes(key)); - write!(f, "Value for key {hex} not present in the advice map") - }, - AdviceMapKeyAlreadyPresent(key) => { - let hex = to_hex(Felt::elements_as_bytes(key)); - write!(f, "Value for key {hex} already present in the advice map") - }, - AdviceStackReadFailed(step) => write!(f, "Advice stack read failed at step {step}"), - CallerNotInSyscall => { - write!(f, "Instruction `caller` used outside of kernel context") - }, - CircularExternalNode(mast_root) => { - write!(f, "External node with root {mast_root} resolved to an external node") - }, - CycleLimitExceeded(max_cycles) => { - write!(f, "Exceeded the allowed number of cycles (max cycles = {max_cycles})") - }, - DecoratorNotFoundInForest { decorator_id } => { - write!(f, "Malformed MAST forest, decorator id {decorator_id} doesn't exist") - }, - DivideByZero(clk) => write!(f, "Division by zero at clock cycle {clk}"), - DuplicateMemoryAccess { ctx, addr, clk } => write!( - f, - "Memory address {addr} in context {ctx} accessed twice in clock cycle {clk}" - ), - DynamicNodeNotFound(digest) => { - let hex = to_hex(digest.as_bytes()); - write!( - f, - "Failed to execute the dynamic code block provided by the stack with root {hex}; the block could not be found" - ) - }, - EventError(error) => write!(f, "Failed to process event - {error}"), - Ext2InttError(err) => write!(f, "Failed to execute Ext2Intt operation: {err}"), - FailedAssertion { clk, err_code, err_msg } => { - if let Some(err_msg) = err_msg { - write!( - f, - "Assertion failed at clock cycle {clk} with error code {err_code}: {err_msg}" - ) - } else { - write!(f, "Assertion failed at clock cycle {clk} with error code {err_code}") - } - }, - FailedSignatureGeneration(signature) => { - write!(f, "Failed to generate signature: {signature}") - }, - InvalidFmpValue(old, new) => { - write!(f, "Updating FMP register from {old} to {new} failed because {new} is outside of {FMP_MIN}..{FMP_MAX}") - }, - InvalidFriDomainSegment(value) => { - write!(f, "FRI domain segment value cannot exceed 3, but was {value}") - }, - InvalidFriLayerFolding(expected, actual) => { - write!(f, "Degree-respecting projection is inconsistent: expected {expected} but was {actual}") - }, - InvalidMemoryRange { start_addr, end_addr } => { - write!(f, "Memory range start address cannot exceed end address, but was ({start_addr}, {end_addr})") - }, - InvalidStackDepthOnReturn(depth) => { - write!(f, "When returning from a call, stack depth must be {MIN_STACK_DEPTH}, but was {depth}") - }, - InvalidTreeDepth { depth } => { - write!(f, "The provided {depth} is out of bounds and cannot be represented as an unsigned 8-bits integer") - }, - InvalidTreeNodeIndex { depth, value } => { - write!(f, "The provided index {value} is out of bounds for a node at depth {depth}") - }, - LogArgumentZero(clk) => { - write!( - f, - "Calculating of the integer logarithm with zero argument at clock cycle {clk}" - ) - }, - MalformedSignatureKey(signature) => write!(f, "Malformed signature key: {signature}"), - MalformedMastForestInHost { root_digest } => { - write!(f, "Malformed host: MAST forest indexed by procedure root {} doesn't contain that root", root_digest) - }, - MastNodeNotFoundInForest { node_id } => { - write!(f, "Malformed MAST forest, node id {node_id} doesn't exist") - }, - MastForestNotFound { root_digest } => { - write!( - f, - "No MAST forest contains the following procedure root digest: {root_digest}" - ) - }, - MemoryAddressOutOfBounds(addr) => { - write!(f, "Memory address cannot exceed 2^32 but was {addr}") - }, - 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} (error code: {err_code})") - }, - MerkleStoreLookupFailed(reason) => { - write!(f, "Advice provider Merkle store backend lookup failed: {reason}") - }, - MerkleStoreMergeFailed(reason) => { - write!(f, "Advice provider Merkle store backend merge failed: {reason}") - }, - MerkleStoreUpdateFailed(reason) => { - write!(f, "Advice provider Merkle store backend update failed: {reason}") - }, - NotBinaryValue(v) => { - write!(f, "An operation expected a binary value, but received {v}") - }, - NotU32Value(v, err_code) => { - write!( - f, - "An operation expected a u32 value, but received {v} (error code: {err_code})" - ) - }, - OutputStackOverflow(n) => { - write!(f, "The stack should have at most {MIN_STACK_DEPTH} elements at the end of program execution, but had {} elements", MIN_STACK_DEPTH + n) - }, - SmtNodeNotFound(node) => { - let node_hex = to_hex(Felt::elements_as_bytes(node)); - write!(f, "Smt node {node_hex} not found") - }, - SmtNodePreImageNotValid(node, preimage_len) => { - let node_hex = to_hex(Felt::elements_as_bytes(node)); - write!(f, "Invalid pre-image for node {node_hex}. Expected pre-image length to be a multiple of 8, but was {preimage_len}") - }, - ProgramAlreadyExecuted => { - write!(f, "a program has already been executed in this process") - }, - ProverError(error) => write!(f, "Proof generation failed: {error}"), - SyscallTargetNotInKernel(proc) => { - let hex = to_hex(proc.as_bytes()); - write!(f, "Syscall failed: procedure with root {hex} was not found in the kernel") - }, - } - } -} - -#[cfg(feature = "std")] -impl Error for ExecutionError {} - impl From for ExecutionError { fn from(value: Ext2InttError) -> Self { Self::Ext2InttError(value) @@ -247,54 +140,34 @@ impl From for ExecutionError { // EXT2INTT ERROR // ================================================================================================ -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, thiserror::Error)] pub enum Ext2InttError { + #[error("input domain size must be a power of two, but was {0}")] DomainSizeNotPowerOf2(u64), + #[error("input domain size ({0} elements) is too small")] DomainSizeTooSmall(u64), + #[error("address of the last input must be smaller than 2^32, but was {0}")] InputEndAddressTooBig(u64), + #[error("input size must be smaller than 2^32, but was {0}")] InputSizeTooBig(u64), + #[error("address of the first input must be smaller than 2^32, but was {0}")] InputStartAddressTooBig(u64), + #[error("output size ({0}) cannot be greater than the input size ({1})")] OutputSizeTooBig(usize, usize), + #[error("output size must be greater than 0")] OutputSizeIsZero, + #[error("uninitialized memory at address {0}")] UninitializedMemoryAddress(u32), } -impl Display for Ext2InttError { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> { - use Ext2InttError::*; +#[cfg(test)] +mod error_assertions { + use super::*; - match self { - DomainSizeNotPowerOf2(v) => { - write!(f, "input domain size must be a power of two, but was {v}") - }, - DomainSizeTooSmall(v) => { - write!(f, "input domain size ({v} elements) is too small") - }, - InputEndAddressTooBig(addr) => { - write!(f, "address of the last input must be smaller than 2^32, but was {addr}") - }, - InputSizeTooBig(size) => { - write!(f, "input size must be smaller than 2^32, but was {size}") - }, - InputStartAddressTooBig(addr) => { - write!(f, "address of the first input must be smaller than 2^32, but was {addr}") - }, - OutputSizeIsZero => { - write!(f, "output size must be greater than 0") - }, - OutputSizeTooBig(output_size, input_size) => { - write!( - f, - "output size ({output_size}) cannot be greater than the input size ({input_size})" - ) - }, + /// Asserts at compile time that the passed error has Send + Sync + 'static bounds. + fn _assert_error_is_send_sync_static(_: E) {} - UninitializedMemoryAddress(address) => { - write!(f, "uninitialized memory at address {address}") - }, - } + fn _assert_execution_error_bounds(err: ExecutionError) { + _assert_error_is_send_sync_static(err); } } - -#[cfg(feature = "std")] -impl Error for Ext2InttError {} diff --git a/processor/src/host/advice/providers.rs b/processor/src/host/advice/providers.rs index 6edcb85ad1..bf80b7575f 100644 --- a/processor/src/host/advice/providers.rs +++ b/processor/src/host/advice/providers.rs @@ -127,8 +127,9 @@ where depth: &Felt, index: &Felt, ) -> Result { - let index = NodeIndex::from_elements(depth, index) - .map_err(|_| ExecutionError::InvalidTreeNodeIndex { depth: *depth, value: *index })?; + let index = NodeIndex::from_elements(depth, index).map_err(|_| { + ExecutionError::InvalidMerkleTreeNodeIndex { depth: *depth, value: *index } + })?; self.store .get_node(root.into(), index) .map(|v| v.into()) @@ -141,8 +142,9 @@ where depth: &Felt, index: &Felt, ) -> Result { - let index = NodeIndex::from_elements(depth, index) - .map_err(|_| ExecutionError::InvalidTreeNodeIndex { depth: *depth, value: *index })?; + let index = NodeIndex::from_elements(depth, index).map_err(|_| { + ExecutionError::InvalidMerkleTreeNodeIndex { depth: *depth, value: *index } + })?; self.store .get_path(root.into(), index) .map(|value| value.path) @@ -156,7 +158,7 @@ where index: &Felt, ) -> Result { let tree_depth = u8::try_from(tree_depth.as_int()) - .map_err(|_| ExecutionError::InvalidTreeDepth { depth: *tree_depth })?; + .map_err(|_| ExecutionError::InvalidMerkleTreeDepth { depth: *tree_depth })?; self.store .get_leaf_depth(root.into(), tree_depth, index.as_int()) .map_err(ExecutionError::MerkleStoreLookupFailed) @@ -169,8 +171,9 @@ where index: &Felt, value: Word, ) -> Result<(MerklePath, Word), ExecutionError> { - let node_index = NodeIndex::from_elements(depth, index) - .map_err(|_| ExecutionError::InvalidTreeNodeIndex { depth: *depth, value: *index })?; + let node_index = NodeIndex::from_elements(depth, index).map_err(|_| { + ExecutionError::InvalidMerkleTreeNodeIndex { depth: *depth, value: *index } + })?; self.store .set_node(root.into(), node_index, value.into()) .map(|root| (root.path, root.root.into())) diff --git a/processor/src/lib.rs b/processor/src/lib.rs index cf0a80bc9c..2f186fd728 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -278,9 +278,9 @@ impl Process { MastNode::Dyn(node) => self.execute_dyn_node(node, program, host)?, MastNode::External(external_node) => { let node_digest = external_node.digest(); - let mast_forest = host - .get_mast_forest(&node_digest) - .ok_or(ExecutionError::MastForestNotFound { root_digest: node_digest })?; + let mast_forest = host.get_mast_forest(&node_digest).ok_or( + ExecutionError::NoMastForestWithProcedure { root_digest: node_digest }, + )?; // We limit the parts of the program that can be called externally to procedure // roots, even though MAST doesn't have that restriction. diff --git a/verifier/Cargo.toml b/verifier/Cargo.toml index a91742fd3c..b83739580a 100644 --- a/verifier/Cargo.toml +++ b/verifier/Cargo.toml @@ -19,10 +19,11 @@ doctest = false [features] default = ["std"] -std = ["air/std", "vm-core/std", "winter-verifier/std"] +std = ["air/std", "vm-core/std", "winter-verifier/std", "thiserror/std"] [dependencies] air = { package = "miden-air", path = "../air", version = "0.11", default-features = false } tracing = { version = "0.1", default-features = false, features = ["attributes"] } vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false } winter-verifier = { package = "winter-verifier", version = "0.11", default-features = false } +thiserror = { workspace = true } diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index 0ccf8fda2e..03962b6070 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -6,7 +6,6 @@ extern crate alloc; extern crate std; use alloc::vec; -use core::fmt; use air::{HashFunction, ProcessorAir, ProvingOptions, PublicInputs}; use vm_core::crypto::{ @@ -62,6 +61,7 @@ pub fn verify( ) -> Result { // get security level of the proof let security_level = proof.security_level(); + let program_hash = *program_info.program_hash(); // build public inputs and try to verify the proof let pub_inputs = PublicInputs::new(program_info, stack_inputs, stack_outputs); @@ -98,7 +98,7 @@ pub fn verify( ) }, } - .map_err(VerificationError::VerifierError)?; + .map_err(|source| VerificationError::ProgramVerificationError(program_hash, source))?; Ok(security_level) } @@ -107,23 +107,12 @@ pub fn verify( // ================================================================================================ /// TODO: add docs -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, thiserror::Error)] pub enum VerificationError { - VerifierError(VerifierError), + #[error("failed to verify proof for program with hash {0}")] + ProgramVerificationError(Digest, #[source] VerifierError), + #[error("the input {0} is not a valid field element")] InputNotFieldElement(u64), + #[error("the output {0} is not a valid field element")] OutputNotFieldElement(u64), } - -impl fmt::Display for VerificationError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use VerificationError::*; - match self { - VerifierError(e) => write!(f, "{e}"), - InputNotFieldElement(i) => write!(f, "the input {i} is not a valid field element!"), - OutputNotFieldElement(o) => write!(f, "the output {o} is not a valid field element!"), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for VerificationError {} From 21da03da2d31ae49b9b5d9ac2e7c552c20ed1447 Mon Sep 17 00:00:00 2001 From: Marco Stronati Date: Mon, 16 Dec 2024 23:09:43 +0100 Subject: [PATCH 33/39] CLI bundle: add debug and kernel options. fixes #1447 (#1597) * cli: bundle add debug option * assembly: rename error EmptyKernel into NoExport * assembly: check library has at least one export * cli: bundle add kernel option * fixup: move tests * cli: bundle add `--output` option * fixup: comment * cli: changelog * fixup: test debug * cli: bundle include stdlib tested with: cargo run --features executable -- bundle ~/miden/miden-base/miden-lib/asm/kernels/transaction/lib --kernel ~/miden/miden-base/miden-lib/asm/kernels/transaction/api.masm * cli: bundle improve checks and errors * fixup test, add iterator for decorators in a node --- CHANGELOG.md | 1 + assembly/src/library/error.rs | 4 +- assembly/src/library/mod.rs | 11 ++- core/src/mast/mod.rs | 4 + miden/src/cli/bundle.rs | 76 ++++++++++++------ miden/tests/integration/cli/cli_test.rs | 79 +++++++++++++++++-- .../integration/cli/data/kernel_main.masm | 9 +++ miden/tests/integration/cli/data/lib/lib.masm | 3 + .../cli/data/lib_noexports/lib.masm | 3 + 9 files changed, 155 insertions(+), 35 deletions(-) create mode 100644 miden/tests/integration/cli/data/kernel_main.masm create mode 100644 miden/tests/integration/cli/data/lib/lib.masm create mode 100644 miden/tests/integration/cli/data/lib_noexports/lib.masm diff --git a/CHANGELOG.md b/CHANGELOG.md index 93797a7f60..32fb2da348 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - [BREAKING] Use `thiserror` 2.0 to derive errors and refactor them (#1588). #### Enhancements +- Added options `--kernel`, `--debug` and `--output` to `miden bundle` (#1447). - Added `miden_core::mast::MastForest::advice_map` to load it into the advice provider before the `MastForest` execution (#1574). ## 0.11.0 (2024-11-04) diff --git a/assembly/src/library/error.rs b/assembly/src/library/error.rs index e5158188e4..9d3c58361f 100644 --- a/assembly/src/library/error.rs +++ b/assembly/src/library/error.rs @@ -4,9 +4,9 @@ use crate::{ast::QualifiedProcedureName, diagnostics::Diagnostic}; #[derive(Debug, thiserror::Error, Diagnostic)] pub enum LibraryError { - #[error("kernel library must contain at least one exported procedure")] + #[error("library must contain at least one exported procedure")] #[diagnostic()] - EmptyKernel, + NoExport, #[error("invalid export in kernel library: {procedure_path}")] InvalidKernelExport { procedure_path: QualifiedProcedureName }, // Technically KernelError is the source error here, but since LibraryError is sometimes diff --git a/assembly/src/library/mod.rs b/assembly/src/library/mod.rs index 8f42223c22..38cadb3ae5 100644 --- a/assembly/src/library/mod.rs +++ b/assembly/src/library/mod.rs @@ -72,12 +72,16 @@ impl Library { /// Constructs a new [`Library`] from the provided MAST forest and a set of exports. /// /// # Errors + /// Returns an error if the set of exports is empty. /// Returns an error if any of the specified exports do not have a corresponding procedure root /// in the provided MAST forest. pub fn new( mast_forest: Arc, exports: BTreeMap, ) -> Result { + if exports.is_empty() { + return Err(LibraryError::NoExport); + } for (fqn, &proc_body_id) in exports.iter() { if !mast_forest.is_procedure_root(proc_body_id) { return Err(LibraryError::NoProcedureRootForExport { procedure_path: fqn.clone() }); @@ -177,6 +181,9 @@ impl Deserializable for Library { let mast_forest = Arc::new(MastForest::read_from(source)?); let num_exports = source.read_usize()?; + if num_exports == 0 { + return Err(DeserializationError::InvalidValue(String::from("No exported procedures"))); + }; let mut exports = BTreeMap::new(); for _ in 0..num_exports { let proc_module = source.read()?; @@ -353,10 +360,6 @@ impl TryFrom for KernelLibrary { type Error = LibraryError; fn try_from(library: Library) -> Result { - if library.exports.is_empty() { - return Err(LibraryError::EmptyKernel); - } - let kernel_path = LibraryPath::from(LibraryNamespace::Kernel); let mut proc_digests = Vec::with_capacity(library.exports.len()); diff --git a/core/src/mast/mod.rs b/core/src/mast/mod.rs index 1d8af06abe..d71be872d9 100644 --- a/core/src/mast/mod.rs +++ b/core/src/mast/mod.rs @@ -465,6 +465,10 @@ impl MastForest { &self.nodes } + pub fn decorators(&self) -> &[Decorator] { + &self.decorators + } + pub fn advice_map(&self) -> &AdviceMap { &self.advice_map } diff --git a/miden/src/cli/bundle.rs b/miden/src/cli/bundle.rs index 07ae7aa562..356762e3e3 100644 --- a/miden/src/cli/bundle.rs +++ b/miden/src/cli/bundle.rs @@ -2,25 +2,37 @@ use std::path::PathBuf; use assembly::{ diagnostics::{IntoDiagnostic, Report}, - Assembler, Library, LibraryNamespace, + Assembler, KernelLibrary, Library, LibraryNamespace, }; use clap::Parser; +use stdlib::StdLibrary; #[derive(Debug, Clone, Parser)] #[clap( name = "Compile Library", - about = "Bundles .masm files into a single .masl library" + about = "Bundles .masm files into a single .masl library with access to the stdlib." )] pub struct BundleCmd { + /// Include debug symbols. + #[clap(short, long, action)] + debug: bool, /// Path to a directory containing the `.masm` files which are part of the library. #[clap(value_parser)] dir: PathBuf, - /// Defines the top-level namespace, e.g. `mylib`, otherwise the directory name is used. + /// Defines the top-level namespace, e.g. `mylib`, otherwise the directory name is used. For a + /// kernel library the namespace defaults to `kernel`. #[clap(short, long)] namespace: Option, /// Version of the library, defaults to `0.1.0`. #[clap(short, long, default_value = "0.1.0")] version: String, + /// Build a kernel library from module `kernel` and using the library `dir` as kernel + /// namespace. The `kernel` file should not be in the directory `dir`. + #[clap(short, long)] + kernel: Option, + /// Path of the output `.masl` file. + #[clap(short, long)] + output: Option, } impl BundleCmd { @@ -29,29 +41,49 @@ impl BundleCmd { println!("Build library"); println!("============================================================"); - let namespace = match &self.namespace { - Some(namespace) => namespace.to_string(), - None => self - .dir - .file_name() - .expect("dir must be a folder") - .to_string_lossy() - .into_owned(), - }; + let mut assembler = Assembler::default().with_debug_mode(self.debug); - let assembler = Assembler::default().with_debug_mode(true); - let library_namespace = - namespace.parse::().expect("invalid base namespace"); - let library = Library::from_dir(&self.dir, library_namespace, assembler)?; + if self.dir.is_file() { + return Err(Report::msg("`dir` must be a directory.")); + } + let dir = self.dir.file_name().ok_or("`dir` cannot end with `..`.").map_err(Report::msg)?; // write the masl output - let output_file = self - .dir - .join(self.namespace.as_deref().unwrap_or("out")) - .with_extension(Library::LIBRARY_EXTENSION); - library.write_to_file(output_file).into_diagnostic()?; + let output_file = match &self.output { + Some(output) => output, + None => { + let parent = + &self.dir.parent().ok_or("Invalid output path").map_err(Report::msg)?; + &parent.join("out").with_extension(Library::LIBRARY_EXTENSION) + }, + }; - println!("Built library {}", namespace); + match &self.kernel { + Some(kernel) => { + if !kernel.is_file() { + return Err(Report::msg("`kernel` must be a file")); + }; + assembler.add_library(StdLibrary::default())?; + let library = KernelLibrary::from_dir(kernel, Some(&self.dir), assembler)?; + library.write_to_file(output_file).into_diagnostic()?; + println!( + "Built kernel module {} with library {}", + kernel.display(), + &self.dir.display() + ); + }, + None => { + let namespace = match &self.namespace { + Some(namespace) => namespace.to_string(), + None => dir.to_string_lossy().into_owned(), + }; + let library_namespace = namespace.parse::()?; + assembler.add_library(StdLibrary::default())?; + let library = Library::from_dir(&self.dir, library_namespace, assembler)?; + library.write_to_file(output_file).into_diagnostic()?; + println!("Built library {}", namespace); + }, + } Ok(()) } diff --git a/miden/tests/integration/cli/cli_test.rs b/miden/tests/integration/cli/cli_test.rs index 8d657bd7aa..b50801e98a 100644 --- a/miden/tests/integration/cli/cli_test.rs +++ b/miden/tests/integration/cli/cli_test.rs @@ -1,12 +1,11 @@ +use std::{fs, path::Path}; + use assert_cmd::prelude::*; use predicates::prelude::*; extern crate escargot; -#[test] -// Tt test might be an overkill to test only that the 'run' cli command -// outputs steps and ms. -fn cli_run() -> Result<(), Box> { - let bin_under_test = escargot::CargoBuild::new() +fn bin_under_test() -> escargot::CargoRun { + escargot::CargoBuild::new() .bin("miden") .features("executable internal") .current_release() @@ -15,9 +14,14 @@ fn cli_run() -> Result<(), Box> { .unwrap_or_else(|err| { eprintln!("{err}"); panic!("failed to build `miden`"); - }); + }) +} - let mut cmd = bin_under_test.command(); +#[test] +// Tt test might be an overkill to test only that the 'run' cli command +// outputs steps and ms. +fn cli_run() -> Result<(), Box> { + let mut cmd = bin_under_test().command(); cmd.arg("run") .arg("-a") @@ -38,3 +42,64 @@ fn cli_run() -> Result<(), Box> { Ok(()) } + +use assembly::Library; +use vm_core::Decorator; + +#[test] +fn cli_bundle_debug() { + let mut cmd = bin_under_test().command(); + cmd.arg("bundle").arg("--debug").arg("./tests/integration/cli/data/lib"); + cmd.assert().success(); + + let lib = Library::deserialize_from_file("./tests/integration/cli/data/out.masl").unwrap(); + // If there are any AsmOp decorators in the forest, the bundle is in debug mode. + let found_one_asm_op = + lib.mast_forest().decorators().iter().any(|d| matches!(d, Decorator::AsmOp(_))); + assert!(found_one_asm_op); + fs::remove_file("./tests/integration/cli/data/out.masl").unwrap(); +} + +#[test] +fn cli_bundle_no_exports() { + let mut cmd = bin_under_test().command(); + cmd.arg("bundle").arg("./tests/integration/cli/data/lib_noexports"); + cmd.assert() + .failure() + .stderr(predicate::str::contains("library must contain at least one exported procedure")); +} + +#[test] +fn cli_bundle_kernel() { + let mut cmd = bin_under_test().command(); + cmd.arg("bundle") + .arg("./tests/integration/cli/data/lib") + .arg("--kernel") + .arg("./tests/integration/cli/data/kernel_main.masm"); + cmd.assert().success(); + fs::remove_file("./tests/integration/cli/data/out.masl").unwrap() +} + +/// A kernel can bundle with a library w/o exports. +#[test] +fn cli_bundle_kernel_noexports() { + let mut cmd = bin_under_test().command(); + cmd.arg("bundle") + .arg("./tests/integration/cli/data/lib_noexports") + .arg("--kernel") + .arg("./tests/integration/cli/data/kernel_main.masm"); + cmd.assert().success(); + fs::remove_file("./tests/integration/cli/data/out.masl").unwrap() +} + +#[test] +fn cli_bundle_output() { + let mut cmd = bin_under_test().command(); + cmd.arg("bundle") + .arg("./tests/integration/cli/data/lib") + .arg("--output") + .arg("test.masl"); + cmd.assert().success(); + assert!(Path::new("test.masl").exists()); + fs::remove_file("test.masl").unwrap() +} diff --git a/miden/tests/integration/cli/data/kernel_main.masm b/miden/tests/integration/cli/data/kernel_main.masm new file mode 100644 index 0000000000..62d062b284 --- /dev/null +++ b/miden/tests/integration/cli/data/kernel_main.masm @@ -0,0 +1,9 @@ +proc.internal_proc + caller + drop + exec.::kernel::lib::lib_proc +end + +export.kernel_proc + exec.internal_proc +end diff --git a/miden/tests/integration/cli/data/lib/lib.masm b/miden/tests/integration/cli/data/lib/lib.masm new file mode 100644 index 0000000000..386650e44a --- /dev/null +++ b/miden/tests/integration/cli/data/lib/lib.masm @@ -0,0 +1,3 @@ +export.lib_proc + swap +end diff --git a/miden/tests/integration/cli/data/lib_noexports/lib.masm b/miden/tests/integration/cli/data/lib_noexports/lib.masm new file mode 100644 index 0000000000..442d645b0e --- /dev/null +++ b/miden/tests/integration/cli/data/lib_noexports/lib.masm @@ -0,0 +1,3 @@ +proc.lib_proc + swap +end From 5944710a5faff1b175d5c4c4b6ddee2c13b3d095 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:27:36 +0100 Subject: [PATCH 34/39] Optimize computation of DEEP queries in recursive verifier (#1594) * feat: optimize computation of DEEP queries chore: update changelog chore: address feedback Signed-off-by: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> * refactor: remove file * feat: add paramter check to recursive verifier chore: changelog update chore: add back file and update it Revert "refactor: remove file" This reverts commit 384a5af794697cd4060d1a80f167fa9a571962b3. --------- Signed-off-by: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> --- CHANGELOG.md | 2 + stdlib/asm/crypto/stark/constants.masm | 12 +- stdlib/asm/crypto/stark/deep_queries.masm | 139 +++++++++++++--------- stdlib/asm/crypto/stark/mod.masm | 19 +-- stdlib/asm/crypto/stark/utils.masm | 32 +++++ stdlib/asm/crypto/stark/verifier.masm | 17 ++- 6 files changed, 149 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32fb2da348..922fa9d0b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ #### Enhancements - Added options `--kernel`, `--debug` and `--output` to `miden bundle` (#1447). - Added `miden_core::mast::MastForest::advice_map` to load it into the advice provider before the `MastForest` execution (#1574). +- Optimized the computation of the DEEP queries in the recursive verifier (#1594). +- Added validity checks for the inputs to the recursive verifier (#1596). ## 0.11.0 (2024-11-04) diff --git a/stdlib/asm/crypto/stark/constants.masm b/stdlib/asm/crypto/stark/constants.masm index cf8784c9d7..8529917593 100644 --- a/stdlib/asm/crypto/stark/constants.masm +++ b/stdlib/asm/crypto/stark/constants.masm @@ -39,11 +39,15 @@ const.COMPOSITION_COEF_PTR=4294900200 # We need 2 Felt for each trace column and each of the 8 constraint composition columns. We thus need # (70 + 7 + 8) * 2 Felt i.e. 43 memory slots. +# Note that there is a cap on the number of such coefficients so that the memory region allocated for +# these coefficients does not overlap with the memory region storing the FRI queries. +# This cap is of a 100 coefficients which is equivalent to 50 memory slots. This gives 150 memory +# slots for all of the FRI queries i.e., 150 FRI queries. const.DEEP_RAND_CC_PTR=4294903000 # FRI # -# (FRI_COM_PTR - 100) ---| +# (FRI_COM_PTR - 150) ---| # . # . | <- FRI queries # . @@ -97,6 +101,7 @@ const.TMP5=4294903319 const.TMP6=4294903320 const.TMP7=4294903321 const.TMP8=4294903322 +const.TMP9=4294903323 @@ -135,6 +140,7 @@ const.TMP8=4294903322 # | TMP6 | 4294903320 | # | TMP7 | 4294903321 | # | TMP8 | 4294903322 | +# | TMP9 | 4294903323 | # +------------------------------------------+-------------------------+ # ACCESSORS @@ -305,3 +311,7 @@ end export.tmp8 push.TMP8 end + +export.tmp9 + push.TMP9 +end diff --git a/stdlib/asm/crypto/stark/deep_queries.masm b/stdlib/asm/crypto/stark/deep_queries.masm index 0ce9e0e962..41c721c066 100644 --- a/stdlib/asm/crypto/stark/deep_queries.masm +++ b/stdlib/asm/crypto/stark/deep_queries.masm @@ -127,22 +127,23 @@ end #! Loads the next query rows in the main, auxiliary and constraint composition polynomials traces. #! It takes a pointer to the current random query index and returns that index. #! -#! Input: [query_ptr, ...] -#! Output: [index, query_ptr, ...] +#! Input: [Y, query_ptr, ...] +#! Output: [Y, Y, index, query_ptr, ...] +#! +#! where: +#! - Y is a "garbage" word. #! -#! Cycles: 219 +#! Cycles: 206 proc.load_query_row # Main trace portion of the query ## Get the next query index - padw dup.4 mem_loadw - swap - #=> [depth, index, y, y, query_ptr, ...] + #=> [index, depth, y, y, query_ptr, ...] where y are "garbage" values ## Get main trace commitment and use it to get the leaf - movup.3 movup.3 + movdn.3 movdn.2 push.0.0 exec.constants::main_trace_com_ptr mem_loadw #=>[R, depth, index, query_ptr, ...] @@ -273,50 +274,48 @@ proc.load_query_row assert_eq #=> [Y, ptr, y, y, y, depth, index, query_ptr, ...] - dropw dropw drop - #=> [index, query_ptr, ...] + drop + #=> [Y, Y, index, query_ptr, ...] end #! Takes a query index and computes x := offset * domain_gen^index. It also computes the denominators #! (x - z) and (x - gz). #! -#! Input: [index, ...] -#! Output: [Z, x, index, ...] where Z := [-gz1, x -gz0, -z1, x - z0] +#! Input: [Y, Y, index, ...] +#! Output: [Z, Y, x, index, ...] +#! +#! where: +#! - Z := [-gz1, x -gz0, -z1, x - z0] +#! - Y is a "garbage" word #! -#! Cycles: 68 +#! Cycles: 58 proc.compute_denominators # Compute x = offset * domain_gen^index - padw exec.constants::lde_size_ptr mem_loadw - #=> [lde_size, depth, domain_gen, 0, index, ...] + #=> [lde_size, depth, domain_gen, 0, Y, index, ...] movup.2 - dup.4 + dup.8 exp.u32 exec.constants::domain_offset mul - #=> [x, lde_size, depth, 0, index, ...] + #=> [x, lde_size, depth, 0, Y, index, ...] # Get z and gz from memory movdn.3 - #=> [lde_size, depth, 0, x, index, ...] + #=> [lde_size, depth, 0, x, Y, index, ...] push.0 exec.constants::tmp1 mem_loadw - #=> [gz1, gz0, z1, z0, x, index, ...] + #=> [-z0, -gz0, -gz1, -z1, x, Y, index, ...] - # Compute Z := [-z1, x - z0, -gz1, x -gz0] - neg - dup.4 - movup.2 - sub - #=> [x-gz0, -gz1, z1, z0, x, index, ...] + dup.4 add + #=> [x-z0, -gz0, -gz1, -z1, x, Y, index, ...] movdn.3 - movdn.2 - #=> [z1, z0, -gz1, x-gz0, x, index, ...] - neg - movdn.3 - dup.4 swap - sub - movdn.3 - #=> [Z, x, index, ...] where Z := [-gz1, x -gz0, -z1, x - z0] + #=> [-gz0, -gz1, -z1, x-z0, x, Y, index, ...] + + movup.4 dup movdn.9 + #=> [x, -gz0, -gz1, -z1, x-z0, Y, x, index, ...] + + add swap + #=> [-gz1, x - gz0, -z1, x-z0, Y, x, index, ...] end #! Computes the random linear combination involving the main trace columns and accumulates @@ -429,7 +428,7 @@ proc.combine_constraint_poly_columns dropw swapw exec.constants::tmp3 mem_loadw - #=> [Acc3, Acc2, y, y, y, y, Acc1`, Acc0`, ...] + #=> [Acc3, Acc2, y, y, y, y, Acc1`, Acc0`, ...] where y are "garbage" values movdn.5 movdn.5 #=> [y, y, y, y, Acc3, Acc2, Acc1`, Acc0`, ...] dropw @@ -466,12 +465,43 @@ end #! Compute the DEEP composition polynomial FRI queries. #! #! Input: [query_ptr, ...] -#! Output: [...] -#! Cycles: 6 + num_queries * 473 +#! Output: [query_ptr, ...] +#! Cycles: 24 + num_queries * 445 export.compute_deep_composition_polynomial_queries + + # Get pointer to help test for the last query to be processed exec.constants::fri_com_ptr dup.1 - #=>[query_ptr, query_end_ptr, ...] + # =>[query_ptr, query_end_ptr, query_ptr...] + # Store the pointers to: + # 1. random values for computing DEEP polynomial + # 2. OOD evaluation frame + # 3. trace row values for the current query + push.0 + exec.constants::deep_rand_coef_ptr + exec.constants::ood_trace_ptr + exec.constants::current_trace_row_ptr + exec.constants::tmp9 mem_storew + # =>[Y, query_ptr, query_end_ptr, query_ptr, ...] + + # Compute the negations of z and gz where z is the OOD point + # We do it here as this computation is common to all queries. + exec.constants::z_ptr mem_loadw + # => [zN_1, zN_0, z1, z0, query_ptr, query_end_ptr, query_ptr, ...] + drop drop + neg swap neg + # => [-z0, -z1, query_ptr, query_end_ptr, query_ptr, ...] + dup.1 exec.constants::trace_domain_generator_ptr mem_load mul + # => [-gz1, -z0, -z1, query_ptr, query_end_ptr, query_ptr, ...] + swap + # => [-z0, -gz1, -z1, query_ptr, query_end_ptr, query_ptr, ...] + dup exec.constants::trace_domain_generator_ptr mem_load mul + # => [-gz0, -z0, -gz1, -z1, query_ptr, query_end_ptr, query_ptr, ...] + swap + # => [-z0, -gz0, -gz1, -z1, query_ptr, query_end_ptr, query_ptr, ...] + # Save to temporary location `tmp1` for later use when computing the denominators + exec.constants::tmp1 mem_storew + # => [Y, query_ptr, query_end_ptr, query_ptr, ...] push.1 while.true @@ -480,19 +510,18 @@ export.compute_deep_composition_polynomial_queries # Load the (main, aux, constraint)-traces rows associated with the current query and get # the index of the query. # - # Cycles: 219 + # Cycles: 206 exec.load_query_row - #=>[index, query_ptr, query_end_ptr, ...] + #=>[Y, Y, index, query_ptr, query_end_ptr, query_ptr, ...] # II) # # Compute x := offset * domain_gen^index and denominators (x - z) and (x - gz) # - # Cycles: 68 + # Cycles: 58 exec.compute_denominators - #=> [Z, x, index, query_ptr, query_end_ptr, ...] where Z := [-gz1, x - gz0, -z1, x - z0] - + #=> [Z, Y, x, index, query_ptr, query_end_ptr, query_ptr, ...] where Z := [-gz1, x - gz0, -z1, x - z0] # III) # @@ -503,26 +532,23 @@ export.compute_deep_composition_polynomial_queries ## a) Push pointers ## - ## Cycles: 4 - push.0 - exec.constants::deep_rand_coef_ptr - exec.constants::ood_trace_ptr - exec.constants::current_trace_row_ptr - #=> [P, Z, x, index, query_ptr, query_end_ptr, ...] + ## Cycles: 3 + swapw exec.constants::tmp9 mem_loadw + #=> [P, Z, x, index, query_ptr, query_end_ptr, query_ptr, ...] # where P := [CURRENT_TRACE_ROW_PTR, OOD_TRACE_PTR, DEEP_RAND_CC_PTR, 0] ## b) Push the accumulators ## ## Cycles: 4 padw - #=> [Acc, P, Z, x, index, query_ptr, query_end_ptr, ...] + #=> [Acc, P, Z, x, index, query_ptr, query_end_ptr, query_ptr, ...] #=> where Acc =: [Acc3, Acc2, Acc1, Acc0] ## c) This will be used to mstream the elements T_i(x) ## ## Cycles: 8 padw padw - #=> [Y, Y, Acc, P, Z, x, index, query_ptr, query_end_ptr, ...] + #=> [Y, Y, Acc, P, Z, x, index, query_ptr, query_end_ptr, query_ptr, ...] ## d) Compute the random linear combination ## @@ -530,13 +556,13 @@ export.compute_deep_composition_polynomial_queries exec.combine_main_trace_columns exec.combine_aux_trace_columns exec.combine_constraint_poly_columns - #=> [Acc, Z, x, index, query_ptr, query_end_ptr, ...] + #=> [Acc, Z, x, index, query_ptr, query_end_ptr, query_ptr, ...] ## e) Divide by denominators and sum to get final result ## ## Cycles: 38 exec.divide_by_denominators_and_sum - #=> [eval1, eval0, x, index, query_ptr, query_end_ptr, ...] + #=> [eval1, eval0, x, index, query_ptr, query_end_ptr, query_ptr, ...] # IV) @@ -549,22 +575,21 @@ export.compute_deep_composition_polynomial_queries ## Cycles: 4 movup.3 movup.3 exec.constants::domain_offset_inv mul - #=> [poe, index, eval1, eval0, query_ptr, query_end_ptr, ...] + #=> [poe, index, eval1, eval0, query_ptr, query_end_ptr, query_ptr, ...] ## b) Store [eval0, eval1, index, poe] ## ## Cycles: 5 dup.4 add.1 swap.5 mem_storew - #=> [poe, index, eval1, eval0, query_ptr+1, query_end_ptr, ...] + #=> [poe, index, eval1, eval0, query_ptr+1, query_end_ptr, query_ptr, ...] ## c) Prepare stack for next iteration ## - ## Cycles: 8 - dropw - dup.1 dup.1 + ## Cycles: 4 + dup.5 dup.5 neq #=> [?, query_ptr+1, query_end_ptr, ...] end - drop drop + dropw drop drop end diff --git a/stdlib/asm/crypto/stark/mod.masm b/stdlib/asm/crypto/stark/mod.masm index 492f6da59d..776b802297 100644 --- a/stdlib/asm/crypto/stark/mod.masm +++ b/stdlib/asm/crypto/stark/mod.masm @@ -4,23 +4,24 @@ use.std::crypto::stark::verifier #! The following simplifying assumptions are currently made: #! - The blowup is set to 8. #! - The maximal allowed degree of the remainder polynomial is 7. -#! - Only the input and output stacks, assumed of fixed size equal to 16, are handled in regards -#! to public inputs. +#! - The public inputs are composed of the input and output stacks, of fixed size equal to 16. #! - There are two trace segments, main and auxiliary. It is assumed that the main trace segment -#! is 73 columns wide while the auxiliary trace segment is 9 columns wide. +#! is 70 columns wide while the auxiliary trace segment is 7 columns wide. #! - The OOD evaluation frame is composed of two interleaved rows, current and next, each composed -#! of 73 elements representing the main trace portion and 9 elements for the auxiliary trace one. +#! of 70 elements representing the main trace portion and 7 elements for the auxiliary trace one. #! - To boost soundness, the protocol is run on a quadratic extension field and this means that #! the OOD evaluation frame is composed of elements in a quadratic extension field i.e. tuples. -#! Similarly, elements of the auxiliary trace are quadratic extension field elements. +#! Similarly, elements of the auxiliary trace are quadratic extension field elements. The random +#! values for computing random linear combinations are also in this extension field. #! - The following procedure makes use of global memory address beyond 3 * 2^30 and these are #! defined in `constants.masm`. #! -#! Input: [log(trace_length), num_queries, log(blowup), grinding] -#! Output: [] +#! Input: [log(trace_length), num_queries, log(blowup), grinding, ...] +#! Output: [...] +#! #! Cycles: #! 1- Remainder codeword size 32: -#! 5000 + num_queries * (40 + num_fri_layers * 76 + 26 + 463) + 83 * num_fri_layers + 10 * log(trace_length) + 1633 +#! 5013 + num_queries * (40 + num_fri_layers * 76 + 26 + 445) + 83 * num_fri_layers + 10 * log(trace_length) + 1633 #! 2- Remainder codeword size 64: -#! 5000 + num_queries * (40 + num_fri_layers * 76 + 26 + 463) + 83 * num_fri_layers + 10 * log(trace_length) + 3109 +#! 5013 + num_queries * (40 + num_fri_layers * 76 + 26 + 445) + 83 * num_fri_layers + 10 * log(trace_length) + 3109 export.verifier::verify diff --git a/stdlib/asm/crypto/stark/utils.masm b/stdlib/asm/crypto/stark/utils.masm index 27553f0dde..5f701da93c 100644 --- a/stdlib/asm/crypto/stark/utils.masm +++ b/stdlib/asm/crypto/stark/utils.masm @@ -16,3 +16,35 @@ export.compute_lde_generator exp.u32 # => [domain_gen, ..] end + +#! Validates the inputs to the recursive verifier. +#! +#! Input: [log(trace_length), num_queries, log(blowup), grinding, ...] +#! Output: [log(trace_length), num_queries, log(blowup), grinding, ...] +#! +#! Cycles: 28 +export.validate_inputs + # 1) Assert that all inputs are u32 so that we can use u32 operations in what follows + dupw + u32assertw + # => [log(trace_length), num_queries, log(blowup), grinding, ...] + + # 2) Assert that the trace length is at most 29. The 2-adicity of our field is 32 and since + # the blowup factor is 8, we need to make sure that the LDE size is at most 2^32. + # We also check that the trace length is greater than the minimal length supported i.e., 2^6. + dup u32lt.30 assert + u32gt.5 assert + + # 3) Assert that the number of FRI queries is at most 150. This restriction is a soft one + # and is due to the memory layout in the `constants.masm` files but can be updated + # therein. + # We also make sure that there is at least one FRI query. + dup u32lt.151 assert + u32gt.0 assert + + # 4) Assert that the the log(blowup) is 3 + eq.3 assert + + # 5) Assert that the grinding factor is at most 31 + u32lt.32 assert +end diff --git a/stdlib/asm/crypto/stark/verifier.masm b/stdlib/asm/crypto/stark/verifier.masm index edcc4adeca..0183fe2a78 100644 --- a/stdlib/asm/crypto/stark/verifier.masm +++ b/stdlib/asm/crypto/stark/verifier.masm @@ -6,7 +6,7 @@ use.std::crypto::stark::random_coin use.std::crypto::stark::ood_frames use.std::crypto::stark::public_inputs use.std::crypto::stark::constants - +use.std::crypto::stark::utils #! Verify a STARK proof attesting to the correct execution of a program in the Miden VM. #! The following simplifying assumptions are currently made: @@ -25,18 +25,25 @@ use.std::crypto::stark::constants #! defined in `constants.masm`. #! #! Input: [log(trace_length), num_queries, log(blowup), grinding, ...] -#! Output: [] +#! Output: [...] +#! #! Cycles: #! 1- Remainder codeword size 32: -#! 4975 + num_queries * (40 + num_fri_layers * 76 + 26 + 473) + 83 * num_fri_layers + 10 * log(trace_length) + 1633 +#! 5013 + num_queries * (40 + num_fri_layers * 76 + 26 + 445) + 83 * num_fri_layers + 10 * log(trace_length) + 1633 #! 2- Remainder codeword size 64: -#! 4975 + num_queries * (40 + num_fri_layers * 76 + 26 + 473) + 83 * num_fri_layers + 10 * log(trace_length) + 3109 +#! 5013 + num_queries * (40 + num_fri_layers * 76 + 26 + 445) + 83 * num_fri_layers + 10 * log(trace_length) + 3109 export.verify #============================================================================================== # I) Hash proof context and hash-&-load public inputs #============================================================================================== + # Validate inputs + # + # Cycles: 28 + exec.utils::validate_inputs + # => [log(trace_length), num_queries, log(blowup), grinding, ...] + # Initialize the seed using proof context # # Cycles: 82 @@ -221,7 +228,7 @@ export.verify # Compute deep compostion polynomial queries # - # Cycles: 14 + num_queries * 473 + # Cycles: 24 + num_queries * 445 #=> [query_ptr, ...] exec.deep_queries::compute_deep_composition_polynomial_queries #=> [query_ptr, ...] From 8420dcd37078c605f90c87d233eb964ae3468fc9 Mon Sep 17 00:00:00 2001 From: Marco Stronati Date: Fri, 20 Dec 2024 13:21:39 +0100 Subject: [PATCH 35/39] Assembly: remove duplicate code and make recompute private (#1606) * Assembly: share code between assemble_{library,kernel} * Assembly: honor warnings_as_errors for assemble_library * Assembly: error is already raised by library * Assembly: make add_module return the index * Assembly: error only for executable case * Assembly: use existing add_module_with_options * Assembly: add multiple modules and recompute graph only once * Assembly: make recompute graph private --- assembly/src/assembler/mod.rs | 117 ++++++++------------- assembly/src/assembler/module_graph/mod.rs | 17 ++- assembly/src/testing.rs | 18 ++-- 3 files changed, 71 insertions(+), 81 deletions(-) diff --git a/assembly/src/assembler/mod.rs b/assembly/src/assembler/mod.rs index 2ddc94e31a..d826838376 100644 --- a/assembly/src/assembler/mod.rs +++ b/assembly/src/assembler/mod.rs @@ -158,7 +158,7 @@ impl Assembler { /// /// The given module must be a library module, or an error will be returned. #[inline] - pub fn add_module(&mut self, module: impl Compile) -> Result<(), Report> { + pub fn add_module(&mut self, module: impl Compile) -> Result { self.add_module_with_options(module, CompileOptions::for_library()) } @@ -169,22 +169,36 @@ impl Assembler { &mut self, module: impl Compile, options: CompileOptions, - ) -> Result<(), Report> { + ) -> Result { + let ids = self.add_modules_with_options(vec![module], options)?; + Ok(ids[0]) + } + + pub fn add_modules_with_options( + &mut self, + modules: impl IntoIterator, + options: CompileOptions, + ) -> Result, Report> { let kind = options.kind; - if kind != ModuleKind::Library { - return Err(Report::msg( - "only library modules are supported by `add_module_with_options`", - )); + if kind == ModuleKind::Executable { + return Err(Report::msg("Executables are not supported by `add_module_with_options`")); } - let module = module.compile_with_options(&self.source_manager, options)?; - assert_eq!(module.kind(), kind, "expected module kind to match compilation options"); - - self.module_graph.add_ast_module(module)?; - - Ok(()) + let modules = modules + .into_iter() + .map(|module| { + let module = module.compile_with_options(&self.source_manager, options.clone())?; + assert_eq!( + module.kind(), + kind, + "expected module kind to match compilation options" + ); + Ok(module) + }) + .collect::, Report>>()?; + let ids = self.module_graph.add_ast_modules(modules.into_iter())?; + Ok(ids) } - /// Adds all modules (defined by ".masm" files) from the specified directory to the module /// of this assembler graph. /// @@ -202,10 +216,8 @@ impl Assembler { namespace: crate::LibraryNamespace, dir: &std::path::Path, ) -> Result<(), Report> { - for module in crate::parser::read_modules_from_dir(namespace, dir, &self.source_manager)? { - self.module_graph.add_ast_module(module)?; - } - + let modules = crate::parser::read_modules_from_dir(namespace, dir, &self.source_manager)?; + self.module_graph.add_ast_modules(modules)?; Ok(()) } @@ -281,24 +293,12 @@ impl Assembler { /// # Errors /// /// Returns an error if parsing or compilation of the specified modules fails. - pub fn assemble_library( + pub fn assemble_common( mut self, modules: impl IntoIterator, + options: CompileOptions, ) -> Result { - let ast_module_indices = - modules.into_iter().try_fold(Vec::default(), |mut acc, module| { - module - .compile_with_options(&self.source_manager, CompileOptions::for_library()) - .and_then(|module| { - self.module_graph.add_ast_module(module).map_err(Report::from) - }) - .map(move |module_id| { - acc.push(module_id); - acc - }) - })?; - - self.module_graph.recompute()?; + let ast_module_indices = self.add_modules_with_options(modules, options)?; let mut mast_forest_builder = MastForestBuilder::default(); @@ -325,7 +325,6 @@ impl Assembler { exports }; - // TODO: show a warning if library exports are empty? let (mast_forest, id_remappings) = mast_forest_builder.build(); if let Some(id_remappings) = id_remappings { for (_proc_name, node_id) in exports.iter_mut() { @@ -338,53 +337,30 @@ impl Assembler { Ok(Library::new(mast_forest.into(), exports)?) } + pub fn assemble_library( + self, + modules: impl IntoIterator, + ) -> Result { + let options = CompileOptions { + kind: ModuleKind::Library, + warnings_as_errors: self.warnings_as_errors, + path: None, + }; + self.assemble_common(modules, options) + } + /// Assembles the provided module into a [KernelLibrary] intended to be used as a Kernel. /// /// # Errors /// /// Returns an error if parsing or compilation of the specified modules fails. - pub fn assemble_kernel(mut self, module: impl Compile) -> Result { + pub fn assemble_kernel(self, module: impl Compile) -> Result { let options = CompileOptions { kind: ModuleKind::Kernel, warnings_as_errors: self.warnings_as_errors, path: Some(LibraryPath::from(LibraryNamespace::Kernel)), }; - - let module = module.compile_with_options(&self.source_manager, options)?; - let module_idx = self.module_graph.add_ast_module(module)?; - - self.module_graph.recompute()?; - - let mut mast_forest_builder = MastForestBuilder::default(); - - // Note: it is safe to use `unwrap_ast()` here, since all modules looped over are - // AST (we just added them to the module graph) - let ast_module = self.module_graph[module_idx].unwrap_ast().clone(); - - let mut exports = ast_module - .exported_procedures() - .map(|(proc_idx, fqn)| { - let gid = module_idx + proc_idx; - self.compile_subgraph(gid, &mut mast_forest_builder)?; - - let proc_root_node_id = mast_forest_builder - .get_procedure(gid) - .expect("compilation succeeded but root not found in cache") - .body_node_id(); - Ok((fqn, proc_root_node_id)) - }) - .collect::, Report>>()?; - - // TODO: show a warning if library exports are empty? - let (mast_forest, id_remappings) = mast_forest_builder.build(); - if let Some(id_remappings) = id_remappings { - for (_proc_name, node_id) in exports.iter_mut() { - if let Some(&new_node_id) = id_remappings.get(node_id) { - *node_id = new_node_id; - } - } - } - let library = Library::new(mast_forest.into(), exports)?; + let library = self.assemble_common([module], options)?; Ok(library.try_into()?) } @@ -407,7 +383,6 @@ impl Assembler { // Recompute graph with executable module, and start compiling let ast_module_index = self.module_graph.add_ast_module(program)?; - self.module_graph.recompute()?; // Find the executable entrypoint Note: it is safe to use `unwrap_ast()` here, since this is // the module we just added, which is in AST representation. diff --git a/assembly/src/assembler/module_graph/mod.rs b/assembly/src/assembler/module_graph/mod.rs index f8116f9985..6c02e89f7b 100644 --- a/assembly/src/assembler/module_graph/mod.rs +++ b/assembly/src/assembler/module_graph/mod.rs @@ -223,7 +223,9 @@ impl ModuleGraph { /// This function will panic if the number of modules exceeds the maximum representable /// [ModuleIndex] value, `u16::MAX`. pub fn add_ast_module(&mut self, module: Box) -> Result { - self.add_module(PendingWrappedModule::Ast(module)) + let res = self.add_module(PendingWrappedModule::Ast(module))?; + self.recompute()?; + Ok(res) } fn add_module(&mut self, module: PendingWrappedModule) -> Result { @@ -238,6 +240,17 @@ impl ModuleGraph { Ok(module_id) } + pub fn add_ast_modules( + &mut self, + modules: impl Iterator>, + ) -> Result, AssemblyError> { + let idx = modules + .into_iter() + .map(|m| self.add_module(PendingWrappedModule::Ast(m))) + .collect::, _>>()?; + self.recompute()?; + Ok(idx) + } fn is_pending(&self, path: &LibraryPath) -> bool { self.pending.iter().any(|m| m.path() == path) } @@ -329,7 +342,7 @@ impl ModuleGraph { /// NOTE: This will return `Err` if we detect a validation error, a cycle in the graph, or an /// operation not supported by the current configuration. Basically, for any reason that would /// cause the resulting graph to represent an invalid program. - pub fn recompute(&mut self) -> Result<(), AssemblyError> { + fn recompute(&mut self) -> Result<(), AssemblyError> { // It is acceptable for there to be no changes, but if the graph is empty and no changes // are being made, we treat that as an error if self.modules.is_empty() && self.pending.is_empty() { diff --git a/assembly/src/testing.rs b/assembly/src/testing.rs index 2ba14bcfe8..d87b0a24e3 100644 --- a/assembly/src/testing.rs +++ b/assembly/src/testing.rs @@ -291,7 +291,7 @@ impl TestContext { /// other modules. #[track_caller] pub fn add_module(&mut self, module: impl Compile) -> Result<(), Report> { - self.assembler.add_module(module) + self.assembler.add_module(module).map(|_| ()) } /// Add a module to the [Assembler] constructed by this context, with the fully-qualified @@ -305,13 +305,15 @@ impl TestContext { path: LibraryPath, source: impl Compile, ) -> Result<(), Report> { - self.assembler.add_module_with_options( - source, - CompileOptions { - path: Some(path), - ..CompileOptions::for_library() - }, - ) + self.assembler + .add_module_with_options( + source, + CompileOptions { + path: Some(path), + ..CompileOptions::for_library() + }, + ) + .map(|_| ()) } /// Add the modules of `library` to the [Assembler] constructed by this context. From 0e7c1d6d837a1b8eaf2a5439587c7120b47b2d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Laferri=C3=A8re?= Date: Fri, 10 Jan 2025 11:55:57 -0500 Subject: [PATCH 36/39] Implement element-addressable memory (#1598) * feat: update memory chiplet to be element-addressable * feat(air): fix air constraints for new memory chiplet * feat(bus): fix bus for element-addressable memory * fix: fix stdlib after element-addressable memory * changelog * more test fixes * fix: recursive verifier * fix sha256 * PR fixes * nomenclature: change `batch` -> `word` * more PR fixes * clippy * cleanup * feat: make the number of locals element-addressable * fix: fix stdlib after locals change * more PR fixes * more PR fixes * fix rounding up of locals * PR fixes --- CHANGELOG.md | 3 + air/src/constraints/chiplets/memory/mod.rs | 324 ++++---- air/src/constraints/chiplets/memory/tests.rs | 114 +-- air/src/constraints/chiplets/mod.rs | 54 +- air/src/trace/chiplets/memory.rs | 75 +- air/src/trace/chiplets/mod.rs | 18 +- air/src/trace/main_trace.rs | 18 +- air/src/trace/mod.rs | 4 +- assembly/src/assembler/instruction/env_ops.rs | 2 +- assembly/src/assembler/instruction/mem_ops.rs | 28 +- assembly/src/assembler/instruction/mod.rs | 45 +- assembly/src/assembler/mod.rs | 16 +- assembly/src/assembler/procedure.rs | 1 + assembly/src/ast/tests.rs | 2 +- assembly/src/errors.rs | 9 + assembly/src/tests.rs | 37 +- core/src/debuginfo/source_file.rs | 2 +- docs/src/user_docs/stdlib/collections.md | 7 +- docs/src/user_docs/stdlib/mem.md | 2 +- miden/masm-examples/debug/debug.masm | 7 +- miden/src/cli/debug/executor.rs | 31 +- miden/src/main.rs | 2 + miden/src/repl/mod.rs | 25 +- miden/src/utils.rs | 6 + .../tests/integration/air/chiplets/memory.rs | 41 +- miden/tests/integration/exec_iters.rs | 50 +- miden/tests/integration/flow_control/mod.rs | 16 +- .../operations/decorators/advice.rs | 11 +- miden/tests/integration/operations/fri_ops.rs | 2 +- .../integration/operations/io_ops/adv_ops.rs | 4 +- .../integration/operations/io_ops/env_ops.rs | 50 +- .../operations/io_ops/local_ops.rs | 52 +- .../integration/operations/io_ops/mem_ops.rs | 16 +- processor/src/chiplets/aux_trace/mod.rs | 325 ++++---- processor/src/chiplets/memory/mod.rs | 216 ++++-- processor/src/chiplets/memory/segment.rs | 296 ++++++-- processor/src/chiplets/memory/tests.rs | 662 +++++++++++------ processor/src/chiplets/mod.rs | 123 +--- processor/src/chiplets/tests.rs | 3 +- processor/src/debug.rs | 21 +- processor/src/decoder/mod.rs | 10 +- processor/src/decoder/tests.rs | 10 +- processor/src/errors.rs | 10 + processor/src/host/debug.rs | 60 +- processor/src/lib.rs | 18 +- processor/src/operations/comb_ops.rs | 34 +- processor/src/operations/fri_ops.rs | 16 +- processor/src/operations/io_ops.rs | 359 +++++---- .../operations/sys_ops/sys_event_handlers.rs | 21 +- processor/src/trace/tests/chiplets/memory.rs | 164 +++-- stdlib/asm/collections/mmr.masm | 57 +- stdlib/asm/crypto/dsa/ecdsa/secp256k1.masm | 148 ++-- stdlib/asm/crypto/dsa/rpo_falcon512.masm | 92 +-- stdlib/asm/crypto/elgamal_ecgfp5.masm | 10 +- stdlib/asm/crypto/fri/ext2fri.masm | 40 +- stdlib/asm/crypto/fri/frie2f4.masm | 90 +-- stdlib/asm/crypto/fri/helper.masm | 37 +- stdlib/asm/crypto/hashes/blake3.masm | 62 +- stdlib/asm/crypto/hashes/keccak256.masm | 358 ++++----- stdlib/asm/crypto/hashes/rpo.masm | 17 +- stdlib/asm/crypto/hashes/sha256.masm | 111 +-- stdlib/asm/crypto/stark/constants.masm | 168 +++-- stdlib/asm/crypto/stark/deep_queries.masm | 38 +- stdlib/asm/crypto/stark/ood_frames.masm | 52 +- stdlib/asm/crypto/stark/public_inputs.masm | 2 +- stdlib/asm/crypto/stark/random_coin.masm | 218 +++--- stdlib/asm/crypto/stark/verifier.masm | 10 +- stdlib/asm/math/ecgfp5/group.masm | 272 +++---- stdlib/asm/math/ecgfp5/scalar_field.masm | 692 +++++++++--------- stdlib/asm/math/secp256k1/base_field.masm | 40 +- stdlib/asm/math/secp256k1/group.masm | 524 ++++++------- stdlib/asm/math/secp256k1/scalar_field.masm | 40 +- stdlib/asm/math/u256.masm | 84 +-- stdlib/asm/mem.masm | 26 +- stdlib/asm/sys.masm | 2 +- stdlib/docs/mem.md | 2 +- stdlib/tests/collections/mmr.rs | 77 +- stdlib/tests/crypto/falcon.rs | 75 +- stdlib/tests/crypto/fri/remainder.rs | 18 +- stdlib/tests/crypto/keccak256.rs | 14 +- stdlib/tests/crypto/rpo.rs | 46 +- stdlib/tests/crypto/sha256.rs | 6 +- stdlib/tests/math/secp256k1/group.rs | 130 ++-- stdlib/tests/mem/mod.rs | 68 +- test-utils/src/lib.rs | 26 +- test-utils/src/test_builders.rs | 4 +- 86 files changed, 3974 insertions(+), 3104 deletions(-) create mode 100644 miden/src/utils.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 922fa9d0b0..3e908eb10d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +#### Highlights +- [BREAKING] Memory is now element-addressable (#1598) + #### Changes - [BREAKING] `Process` no longer takes ownership of the `Host` (#1571). - [BREAKING] `ProcessState` was converted from a trait to a struct (#1571). diff --git a/air/src/constraints/chiplets/memory/mod.rs b/air/src/constraints/chiplets/memory/mod.rs index 1f1289be8e..729dd3138c 100644 --- a/air/src/constraints/chiplets/memory/mod.rs +++ b/air/src/constraints/chiplets/memory/mod.rs @@ -5,9 +5,10 @@ use winter_air::TransitionConstraintDegree; use super::{EvaluationFrame, FieldElement}; use crate::{ trace::chiplets::{ - memory::NUM_ELEMENTS, MEMORY_ADDR_COL_IDX, MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, - MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX, MEMORY_D_INV_COL_IDX, MEMORY_TRACE_OFFSET, - MEMORY_V_COL_RANGE, + MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX, + MEMORY_D_INV_COL_IDX, MEMORY_FLAG_SAME_CONTEXT_AND_WORD, MEMORY_IDX0_COL_IDX, + MEMORY_IDX1_COL_IDX, MEMORY_IS_READ_COL_IDX, MEMORY_IS_WORD_ACCESS_COL_IDX, + MEMORY_V_COL_RANGE, MEMORY_WORD_COL_IDX, }, utils::{binary_not, is_binary, EvaluationResult}, }; @@ -19,16 +20,17 @@ mod tests; // ================================================================================================ /// The number of constraints on the management of the memory chiplet. -pub const NUM_CONSTRAINTS: usize = 17; +pub const NUM_CONSTRAINTS: usize = 22; /// The degrees of constraints on the management of the memory chiplet. All constraint degrees are /// increased by 3 due to the selectors for the memory chiplet. pub const CONSTRAINT_DEGREES: [usize; NUM_CONSTRAINTS] = [ - 5, 5, // Enforce that the memory selectors are binary. - 9, 8, // Enforce s1 is set to 1 when reading existing memory and 0 otherwise. + 5, 5, 5, 5, // Enforce that rw, ew, idx0 and idx1 are binary. 7, 6, 9, 8, // Constrain the values in the d inverse column. - 8, // Enforce values in ctx, addr, clk transition correctly. - 6, 6, 6, 6, // Enforce correct memory initialization when reading from new memory. - 5, 5, 5, 5, // Enforce correct memory copy when reading from existing memory + 8, // Enforce values in ctx, word, clk transition correctly. + 7, // Enforce the correct value for the f_scw flag. + 9, 9, 9, 9, // Constrain the values in the first row of the chiplet. + 9, 9, 9, 9, // Constrain the values in non-first rows, new word or context is started. + 9, 9, 9, 9, // Constrain the values in non-first rows, same word or context. ]; // MEMORY TRANSITION CONSTRAINTS @@ -48,56 +50,52 @@ pub fn get_transition_constraint_count() -> usize { } /// Enforces constraints for the memory chiplet. +/// +/// The flags are: +/// - `memory_flag_all_rows`: a flag that is set to 1 when the current row is part of the memory +/// chiplet, +/// - `memory_flag_not_last_row`: a flag that is set to 1 when the current row is part of the memory +/// chiplet, but excludes the last row of the chiplet, +/// - `memory_flag_first_row`: a flag that is set to 1 when the *next* row is the first row of the +/// memory chiplet. pub fn enforce_constraints( frame: &EvaluationFrame, result: &mut [E], - memory_flag: E, + memory_flag_all_rows: E, + memory_flag_not_last_row: E, + memory_flag_first_row: E, ) { - // Constrain the operation selectors. - let mut index = enforce_selectors(frame, result, memory_flag); + // Constrain the binary columns. + let mut index = enforce_binary_columns(frame, result, memory_flag_all_rows); // Constrain the values in the d inverse column. - index += enforce_d_inv(frame, &mut result[index..], memory_flag); + index += enforce_d_inv(frame, &mut result[index..], memory_flag_not_last_row); + + // Enforce values in ctx, word_addr, clk transition correctly. + index += enforce_delta(frame, &mut result[index..], memory_flag_not_last_row); - // Enforce values in ctx, addr, clk transition correctly. - index += enforce_delta(frame, &mut result[index..], memory_flag); + // Enforce the correct value for the f_scw flag. + index += + enforce_flag_same_context_and_word(frame, &mut result[index..], memory_flag_not_last_row); // Constrain the memory values. - enforce_values(frame, &mut result[index..], memory_flag); + enforce_values(frame, &mut result[index..], memory_flag_not_last_row, memory_flag_first_row); } // TRANSITION CONSTRAINT HELPERS // ================================================================================================ -fn enforce_selectors( +fn enforce_binary_columns( frame: &EvaluationFrame, result: &mut [E], memory_flag: E, ) -> usize { - let mut index = 0; - - // s0 and s1 are binary. - result[index] = memory_flag * is_binary(frame.selector(0)); - index += 1; - result[index] = memory_flag * is_binary(frame.selector(1)); - index += 1; - - // s1 is set to 1 when existing memory is being read. this happens when ctx and addr haven't - // changed, and the next operation is a read (s0 is set). - result[index] = memory_flag - * frame.reaccess_flag() - * frame.selector_next(0) - * binary_not(frame.selector_next(1)); - index += 1; - - // s1 is set to 0 in all other cases. this happens when ctx changed, or ctx stayed the same but - // addr changed, or the operation was a write. - result[index] = memory_flag - * (frame.n0() + frame.not_n0() * frame.n1() + binary_not(frame.selector_next(0))) - * frame.selector_next(1); - index += 1; - - index + result[0] = memory_flag * is_binary(frame.is_read()); + result[1] = memory_flag * is_binary(frame.is_word_access()); + result[2] = memory_flag * is_binary(frame.idx0()); + result[3] = memory_flag * is_binary(frame.idx1()); + + 4 } /// A constraint evaluation function to enforce that the `d_inv` "delta inverse" column used to @@ -106,14 +104,19 @@ fn enforce_selectors( fn enforce_d_inv( frame: &EvaluationFrame, result: &mut [E], - memory_flag: E, + memory_flag_not_last_row: E, ) -> usize { let constraint_count = 4; - result.agg_constraint(0, memory_flag, is_binary(frame.n0())); - result.agg_constraint(1, memory_flag * frame.not_n0(), frame.ctx_change()); - result.agg_constraint(2, memory_flag * frame.not_n0(), is_binary(frame.n1())); - result.agg_constraint(3, memory_flag * frame.reaccess_flag(), frame.addr_change()); + // n0 is binary + result[0] = memory_flag_not_last_row * is_binary(frame.n0()); + // when the context changes, n0 should be set to 1. + result[1] = memory_flag_not_last_row * frame.not_n0() * frame.ctx_change(); + // when n0 is 0, n1 is binary. + result[2] = memory_flag_not_last_row * frame.not_n0() * is_binary(frame.n1()); + // when n0 and n1 are 0, then `word_addr` doesn't change. + result[3] = + memory_flag_not_last_row * frame.not_n0() * frame.not_n1() * frame.word_addr_change(); constraint_count } @@ -123,47 +126,106 @@ fn enforce_d_inv( fn enforce_delta( frame: &EvaluationFrame, result: &mut [E], - memory_flag: E, + memory_flag_not_last_row: E, ) -> usize { let constraint_count = 1; // If the context changed, include the difference. - result.agg_constraint(0, memory_flag * frame.n0(), frame.ctx_change()); - // If the context is the same, include the address difference if it changed or else include the - // clock change. + result[0] = memory_flag_not_last_row * frame.n0() * frame.ctx_change(); + // If the context is the same, include the word address difference if it changed or else include + // the clock change. result.agg_constraint( 0, - memory_flag * frame.not_n0(), - frame.n1() * frame.addr_change() + frame.not_n1() * frame.clk_change(), + memory_flag_not_last_row * frame.not_n0(), + frame.n1() * frame.word_addr_change() + frame.not_n1() * frame.clk_change(), ); // Always subtract the delta. It should offset the other changes. - result[0] -= memory_flag * frame.delta_next(); + result[0] -= memory_flag_not_last_row * frame.delta_next(); constraint_count } +/// A constraint evaluation function to enforce that the `f_scw` flag is set to 1 when the next row +/// is in the same context and word, and 0 otherwise. +fn enforce_flag_same_context_and_word( + frame: &EvaluationFrame, + result: &mut [E], + memory_flag_not_last_row: E, +) -> usize { + result[0] = memory_flag_not_last_row + * (frame.f_scw_next() - binary_not(frame.n0() + frame.not_n0() * frame.n1())); + + 1 +} + /// A constraint evaluation function to enforce that memory is initialized to zero when it is read /// before being written and that when existing memory values are read they remain unchanged. +/// +/// The constraints on the values depend on a few factors: +/// - When in the first row of a new context or word, any of the 4 values of the word that are not +/// written to must be set to 0. This is because the memory is initialized to 0 when a new context +/// or word is started. +/// - When we remain in the same context and word, then this is when we want to enforce the "memory +/// property" that what was previously written must be read. Therefore, the values that are not +/// being written need to be equal to the values in the previous row (i.e. were either previously +/// written or are still initialized to 0). fn enforce_values( frame: &EvaluationFrame, result: &mut [E], - memory_flag: E, + memory_flag_no_last: E, + memory_flag_first_row: E, ) -> usize { - let mut index = 0; - - // initialize memory to zero when reading from new context and address pair. - for i in 0..NUM_ELEMENTS { - result[index] = memory_flag * frame.init_read_flag() * frame.v(i); - index += 1; - } - - // copy previous values when reading memory that was previously accessed. - for i in 0..NUM_ELEMENTS { - result[index] = memory_flag * frame.copy_read_flag() * (frame.v_next(i) - frame.v(i)); - index += 1; - } - - index + // c_i is set to 1 when `v'[i]` is not written to, and 0 otherwise. + // + // In other words, c_i is set to 1 when `v'[i]` needs to be constrained (to either 0 or `v[i]`). + // + // Note that `c_i` only uses values in the "next" row. This is because it must be used to + // constrain the first row of the memory chiplet, where that row sits in the "next" position of + // the frame, and the "current" row belongs to the previous chiplet (and hence the "current" row + // must not be accessed). + // + // As a result, `c_i` does not include the constraint of being in the memory chiplet, or in the + // same context and word - these must be enforced separately. + let (c0, c1, c2, c3) = { + // intuition: the i'th `f` flag is set to 1 when `i == 2 * idx1 + idx0` + let f0 = binary_not(frame.idx1_next()) * binary_not(frame.idx0_next()); + let f1 = binary_not(frame.idx1_next()) * frame.idx0_next(); + let f2 = frame.idx1_next() * binary_not(frame.idx0_next()); + let f3 = frame.idx1_next() * frame.idx0_next(); + + let c_i = |f_i| { + // z_i is set to 1 when we are operating on elements but not the i-th element + let z_i = binary_not(frame.is_word_access_next()) * binary_not(f_i); + let is_read_next = frame.is_read_next(); + + is_read_next + binary_not(is_read_next) * z_i + }; + + (c_i(f0), c_i(f1), c_i(f2), c_i(f3)) + }; + + // first row constraints: when row' is the first row, and v'[i] is not written to, then v'[i] + // must be 0. + result[0] = memory_flag_first_row * c0 * frame.v_next(0); + result[1] = memory_flag_first_row * c1 * frame.v_next(1); + result[2] = memory_flag_first_row * c2 * frame.v_next(2); + result[3] = memory_flag_first_row * c3 * frame.v_next(3); + + // non-first row, new word address or context constraints: when row' is a new word address/ctx, + // and v'[i] is not written to, then v'[i] must be 0. + result[4] = memory_flag_no_last * binary_not(frame.f_scw_next()) * c0 * frame.v_next(0); + result[5] = memory_flag_no_last * binary_not(frame.f_scw_next()) * c1 * frame.v_next(1); + result[6] = memory_flag_no_last * binary_not(frame.f_scw_next()) * c2 * frame.v_next(2); + result[7] = memory_flag_no_last * binary_not(frame.f_scw_next()) * c3 * frame.v_next(3); + + // non-first row, same word address and context constraints: when row' is in the same word + // address/ctx, and v'[i] is not written to, then v'[i] must be equal to v[i]. + result[8] = memory_flag_no_last * frame.f_scw_next() * c0 * (frame.v_next(0) - frame.v(0)); + result[9] = memory_flag_no_last * frame.f_scw_next() * c1 * (frame.v_next(1) - frame.v(1)); + result[10] = memory_flag_no_last * frame.f_scw_next() * c2 * (frame.v_next(2) - frame.v(2)); + result[11] = memory_flag_no_last * frame.f_scw_next() * c3 * (frame.v_next(3) - frame.v(3)); + + 12 } // MEMORY FRAME EXTENSION TRAIT @@ -174,22 +236,30 @@ fn enforce_values( trait EvaluationFrameExt { // --- Column accessors ----------------------------------------------------------------------- - /// Gets the value of the specified selector column in the current row. - fn selector(&self, idx: usize) -> E; - /// Gets the value of the specified selector column in the next row. - fn selector_next(&self, idx: usize) -> E; - /// The current context value. - #[allow(dead_code)] - fn ctx(&self) -> E; - /// The current address. - #[allow(dead_code)] - fn addr(&self) -> E; - /// The current clock cycle. - #[allow(dead_code)] - fn clk(&self) -> E; - /// The next clock cycle. - #[allow(dead_code)] - fn clk_next(&self) -> E; + /// The value of the read/write column in the current row. + /// + /// 0: write, 1: read + fn is_read(&self) -> E; + /// The value of the read/write column in the next row. + /// + /// 0: write, 1: read + fn is_read_next(&self) -> E; + /// The value of the element/word column in the current row. + /// + /// 0: element, 1: word + fn is_word_access(&self) -> E; + /// The value of the element/word column in the next row. + /// + /// 0: element, 1: word + fn is_word_access_next(&self) -> E; + /// The 0'th bit of the index of the memory address in the current word. + fn idx0(&self) -> E; + /// The 0'th bit of the index of the memory address in the next word. + fn idx0_next(&self) -> E; + /// The 1st bit of the index of the memory address in the current word. + fn idx1(&self) -> E; + /// The 1st bit of the index of the memory address in the next word. + fn idx1_next(&self) -> E; /// The value from the specified index of the values (0, 1, 2, 3) in the current row. fn v(&self, index: usize) -> E; /// The value from the specified index of the values (0, 1, 2, 3) in the next row. @@ -203,6 +273,10 @@ trait EvaluationFrameExt { /// The next value of the column tracking the inverse delta used for constraint evaluations. fn d_inv_next(&self) -> E; + // The flag that indicates whether the next row is in the same word and context as the current + // row. + fn f_scw_next(&self) -> E; + // --- Intermediate variables & helpers ------------------------------------------------------- /// The change between the current value in the specified column and the next value, calculated @@ -220,59 +294,55 @@ trait EvaluationFrameExt { fn not_n1(&self) -> E; /// The difference between the next context and the current context. fn ctx_change(&self) -> E; - /// The difference between the next address and the current address. - fn addr_change(&self) -> E; + /// The difference between the next word address and the current word address. + fn word_addr_change(&self) -> E; /// The difference between the next clock value and the current one, minus 1. fn clk_change(&self) -> E; /// The delta between two consecutive context IDs, addresses, or clock cycles. fn delta_next(&self) -> E; - - // --- Flags ---------------------------------------------------------------------------------- - - /// A flag to indicate that previously assigned memory is being accessed. In other words, the - /// context and address have not changed. - fn reaccess_flag(&self) -> E; - - /// A flag to indicate that there is a read in the current row which requires the values to be - /// initialized to zero. - fn init_read_flag(&self) -> E; - - /// A flag to indicate that the operation in the next row is a read which requires copying the - /// values from the current row to the next row. - fn copy_read_flag(&self) -> E; } impl EvaluationFrameExt for &EvaluationFrame { // --- Column accessors ----------------------------------------------------------------------- #[inline(always)] - fn selector(&self, idx: usize) -> E { - self.current()[MEMORY_TRACE_OFFSET + idx] + fn is_read(&self) -> E { + self.current()[MEMORY_IS_READ_COL_IDX] + } + + #[inline(always)] + fn is_read_next(&self) -> E { + self.next()[MEMORY_IS_READ_COL_IDX] + } + + #[inline(always)] + fn is_word_access(&self) -> E { + self.current()[MEMORY_IS_WORD_ACCESS_COL_IDX] } #[inline(always)] - fn selector_next(&self, idx: usize) -> E { - self.next()[MEMORY_TRACE_OFFSET + idx] + fn is_word_access_next(&self) -> E { + self.next()[MEMORY_IS_WORD_ACCESS_COL_IDX] } #[inline(always)] - fn ctx(&self) -> E { - self.current()[MEMORY_CTX_COL_IDX] + fn idx0(&self) -> E { + self.current()[MEMORY_IDX0_COL_IDX] } #[inline(always)] - fn addr(&self) -> E { - self.next()[MEMORY_ADDR_COL_IDX] + fn idx0_next(&self) -> E { + self.next()[MEMORY_IDX0_COL_IDX] } #[inline(always)] - fn clk(&self) -> E { - self.current()[MEMORY_CLK_COL_IDX] + fn idx1(&self) -> E { + self.current()[MEMORY_IDX1_COL_IDX] } #[inline(always)] - fn clk_next(&self) -> E { - self.next()[MEMORY_CLK_COL_IDX] + fn idx1_next(&self) -> E { + self.next()[MEMORY_IDX1_COL_IDX] } #[inline(always)] @@ -300,6 +370,11 @@ impl EvaluationFrameExt for &EvaluationFrame { self.next()[MEMORY_D_INV_COL_IDX] } + #[inline(always)] + fn f_scw_next(&self) -> E { + self.next()[MEMORY_FLAG_SAME_CONTEXT_AND_WORD] + } + // --- Intermediate variables & helpers ------------------------------------------------------- #[inline(always)] @@ -319,7 +394,7 @@ impl EvaluationFrameExt for &EvaluationFrame { #[inline(always)] fn n1(&self) -> E { - self.change(MEMORY_ADDR_COL_IDX) * self.d_inv_next() + self.change(MEMORY_WORD_COL_IDX) * self.d_inv_next() } #[inline(always)] @@ -333,8 +408,8 @@ impl EvaluationFrameExt for &EvaluationFrame { } #[inline(always)] - fn addr_change(&self) -> E { - self.change(MEMORY_ADDR_COL_IDX) + fn word_addr_change(&self) -> E { + self.change(MEMORY_WORD_COL_IDX) } #[inline(always)] @@ -346,21 +421,4 @@ impl EvaluationFrameExt for &EvaluationFrame { fn delta_next(&self) -> E { E::from(2_u32.pow(16)) * self.d1_next() + self.d0_next() } - - // --- Flags ---------------------------------------------------------------------------------- - - #[inline(always)] - fn reaccess_flag(&self) -> E { - self.not_n0() * self.not_n1() - } - - #[inline(always)] - fn init_read_flag(&self) -> E { - self.selector(0) * binary_not(self.selector(1)) - } - - #[inline(always)] - fn copy_read_flag(&self) -> E { - self.selector_next(1) - } } diff --git a/air/src/constraints/chiplets/memory/tests.rs b/air/src/constraints/chiplets/memory/tests.rs index cdb34b117d..fb0cacf934 100644 --- a/air/src/constraints/chiplets/memory/tests.rs +++ b/air/src/constraints/chiplets/memory/tests.rs @@ -1,21 +1,23 @@ use alloc::vec::Vec; use rand_utils::rand_value; +use vm_core::{Felt, FieldElement, WORD_SIZE}; use super::{ - EvaluationFrame, MEMORY_ADDR_COL_IDX, MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, - MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX, MEMORY_D_INV_COL_IDX, MEMORY_V_COL_RANGE, NUM_ELEMENTS, + EvaluationFrame, MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX, + MEMORY_D_INV_COL_IDX, MEMORY_V_COL_RANGE, MEMORY_WORD_COL_IDX, }; use crate::{ chiplets::memory, trace::{ chiplets::{ - memory::{Selectors, MEMORY_COPY_READ, MEMORY_INIT_READ, MEMORY_WRITE}, - MEMORY_TRACE_OFFSET, + memory::{MEMORY_ACCESS_WORD, MEMORY_READ, MEMORY_WRITE}, + MEMORY_FLAG_SAME_CONTEXT_AND_WORD, MEMORY_IDX0_COL_IDX, MEMORY_IDX1_COL_IDX, + MEMORY_IS_READ_COL_IDX, MEMORY_IS_WORD_ACCESS_COL_IDX, }, TRACE_WIDTH, }, - Felt, FieldElement, ONE, ZERO, + ONE, ZERO, }; // UNIT TESTS @@ -23,37 +25,25 @@ use crate::{ #[test] fn test_memory_write() { - let expected = [ZERO; memory::NUM_CONSTRAINTS]; + let expected_constraint_evals = [ZERO; memory::NUM_CONSTRAINTS]; - let old_values = vec![0, 0, 0, 0]; - let new_values = vec![1, 0, 0, 0]; + let old_word = vec![0, 0, 0, 0]; + let new_word = vec![1, 0, 0, 0]; // Write to a new context. - let result = get_constraint_evaluation( - MEMORY_WRITE, - MemoryTestDeltaType::Context, - &old_values, - &new_values, - ); - assert_eq!(expected, result); + let result = + get_constraint_evaluation(MEMORY_WRITE, MemoryTestDeltaType::Context, &old_word, &new_word); + assert_eq!(expected_constraint_evals, result); // Write to a new address in the same context. - let result = get_constraint_evaluation( - MEMORY_WRITE, - MemoryTestDeltaType::Address, - &old_values, - &new_values, - ); - assert_eq!(expected, result); + let result = + get_constraint_evaluation(MEMORY_WRITE, MemoryTestDeltaType::Word, &old_word, &new_word); + assert_eq!(expected_constraint_evals, result); // Write to the same context and address at a new clock cycle. - let result = get_constraint_evaluation( - MEMORY_WRITE, - MemoryTestDeltaType::Clock, - &old_values, - &new_values, - ); - assert_eq!(expected, result); + let result = + get_constraint_evaluation(MEMORY_WRITE, MemoryTestDeltaType::Clock, &old_word, &new_word); + assert_eq!(expected_constraint_evals, result); } #[test] @@ -65,7 +55,7 @@ fn test_memory_read() { // Read from a new context. let result = get_constraint_evaluation( - MEMORY_INIT_READ, + MEMORY_READ, MemoryTestDeltaType::Context, &old_values, &init_values, @@ -74,8 +64,8 @@ fn test_memory_read() { // Read from a new address in the same context. let result = get_constraint_evaluation( - MEMORY_INIT_READ, - MemoryTestDeltaType::Address, + MEMORY_READ, + MemoryTestDeltaType::Word, &old_values, &init_values, ); @@ -83,7 +73,7 @@ fn test_memory_read() { // Read from the same context and address at a new clock cycle. let result = get_constraint_evaluation( - MEMORY_COPY_READ, + MEMORY_READ, MemoryTestDeltaType::Clock, &old_values, &old_values, @@ -100,7 +90,7 @@ fn test_memory_read() { /// - Clock: when the delta occurs in the clock column, context and address must stay fixed. enum MemoryTestDeltaType { Context, - Address, + Word, Clock, } @@ -113,17 +103,17 @@ enum MemoryTestDeltaType { /// - To test a valid read, the `delta_type` must be Clock and the `old_values` and `new_values` /// must be equal. fn get_constraint_evaluation( - selectors: Selectors, + read_write: Felt, delta_type: MemoryTestDeltaType, old_values: &[u32], new_values: &[u32], ) -> [Felt; memory::NUM_CONSTRAINTS] { let delta_row = get_test_delta_row(&delta_type); - let frame = get_test_frame(selectors, &delta_type, &delta_row, old_values, new_values); + let frame = get_test_frame(read_write, &delta_type, &delta_row, old_values, new_values); let mut result = [ZERO; memory::NUM_CONSTRAINTS]; - memory::enforce_constraints(&frame, &mut result, ONE); + memory::enforce_constraints(&frame, &mut result, ONE, ONE, ZERO); result } @@ -141,7 +131,7 @@ fn get_constraint_evaluation( /// row. /// - `new_values`: specifies the new values, which are placed in the value columns of the next row. fn get_test_frame( - selectors: Selectors, + read_write: Felt, delta_type: &MemoryTestDeltaType, delta_row: &[u64], old_values: &[u32], @@ -151,16 +141,16 @@ fn get_test_frame( let mut next = vec![ZERO; TRACE_WIDTH]; // Set the operation in the next row. - next[MEMORY_TRACE_OFFSET] = selectors[0]; - next[MEMORY_TRACE_OFFSET + 1] = selectors[1]; + next[MEMORY_IS_READ_COL_IDX] = read_write; + next[MEMORY_IS_WORD_ACCESS_COL_IDX] = MEMORY_ACCESS_WORD; // Set the context, addr, and clock columns in the next row to the values in the delta row. next[MEMORY_CTX_COL_IDX] = Felt::new(delta_row[0]); - next[MEMORY_ADDR_COL_IDX] = Felt::new(delta_row[1]); + next[MEMORY_WORD_COL_IDX] = Felt::new(delta_row[1]); next[MEMORY_CLK_COL_IDX] = Felt::new(delta_row[2]); // Set the old and new values. - for idx in 0..NUM_ELEMENTS { + for idx in 0..WORD_SIZE { let old_value = Felt::new(old_values[idx] as u64); // Add a write for the old values to the current row. current[MEMORY_V_COL_RANGE.start + idx] = old_value; @@ -177,27 +167,41 @@ fn get_test_frame( let delta: u64 = match delta_type { MemoryTestDeltaType::Clock => delta_row[MemoryTestDeltaType::Clock as usize] - 1, MemoryTestDeltaType::Context => delta_row[MemoryTestDeltaType::Context as usize], - MemoryTestDeltaType::Address => delta_row[MemoryTestDeltaType::Address as usize], + MemoryTestDeltaType::Word => delta_row[MemoryTestDeltaType::Word as usize], }; next[MEMORY_D0_COL_IDX] = Felt::new(delta as u16 as u64); next[MEMORY_D1_COL_IDX] = Felt::new(delta >> 16); next[MEMORY_D_INV_COL_IDX] = (Felt::new(delta)).inv(); + // since we're always writing a word, the idx0 and idx1 columns should be zero + next[MEMORY_IDX0_COL_IDX] = ZERO; + next[MEMORY_IDX1_COL_IDX] = ZERO; + + // If the context or word columns are changed, the "same context and word" flag should be + // zero. + if delta_row[MemoryTestDeltaType::Word as usize] > 0 + || delta_row[MemoryTestDeltaType::Context as usize] > 0 + { + next[MEMORY_FLAG_SAME_CONTEXT_AND_WORD] = ZERO; + } else { + next[MEMORY_FLAG_SAME_CONTEXT_AND_WORD] = ONE; + } + EvaluationFrame::::from_rows(current, next) } -/// Generates a row of valid test values for the context, address, and clock columns according to +/// Generates a row of valid test values for the context, word, and clock columns according to /// the specified delta type, which determines the column over which the delta and delta inverse /// values of the trace would be calculated. /// -/// - When the delta type is Context, the address and clock columns can be anything. -/// - When the delta type is Address, the context must remain unchanged but the clock can change. -/// - When the delta type is Clock, both the context and address columns must remain unchanged. +/// - When the delta type is Context, the word and clock columns can be anything. +/// - When the delta type is Word, the context must remain unchanged but the clock can change. +/// - When the delta type is Clock, both the context and word columns must remain unchanged. fn get_test_delta_row(delta_type: &MemoryTestDeltaType) -> Vec { - let delta_value = rand_value::() as u64; + let delta_value = word_aligned_rand_value() as u64; let mut row = vec![0; 3]; let ctx_idx = MemoryTestDeltaType::Context as usize; - let addr_idx = MemoryTestDeltaType::Address as usize; + let word_addr_idx = MemoryTestDeltaType::Word as usize; let clk_idx = MemoryTestDeltaType::Clock as usize; // Set the context, addr, and clock columns according to the specified delta type. @@ -207,13 +211,13 @@ fn get_test_delta_row(delta_type: &MemoryTestDeltaType) -> Vec { row[ctx_idx] = delta_value; // Set addr and clock in the row column to random values. - row[addr_idx] = rand_value::() as u64; + row[word_addr_idx] = word_aligned_rand_value() as u64; row[clk_idx] = rand_value::() as u64; }, - MemoryTestDeltaType::Address => { + MemoryTestDeltaType::Word => { // Keep the context value the same in current and row rows (leave it as ZERO). // Set the row value for the address. - row[addr_idx] = delta_value; + row[word_addr_idx] = delta_value; // Set clock in the row column to a random value. row[clk_idx] = rand_value::() as u64; @@ -227,3 +231,9 @@ fn get_test_delta_row(delta_type: &MemoryTestDeltaType) -> Vec { row } + +/// Returns a random value that is divisible by 4 (i.e. "word aligned" when treated as an address). +fn word_aligned_rand_value() -> u32 { + let value = rand_value::(); + value - (value % 4) +} diff --git a/air/src/constraints/chiplets/mod.rs b/air/src/constraints/chiplets/mod.rs index ea950755fc..3ecbcae95d 100644 --- a/air/src/constraints/chiplets/mod.rs +++ b/air/src/constraints/chiplets/mod.rs @@ -87,7 +87,13 @@ pub fn enforce_constraints>( constraint_offset += bitwise::get_transition_constraint_count(); // memory transition constraints - memory::enforce_constraints(frame, &mut result[constraint_offset..], frame.memory_flag(false)); + memory::enforce_constraints( + frame, + &mut result[constraint_offset..], + frame.memory_flag(), + frame.memory_flag_not_last_row(), + frame.memory_flag_first_row(), + ); } // TRANSITION CONSTRAINT HELPERS @@ -143,12 +149,21 @@ trait EvaluationFrameExt { /// Flag to indicate whether the frame is in the bitwise portion of the Chiplets trace. fn bitwise_flag(&self) -> E; - /// Flag to indicate whether the frame is in the memory portion of the Chiplets trace. - /// When `include_last_row` is true, the memory flag is true for every row where the memory - /// selectors are set. When false, the last row is excluded. When this flag is used for - /// transition constraints with `include_last_row = false`, they will not be applied to the - /// final row of the memory trace. - fn memory_flag(&self, include_last_row: bool) -> E; + /// Flag to indicate whether the current row of the frame is in the memory portion of the + /// Chiplets trace. + fn memory_flag(&self) -> E; + + /// Flag to indicate whether the current row of the frame is in the memory portion of the + /// Chiplets trace, except for the last memory chiplet row. + fn memory_flag_not_last_row(&self) -> E; + + /// Flag to indicate whether the next row of the frame is in the memory portion of the Chiplets + /// trace. + fn memory_flag_next(&self) -> E; + + /// Flag to indicate whether the next row of the frame is the first row of the memory portion of + /// the Chiplets trace. + fn memory_flag_first_row(&self) -> E; } impl EvaluationFrameExt for &EvaluationFrame { @@ -175,12 +190,23 @@ impl EvaluationFrameExt for &EvaluationFrame { } #[inline(always)] - fn memory_flag(&self, include_last_row: bool) -> E { - if include_last_row { - self.s(0) * self.s(1) * binary_not(self.s(2)) - } else { - self.s(0) * self.s(1) * binary_not(self.s_next(2)) - } + fn memory_flag(&self) -> E { + self.s(0) * self.s(1) * binary_not(self.s(2)) + } + + #[inline(always)] + fn memory_flag_not_last_row(&self) -> E { + self.s(0) * self.s(1) * binary_not(self.s_next(2)) + } + + #[inline(always)] + fn memory_flag_next(&self) -> E { + self.s_next(0) * self.s_next(1) * binary_not(self.s_next(2)) + } + + #[inline(always)] + fn memory_flag_first_row(&self) -> E { + self.hasher_flag() * self.memory_flag_next() } } @@ -196,6 +222,6 @@ pub trait ChipletsFrameExt { impl ChipletsFrameExt for &EvaluationFrame { #[inline(always)] fn chiplets_memory_flag(&self) -> E { - self.memory_flag(true) + self.memory_flag() } } diff --git a/air/src/trace/chiplets/memory.rs b/air/src/trace/chiplets/memory.rs index 6e531d30d1..f7e1af37ac 100644 --- a/air/src/trace/chiplets/memory.rs +++ b/air/src/trace/chiplets/memory.rs @@ -1,53 +1,63 @@ +use vm_core::WORD_SIZE; + use super::{create_range, Felt, Range, ONE, ZERO}; // CONSTANTS // ================================================================================================ /// Number of columns needed to record an execution trace of the memory chiplet. -pub const TRACE_WIDTH: usize = 12; +pub const TRACE_WIDTH: usize = 15; -/// Number of selector columns in the trace. -pub const NUM_SELECTORS: usize = 2; +// --- OPERATION SELECTORS ------------------------------------------------------------------------ -/// Type for Memory trace selectors. -/// -/// These selectors are used to define which operation and memory state update (init & read / copy & -/// read / write) is to be applied at a specific row of the memory execution trace. -pub type Selectors = [Felt; NUM_SELECTORS]; +/// Specifies the value of the `READ_WRITE` column when the operation is a write. +pub const MEMORY_WRITE: Felt = ZERO; +/// Specifies the value of the `READ_WRITE` column when the operation is a read. +pub const MEMORY_READ: Felt = ONE; +/// Specifies the value of the `ELEMENT_OR_WORD` column when the operation is over an element. +pub const MEMORY_ACCESS_ELEMENT: Felt = ZERO; +/// Specifies the value of the `ELEMENT_OR_WORD` column when the operation is over a word. +pub const MEMORY_ACCESS_WORD: Felt = ONE; -// --- OPERATION SELECTORS ------------------------------------------------------------------------ +// --- BUS LABELS ------------------------------------------------------------------------ -/// Specifies an operation that initializes new memory and then reads it. -pub const MEMORY_INIT_READ: Selectors = [ONE, ZERO]; +// All bus labels encode the chiplet selector (1, 1, 0), as well as the read/write and element/word +// columns. The purpose of the label is to force the chiplet to assign the correct values to the +// read/write and element/word columns. We also include the chiplet selector as a unique identifier +// for memory chiplet labels (to ensure they don't collide with labels from other chiplets). -/// Specifies an operation that copies existing memory and then reads it. -pub const MEMORY_COPY_READ: Selectors = [ONE, ONE]; +/// Unique label when r/w=0 and e/w=0. +pub const MEMORY_WRITE_ELEMENT_LABEL: u8 = 0b11000; -/// Specifies a memory write operation. -pub const MEMORY_WRITE: Selectors = [ZERO, ZERO]; +/// Unique label when r/w=0 and e/w=1. +pub const MEMORY_WRITE_WORD_LABEL: u8 = 0b11001; -/// Unique label computed as 1 plus the full chiplet selector with the bits reversed. -/// mem_read selector=[1, 1, 0, 1], rev(selector)=[1, 0, 1, 1], +1=[1, 1, 0, 0] -pub const MEMORY_READ_LABEL: u8 = 0b1100; +/// Unique label when r/w=1 and e/w=0. +pub const MEMORY_READ_ELEMENT_LABEL: u8 = 0b11010; -/// Unique label computed as 1 plus the full chiplet selector with the bits reversed. -/// mem_write selector=[1, 1, 0, 0] rev(selector)=[0, 0, 1, 1] +1=[0, 1, 0, 0] -pub const MEMORY_WRITE_LABEL: u8 = 0b0100; +/// Unique label when r/w=1 and e/w=1. +pub const MEMORY_READ_WORD_LABEL: u8 = 0b11011; // --- COLUMN ACCESSOR INDICES WITHIN THE CHIPLET ------------------------------------------------- -/// The number of elements accessible in one read or write memory access. -pub const NUM_ELEMENTS: usize = 4; - +/// Column to hold whether the operation is a read or write. +pub const IS_READ_COL_IDX: usize = 0; +/// Column to hold the whether the operation was over an element or a word. +pub const IS_WORD_ACCESS_COL_IDX: usize = IS_READ_COL_IDX + 1; /// Column to hold the context ID of the current memory context. -pub const CTX_COL_IDX: usize = NUM_SELECTORS; -/// Column to hold the memory address. -pub const ADDR_COL_IDX: usize = CTX_COL_IDX + 1; +pub const CTX_COL_IDX: usize = IS_WORD_ACCESS_COL_IDX + 1; +/// Column to hold the word (i.e. group of 4 memory slots, referred to by the address of the first +/// slot in the word). +pub const WORD_COL_IDX: usize = CTX_COL_IDX + 1; +/// Column to hold the first bit of the index of the address in the word. +pub const IDX0_COL_IDX: usize = WORD_COL_IDX + 1; +/// Column to hold the second bit of the index of the address in the word. +pub const IDX1_COL_IDX: usize = IDX0_COL_IDX + 1; /// Column for the clock cycle in which the memory operation occurred. -pub const CLK_COL_IDX: usize = ADDR_COL_IDX + 1; -/// Columns to hold the values stored at a given memory context, address, and clock cycle after -/// the memory operation. When reading from a new address, these are initialized to zero. -pub const V_COL_RANGE: Range = create_range(CLK_COL_IDX + 1, NUM_ELEMENTS); +pub const CLK_COL_IDX: usize = IDX1_COL_IDX + 1; +/// Columns to hold the values stored at a given memory context, word, and clock cycle after +/// the memory operation. When reading from a new word, these are initialized to zero. +pub const V_COL_RANGE: Range = create_range(CLK_COL_IDX + 1, WORD_SIZE); /// Column for the lower 16-bits of the delta between two consecutive context IDs, addresses, or /// clock cycles. pub const D0_COL_IDX: usize = V_COL_RANGE.end; @@ -57,3 +67,6 @@ pub const D1_COL_IDX: usize = D0_COL_IDX + 1; /// Column for the inverse of the delta between two consecutive context IDs, addresses, or clock /// cycles, used to enforce that changes are correctly constrained. pub const D_INV_COL_IDX: usize = D1_COL_IDX + 1; +/// Column to hold the flag indicating whether the current memory operation is in the same word and +/// same context as the previous operation. +pub const FLAG_SAME_CONTEXT_AND_WORD: usize = D_INV_COL_IDX + 1; diff --git a/air/src/trace/chiplets/mod.rs b/air/src/trace/chiplets/mod.rs index d892c67e36..5186254c0a 100644 --- a/air/src/trace/chiplets/mod.rs +++ b/air/src/trace/chiplets/mod.rs @@ -86,13 +86,19 @@ pub const BITWISE_OUTPUT_COL_IDX: usize = BITWISE_TRACE_OFFSET + bitwise::OUTPUT // --- GLOBALLY-INDEXED CHIPLET COLUMN ACCESSORS: MEMORY ------------------------------------------ -/// The index within the main trace of the column containing the first memory selector, which -/// indicates the operation (read or write). -pub const MEMORY_SELECTORS_COL_IDX: usize = MEMORY_TRACE_OFFSET; +/// The index within the main trace of the column containing the memory read/write column. +pub const MEMORY_IS_READ_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::IS_READ_COL_IDX; +/// The index within the main trace of the column containing the memory element/word column. +pub const MEMORY_IS_WORD_ACCESS_COL_IDX: usize = + MEMORY_TRACE_OFFSET + memory::IS_WORD_ACCESS_COL_IDX; /// The index within the main trace of the column containing the memory context. pub const MEMORY_CTX_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::CTX_COL_IDX; /// The index within the main trace of the column containing the memory address. -pub const MEMORY_ADDR_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::ADDR_COL_IDX; +pub const MEMORY_WORD_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::WORD_COL_IDX; +/// The index within the main trace of the column containing the 0'th memory index. +pub const MEMORY_IDX0_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::IDX0_COL_IDX; +/// The index within the main trace of the column containing the 1st memory index. +pub const MEMORY_IDX1_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::IDX1_COL_IDX; /// The index within the main trace of the column containing the clock cycle of the memory /// access. pub const MEMORY_CLK_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::CLK_COL_IDX; @@ -111,3 +117,7 @@ pub const MEMORY_D1_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::D1_COL_IDX; /// memory context IDs, addresses, or clock cycles, used to enforce that changes are correctly /// constrained. pub const MEMORY_D_INV_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::D_INV_COL_IDX; +/// Column to hold the flag indicating whether the current memory operation is in the same context +/// and same word as the previous operation. +pub const MEMORY_FLAG_SAME_CONTEXT_AND_WORD: usize = + MEMORY_TRACE_OFFSET + memory::FLAG_SAME_CONTEXT_AND_WORD; diff --git a/air/src/trace/main_trace.rs b/air/src/trace/main_trace.rs index eebc8e2779..9c0d101ff0 100644 --- a/air/src/trace/main_trace.rs +++ b/air/src/trace/main_trace.rs @@ -9,8 +9,8 @@ use super::{ chiplets::{ hasher::{DIGEST_LEN, HASH_CYCLE_LEN, STATE_WIDTH}, BITWISE_A_COL_IDX, BITWISE_B_COL_IDX, BITWISE_OUTPUT_COL_IDX, HASHER_NODE_INDEX_COL_IDX, - HASHER_STATE_COL_RANGE, MEMORY_ADDR_COL_IDX, MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, - MEMORY_V_COL_RANGE, + HASHER_STATE_COL_RANGE, MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, MEMORY_IDX0_COL_IDX, + MEMORY_IDX1_COL_IDX, MEMORY_V_COL_RANGE, MEMORY_WORD_COL_IDX, }, decoder::{ GROUP_COUNT_COL_IDX, HASHER_STATE_OFFSET, IN_SPAN_COL_IDX, IS_CALL_FLAG_COL_IDX, @@ -371,8 +371,18 @@ impl MainTrace { } /// Returns the i-th row of the chiplet column containing memory address. - pub fn chiplet_memory_addr(&self, i: RowIndex) -> Felt { - self.columns.get_column(MEMORY_ADDR_COL_IDX)[i] + pub fn chiplet_memory_word(&self, i: RowIndex) -> Felt { + self.columns.get_column(MEMORY_WORD_COL_IDX)[i] + } + + /// Returns the i-th row of the chiplet column containing 0th bit of the word index. + pub fn chiplet_memory_idx0(&self, i: RowIndex) -> Felt { + self.columns.get_column(MEMORY_IDX0_COL_IDX)[i] + } + + /// Returns the i-th row of the chiplet column containing 1st bit of the word index. + pub fn chiplet_memory_idx1(&self, i: RowIndex) -> Felt { + self.columns.get_column(MEMORY_IDX1_COL_IDX)[i] } /// Returns the i-th row of the chiplet column containing clock cycle. diff --git a/air/src/trace/mod.rs b/air/src/trace/mod.rs index 62c7a910f5..6c2c109268 100644 --- a/air/src/trace/mod.rs +++ b/air/src/trace/mod.rs @@ -19,7 +19,7 @@ pub const MIN_TRACE_LEN: usize = 64; // ------------------------------------------------------------------------------------------------ // system decoder stack range checks chiplets -// (8 columns) (24 columns) (19 columns) (3 columns) (17 columns) +// (8 columns) (24 columns) (19 columns) (2 columns) (18 columns) // ├───────────────┴───────────────┴───────────────┴───────────────┴─────────────────┤ pub const SYS_TRACE_OFFSET: usize = 0; @@ -51,7 +51,7 @@ pub const RANGE_CHECK_TRACE_RANGE: Range = // Chiplets trace pub const CHIPLETS_OFFSET: usize = RANGE_CHECK_TRACE_RANGE.end; -pub const CHIPLETS_WIDTH: usize = 17; +pub const CHIPLETS_WIDTH: usize = 18; pub const CHIPLETS_RANGE: Range = range(CHIPLETS_OFFSET, CHIPLETS_WIDTH); pub const TRACE_WIDTH: usize = CHIPLETS_OFFSET + CHIPLETS_WIDTH; diff --git a/assembly/src/assembler/instruction/env_ops.rs b/assembly/src/assembler/instruction/env_ops.rs index 2026af71f5..8688bc89ce 100644 --- a/assembly/src/assembler/instruction/env_ops.rs +++ b/assembly/src/assembler/instruction/env_ops.rs @@ -44,7 +44,7 @@ pub fn locaddr( index: u16, proc_ctx: &ProcedureContext, ) -> Result<(), AssemblyError> { - local_to_absolute_addr(block_builder, index, proc_ctx.num_locals()) + local_to_absolute_addr(block_builder, index, proc_ctx.num_locals(), true) } /// Appends CALLER operation to the span which puts the hash of the function which initiated the diff --git a/assembly/src/assembler/instruction/mem_ops.rs b/assembly/src/assembler/instruction/mem_ops.rs index cdfbdc79c5..c3eaa36332 100644 --- a/assembly/src/assembler/instruction/mem_ops.rs +++ b/assembly/src/assembler/instruction/mem_ops.rs @@ -33,7 +33,7 @@ pub fn mem_read( if let Some(addr) = addr { if is_local { let num_locals = proc_ctx.num_locals(); - local_to_absolute_addr(block_builder, addr as u16, num_locals)?; + local_to_absolute_addr(block_builder, addr as u16, num_locals, is_single)?; } else { push_u32_value(block_builder, addr); } @@ -81,7 +81,7 @@ pub fn mem_write_imm( is_single: bool, ) -> Result<(), AssemblyError> { if is_local { - local_to_absolute_addr(block_builder, addr as u16, proc_ctx.num_locals())?; + local_to_absolute_addr(block_builder, addr as u16, proc_ctx.num_locals(), is_single)?; } else { push_u32_value(block_builder, addr); } @@ -100,8 +100,8 @@ pub fn mem_write_imm( // ================================================================================================ /// Appends a sequence of operations to the span needed for converting procedure local index to -/// absolute memory address. This consists of putting index onto the stack and then executing -/// LOCADDR operation. +/// absolute memory address. This consists in calculating the offset of the local value from the +/// frame pointer and pushing the result onto the stack. /// /// This operation takes: /// - 3 VM cycles if index == 1 @@ -111,8 +111,9 @@ pub fn mem_write_imm( /// Returns an error if index is greater than the number of procedure locals. pub fn local_to_absolute_addr( block_builder: &mut BasicBlockBuilder, - index: u16, + index_of_local: u16, num_proc_locals: u16, + is_single: bool, ) -> Result<(), AssemblyError> { if num_proc_locals == 0 { return Err(AssemblyError::Other( @@ -124,10 +125,21 @@ pub fn local_to_absolute_addr( )); } - let max = num_proc_locals - 1; - validate_param(index, 0..=max)?; + // If a single local value is being accessed, then the index can take the full range + // [0, num_proc_locals - 1]. Otherwise, the index can take the range [0, num_proc_locals - 4] + // to account for the fact that a full word is being accessed. + let max = if is_single { + num_proc_locals - 1 + } else { + num_proc_locals - 4 + }; + validate_param(index_of_local, 0..=max)?; - push_felt(block_builder, -Felt::from(max - index)); + // Local values are placed under the frame pointer, so we need to calculate the offset of the + // local value from the frame pointer. + // The offset is in the range [1, num_proc_locals], which is then subtracted from `fmp`. + let fmp_offset_of_local = num_proc_locals - index_of_local; + push_felt(block_builder, -Felt::from(fmp_offset_of_local)); block_builder.push_op(FmpAdd); Ok(()) diff --git a/assembly/src/assembler/instruction/mod.rs b/assembly/src/assembler/instruction/mod.rs index 34b45bc156..889e7c3685 100644 --- a/assembly/src/assembler/instruction/mod.rs +++ b/assembly/src/assembler/instruction/mod.rs @@ -1,7 +1,7 @@ use core::ops::RangeBounds; use miette::miette; -use vm_core::{mast::MastNodeId, Decorator, ONE, ZERO}; +use vm_core::{debuginfo::Spanned, mast::MastNodeId, Decorator, ONE, WORD_SIZE, ZERO}; use super::{ast::InvokeKind, Assembler, BasicBlockBuilder, Felt, Operation, ProcedureContext}; use crate::{ast::Instruction, utils::bound_into_included_u64, AssemblyError, Span}; @@ -331,13 +331,21 @@ impl Assembler { true, true, )?, - Instruction::LocLoadW(v) => mem_ops::mem_read( - block_builder, - proc_ctx, - Some(v.expect_value() as u32), - true, - false, - )?, + Instruction::LocLoadW(v) => { + let local_addr = v.expect_value(); + if local_addr % WORD_SIZE as u16 != 0 { + return Err(AssemblyError::InvalidLocalWordIndex { + span: instruction.span(), + source_file: proc_ctx + .source_manager() + .get(proc_ctx.span().source_id()) + .ok(), + local_addr, + }); + } + + mem_ops::mem_read(block_builder, proc_ctx, Some(local_addr as u32), true, false)? + }, Instruction::MemStore => block_builder.push_ops([MStore, Drop]), Instruction::MemStoreW => block_builder.push_ops([MStoreW]), Instruction::MemStoreImm(v) => { @@ -353,14 +361,21 @@ impl Assembler { true, true, )?, - Instruction::LocStoreW(v) => mem_ops::mem_write_imm( - block_builder, - proc_ctx, - v.expect_value() as u32, - true, - false, - )?, + Instruction::LocStoreW(v) => { + let local_addr = v.expect_value(); + if local_addr % WORD_SIZE as u16 != 0 { + return Err(AssemblyError::InvalidLocalWordIndex { + span: instruction.span(), + source_file: proc_ctx + .source_manager() + .get(proc_ctx.span().source_id()) + .ok(), + local_addr, + }); + } + mem_ops::mem_write_imm(block_builder, proc_ctx, local_addr as u32, true, false)? + }, Instruction::SysEvent(system_event) => { block_builder.push_system_event(system_event.into()) }, diff --git a/assembly/src/assembler/mod.rs b/assembly/src/assembler/mod.rs index d826838376..25df78c870 100644 --- a/assembly/src/assembler/mod.rs +++ b/assembly/src/assembler/mod.rs @@ -7,7 +7,7 @@ use vm_core::{ crypto::hash::RpoDigest, debuginfo::SourceSpan, mast::{DecoratorId, MastNodeId}, - DecoratorList, Felt, Kernel, Operation, Program, + DecoratorList, Felt, Kernel, Operation, Program, WORD_SIZE, }; use crate::{ @@ -542,19 +542,21 @@ impl Assembler { ) -> Result { // Make sure the current procedure context is available during codegen let gid = proc_ctx.id(); + let num_locals = proc_ctx.num_locals(); let wrapper_proc = self.module_graph.get_procedure_unsafe(gid); let proc = wrapper_proc.unwrap_ast().unwrap_procedure(); let proc_body_id = if num_locals > 0 { - // for procedures with locals, we need to update fmp register before and after the - // procedure body is executed. specifically: + // For procedures with locals, we need to update fmp register before and after the + // procedure body is executed. Specifically: // - to allocate procedure locals we need to increment fmp by the number of locals - // - to deallocate procedure locals we need to decrement it by the same amount - let num_locals = Felt::from(num_locals); + // (rounded up to the word size), and + // - to deallocate procedure locals we need to decrement it by the same amount. + let locals_frame = Felt::from(num_locals.next_multiple_of(WORD_SIZE as u16)); let wrapper = BodyWrapper { - prologue: vec![Operation::Push(num_locals), Operation::FmpUpdate], - epilogue: vec![Operation::Push(-num_locals), Operation::FmpUpdate], + prologue: vec![Operation::Push(locals_frame), Operation::FmpUpdate], + epilogue: vec![Operation::Push(-locals_frame), Operation::FmpUpdate], }; self.compile_body(proc.iter(), &mut proc_ctx, Some(wrapper), mast_forest_builder)? } else { diff --git a/assembly/src/assembler/procedure.rs b/assembly/src/assembler/procedure.rs index 167a25806e..591cd5ac76 100644 --- a/assembly/src/assembler/procedure.rs +++ b/assembly/src/assembler/procedure.rs @@ -44,6 +44,7 @@ impl ProcedureContext { } } + /// Sets the number of locals to allocate for the procedure. pub fn with_num_locals(mut self, num_locals: u16) -> Self { self.num_locals = num_locals; self diff --git a/assembly/src/ast/tests.rs b/assembly/src/ast/tests.rs index dd9e910297..a708143e2e 100644 --- a/assembly/src/ast/tests.rs +++ b/assembly/src/ast/tests.rs @@ -1120,7 +1120,7 @@ fn assert_parsing_line_invalid_op() { while.true push.5.7 u32wrapping_add - loc_store.1 + loc_store.4 push.0 end diff --git a/assembly/src/errors.rs b/assembly/src/errors.rs index bee90c05f1..06ff9ab3b9 100644 --- a/assembly/src/errors.rs +++ b/assembly/src/errors.rs @@ -61,6 +61,15 @@ pub enum AssemblyError { source_file: Option>, callee: QualifiedProcedureName, }, + #[error("invalid local word index: {local_addr}")] + #[diagnostic(help("the index to a local word must be a multiple of 4"))] + InvalidLocalWordIndex { + #[label] + span: SourceSpan, + #[source_code] + source_file: Option>, + local_addr: u16, + }, #[error("invalid use of 'caller' instruction outside of kernel")] #[diagnostic(help( "the 'caller' instruction is only allowed in procedures defined in a kernel" diff --git a/assembly/src/tests.rs b/assembly/src/tests.rs index 0cc537e3a2..e62802cce9 100644 --- a/assembly/src/tests.rs +++ b/assembly/src/tests.rs @@ -822,12 +822,12 @@ fn mem_operations_with_constants() -> TestResult { // Define constant values const PROC_LOC_STORE_PTR: u64 = 0; const PROC_LOC_LOAD_PTR: u64 = 1; - const PROC_LOC_STOREW_PTR: u64 = 2; - const PROC_LOC_LOADW_PTR: u64 = 3; - const GLOBAL_STORE_PTR: u64 = 4; - const GLOBAL_LOAD_PTR: u64 = 5; - const GLOBAL_STOREW_PTR: u64 = 6; - const GLOBAL_LOADW_PTR: u64 = 7; + const PROC_LOC_STOREW_PTR: u64 = 4; + const PROC_LOC_LOADW_PTR: u64 = 8; + const GLOBAL_STORE_PTR: u64 = 12; + const GLOBAL_LOAD_PTR: u64 = 13; + const GLOBAL_STOREW_PTR: u64 = 16; + const GLOBAL_LOADW_PTR: u64 = 20; let source = source_file!( &context, @@ -842,7 +842,7 @@ fn mem_operations_with_constants() -> TestResult { const.GLOBAL_STOREW_PTR={GLOBAL_STOREW_PTR} const.GLOBAL_LOADW_PTR={GLOBAL_LOADW_PTR} - proc.test_const_loc.4 + proc.test_const_loc.12 # constant should resolve using locaddr operation locaddr.PROC_LOC_STORE_PTR @@ -885,7 +885,7 @@ fn mem_operations_with_constants() -> TestResult { &context, format!( "\ - proc.test_const_loc.4 + proc.test_const_loc.12 # constant should resolve using locaddr operation locaddr.{PROC_LOC_STORE_PTR} @@ -1793,36 +1793,37 @@ fn program_with_proc_locals() -> TestResult { let source = source_file!( &context, "\ - proc.foo.1 \ + proc.foo.4 \ loc_store.0 \ add \ loc_load.0 \ mul \ end \ begin \ - push.4 push.3 push.2 \ + push.10 push.9 push.8 \ exec.foo \ end" ); let program = context.assemble(source)?; + // Note: 18446744069414584317 == -4 (mod 2^64 - 2^32 + 1) let expected = "\ begin basic_block + push(10) + push(9) + push(8) push(4) - push(3) - push(2) - push(1) fmpupdate - pad + push(18446744069414584317) fmpadd mstore drop add - pad + push(18446744069414584317) fmpadd mload mul - push(18446744069414584320) + push(18446744069414584317) fmpupdate end end"; @@ -2924,7 +2925,7 @@ fn test_reexported_proc_with_same_name_as_local_proc_diff_locals() { let mod1 = { let source = source_file!( &context, - "export.foo.2 + "export.foo.8 push.1 drop end @@ -2961,7 +2962,7 @@ fn test_reexported_proc_with_same_name_as_local_proc_diff_locals() { use.test::mod1 use.test::mod2 - proc.foo.1 + proc.foo.4 exec.mod1::foo exec.mod2::foo end diff --git a/core/src/debuginfo/source_file.rs b/core/src/debuginfo/source_file.rs index 2f7fc69f76..9b750a6e0d 100644 --- a/core/src/debuginfo/source_file.rs +++ b/core/src/debuginfo/source_file.rs @@ -515,7 +515,7 @@ impl SourceContent { /// /// Returns `None` if the given index is out of bounds pub fn line_start(&self, line_index: LineIndex) -> Option { - self.line_starts.get(line_index.to_usize()).copied().map(ByteIndex::from) + self.line_starts.get(line_index.to_usize()).copied() } /// Returns the index of the last line in this file diff --git a/docs/src/user_docs/stdlib/collections.md b/docs/src/user_docs/stdlib/collections.md index 3b3b26138a..759ef1f640 100644 --- a/docs/src/user_docs/stdlib/collections.md +++ b/docs/src/user_docs/stdlib/collections.md @@ -15,7 +15,12 @@ The following procedures are available to read data from and make updates to a M | get | Loads the leaf at the absolute position `pos` in the MMR onto the stack.

Valid range for `pos` is between $0$ and $2^{32} - 1$ (both inclusive).

Inputs: `[pos, mmr_ptr, ...]`
Output: `[N, ...]`

Where `N` is the leaf loaded from the MMR whose memory location starts at `mmr_ptr`. | | add | Adds a new leaf to the MMR.

This will update the MMR peaks in the VM's memory and the advice provider with any merged nodes.

Inputs: `[N, mmr_ptr, ...]`
Outputs: `[...]`

Where `N` is the leaf added to the MMR whose memory locations starts at `mmr_ptr`. | | pack | Computes a commitment to the given MMR and copies the MMR to the Advice Map using the commitment as a key.

Inputs: `[mmr_ptr, ...]`
Outputs: `[HASH, ...]`

| -| unpack | Load the MMR peak data based on its hash.

Inputs: `[HASH, mmr_ptr, ...]`
Outputs: `[...]`

Where:
- `HASH`: is the MMR peak hash, the hash is expected to be padded to an even length and to have a minimum size of 16 elements.
- The advice map must contain a key with `HASH`, and its value is `num_leaves \|\| hash_data`, and hash_data is the data used to computed `HASH`
- `mmt_ptr`: the memory location where the MMR data will be written, starting with the MMR forest (the total count of its leaves) followed by its peaks. | +| unpack | Writes the MMR who's peaks hash to `HASH` to the memory location pointed to by `mmr_ptr`.

Inputs: `[HASH, mmr_ptr, ...]`
Outputs: `[...]`

Where:
- `HASH`: is the MMR peak hash, the hash is expected to be padded to an even length and to have a minimum size of 16 elements.
- The advice map must contain a key with `HASH`, and its value is `[num_leaves, 0, 0, 0] \|\| hash_data`, and hash_data is the data used to computed `HASH`
- `mmr_ptr`: the memory location where the MMR data will be written, starting with the MMR forest (the total count of its leaves) followed by its peaks. The memory location must be word-aligned. | + +`mmr_ptr` is a pointer to the `mmr` data structure, which is defined as: +1. `mmr_ptr[0]` contains the number of leaves in the MMR +2. `mmr_ptr[1..4]` are padding and are ignored +3. `mmr_ptr[4..8], mmr_ptr[8..12], ...` contain the 1st MMR peak, 2nd MMR peak, etc. ## Sparse Merkle Tree diff --git a/docs/src/user_docs/stdlib/mem.md b/docs/src/user_docs/stdlib/mem.md index 1c9360116d..399277c747 100644 --- a/docs/src/user_docs/stdlib/mem.md +++ b/docs/src/user_docs/stdlib/mem.md @@ -3,7 +3,7 @@ Module `std::mem` contains a set of utility procedures for working with random a | Procedure | Description | | ----------- | ------------- | -| memcopy | Copies `n` words from `read_ptr` to `write_ptr`.

Stack transition looks as follows:

[n, read_ptr, write_ptr, ...] -> [...]

Cycles: 15 + 16n | +| memcopy_words | Copies `n` words from `read_ptr` to `write_ptr`; both pointers must be word-aligned.

Stack transition looks as follows:

[n, read_ptr, write_ptr, ...] -> [...]

Cycles: 15 + 16n | | pipe_double_words_to_memory | Moves an even number of words from the advice stack to memory.

Input: [C, B, A, write_ptr, end_ptr, ...]
Output: [C, B, A, write_ptr, ...]

Where:
- The words C, B, and A are the RPO hasher state
- A is the capacity
- C, B are the rate portion of the state
- The value `num_words = end_ptr - write_ptr` must be positive and even

Cycles: 10 + 9 * num_words / 2 | | pipe_words_to_memory | Moves an arbitrary number of words from the advice stack to memory.

Input: [num_words, write_ptr, ...]
Output: [HASH, write_ptr', ...]

Where `HASH` is the sequential RPO hash of all copied words.

Cycles:
- Even num_words: 48 + 9 * num_words / 2
- Odd num_words: 65 + 9 * round_down(num_words / 2) | | pipe_preimage_to_memory | Moves an arbitrary number of words from the advice stack to memory and asserts it matches the commitment.

Input: [num_words, write_ptr, COM, ...]
Output: [write_ptr', ...]

Cycles:
- Even num_words: 58 + 9 * num_words / 2
- Odd num_words: 75 + 9 * round_down(num_words / 2) | diff --git a/miden/masm-examples/debug/debug.masm b/miden/masm-examples/debug/debug.masm index 0c4c27f237..a79f24b018 100644 --- a/miden/masm-examples/debug/debug.masm +++ b/miden/masm-examples/debug/debug.masm @@ -1,4 +1,4 @@ -proc.foo.3 +proc.foo.2 push.11 loc_store.0 push.101 @@ -12,7 +12,7 @@ proc.foo.3 # will fail: debug.local.1.65540 end -proc.bar.4 +proc.bar.2 push.21 loc_store.0 push.121 @@ -39,4 +39,7 @@ begin exec.foo exec.bar + + # Clean stack + dropw end diff --git a/miden/src/cli/debug/executor.rs b/miden/src/cli/debug/executor.rs index d2a145fe03..c068175fc2 100644 --- a/miden/src/cli/debug/executor.rs +++ b/miden/src/cli/debug/executor.rs @@ -1,10 +1,9 @@ use std::sync::Arc; -use miden_vm::{ - math::Felt, DefaultHost, MemAdviceProvider, Program, StackInputs, VmState, VmStateIterator, -}; +use miden_vm::{DefaultHost, MemAdviceProvider, Program, StackInputs, VmState, VmStateIterator}; use super::DebugCommand; +use crate::utils::print_mem_address; /// Holds debugger state and iterator used for debugging. pub struct DebugExecutor { @@ -44,7 +43,7 @@ impl DebugExecutor { // MODIFIERS // -------------------------------------------------------------------------------------------- - /// executes a debug command against the vm in it's current state. + /// Executes a debug command against the vm in it's current state. pub fn execute(&mut self, command: DebugCommand) -> bool { match command { DebugCommand::Continue => { @@ -122,12 +121,12 @@ impl DebugExecutor { // ACCESSORS // -------------------------------------------------------------------------------------------- - /// print general VM state information. + /// Prints general VM state information. fn print_vm_state(&self) { println!("{}", self.vm_state) } - /// print all stack items. + /// Prints all stack items. pub fn print_stack(&self) { println!( "{}", @@ -141,7 +140,7 @@ impl DebugExecutor { ) } - /// print specified stack item. + /// Prints specified stack item. pub fn print_stack_item(&self, index: usize) { let len = self.vm_state.stack.len(); println!("stack len {}", len); @@ -152,14 +151,14 @@ impl DebugExecutor { } } - /// print all memory entries. + /// Prints all memory entries. pub fn print_memory(&self) { - for (address, mem) in self.vm_state.memory.iter() { - Self::print_memory_data(address, mem) + for &(address, mem) in self.vm_state.memory.iter() { + print_mem_address(address, mem) } } - /// print specified memory entry. + /// Prints specified memory entry. pub fn print_memory_entry(&self, address: u64) { let entry = self.vm_state.memory.iter().find_map(|(addr, mem)| match address == *addr { true => Some(mem), @@ -167,7 +166,7 @@ impl DebugExecutor { }); match entry { - Some(mem) => Self::print_memory_data(&address, mem), + Some(&mem) => print_mem_address(address, mem), None => println!("memory at address '{address}' not found"), } } @@ -175,13 +174,7 @@ impl DebugExecutor { // HELPERS // -------------------------------------------------------------------------------------------- - /// print memory data. - fn print_memory_data(address: &u64, memory: &[Felt]) { - let mem_int = memory.iter().map(|&x| x.as_int()).collect::>(); - println!("{address} {mem_int:?}"); - } - - /// print help message + /// Prints help message fn print_help() { let message = "---------------------------------------------------------------------\n\ Miden Assembly Debug CLI\n\ diff --git a/miden/src/main.rs b/miden/src/main.rs index 85d15d73a7..f5ead91f0c 100644 --- a/miden/src/main.rs +++ b/miden/src/main.rs @@ -10,6 +10,8 @@ mod cli; mod repl; mod tools; +pub(crate) mod utils; + /// Root CLI struct #[derive(Parser, Debug)] #[clap(name = "Miden", about = "Miden CLI", version, rename_all = "kebab-case")] diff --git a/miden/src/repl/mod.rs b/miden/src/repl/mod.rs index 84dfe8df47..037df6e0fd 100644 --- a/miden/src/repl/mod.rs +++ b/miden/src/repl/mod.rs @@ -1,11 +1,13 @@ use std::{collections::BTreeSet, path::PathBuf}; use assembly::{Assembler, Library}; -use miden_vm::{math::Felt, DefaultHost, StackInputs, Word}; +use miden_vm::{math::Felt, DefaultHost, StackInputs}; use processor::ContextId; use rustyline::{error::ReadlineError, DefaultEditor}; use stdlib::StdLibrary; +use crate::utils::print_mem_address; + // This work is in continuation to the amazing work done by team `Scribe` // [here](https://github.com/ControlCplusControlV/Scribe/blob/main/transpiler/src/repl.rs#L8) // @@ -171,7 +173,7 @@ pub fn start_repl(library_paths: &Vec, use_stdlib: bool) { let mut should_print_stack = false; // state of the entire memory at the latest clock cycle. - let mut memory: Vec<(u64, Word)> = Vec::new(); + let mut memory: Vec<(u64, Felt)> = Vec::new(); // initializing readline. let mut rl = DefaultEditor::new().expect("Readline couldn't be initialized"); @@ -224,9 +226,9 @@ pub fn start_repl(library_paths: &Vec, use_stdlib: bool) { println!("The memory has not been initialized yet"); continue; } - for (addr, mem) in &memory { + for &(addr, mem) in &memory { // prints out the address and memory value at that address. - print_mem_address(*addr, mem); + print_mem_address(addr, mem); } } else if line.len() > 6 && &line[..5] == "!mem[" { // if user wants to see the state of a particular address in a memory, the input @@ -238,8 +240,8 @@ pub fn start_repl(library_paths: &Vec, use_stdlib: bool) { // extracts the address from user input. match read_mem_address(&line) { Ok(addr) => { - for (i, memory_value) in &memory { - if *i == addr { + for &(i, memory_value) in &memory { + if i == addr { // prints the address and memory value at that address. print_mem_address(addr, memory_value); // sets the flag to true as the address has been initialized. @@ -305,7 +307,7 @@ pub fn start_repl(library_paths: &Vec, use_stdlib: bool) { fn execute( program: String, provided_libraries: &[Library], -) -> Result<(Vec<(u64, Word)>, Vec), String> { +) -> Result<(Vec<(u64, Felt)>, Vec), String> { // compile program let mut assembler = Assembler::default(); @@ -329,7 +331,7 @@ fn execute( } // loads the memory at the latest clock cycle. - let mem_state = chiplets.get_mem_state_at(ContextId::root(), system.clk()); + let mem_state = chiplets.memory().get_state_at(ContextId::root(), system.clk()); // loads the stack along with the overflow values at the latest clock cycle. let stack_state = stack.get_state_at(system.clk()); @@ -401,10 +403,3 @@ fn print_stack(stack: Vec) { // converts the stack which is a vector of felt into string and prints it. println!("{}", stack.iter().map(|f| format!("{}", f)).collect::>().join(" "),) } - -/// Accepts and returns a memory at an address by converting its register into integer -/// from Felt. -fn print_mem_address(addr: u64, mem: &Word) { - let mem_int = mem.iter().map(|&x| x.as_int()).collect::>(); - println!("{} {:?}", addr, mem_int) -} diff --git a/miden/src/utils.rs b/miden/src/utils.rs new file mode 100644 index 0000000000..a83c65e753 --- /dev/null +++ b/miden/src/utils.rs @@ -0,0 +1,6 @@ +use vm_core::Felt; + +/// Prints the memory address along with the memory value at that address. +pub fn print_mem_address(addr: u64, mem_value: Felt) { + println!("{addr} {mem_value}") +} diff --git a/miden/tests/integration/air/chiplets/memory.rs b/miden/tests/integration/air/chiplets/memory.rs index b339720640..b558277b02 100644 --- a/miden/tests/integration/air/chiplets/memory.rs +++ b/miden/tests/integration/air/chiplets/memory.rs @@ -1,4 +1,4 @@ -use test_utils::{build_op_test, build_test, ToElements}; +use test_utils::{build_op_test, build_test}; #[test] fn mem_load() { @@ -15,29 +15,6 @@ fn mem_store() { build_op_test!(asm_op, &pub_inputs).prove_and_verify(pub_inputs, false); } -#[test] -fn helper_mem_store() { - // Sequence of operations: [Span, Pad, MStoreW, Drop, Drop, Drop, Drop, Pad, Mstore, Drop, Pad, - // MStoreW, Drop, Pad, Mstore, Drop] - let asm_op = - "begin mem_storew.0 drop drop drop drop mem_store.0 mem_storew.0 drop mem_store.0 end"; - let pub_inputs = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - - let trace = build_test!(asm_op, &pub_inputs).execute().unwrap(); - // Since MStore only writes 1 element to memory, the 3 elements in the word at that location - // that are not touched are placed in the helper registers. - let helper_regs = [10, 9, 8, 0, 0, 0].to_elements(); - // We need to check helper registers state after the MStore operation at clock cycle 8. - assert_eq!(helper_regs, trace.get_user_op_helpers_at(8)); - // After the second MStoreW call, the helper registers should be zero. - let helper_regs = [0, 0, 0, 0, 0, 0].to_elements(); - assert_eq!(helper_regs, trace.get_user_op_helpers_at(11)); - - // We need to check helper registers state after the MStore operation at clock cycle 14. - let helper_regs = [5, 4, 3, 0, 0, 0].to_elements(); - assert_eq!(helper_regs, trace.get_user_op_helpers_at(14)); -} - #[test] fn mem_loadw() { let asm_op = "mem_loadw.0"; @@ -62,20 +39,6 @@ fn write_read() { build_test!(source, &pub_inputs).prove_and_verify(pub_inputs, false); } -#[test] -fn helper_write_read() { - // Sequence of operations: [Span, Pad, MStorew, Drop, Drop, Drop, Drop, Pad, MLoad, ... ] - let source = "begin mem_storew.0 dropw mem_load.0 movup.4 drop end"; - let pub_inputs = vec![4, 3, 2, 1]; - - let trace = build_test!(source, &pub_inputs).execute().unwrap(); - // When the MLoad operation is called, word elements that were not pushed on the stack - // are written to helper registers. So, 3, 2 and 1 will be written after this operation - let helper_regs = [1, 2, 3, 0, 0, 0].to_elements(); - // We need to check helper registers state after first MLoad, which index is 8 - assert_eq!(helper_regs, trace.get_user_op_helpers_at(8)); -} - #[test] fn update() { let source = " @@ -92,7 +55,7 @@ fn update() { #[test] fn incr_write_addr() { - let source = "begin mem_storew.0 mem_storew.1 end"; + let source = "begin mem_storew.0 mem_storew.4 end"; let pub_inputs = vec![4, 3, 2, 1]; build_test!(source, &pub_inputs).prove_and_verify(pub_inputs, false); diff --git a/miden/tests/integration/exec_iters.rs b/miden/tests/integration/exec_iters.rs index dd9b4f1208..4f05bd82ee 100644 --- a/miden/tests/integration/exec_iters.rs +++ b/miden/tests/integration/exec_iters.rs @@ -5,6 +5,7 @@ use vm_core::{debuginfo::Location, AssemblyOp, Operation}; // EXEC ITER TESTS // ================================================================= /// TODO: Reenable (and fix) after we stabilized the assembler +/// Note: expect the memory values to be very wrong. #[test] #[ignore] fn test_exec_iter() { @@ -18,7 +19,8 @@ fn test_exec_iter() { let traces = test.execute_iter(); let fmp = Felt::new(2u64.pow(30)); let next_fmp = fmp + ONE; - let mem = vec![(1_u64, slice_to_word(&[13, 14, 15, 16]))]; + // TODO: double check this value + let mem = vec![(1_u64, Felt::from(13_u32))]; let mem_storew1_loc = Some(Location { path: path.clone(), start: 33.into(), @@ -309,10 +311,7 @@ fn test_exec_iter() { )), stack: [17, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0].to_elements(), fmp: next_fmp, - memory: vec![ - (1_u64, slice_to_word(&[13, 14, 15, 16])), - (2u64.pow(30) + 1, slice_to_word(&[17, 0, 0, 0])), - ], + memory: vec![(1_u64, 13_u32.into()), (2u64.pow(30) + 1, 17_u32.into())], }, VmState { clk: RowIndex::from(19), @@ -330,10 +329,7 @@ fn test_exec_iter() { )), stack: [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0].to_elements(), fmp: next_fmp, - memory: vec![ - (1_u64, slice_to_word(&[13, 14, 15, 16])), - (2u64.pow(30) + 1, slice_to_word(&[17, 0, 0, 0])), - ], + memory: vec![(1_u64, 13_u32.into()), (2u64.pow(30) + 1, 17_u32.into())], }, VmState { clk: RowIndex::from(20), @@ -343,10 +339,7 @@ fn test_exec_iter() { stack: [18446744069414584320, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0] .to_elements(), fmp: next_fmp, - memory: vec![ - (1_u64, slice_to_word(&[13, 14, 15, 16])), - (2u64.pow(30) + 1, slice_to_word(&[17, 0, 0, 0])), - ], + memory: vec![(1_u64, 13_u32.into()), (2u64.pow(30) + 1, 17_u32.into())], }, VmState { clk: RowIndex::from(21), @@ -355,10 +348,7 @@ fn test_exec_iter() { asmop: None, stack: [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0].to_elements(), fmp, - memory: vec![ - (1_u64, slice_to_word(&[13, 14, 15, 16])), - (2u64.pow(30) + 1, slice_to_word(&[17, 0, 0, 0])), - ], + memory: vec![(1_u64, 13_u32.into()), (2u64.pow(30) + 1, 17_u32.into())], }, VmState { clk: RowIndex::from(22), @@ -367,10 +357,7 @@ fn test_exec_iter() { asmop: None, stack: [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0].to_elements(), fmp, - memory: vec![ - (1_u64, slice_to_word(&[13, 14, 15, 16])), - (2u64.pow(30) + 1, slice_to_word(&[17, 0, 0, 0])), - ], + memory: vec![(1_u64, 13_u32.into()), (2u64.pow(30) + 1, 17_u32.into())], }, VmState { clk: RowIndex::from(23), @@ -379,10 +366,7 @@ fn test_exec_iter() { asmop: None, stack: [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0].to_elements(), fmp, - memory: vec![ - (1_u64, slice_to_word(&[13, 14, 15, 16])), - (2u64.pow(30) + 1, slice_to_word(&[17, 0, 0, 0])), - ], + memory: vec![(1_u64, 13_u32.into()), (2u64.pow(30) + 1, 17_u32.into())], }, VmState { clk: RowIndex::from(24), @@ -391,10 +375,7 @@ fn test_exec_iter() { asmop: None, stack: [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0].to_elements(), fmp, - memory: vec![ - (1_u64, slice_to_word(&[13, 14, 15, 16])), - (2u64.pow(30) + 1, slice_to_word(&[17, 0, 0, 0])), - ], + memory: vec![(1_u64, 13_u32.into()), (2u64.pow(30) + 1, 17_u32.into())], }, ]; for (expected, t) in expected_states.iter().zip(traces) { @@ -402,14 +383,3 @@ fn test_exec_iter() { assert_eq!(*expected, *state); } } - -// HELPER FUNCTIONS -// ================================================================= -fn slice_to_word(values: &[i32]) -> [Felt; 4] { - [ - Felt::new(values[0] as u64), - Felt::new(values[1] as u64), - Felt::new(values[2] as u64), - Felt::new(values[3] as u64), - ] -} diff --git a/miden/tests/integration/flow_control/mod.rs b/miden/tests/integration/flow_control/mod.rs index ccbbc1c552..819741b7af 100644 --- a/miden/tests/integration/flow_control/mod.rs +++ b/miden/tests/integration/flow_control/mod.rs @@ -272,9 +272,9 @@ fn simple_dyn_exec() { # move the first result of foo out of the way movdn.4 - # use dynexec to call foo again via its hash, which is stored at memory location 42 - mem_storew.42 dropw - push.42 + # use dynexec to call foo again via its hash, which is stored at memory location 40 + mem_storew.40 dropw + push.40 dynexec end"; @@ -320,10 +320,10 @@ fn dynexec_with_procref() { end begin - procref.foo mem_storew.42 dropw push.42 + procref.foo mem_storew.40 dropw push.40 dynexec - procref.module::func mem_storew.42 dropw push.42 + procref.module::func mem_storew.40 dropw push.40 dynexec dup @@ -369,8 +369,8 @@ fn simple_dyncall() { movdn.4 # use dyncall to call foo again via its hash, which is on the stack - mem_storew.42 dropw - push.42 + mem_storew.40 dropw + push.40 dyncall swapw dropw @@ -442,7 +442,7 @@ fn dyncall_with_syscall_and_caller() { push.1 push.2 push.3 push.4 padw # Prepare dyncall - procref.bar mem_storew.42 dropw push.42 + procref.bar mem_storew.40 dropw push.40 dyncall # Truncate stack diff --git a/miden/tests/integration/operations/decorators/advice.rs b/miden/tests/integration/operations/decorators/advice.rs index b48c9af7b2..727d21712f 100644 --- a/miden/tests/integration/operations/decorators/advice.rs +++ b/miden/tests/integration/operations/decorators/advice.rs @@ -176,24 +176,25 @@ fn advice_insert_mem() { # write to memory and drop first word from stack to use second word as the key for advice map. # mem_storew reverses the order of field elements in the word when it's stored in memory. - mem_storew.2 dropw mem_storew.3 + mem_storew.8 dropw mem_storew.12 # State Transition: # stack: [5, 6, 7, 8] - # mem[2]: [4, 3, 2, 1] - # mem[3]: [8, 7, 6, 5] + # mem[8..11]: [4, 3, 2, 1] + # mem[12..15]: [8, 7, 6, 5] # copy from memory to advice map # the key used is in the reverse order of the field elements in the word at the top of the # stack. - push.2.4 movdn.4 movdn.4 + push.16 movdn.4 push.8 movdn.4 adv.insert_mem # State Transition: + # stack: [5, 6, 7, 8, 4, 16] # advice_map: k: [8, 7, 6, 5], v: [4, 3, 2, 1, 8, 7, 6, 5] # copy from advice map to advice stack adv.push_mapval dropw # State Transition: - # stack: [0, 0, 0, 0] + # stack: [4, 16, 0, 0] # advice_stack: [4, 3, 2, 1, 8, 7, 6, 5] # copy first word from advice stack to stack diff --git a/miden/tests/integration/operations/fri_ops.rs b/miden/tests/integration/operations/fri_ops.rs index 56949c306f..1ae051d160 100644 --- a/miden/tests/integration/operations/fri_ops.rs +++ b/miden/tests/integration/operations/fri_ops.rs @@ -40,7 +40,7 @@ fn fri_ext2fold4() { // processor tests let stack_state = test.get_last_stack_state(); assert_eq!(stack_state[8], Felt::new(poe).square()); - assert_eq!(stack_state[10], Felt::new(layer_ptr + 2)); + assert_eq!(stack_state[10], Felt::new(layer_ptr + 8)); assert_eq!(stack_state[11], Felt::new(poe).exp(4)); assert_eq!(stack_state[12], Felt::new(f_pos)); assert_eq!(stack_state[15], Felt::new(end_ptr)); diff --git a/miden/tests/integration/operations/io_ops/adv_ops.rs b/miden/tests/integration/operations/io_ops/adv_ops.rs index 5bb3147702..c67ffa7a19 100644 --- a/miden/tests/integration/operations/io_ops/adv_ops.rs +++ b/miden/tests/integration/operations/io_ops/adv_ops.rs @@ -92,7 +92,7 @@ fn adv_pipe() { // to the end (the address will be 2 since 0 + 2 = 2). let mut final_stack = state.iter().map(|&v| v.as_int()).collect::>(); final_stack.reverse(); - final_stack.push(2); + final_stack.push(8); let test = build_test!(source, &[], &advice_stack); test.expect_stack(&final_stack); @@ -129,7 +129,7 @@ fn adv_pipe_with_hperm() { // to the end (the address will be 2 since 0 + 2 = 2). let mut final_stack = state.iter().map(|&v| v.as_int()).collect::>(); final_stack.reverse(); - final_stack.push(2); + final_stack.push(8); let test = build_test!(source, &[], &advice_stack); test.expect_stack(&final_stack); diff --git a/miden/tests/integration/operations/io_ops/env_ops.rs b/miden/tests/integration/operations/io_ops/env_ops.rs index d8bc13b594..a74508c2c1 100644 --- a/miden/tests/integration/operations/io_ops/env_ops.rs +++ b/miden/tests/integration/operations/io_ops/env_ops.rs @@ -49,9 +49,9 @@ fn sdepth() { fn locaddr() { // --- locaddr returns expected address ------------------------------------------------------- let source = " - proc.foo.2 + proc.foo.5 locaddr.0 - locaddr.1 + locaddr.4 end begin exec.foo @@ -59,19 +59,21 @@ fn locaddr() { end"; let test = build_test!(source, &[10]); - test.expect_stack(&[FMP_MIN + 2, FMP_MIN + 1, 10]); + // Note: internally, we round 5 up to 8 for word-aligned purposes, so the local addresses are + // offset from 8 rather than 5. + test.expect_stack(&[FMP_MIN + 7, FMP_MIN + 3, 10]); // --- accessing mem via locaddr updates the correct variables -------------------------------- let source = " - proc.foo.2 + proc.foo.8 locaddr.0 mem_store - locaddr.1 + locaddr.4 mem_storew dropw loc_load.0 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 end begin exec.foo @@ -86,15 +88,15 @@ fn locaddr() { " {TRUNCATE_STACK_PROC} - proc.foo.3 + proc.foo.12 locaddr.0 - locaddr.1 - locaddr.2 + locaddr.4 + locaddr.8 end - proc.bar.2 + proc.bar.8 locaddr.0 exec.foo - locaddr.1 + locaddr.4 end begin exec.bar @@ -106,35 +108,35 @@ fn locaddr() { let test = build_test!(source, &[10]); test.expect_stack(&[ - FMP_MIN + 3, - FMP_MIN + 2, - FMP_MIN + 1, - FMP_MIN + 2, - FMP_MIN + 5, + FMP_MIN + 8, FMP_MIN + 4, - FMP_MIN + 3, - FMP_MIN + 1, + FMP_MIN, + FMP_MIN + 4, + FMP_MIN + 16, + FMP_MIN + 12, + FMP_MIN + 8, + FMP_MIN, 10, ]); // --- accessing mem via locaddr in nested procedures updates the correct variables ----------- let source = " - proc.foo.2 + proc.foo.8 locaddr.0 mem_store - locaddr.1 + locaddr.4 mem_storew dropw push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 loc_load.0 end - proc.bar.2 + proc.bar.8 locaddr.0 mem_store - loc_store.1 + loc_store.4 exec.foo - locaddr.1 + locaddr.4 mem_load loc_load.0 end diff --git a/miden/tests/integration/operations/io_ops/local_ops.rs b/miden/tests/integration/operations/io_ops/local_ops.rs index f1fc61e1b3..25c021c4e3 100644 --- a/miden/tests/integration/operations/io_ops/local_ops.rs +++ b/miden/tests/integration/operations/io_ops/local_ops.rs @@ -6,7 +6,7 @@ use super::{build_test, TRUNCATE_STACK_PROC}; #[test] fn push_local() { let source = " - proc.foo.1 + proc.foo.4 loc_load.0 end @@ -31,11 +31,11 @@ fn push_local() { fn pop_local() { // --- test write to local memory ------------------------------------------------------------- let source = " - proc.foo.2 + proc.foo.8 loc_store.0 - loc_store.1 + loc_store.4 loc_load.0 - loc_load.1 + loc_load.4 end begin exec.foo @@ -47,7 +47,7 @@ fn pop_local() { // --- test existing memory is not affected --------------------------------------------------- let source = " - proc.foo.1 + proc.foo.4 loc_store.0 end begin @@ -67,7 +67,7 @@ fn pop_local() { #[test] fn loadw_local() { let source = " - proc.foo.1 + proc.foo.4 loc_loadw.0 end begin @@ -93,15 +93,15 @@ fn storew_local() { " {TRUNCATE_STACK_PROC} - proc.foo.2 + proc.foo.8 loc_storew.0 swapw - loc_storew.1 + loc_storew.4 swapw push.0.0.0.0 loc_loadw.0 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 end begin exec.foo @@ -115,17 +115,17 @@ fn storew_local() { // --- test existing memory is not affected --------------------------------------------------- let source = " - proc.foo.1 + proc.foo.8 loc_storew.0 end begin mem_storew.0 dropw - mem_storew.1 + mem_storew.4 dropw exec.foo end"; - let mem_addr = 1; + let mem_addr = 4; let test = build_test!(source, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); test.expect_stack_and_memory(&[4, 3, 2, 1], mem_addr, &[5, 6, 7, 8]); @@ -138,7 +138,7 @@ fn storew_local() { fn inverse_operations() { // --- pop and push are inverse operations, so the stack should be left unchanged ------------- let source = " - proc.foo.1 + proc.foo.4 loc_store.0 loc_load.0 end @@ -156,7 +156,7 @@ fn inverse_operations() { // --- popw and pushw are inverse operations, so the stack should be left unchanged ----------- let source = " - proc.foo.1 + proc.foo.4 loc_storew.0 dropw push.0.0.0.0 @@ -176,7 +176,7 @@ fn inverse_operations() { // --- storew and loadw are inverse operations, so the stack should be left unchanged --------- let source = " - proc.foo.1 + proc.foo.4 loc_storew.0 loc_loadw.0 end @@ -196,7 +196,7 @@ fn inverse_operations() { fn read_after_write() { // --- write to memory first, then test read with push -------------------------------------- let source = " - proc.foo.1 + proc.foo.4 loc_storew.0 loc_load.0 end @@ -210,7 +210,7 @@ fn read_after_write() { // --- write to memory first, then test read with pushw -------------------------------------- let source = " - proc.foo.1 + proc.foo.4 loc_storew.0 push.0.0.0.0 loc_loadw.0 @@ -225,7 +225,7 @@ fn read_after_write() { // --- write to memory first, then test read with loadw -------------------------------------- let source = " - proc.foo.1 + proc.foo.4 loc_storew.0 dropw loc_loadw.0 @@ -242,11 +242,11 @@ fn read_after_write() { fn nested_procedures() { // --- test nested procedures - pop/push ------------------------------------------------------ let source = " - proc.foo.1 + proc.foo.4 loc_store.0 end - proc.bar.1 + proc.bar.4 loc_store.0 exec.foo loc_load.0 @@ -263,11 +263,11 @@ fn nested_procedures() { // --- test nested procedures - popw/pushw ---------------------------------------------------- let source = " - proc.foo.1 + proc.foo.4 loc_storew.0 dropw end - proc.bar.1 + proc.bar.4 loc_storew.0 dropw exec.foo @@ -285,11 +285,11 @@ fn nested_procedures() { // --- test nested procedures - storew/loadw -------------------------------------------------- let source = " - proc.foo.1 + proc.foo.4 push.0 push.0 loc_storew.0 end - proc.bar.1 + proc.bar.4 loc_storew.0 exec.foo loc_loadw.0 @@ -308,9 +308,9 @@ fn nested_procedures() { fn free_memory_pointer() { // ensure local procedure memory doesn't overwrite memory from outer scope let source = " - proc.bar.2 + proc.bar.8 loc_store.0 - loc_store.1 + loc_store.4 end begin mem_store.0 diff --git a/miden/tests/integration/operations/io_ops/mem_ops.rs b/miden/tests/integration/operations/io_ops/mem_ops.rs index a206e6cd4f..0b0b5ab2d1 100644 --- a/miden/tests/integration/operations/io_ops/mem_ops.rs +++ b/miden/tests/integration/operations/io_ops/mem_ops.rs @@ -50,7 +50,7 @@ fn mem_store() { #[test] fn mem_loadw() { - let addr = 1; + let addr = 4; let asm_op = "mem_loadw"; // --- read from uninitialized memory - address provided via the stack ------------------------ @@ -101,7 +101,7 @@ fn mem_stream() { {TRUNCATE_STACK_PROC} begin - push.1 + push.4 mem_storew dropw push.0 @@ -117,7 +117,7 @@ fn mem_stream() { let inputs = [1, 2, 3, 4, 5, 6, 7, 8]; // the state is built by replacing the values on the top of the stack with the values in memory - // addresses 0 and 1 (i.e., 1 through 8). Thus, the first 8 elements on the stack will be 1 + // addresses `[0..8)`. Thus, the first 8 elements on the stack will be 1 // through 8 (in stack order, with 8 at stack[0]), and the remaining 4 are untouched (i.e., 9, // 10, 11, 12). let state: [Felt; 12] = @@ -127,7 +127,7 @@ fn mem_stream() { // to the end (the address will be 2 since 0 + 2 = 2). let mut final_stack = state.iter().map(|&v| v.as_int()).collect::>(); final_stack.reverse(); - final_stack.push(2); + final_stack.push(8); let test = build_test!(source, &inputs); test.expect_stack(&final_stack); @@ -140,7 +140,7 @@ fn mem_stream_with_hperm() { {TRUNCATE_STACK_PROC} begin - push.1 + push.4 mem_storew dropw push.0 @@ -169,7 +169,7 @@ fn mem_stream_with_hperm() { // to the end (the address will be 2 since 0 + 2 = 2). let mut final_stack = state.iter().map(|&v| v.as_int()).collect::>(); final_stack.reverse(); - final_stack.push(2); + final_stack.push(8); let test = build_test!(source, &inputs); test.expect_stack(&final_stack); @@ -205,8 +205,8 @@ fn inverse_operations() { begin push.0 mem_storew - mem_storew.1 - push.1 + mem_storew.4 + push.4 mem_loadw mem_loadw.0 end"; diff --git a/processor/src/chiplets/aux_trace/mod.rs b/processor/src/chiplets/aux_trace/mod.rs index db3e637dc5..b591844f10 100644 --- a/processor/src/chiplets/aux_trace/mod.rs +++ b/processor/src/chiplets/aux_trace/mod.rs @@ -10,7 +10,10 @@ use miden_air::{ RETURN_STATE_LABEL, STATE_WIDTH, }, kernel_rom::KERNEL_PROC_LABEL, - memory::{MEMORY_READ_LABEL, MEMORY_WRITE_LABEL}, + memory::{ + MEMORY_ACCESS_ELEMENT, MEMORY_ACCESS_WORD, MEMORY_READ_ELEMENT_LABEL, + MEMORY_READ_WORD_LABEL, MEMORY_WRITE_ELEMENT_LABEL, MEMORY_WRITE_WORD_LABEL, + }, }, main_trace::MainTrace, }, @@ -29,6 +32,7 @@ use super::{super::trace::AuxColumnBuilder, Felt, FieldElement}; // ================================================================================================ const NUM_HEADER_ALPHAS: usize = 4; +const FOUR: Felt = Felt::new(4); // CHIPLETS AUXILIARY TRACE BUILDER // ================================================================================================ @@ -297,10 +301,18 @@ impl> AuxColumnBuilder for BusColumnBuilder OPCODE_END => build_end_block_request(main_trace, alphas, row), OPCODE_U32AND => build_bitwise_request(main_trace, ZERO, alphas, row), OPCODE_U32XOR => build_bitwise_request(main_trace, ONE, alphas, row), - OPCODE_MLOADW => build_mem_request_word(main_trace, MEMORY_READ_LABEL, alphas, row), - OPCODE_MSTOREW => build_mem_request_word(main_trace, MEMORY_WRITE_LABEL, alphas, row), - OPCODE_MLOAD => build_mem_request_element(main_trace, MEMORY_READ_LABEL, alphas, row), - OPCODE_MSTORE => build_mem_request_element(main_trace, MEMORY_WRITE_LABEL, alphas, row), + OPCODE_MLOADW => { + build_mem_mloadw_mstorew_request(main_trace, MEMORY_READ_WORD_LABEL, alphas, row) + }, + OPCODE_MSTOREW => { + build_mem_mloadw_mstorew_request(main_trace, MEMORY_WRITE_WORD_LABEL, alphas, row) + }, + OPCODE_MLOAD => { + build_mem_mload_mstore_request(main_trace, MEMORY_READ_ELEMENT_LABEL, alphas, row) + }, + OPCODE_MSTORE => { + build_mem_mload_mstore_request(main_trace, MEMORY_WRITE_ELEMENT_LABEL, alphas, row) + }, OPCODE_MSTREAM => build_mstream_request(main_trace, alphas, row), OPCODE_RCOMBBASE => build_rcomb_base_request(main_trace, alphas, row), OPCODE_HPERM => build_hperm_request(main_trace, alphas, row), @@ -348,7 +360,7 @@ fn build_control_block_request>( let header = alphas[0] + alphas[1].mul_base(Felt::from(transition_label)) + alphas[2].mul_base(addr_nxt); - header + build_value(&alphas[8..16], &decoder_hasher_state) + alphas[5].mul_base(op_code_felt) + header + build_value(&alphas[8..16], decoder_hasher_state) + alphas[5].mul_base(op_code_felt) } /// Builds requests made on a `DYN` or `DYNCALL` operation. @@ -365,7 +377,14 @@ fn build_dyn_block_request>( let mem_addr = main_trace.stack_element(0, row); let mem_value = main_trace.decoder_hasher_state_first_half(row); - compute_memory_request(main_trace, MEMORY_READ_LABEL, alphas, row, mem_addr, mem_value) + compute_mem_request_word( + main_trace, + MEMORY_READ_WORD_LABEL, + alphas, + row, + mem_addr, + mem_value, + ) }; control_block_req * memory_req @@ -412,7 +431,7 @@ fn build_span_block_request>( alphas[0] + alphas[1].mul_base(Felt::from(transition_label)) + alphas[2].mul_base(addr_nxt); let state = main_trace.decoder_hasher_state(row); - header + build_value(&alphas[8..16], &state) + header + build_value(&alphas[8..16], state) } /// Builds requests made to the hasher chiplet at the start of a respan block. @@ -432,7 +451,7 @@ fn build_respan_block_request>( let state = main_trace.decoder_hasher_state(row); - header + build_value(&alphas[8..16], &state) + header + build_value(&alphas[8..16], state) } /// Builds requests made to the hasher chiplet at the end of a block. @@ -449,7 +468,7 @@ fn build_end_block_request>( alphas[0] + alphas[1].mul_base(Felt::from(transition_label)) + alphas[2].mul_base(addr); let state = main_trace.decoder_hasher_state(row); - let digest = &state[..4]; + let digest: [Felt; 4] = state[..4].try_into().unwrap(); header + build_value(&alphas[8..12], digest) } @@ -467,47 +486,7 @@ fn build_bitwise_request>( let b = main_trace.stack_element(0, row); let z = main_trace.stack_element(0, row + 1); - alphas[0] - + alphas[1].mul_base(op_label) - + alphas[2].mul_base(a) - + alphas[3].mul_base(b) - + alphas[4].mul_base(z) -} - -/// Builds `MLOAD` and `MSTORE` requests made to the memory chiplet. -fn build_mem_request_element>( - main_trace: &MainTrace, - op_label: u8, - alphas: &[E], - row: RowIndex, -) -> E { - let word = [ - main_trace.stack_element(0, row + 1), - main_trace.helper_register(2, row), - main_trace.helper_register(1, row), - main_trace.helper_register(0, row), - ]; - let addr = main_trace.stack_element(0, row); - - compute_memory_request(main_trace, op_label, alphas, row, addr, word) -} - -/// Builds `MLOADW` and `MSTOREW` requests made to the memory chiplet. -fn build_mem_request_word>( - main_trace: &MainTrace, - op_label: u8, - alphas: &[E], - row: RowIndex, -) -> E { - let word = [ - main_trace.stack_element(3, row + 1), - main_trace.stack_element(2, row + 1), - main_trace.stack_element(1, row + 1), - main_trace.stack_element(0, row + 1), - ]; - let addr = main_trace.stack_element(0, row); - - compute_memory_request(main_trace, op_label, alphas, row, addr, word) + alphas[0] + build_value(&alphas[1..5], [op_label, a, b, z]) } /// Builds `MSTREAM` requests made to the memory chiplet. @@ -529,10 +508,10 @@ fn build_mstream_request>( main_trace.stack_element(0, row + 1), ]; let addr = main_trace.stack_element(12, row); - let op_label = MEMORY_READ_LABEL; + let op_label = MEMORY_READ_WORD_LABEL; - let factor1 = compute_memory_request(main_trace, op_label, alphas, row, addr, word1); - let factor2 = compute_memory_request(main_trace, op_label, alphas, row, addr + ONE, word2); + let factor1 = compute_mem_request_word(main_trace, op_label, alphas, row, addr, word1); + let factor2 = compute_mem_request_word(main_trace, op_label, alphas, row, addr + FOUR, word2); factor1 * factor2 } @@ -556,12 +535,12 @@ fn build_pipe_request>( main_trace.stack_element(0, row + 1), ]; let addr = main_trace.stack_element(12, row); - let op_label = MEMORY_WRITE_LABEL; + let op_label = MEMORY_WRITE_WORD_LABEL; - let factor1 = compute_memory_request(main_trace, op_label, alphas, row, addr, word1); - let factor2 = compute_memory_request(main_trace, op_label, alphas, row, addr + ONE, word2); + let req1 = compute_mem_request_word(main_trace, op_label, alphas, row, addr, word1); + let req2 = compute_mem_request_word(main_trace, op_label, alphas, row, addr + FOUR, word2); - factor1 * factor2 + req1 * req2 } /// Builds `RCOMBBASE` requests made to the memory chiplet. @@ -578,14 +557,14 @@ fn build_rcomb_base_request>( let a1 = main_trace.helper_register(5, row); let z_ptr = main_trace.stack_element(13, row); let a_ptr = main_trace.stack_element(14, row); - let op_label = MEMORY_READ_LABEL; + let op_label = MEMORY_READ_WORD_LABEL; - let factor1 = - compute_memory_request(main_trace, op_label, alphas, row, z_ptr, [tz0, tz1, tzg0, tzg1]); - let factor2 = - compute_memory_request(main_trace, op_label, alphas, row, a_ptr, [a0, a1, ZERO, ZERO]); + let req1 = + compute_mem_request_word(main_trace, op_label, alphas, row, z_ptr, [tz0, tz1, tzg0, tzg1]); + let req2 = + compute_mem_request_word(main_trace, op_label, alphas, row, a_ptr, [a0, a1, ZERO, ZERO]); - factor1 * factor2 + req1 * req2 } /// Builds `HPERM` requests made to the hash chiplet. @@ -818,24 +797,26 @@ where // v_all = v_h + v_a + v_b + v_c if selector1 == ONE && selector2 == ZERO && selector3 == ZERO { let header = alphas[0] - + alphas[1].mul_base(transition_label) - + alphas[2].mul_base(Felt::from(row + 1)) - + alphas[3].mul_base(node_index); + + build_value(&alphas[1..4], [transition_label, Felt::from(row + 1), node_index]); - multiplicand = header + build_value(alphas_state, &state); + multiplicand = header + build_value(alphas_state, state); } // f_mp or f_mv or f_mu == 1 // v_leaf = v_h + (1 - b) * v_b + b * v_d if selector1 == ONE && !(selector2 == ZERO && selector3 == ZERO) { let header = alphas[0] - + alphas[1].mul_base(transition_label) - + alphas[2].mul_base(Felt::from(row + 1)) - + alphas[3].mul_base(node_index); + + build_value(&alphas[1..4], [transition_label, Felt::from(row + 1), node_index]); let bit = (node_index.as_int() & 1) as u8; - let left_word = build_value(&alphas_state[DIGEST_RANGE], &state[DIGEST_RANGE]); - let right_word = build_value(&alphas_state[DIGEST_RANGE], &state[DIGEST_RANGE.end..]); + let left_word = build_value::<_, 4>( + &alphas_state[DIGEST_RANGE], + state[DIGEST_RANGE].try_into().unwrap(), + ); + let right_word = build_value::<_, 4>( + &alphas_state[DIGEST_RANGE], + state[DIGEST_RANGE.end..].try_into().unwrap(), + ); multiplicand = header + E::from(1 - bit).mul(left_word) + E::from(bit).mul(right_word); } @@ -852,38 +833,39 @@ where // v_res = v_h + v_b; if selector1 == ZERO && selector2 == ZERO && selector3 == ZERO { let header = alphas[0] - + alphas[1].mul_base(transition_label) - + alphas[2].mul_base(Felt::from(row + 1)) - + alphas[3].mul_base(node_index); + + build_value(&alphas[1..4], [transition_label, Felt::from(row + 1), node_index]); - multiplicand = header + build_value(&alphas_state[DIGEST_RANGE], &state[DIGEST_RANGE]); + multiplicand = header + + build_value::<_, 4>( + &alphas_state[DIGEST_RANGE], + state[DIGEST_RANGE].try_into().unwrap(), + ); } // f_sout == 1 // v_all = v_h + v_a + v_b + v_c if selector1 == ZERO && selector2 == ZERO && selector3 == ONE { let header = alphas[0] - + alphas[1].mul_base(transition_label) - + alphas[2].mul_base(Felt::from(row + 1)) - + alphas[3].mul_base(node_index); + + build_value(&alphas[1..4], [transition_label, Felt::from(row + 1), node_index]); - multiplicand = header + build_value(alphas_state, &state); + multiplicand = header + build_value(alphas_state, state); } // f_abp == 1 // v_abp = v_h + v_b' + v_c' - v_b - v_c if selector1 == ONE && selector2 == ZERO && selector3 == ZERO { let header = alphas[0] - + alphas[1].mul_base(transition_label) - + alphas[2].mul_base(Felt::from(row + 1)) - + alphas[3].mul_base(node_index); + + build_value(&alphas[1..4], [transition_label, Felt::from(row + 1), node_index]); let state_nxt = main_trace.chiplet_hasher_state(row + 1); // build the value from the hasher state's just right after the absorption of new // elements. - let next_state_value = - build_value(&alphas_state[CAPACITY_LEN..], &state_nxt[CAPACITY_LEN..]); + const SIZE: usize = STATE_WIDTH - CAPACITY_LEN; + let next_state_value = build_value::<_, SIZE>( + &alphas_state[CAPACITY_LEN..], + state_nxt[CAPACITY_LEN..].try_into().unwrap(), + ); multiplicand = header + next_state_value; } @@ -904,11 +886,7 @@ where let b = main_trace.chiplet_bitwise_b(row); let z = main_trace.chiplet_bitwise_z(row); - alphas[0] - + alphas[1].mul_base(op_label) - + alphas[2].mul_base(a) - + alphas[3].mul_base(b) - + alphas[4].mul_base(z) + alphas[0] + build_value(&alphas[1..5], [op_label, a, b, z]) } else { E::ONE } @@ -919,26 +897,51 @@ fn build_memory_chiplet_responses(main_trace: &MainTrace, row: RowIndex, alph where E: FieldElement, { - let is_read = main_trace.chiplet_selector_3(row); - let op_label = get_op_label(ONE, ONE, ZERO, is_read); + let is_word_access = main_trace.chiplet_selector_4(row); + let header = { + let is_read = main_trace.chiplet_selector_3(row); + let op_label = get_memory_op_label(is_read, is_word_access); + + let ctx = main_trace.chiplet_memory_ctx(row); + let clk = main_trace.chiplet_memory_clk(row); + let address = { + let word = main_trace.chiplet_memory_word(row); + let idx0 = main_trace.chiplet_memory_idx0(row); + let idx1 = main_trace.chiplet_memory_idx1(row); + + word + idx1.mul_small(2) + idx0 + }; - let ctx = main_trace.chiplet_memory_ctx(row); - let clk = main_trace.chiplet_memory_clk(row); - let addr = main_trace.chiplet_memory_addr(row); - let value0 = main_trace.chiplet_memory_value_0(row); - let value1 = main_trace.chiplet_memory_value_1(row); - let value2 = main_trace.chiplet_memory_value_2(row); - let value3 = main_trace.chiplet_memory_value_3(row); + alphas[0] + build_value(&alphas[1..5], [op_label, ctx, address, clk]) + }; - alphas[0] - + alphas[1].mul_base(op_label) - + alphas[2].mul_base(ctx) - + alphas[3].mul_base(addr) - + alphas[4].mul_base(clk) - + alphas[5].mul_base(value0) - + alphas[6].mul_base(value1) - + alphas[7].mul_base(value2) - + alphas[8].mul_base(value3) + if is_word_access == MEMORY_ACCESS_ELEMENT { + let idx0 = main_trace.chiplet_memory_idx0(row); + let idx1 = main_trace.chiplet_memory_idx1(row); + + let value = if idx1 == ZERO && idx0 == ZERO { + main_trace.chiplet_memory_value_0(row) + } else if idx1 == ZERO && idx0 == ONE { + main_trace.chiplet_memory_value_1(row) + } else if idx1 == ONE && idx0 == ZERO { + main_trace.chiplet_memory_value_2(row) + } else if idx1 == ONE && idx0 == ONE { + main_trace.chiplet_memory_value_3(row) + } else { + panic!("Invalid word indices. idx0: {idx0}, idx1: {idx1}"); + }; + + header + alphas[5].mul_base(value) + } else if is_word_access == MEMORY_ACCESS_WORD { + let value0 = main_trace.chiplet_memory_value_0(row); + let value1 = main_trace.chiplet_memory_value_1(row); + let value2 = main_trace.chiplet_memory_value_2(row); + let value3 = main_trace.chiplet_memory_value_3(row); + + header + build_value(&alphas[5..9], [value0, value1, value2, value3]) + } else { + panic!("Invalid memory element/word column value: {is_word_access}"); + } } /// Builds the response from the kernel chiplet at `row`. @@ -953,12 +956,7 @@ where let root2 = main_trace.chiplet_kernel_root_2(row); let root3 = main_trace.chiplet_kernel_root_3(row); - let v = alphas[0] - + alphas[1].mul_base(op_label) - + alphas[2].mul_base(root0) - + alphas[3].mul_base(root1) - + alphas[4].mul_base(root2) - + alphas[5].mul_base(root3); + let v = alphas[0] + build_value(&alphas[1..6], [op_label, root0, root1, root2, root3]); let kernel_chiplet_selector = main_trace.chiplet_selector_4(row); v.mul_base(kernel_chiplet_selector) + E::from(ONE - kernel_chiplet_selector) @@ -967,14 +965,16 @@ where // HELPER FUNCTIONS // ================================================================================================ -/// Reduces a slice of elements to a single field element in the field specified by E using a slice -/// of alphas of matching length. This can be used to build the value for a single word or for an -/// entire [HasherState]. -fn build_value>(alphas: &[E], elements: &[Felt]) -> E { - assert_eq!(alphas.len(), elements.len()); +/// Runs an inner product between the alphas and the elements. +#[inline(always)] +fn build_value, const N: usize>( + alphas: &[E], + elements: [Felt; N], +) -> E { + debug_assert_eq!(alphas.len(), elements.len()); let mut value = E::ZERO; - for (&alpha, &element) in alphas.iter().zip(elements.iter()) { - value += alpha.mul_base(element); + for i in 0..N { + value += alphas[i].mul_base(elements[i]); } value } @@ -984,26 +984,83 @@ fn get_op_label(s0: Felt, s1: Felt, s2: Felt, s3: Felt) -> Felt { s3.mul_small(1 << 3) + s2.mul_small(1 << 2) + s1.mul_small(2) + s0 + ONE } -/// Computes a memory read or write request at `row` given randomness `alphas`, memory address -/// `addr` and value `value`. -fn compute_memory_request>( +/// Returns the operation unique label for memory operations. +/// +/// The memory operation label is currently the only label that is built differently (or *simpler*) +/// from the other chiplets. We should refactor the other chiplets to use a similar (simpler) +/// approach. +fn get_memory_op_label(is_read: Felt, is_word_access: Felt) -> Felt { + const MEMORY_SELECTOR: u8 = 0b110; + // Equivalent to `is_read << 1` + let is_read_left_shift_1 = is_read + is_read; + + Felt::from(MEMORY_SELECTOR << 2) + is_read_left_shift_1 + is_word_access +} + +/// Builds `MLOADW` and `MSTOREW` requests made to the memory chiplet. +fn build_mem_mloadw_mstorew_request>( + main_trace: &MainTrace, + op_label: u8, + alphas: &[E], + row: RowIndex, +) -> E { + let word = [ + main_trace.stack_element(3, row + 1), + main_trace.stack_element(2, row + 1), + main_trace.stack_element(1, row + 1), + main_trace.stack_element(0, row + 1), + ]; + let addr = main_trace.stack_element(0, row); + + compute_mem_request_word(main_trace, op_label, alphas, row, addr, word) +} + +/// Builds `MLOAD` and `MSTORE` requests made to the memory chiplet. +fn build_mem_mload_mstore_request>( + main_trace: &MainTrace, + op_label: u8, + alphas: &[E], + row: RowIndex, +) -> E { + let element = main_trace.stack_element(0, row + 1); + let addr = main_trace.stack_element(0, row); + + compute_mem_request_element(main_trace, op_label, alphas, row, addr, element) +} + +/// Computes a memory request for a read or write of a single element. +fn compute_mem_request_element>( main_trace: &MainTrace, op_label: u8, alphas: &[E], row: RowIndex, addr: Felt, - value: Word, + element: Felt, ) -> E { + debug_assert!(op_label == MEMORY_READ_ELEMENT_LABEL || op_label == MEMORY_WRITE_ELEMENT_LABEL); + + let ctx = main_trace.ctx(row); + let clk = main_trace.clk(row); + + alphas[0] + build_value(&alphas[1..6], [Felt::from(op_label), ctx, addr, clk, element]) +} + +/// Computes a memory request for a read or write of a word. +fn compute_mem_request_word>( + main_trace: &MainTrace, + op_label: u8, + alphas: &[E], + row: RowIndex, + addr: Felt, + word: Word, +) -> E { + debug_assert!(op_label == MEMORY_READ_WORD_LABEL || op_label == MEMORY_WRITE_WORD_LABEL); let ctx = main_trace.ctx(row); let clk = main_trace.clk(row); alphas[0] - + alphas[1].mul_base(Felt::from(op_label)) - + alphas[2].mul_base(ctx) - + alphas[3].mul_base(addr) - + alphas[4].mul_base(clk) - + alphas[5].mul_base(value[0]) - + alphas[6].mul_base(value[1]) - + alphas[7].mul_base(value[2]) - + alphas[8].mul_base(value[3]) + + build_value( + &alphas[1..9], + [Felt::from(op_label), ctx, addr, clk, word[0], word[1], word[2], word[3]], + ) } diff --git a/processor/src/chiplets/memory/mod.rs b/processor/src/chiplets/memory/mod.rs index 99f78d0a62..630c69b92d 100644 --- a/processor/src/chiplets/memory/mod.rs +++ b/processor/src/chiplets/memory/mod.rs @@ -2,10 +2,14 @@ use alloc::{collections::BTreeMap, vec::Vec}; use miden_air::{ trace::chiplets::memory::{ - ADDR_COL_IDX, CLK_COL_IDX, CTX_COL_IDX, D0_COL_IDX, D1_COL_IDX, D_INV_COL_IDX, V_COL_RANGE, + CLK_COL_IDX, CTX_COL_IDX, D0_COL_IDX, D1_COL_IDX, D_INV_COL_IDX, + FLAG_SAME_CONTEXT_AND_WORD, IDX0_COL_IDX, IDX1_COL_IDX, IS_READ_COL_IDX, + IS_WORD_ACCESS_COL_IDX, MEMORY_ACCESS_ELEMENT, MEMORY_ACCESS_WORD, MEMORY_READ, + MEMORY_WRITE, V_COL_RANGE, WORD_COL_IDX, }, RowIndex, }; +use vm_core::{WORD_SIZE, ZERO}; use super::{ utils::{split_element_u32_into_u16, split_u32_into_u16}, @@ -14,7 +18,7 @@ use super::{ use crate::{system::ContextId, ExecutionError}; mod segment; -use segment::MemorySegmentTrace; +use segment::{MemoryOperation, MemorySegmentTrace}; #[cfg(test)] mod tests; @@ -34,46 +38,50 @@ const INIT_MEM_VALUE: Word = EMPTY_WORD; /// building an execution trace of all memory accesses. /// /// The memory is comprised of one or more segments, each segment accessible from a specific -/// execution context. The root (kernel) context has context ID 0, and all additional contexts -/// have increasing IDs. Within each segment, the memory is word-addressable. That is, four field -/// elements are located at each memory address, and we can read and write elements to/from memory -/// in batches of four. +/// execution context. The root (kernel) context has context ID 0, and all additional contexts have +/// increasing IDs. Within each segment, the memory is element-addressable, even though the trace +/// tracks words for optimization purposes. That is, a single field element is located at each +/// memory address, and we can read and write elements to/from memory either individually or in +/// groups of four. /// -/// Memory for a a given address is always initialized to zeros. That is, reading from an address -/// before writing to it will return four ZERO elements. +/// Memory for a given address is always initialized to zero. That is, reading from an address +/// before writing to it will return ZERO. /// /// ## Execution trace /// The layout of the memory access trace is shown below. /// -/// s0 s1 ctx addr clk v0 v1 v2 v3 d0 d1 d_inv -/// ├────┴────┴────┴──────┴─────┴────┴────┴────┴────┴────┴────┴───────┤ +/// rw ew ctx word_addr idx0 idx1 clk v0 v1 v2 v3 d0 d1 d_inv f_scw +/// ├────┴────┴────┴───────────┴──────┴──────┴────┴────┴────┴────┴────┴────┴────┴───────┴───────┤ /// /// In the above, the meaning of the columns is as follows: -/// - `s0` is a selector column used to identify whether the memory access is a read or a write. A -/// value of ZERO indicates a write, and ONE indicates a read. -/// - `s1` is a selector column used to identify whether the memory access is a read of an existing -/// memory value or not (i.e., this context/addr combination already existed and is being read). A -/// value of ONE indicates a read of existing memory, meaning the previous value must be copied. +/// - `rw` is a selector column used to identify whether the memory operation is a read or a write +/// (1 indicates a read). +/// - `ew` is a selector column used to identify whether the memory operation is over an element or +/// a word (1 indicates a word). /// - `ctx` contains execution context ID. Values in this column must increase monotonically but /// there can be gaps between two consecutive context IDs of up to 2^32. Also, two consecutive /// values can be the same. -/// - `addr` contains memory address. Values in this column must increase monotonically for a given -/// context but there can be gaps between two consecutive values of up to 2^32. Also, two -/// consecutive values can be the same. -/// - `clk` contains clock cycle at which a memory operation happened. Values in this column must -/// increase monotonically for a given context and memory address but there can be gaps between -/// two consecutive values of up to 2^32. -/// - Columns `v0`, `v1`, `v2`, `v3` contain field elements stored at a given context/address/clock +/// - `word_addr` contains the address of the first element in the word. For example, the value of +/// `word_addr` for the group of addresses 40, 41, 42, 43 is 40. Note then that `word_addr` *must* +/// be divisible by 4. Values in this column must increase monotonically for a given context but +/// there can be gaps between two consecutive values of up to 2^32. Also, two consecutive values +/// can be the same. +/// - `clk` contains the clock cycle at which a memory operation happened. Values in this column +/// must increase monotonically for a given context and word but there can be gaps between two +/// consecutive values of up to 2^32. +/// - Columns `v0`, `v1`, `v2`, `v3` contain field elements stored at a given context/word/clock /// cycle after the memory operation. /// - Columns `d0` and `d1` contain lower and upper 16 bits of the delta between two consecutive -/// context IDs, addresses, or clock cycles. Specifically: +/// context IDs, words, or clock cycles. Specifically: /// - When the context changes, these columns contain (`new_ctx` - `old_ctx`). -/// - When the context remains the same but the address changes, these columns contain (`new_addr` -/// - `old-addr`). -/// - When both the context and the address remain the same, these columns contain (`new_clk` - +/// - When the context remains the same but the word changes, these columns contain (`new_word` +/// - `old_word`). +/// - When both the context and the word remain the same, these columns contain (`new_clk` - /// `old_clk` - 1). -/// - `d_inv` contains the inverse of the delta between two consecutive context IDs, addresses, or -/// clock cycles computed as described above. +/// - `d_inv` contains the inverse of the delta between two consecutive context IDs, words, or clock +/// cycles computed as described above. It is the field inverse of `(d_1 * 2^16) + d_0` +/// - `f_scw` is a flag indicating whether the context and the word of the current row are the same +/// as in the next row. /// /// For the first row of the trace, values in `d0`, `d1`, and `d_inv` are set to zeros. #[derive(Debug, Default)] @@ -96,30 +104,36 @@ impl Memory { self.num_trace_rows } - /// Returns a word located at the specified context/address, or None if the address hasn't + /// Returns the element located at the specified context/address, or None if the address hasn't /// been accessed previously. /// /// Unlike read() which modifies the memory access trace, this method returns the value at the /// specified address (if one exists) without altering the memory access trace. - pub fn get_value(&self, ctx: ContextId, addr: u32) -> Option { + pub fn get_value(&self, ctx: ContextId, addr: u32) -> Option { match self.trace.get(&ctx) { Some(segment) => segment.get_value(addr), None => None, } } - /// Returns the word at the specified context/address which should be used as the "old value" - /// for a write request. It will be the previously stored value, if one exists, or - /// initialized memory. - pub fn get_old_value(&self, ctx: ContextId, addr: u32) -> Word { - // get the stored word or return [0, 0, 0, 0], since the memory is initialized with zeros - self.get_value(ctx, addr).unwrap_or(INIT_MEM_VALUE) + /// Returns the word located in memory starting at the specified address, which must be word + /// aligned. + /// + /// # Errors + /// - Returns an error if `addr` is not word aligned. + pub fn get_word(&self, ctx: ContextId, addr: u32) -> Result, ExecutionError> { + match self.trace.get(&ctx) { + Some(segment) => segment + .get_word(addr) + .map_err(|_| ExecutionError::MemoryUnalignedWordAccessNoClk { addr, ctx }), + None => Ok(None), + } } /// Returns the entire memory state for the specified execution context at the specified cycle. /// The state is returned as a vector of (address, value) tuples, and includes addresses which /// have been accessed at least once. - pub fn get_state_at(&self, ctx: ContextId, clk: RowIndex) -> Vec<(u64, Word)> { + pub fn get_state_at(&self, ctx: ContextId, clk: RowIndex) -> Vec<(u64, Felt)> { if clk == 0 { return vec![]; } @@ -130,41 +144,111 @@ impl Memory { } } - // STATE ACCESSORS AND MUTATORS + // STATE MUTATORS // -------------------------------------------------------------------------------------------- + /// Returns the field element located in memory at the specified context/address. + /// + /// If the specified address hasn't been previously written to, ZERO is returned. This + /// effectively implies that memory is initialized to ZERO. + /// + /// # Errors + /// - Returns an error if the address is equal or greater than 2^32. + /// - Returns an error if the same address is accessed more than once in the same clock cycle. + pub fn read( + &mut self, + ctx: ContextId, + addr: Felt, + clk: RowIndex, + ) -> Result { + let addr: u32 = addr + .as_int() + .try_into() + .map_err(|_| ExecutionError::MemoryAddressOutOfBounds(addr.as_int()))?; + self.num_trace_rows += 1; + self.trace.entry(ctx).or_default().read(ctx, addr, Felt::from(clk)) + } + /// Returns a word located in memory at the specified context/address. /// /// If the specified address hasn't been previously written to, four ZERO elements are /// returned. This effectively implies that memory is initialized to ZERO. /// /// # Errors + /// - Returns an error if the address is equal or greater than 2^32. + /// - Returns an error if the address is not aligned to a word boundary. /// - Returns an error if the same address is accessed more than once in the same clock cycle. - pub fn read( + pub fn read_word( &mut self, ctx: ContextId, - addr: u32, + addr: Felt, clk: RowIndex, ) -> Result { + let addr: u32 = addr + .as_int() + .try_into() + .map_err(|_| ExecutionError::MemoryAddressOutOfBounds(addr.as_int()))?; + if addr % WORD_SIZE as u32 != 0 { + return Err(ExecutionError::MemoryUnalignedWordAccess { + addr, + ctx, + clk: Felt::from(clk), + }); + } + self.num_trace_rows += 1; - self.trace.entry(ctx).or_default().read(ctx, addr, Felt::from(clk)) + self.trace.entry(ctx).or_default().read_word(ctx, addr, Felt::from(clk)) } - /// Writes the provided word at the specified context/address. + /// Writes the provided field element at the specified context/address. /// /// # Errors + /// - Returns an error if the address is equal or greater than 2^32. /// - Returns an error if the same address is accessed more than once in the same clock cycle. pub fn write( &mut self, ctx: ContextId, - addr: u32, + addr: Felt, clk: RowIndex, - value: Word, + value: Felt, ) -> Result<(), ExecutionError> { + let addr: u32 = addr + .as_int() + .try_into() + .map_err(|_| ExecutionError::MemoryAddressOutOfBounds(addr.as_int()))?; self.num_trace_rows += 1; self.trace.entry(ctx).or_default().write(ctx, addr, Felt::from(clk), value) } + /// Writes the provided word at the specified context/address. + /// + /// # Errors + /// - Returns an error if the address is equal or greater than 2^32. + /// - Returns an error if the address is not aligned to a word boundary. + /// - Returns an error if the same address is accessed more than once in the same clock cycle. + pub fn write_word( + &mut self, + ctx: ContextId, + addr: Felt, + clk: RowIndex, + value: Word, + ) -> Result<(), ExecutionError> { + let addr: u32 = addr + .as_int() + .try_into() + .map_err(|_| ExecutionError::MemoryAddressOutOfBounds(addr.as_int()))?; + if addr % WORD_SIZE as u32 != 0 { + return Err(ExecutionError::MemoryUnalignedWordAccess { + addr, + ctx, + clk: Felt::from(clk), + }); + } + + self.num_trace_rows += 1; + self.trace.entry(ctx).or_default().write_word(ctx, addr, Felt::from(clk), value) + } + // EXECUTION TRACE GENERATION // -------------------------------------------------------------------------------------------- @@ -224,7 +308,7 @@ impl Memory { }; // iterate through addresses in ascending order, and write trace row for each memory access - // into the trace. we expect the trace to be 14 columns wide. + // into the trace. we expect the trace to be 15 columns wide. let mut row: RowIndex = 0.into(); for (ctx, segment) in self.trace { @@ -235,13 +319,33 @@ impl Memory { let felt_addr = Felt::from(addr); for memory_access in addr_trace { let clk = memory_access.clk(); - let value = memory_access.value(); + let value = memory_access.word(); - let selectors = memory_access.op_selectors(); - trace.set(row, 0, selectors[0]); - trace.set(row, 1, selectors[1]); + match memory_access.operation() { + MemoryOperation::Read => trace.set(row, IS_READ_COL_IDX, MEMORY_READ), + MemoryOperation::Write => trace.set(row, IS_READ_COL_IDX, MEMORY_WRITE), + } + let (idx1, idx0) = match memory_access.access_type() { + segment::MemoryAccessType::Element { addr_idx_in_word } => { + trace.set(row, IS_WORD_ACCESS_COL_IDX, MEMORY_ACCESS_ELEMENT); + + match addr_idx_in_word { + 0 => (ZERO, ZERO), + 1 => (ZERO, ONE), + 2 => (ONE, ZERO), + 3 => (ONE, ONE), + _ => panic!("invalid address index in word: {addr_idx_in_word}"), + } + }, + segment::MemoryAccessType::Word => { + trace.set(row, IS_WORD_ACCESS_COL_IDX, MEMORY_ACCESS_WORD); + (ZERO, ZERO) + }, + }; trace.set(row, CTX_COL_IDX, ctx); - trace.set(row, ADDR_COL_IDX, felt_addr); + trace.set(row, WORD_COL_IDX, felt_addr); + trace.set(row, IDX0_COL_IDX, idx0); + trace.set(row, IDX1_COL_IDX, idx1); trace.set(row, CLK_COL_IDX, clk); for (idx, col) in V_COL_RANGE.enumerate() { trace.set(row, col, value[idx]); @@ -262,6 +366,12 @@ impl Memory { // TODO: switch to batch inversion to improve efficiency. trace.set(row, D_INV_COL_IDX, delta.inv()); + if prev_ctx == ctx && prev_addr == felt_addr { + trace.set(row, FLAG_SAME_CONTEXT_AND_WORD, ONE); + } else { + trace.set(row, FLAG_SAME_CONTEXT_AND_WORD, ZERO); + }; + // update values for the next iteration of the loop prev_ctx = ctx; prev_addr = felt_addr; @@ -291,9 +401,9 @@ impl Memory { // TEST HELPERS // -------------------------------------------------------------------------------------------- - /// Returns current size of the memory (in words) across all contexts. + /// Returns the number of words that were accessed at least once across all contexts. #[cfg(test)] - pub fn size(&self) -> usize { - self.trace.iter().fold(0, |acc, (_, s)| acc + s.size()) + pub fn num_accessed_words(&self) -> usize { + self.trace.iter().fold(0, |acc, (_, s)| acc + s.num_accessed_words()) } } diff --git a/processor/src/chiplets/memory/segment.rs b/processor/src/chiplets/memory/segment.rs index 4957286564..20ea1bb4f0 100644 --- a/processor/src/chiplets/memory/segment.rs +++ b/processor/src/chiplets/memory/segment.rs @@ -3,10 +3,8 @@ use alloc::{ vec::Vec, }; -use miden_air::{ - trace::chiplets::memory::{Selectors, MEMORY_COPY_READ, MEMORY_INIT_READ, MEMORY_WRITE}, - RowIndex, -}; +use miden_air::RowIndex; +use vm_core::WORD_SIZE; use super::{Felt, Word, INIT_MEM_VALUE}; use crate::{ContextId, ExecutionError}; @@ -18,7 +16,7 @@ use crate::{ContextId, ExecutionError}; /// /// A memory segment is an isolated address space accessible from a specific execution context. /// Within each segment, the memory is word-addressable. That is, four field elements are located -/// at each memory address, and we can read and write elements to/from memory in batches of four. +/// at each memory address, and we can read and write elements to/from memory in groups of four. #[derive(Debug, Default)] pub struct MemorySegmentTrace(BTreeMap>); @@ -26,21 +24,43 @@ impl MemorySegmentTrace { // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns a word located at the specified address, or None if the address hasn't been + /// Returns the element located at the specified address, or None if the address hasn't been /// accessed previously. /// /// Unlike read() which modifies the memory access trace, this method returns the value at the /// specified address (if one exists) without altering the memory access trace. - pub fn get_value(&self, addr: u32) -> Option { - match self.0.get(&addr) { - Some(addr_trace) => addr_trace.last().map(|access| access.value()), + pub fn get_value(&self, addr: u32) -> Option { + let (word_addr, addr_idx_in_word) = addr_to_word_addr_and_idx(addr); + + match self.0.get(&word_addr) { + Some(addr_trace) => { + addr_trace.last().map(|access| access.word()[addr_idx_in_word as usize]) + }, None => None, } } + /// Returns the word located in memory starting at the specified address, which must be word + /// aligned. + /// + /// # Errors + /// - Returns an error if `addr` is not word aligned. + pub fn get_word(&self, addr: u32) -> Result, ()> { + if addr % WORD_SIZE as u32 != 0 { + return Err(()); + } + + let (word_addr, _) = addr_to_word_addr_and_idx(addr); + + match self.0.get(&word_addr) { + Some(addr_trace) => Ok(addr_trace.last().map(|access| access.word())), + None => Ok(None), + } + } + /// Returns the entire memory state at the beginning of the specified cycle. - pub fn get_state_at(&self, clk: RowIndex) -> Vec<(u64, Word)> { - let mut result: Vec<(u64, Word)> = Vec::new(); + pub fn get_state_at(&self, clk: RowIndex) -> Vec<(u64, Felt)> { + let mut result: Vec<(u64, Felt)> = Vec::new(); if clk == 0 { return result; @@ -53,13 +73,29 @@ impl MemorySegmentTrace { for (&addr, addr_trace) in self.0.iter() { match addr_trace.binary_search_by(|access| access.clk().as_int().cmp(&search_clk)) { - Ok(i) => result.push((addr.into(), addr_trace[i].value())), + Ok(i) => { + let word_addr = addr_trace[i].word(); + let addr: u64 = addr.into(); + result.extend([ + (addr, word_addr[0]), + (addr + 1, word_addr[1]), + (addr + 2, word_addr[2]), + (addr + 3, word_addr[3]), + ]); + }, Err(i) => { // Binary search finds the index of the data with the specified clock cycle. // Decrement the index to get the trace from the previously accessed clock // cycle to insert into the results. if i > 0 { - result.push((addr.into(), addr_trace[i - 1].value())); + let word_addr = addr_trace[i - 1].word(); + let addr: u64 = addr.into(); + result.extend([ + (addr, word_addr[0]), + (addr + 1, word_addr[1]), + (addr + 2, word_addr[2]), + (addr + 3, word_addr[3]), + ]); } }, } @@ -71,62 +107,142 @@ impl MemorySegmentTrace { // STATE MUTATORS // -------------------------------------------------------------------------------------------- - /// Returns a word located in memory at the specified address. The memory access is assumed - /// to happen at the provided clock cycle. + /// Returns the element located at the specified address. The memory access is assumed to happen + /// at the provided clock cycle. + /// + /// If the element at the specified address hasn't been previously written to, ZERO is returned. + /// + /// # Errors + /// - Returns an error if the same address is accessed more than once in the same clock cycle. + pub fn read(&mut self, ctx: ContextId, addr: u32, clk: Felt) -> Result { + let (word_addr, addr_idx_in_word) = addr_to_word_addr_and_idx(addr); + + let word = self.read_word_helper( + ctx, + word_addr, + clk, + MemoryAccessType::Element { addr_idx_in_word }, + )?; + + Ok(word[addr_idx_in_word as usize]) + } + + /// Returns a word located in memory starting at the specified address, which must be word + /// aligned. The memory access is assumed to happen at the provided clock cycle. + /// + /// If the word starting at the specified address hasn't been previously written to, four ZERO + /// elements are returned. This effectively implies that memory is initialized to ZERO. /// - /// If the specified address hasn't been previously written to, four ZERO elements are - /// returned. This effectively implies that memory is initialized to ZERO. + /// # Preconditions + /// - Assumes that the address is word aligned. /// /// # Errors /// - Returns an error if the same address is accessed more than once in the same clock cycle. - pub fn read(&mut self, ctx: ContextId, addr: u32, clk: Felt) -> Result { - // look up the previous value in the appropriate address trace and add (clk, prev_value) - // to it; if this is the first time we access this address, create address trace for it - // with entry (clk, [ZERO, 4]). in both cases, return the last value in the address trace. - match self.0.entry(addr) { + pub fn read_word( + &mut self, + ctx: ContextId, + word_addr: u32, + clk: Felt, + ) -> Result { + debug_assert!(word_addr % 4 == 0, "unaligned word access: {word_addr}"); + + let (word_addr, _) = addr_to_word_addr_and_idx(word_addr); + self.read_word_helper(ctx, word_addr, clk, MemoryAccessType::Word) + } + + /// Writes the element located at the specified address. The memory access is assumed to happen + /// at the provided clock cycle. + /// + /// If the element at the specified address hasn't been previously written to, ZERO is returned. + /// + /// # Errors + /// - Returns an error if the same address is accessed more than once in the same clock cycle. + pub fn write( + &mut self, + ctx: ContextId, + addr: u32, + clk: Felt, + value: Felt, + ) -> Result<(), ExecutionError> { + let (word_addr, addr_idx_in_word) = addr_to_word_addr_and_idx(addr); + + match self.0.entry(word_addr) { Entry::Vacant(vacant_entry) => { - let access = - MemorySegmentAccess::new(clk, MemoryOperation::InitRead, INIT_MEM_VALUE); + // If this is the first access to the ctx/word pair, then all values in the word + // are initialized to 0, except for the address being written. + let word = { + let mut word = Word::default(); + word[addr_idx_in_word as usize] = value; + word + }; + + let access = MemorySegmentAccess::new( + clk, + MemoryOperation::Write, + MemoryAccessType::Element { addr_idx_in_word }, + word, + ); vacant_entry.insert(vec![access]); - Ok(INIT_MEM_VALUE) + Ok(()) }, Entry::Occupied(mut occupied_entry) => { + // If the ctx/word pair has been accessed before, then the values in the word are + // the same as the previous access, except for the address being written. let addr_trace = occupied_entry.get_mut(); if addr_trace.last().expect("empty address trace").clk() == clk { Err(ExecutionError::DuplicateMemoryAccess { ctx, addr, clk }) } else { - let last_value = addr_trace.last().expect("empty address trace").value(); - let access = - MemorySegmentAccess::new(clk, MemoryOperation::CopyRead, last_value); + let word = { + let mut last_word = addr_trace.last().expect("empty address trace").word(); + last_word[addr_idx_in_word as usize] = value; + + last_word + }; + + let access = MemorySegmentAccess::new( + clk, + MemoryOperation::Write, + MemoryAccessType::Element { addr_idx_in_word }, + word, + ); addr_trace.push(access); - Ok(last_value) + Ok(()) } }, } } - /// Writes the provided word at the specified address. The memory access is assumed to happen - /// at the provided clock cycle. + /// Writes the provided word starting at the specified address. The memory access is assumed to + /// happen at the provided clock cycle. + /// + /// # Preconditions + /// + /// - Assumes that the address is word aligned. /// /// # Errors /// - Returns an error if the same address is accessed more than once in the same clock cycle. - pub fn write( + pub fn write_word( &mut self, ctx: ContextId, addr: u32, clk: Felt, - value: Word, + word: Word, ) -> Result<(), ExecutionError> { - // add a memory access to the appropriate address trace; if this is the first time - // we access this address, initialize address trace. - let access = MemorySegmentAccess::new(clk, MemoryOperation::Write, value); - match self.0.entry(addr) { + debug_assert!(addr % 4 == 0, "unaligned memory access: {addr}"); + + let (word_addr, _) = addr_to_word_addr_and_idx(addr); + + let access = + MemorySegmentAccess::new(clk, MemoryOperation::Write, MemoryAccessType::Word, word); + match self.0.entry(word_addr) { Entry::Vacant(vacant_entry) => { + // All values in the word are set to the word being written. vacant_entry.insert(vec![access]); Ok(()) }, Entry::Occupied(mut occupied_entry) => { + // All values in the word are set to the word being written. let addr_trace = occupied_entry.get_mut(); if addr_trace.last().expect("empty address trace").clk() == clk { Err(ExecutionError::DuplicateMemoryAccess { ctx, addr, clk }) @@ -154,9 +270,58 @@ impl MemorySegmentTrace { // HELPER FUNCTIONS // -------------------------------------------------------------------------------------------- - /// Returns current size (in words) of this memory segment. + /// Records a read operation on the specified word at the specified clock cycle. + /// + /// The access type either specifies the element in word that was read, or that the entire word + /// was read. + /// + /// # Errors + /// - Returns an error if the same address is accessed more than once in the same clock cycle. + fn read_word_helper( + &mut self, + ctx: ContextId, + word_addr: u32, + clk: Felt, + access_type: MemoryAccessType, + ) -> Result { + match self.0.entry(word_addr) { + Entry::Vacant(vacant_entry) => { + // If this is the first access to the ctx/word pair, then all values in the word + // are initialized to 0. + let access = MemorySegmentAccess::new( + clk, + MemoryOperation::Read, + access_type, + INIT_MEM_VALUE, + ); + vacant_entry.insert(vec![access]); + Ok(INIT_MEM_VALUE) + }, + Entry::Occupied(mut occupied_entry) => { + // If the ctx/word pair has been accessed before, then the values in the word are + // the same as the previous access. + let addr_trace = occupied_entry.get_mut(); + if addr_trace.last().expect("empty address trace").clk() == clk { + Err(ExecutionError::DuplicateMemoryAccess { ctx, addr: word_addr, clk }) + } else { + let last_word = addr_trace.last().expect("empty address trace").word(); + let access = MemorySegmentAccess::new( + clk, + MemoryOperation::Read, + access_type, + last_word, + ); + addr_trace.push(access); + + Ok(last_word) + } + }, + } + } + + /// Returns the number of words that were accessed at least once. #[cfg(test)] - pub fn size(&self) -> usize { + pub fn num_accessed_words(&self) -> usize { self.0.len() } } @@ -166,23 +331,29 @@ impl MemorySegmentTrace { #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum MemoryOperation { - InitRead, - CopyRead, + Read, Write, } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum MemoryAccessType { + Element { addr_idx_in_word: u8 }, + Word, +} + /// A single memory access representing the specified memory operation with the specified value at /// the specified clock cycle. #[derive(Copy, Debug, Clone)] pub struct MemorySegmentAccess { clk: Felt, - op: MemoryOperation, - value: Word, + operation: MemoryOperation, + access_type: MemoryAccessType, + word: Word, } impl MemorySegmentAccess { - fn new(clk: Felt, op: MemoryOperation, value: Word) -> Self { - Self { clk, op, value } + fn new(clk: Felt, op: MemoryOperation, access_type: MemoryAccessType, word: Word) -> Self { + Self { clk, operation: op, access_type, word } } /// Returns the clock cycle at which this memory access happened. @@ -190,17 +361,32 @@ impl MemorySegmentAccess { self.clk } - /// Returns the selector values matching the operation used in this memory access. - pub(super) fn op_selectors(&self) -> Selectors { - match self.op { - MemoryOperation::InitRead => MEMORY_INIT_READ, - MemoryOperation::CopyRead => MEMORY_COPY_READ, - MemoryOperation::Write => MEMORY_WRITE, - } + /// Returns the operation associated with this memory access. + pub(super) fn operation(&self) -> MemoryOperation { + self.operation + } + + /// Returns the access type associated with this memory access. + pub(super) fn access_type(&self) -> MemoryAccessType { + self.access_type } - /// Returns the word value for this memory access. - pub(super) fn value(&self) -> Word { - self.value + /// Returns the word associated with this memory access. + /// + /// For example, if the memory access is an element read of address 42, the word will contain + /// the values of addresses 40, 41, 42, and 43. + pub(super) fn word(&self) -> Word { + self.word } } + +// HELPERS +// ================================================================================================ + +/// Splits an address into two components: +/// 1. a word, which is the closest value to `addr` that is both smaller and word aligned, and +/// 2. the index within the word which `addr` represents. +pub fn addr_to_word_addr_and_idx(addr: u32) -> (u32, u8) { + let idx = addr % WORD_SIZE as u32; + (addr - idx, idx as u8) +} diff --git a/processor/src/chiplets/memory/tests.rs b/processor/src/chiplets/memory/tests.rs index 5c169507f5..74123efa68 100644 --- a/processor/src/chiplets/memory/tests.rs +++ b/processor/src/chiplets/memory/tests.rs @@ -2,23 +2,26 @@ use alloc::vec::Vec; use miden_air::{ trace::chiplets::memory::{ - Selectors, MEMORY_COPY_READ, MEMORY_INIT_READ, MEMORY_WRITE, - TRACE_WIDTH as MEMORY_TRACE_WIDTH, + FLAG_SAME_CONTEXT_AND_WORD, IDX0_COL_IDX, IDX1_COL_IDX, IS_READ_COL_IDX, + IS_WORD_ACCESS_COL_IDX, MEMORY_ACCESS_ELEMENT, MEMORY_ACCESS_WORD, MEMORY_READ, + MEMORY_WRITE, TRACE_WIDTH as MEMORY_TRACE_WIDTH, }, RowIndex, }; -use vm_core::Word; +use vm_core::{assert_matches, Word, WORD_SIZE}; use super::{ - super::ZERO, Felt, FieldElement, Memory, TraceFragment, ADDR_COL_IDX, CLK_COL_IDX, CTX_COL_IDX, - D0_COL_IDX, D1_COL_IDX, D_INV_COL_IDX, EMPTY_WORD, ONE, V_COL_RANGE, + super::ZERO, + segment::{MemoryAccessType, MemoryOperation}, + Felt, FieldElement, Memory, TraceFragment, CLK_COL_IDX, CTX_COL_IDX, D0_COL_IDX, D1_COL_IDX, + D_INV_COL_IDX, EMPTY_WORD, ONE, V_COL_RANGE, WORD_COL_IDX, }; -use crate::ContextId; +use crate::{ContextId, ExecutionError}; #[test] fn mem_init() { let mem = Memory::default(); - assert_eq!(0, mem.size()); + assert_eq!(0, mem.num_accessed_words()); assert_eq!(0, mem.trace_len()); } @@ -27,51 +30,98 @@ fn mem_read() { let mut mem = Memory::default(); // read a value from address 0; clk = 1 - let addr0 = 0; + let addr0 = ZERO; let value = mem.read(ContextId::root(), addr0, 1.into()).unwrap(); - assert_eq!(EMPTY_WORD, value); - assert_eq!(1, mem.size()); + assert_eq!(ZERO, value); + assert_eq!(1, mem.num_accessed_words()); assert_eq!(1, mem.trace_len()); // read a value from address 3; clk = 2 - let addr3 = 3; + let addr3 = Felt::from(3_u32); let value = mem.read(ContextId::root(), addr3, 2.into()).unwrap(); - assert_eq!(EMPTY_WORD, value); - assert_eq!(2, mem.size()); + assert_eq!(ZERO, value); + assert_eq!(1, mem.num_accessed_words()); assert_eq!(2, mem.trace_len()); // read a value from address 0 again; clk = 3 let value = mem.read(ContextId::root(), addr0, 3.into()).unwrap(); - assert_eq!(EMPTY_WORD, value); - assert_eq!(2, mem.size()); + assert_eq!(ZERO, value); + assert_eq!(1, mem.num_accessed_words()); assert_eq!(3, mem.trace_len()); // read a value from address 2; clk = 4 - let addr2 = 2; + let addr2 = Felt::from(2_u32); let value = mem.read(ContextId::root(), addr2, 4.into()).unwrap(); - assert_eq!(EMPTY_WORD, value); - assert_eq!(3, mem.size()); + assert_eq!(ZERO, value); + assert_eq!(1, mem.num_accessed_words()); assert_eq!(4, mem.trace_len()); - // check generated trace and memory data provided to the ChipletsBus; rows should be sorted by - // address and then clock cycle + // check generated trace and memory data provided to the ChipletsBus; rows should be sorted only + // by clock cycle, since they all access the same word let trace = build_trace(mem, 4); - // address 0 + // clk 1 let mut prev_row = [ZERO; MEMORY_TRACE_WIDTH]; - let memory_access = MemoryAccess::new(ContextId::root(), addr0, 1.into(), EMPTY_WORD); - prev_row = verify_memory_access(&trace, 0, MEMORY_INIT_READ, &memory_access, prev_row); - - let memory_access = MemoryAccess::new(ContextId::root(), addr0, 3.into(), EMPTY_WORD); - prev_row = verify_memory_access(&trace, 1, MEMORY_COPY_READ, &memory_access, prev_row); + let memory_access = MemoryAccess::new( + MemoryOperation::Read, + MemoryAccessType::Element { addr_idx_in_word: 0 }, + ContextId::root(), + addr0, + 1.into(), + EMPTY_WORD, + ); + prev_row = verify_memory_access(&trace, 0, memory_access, prev_row); + + // clk 2 + let memory_access = MemoryAccess::new( + MemoryOperation::Read, + MemoryAccessType::Element { addr_idx_in_word: 3 }, + ContextId::root(), + addr3, + 2.into(), + EMPTY_WORD, + ); + prev_row = verify_memory_access(&trace, 1, memory_access, prev_row); + + // clk 3 + let memory_access = MemoryAccess::new( + MemoryOperation::Read, + MemoryAccessType::Element { addr_idx_in_word: 0 }, + ContextId::root(), + addr0, + 3.into(), + EMPTY_WORD, + ); + prev_row = verify_memory_access(&trace, 2, memory_access, prev_row); + + // clk 4 + let memory_access = MemoryAccess::new( + MemoryOperation::Read, + MemoryAccessType::Element { addr_idx_in_word: 2 }, + ContextId::root(), + addr2, + 4.into(), + EMPTY_WORD, + ); + verify_memory_access(&trace, 3, memory_access, prev_row); +} - // address 2 - let memory_access = MemoryAccess::new(ContextId::root(), addr2, 4.into(), EMPTY_WORD); - prev_row = verify_memory_access(&trace, 2, MEMORY_INIT_READ, &memory_access, prev_row); +/// Tests that writing a word to an address that is not aligned with the word boundary results in an +/// error. +#[test] +fn mem_read_word_unaligned() { + let mut mem = Memory::default(); - // address 3 - let memory_access = MemoryAccess::new(ContextId::root(), addr3, 2.into(), EMPTY_WORD); - verify_memory_access(&trace, 3, MEMORY_INIT_READ, &memory_access, prev_row); + // write a value into address 0; clk = 1 + let addr = ONE; + let clk = 1.into(); + let ctx = ContextId::root(); + let ret = mem.read_word(ctx, addr, clk); + + assert_matches!( + ret, + Err(ExecutionError::MemoryUnalignedWordAccess { addr: _, ctx: _, clk: _ }) + ); } #[test] @@ -79,224 +129,330 @@ fn mem_write() { let mut mem = Memory::default(); // write a value into address 0; clk = 1 - let addr0 = 0; - let value1 = [ONE, ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr0, 1.into(), value1).unwrap(); - assert_eq!(value1, mem.get_value(ContextId::root(), addr0).unwrap()); - assert_eq!(1, mem.size()); + let addr0 = 0_u32; + let word1 = [ONE, ZERO, ZERO, ZERO]; + mem.write_word(ContextId::root(), addr0.into(), 1.into(), word1).unwrap(); + assert_eq!(word1, mem.get_word(ContextId::root(), addr0).unwrap().unwrap()); + assert_eq!(1, mem.num_accessed_words()); assert_eq!(1, mem.trace_len()); // write a value into address 2; clk = 2 - let addr2 = 2; - let value5 = [Felt::new(5), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr2, 2.into(), value5).unwrap(); + let addr2 = 2_u32; + let value5 = Felt::new(5); + mem.write(ContextId::root(), addr2.into(), 2.into(), value5).unwrap(); assert_eq!(value5, mem.get_value(ContextId::root(), addr2).unwrap()); - assert_eq!(2, mem.size()); + assert_eq!(1, mem.num_accessed_words()); assert_eq!(2, mem.trace_len()); // write a value into address 1; clk = 3 - let addr1 = 1; - let value7 = [Felt::new(7), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr1, 3.into(), value7).unwrap(); + let addr1 = 1_u32; + let value7 = Felt::new(7); + mem.write(ContextId::root(), addr1.into(), 3.into(), value7).unwrap(); assert_eq!(value7, mem.get_value(ContextId::root(), addr1).unwrap()); - assert_eq!(3, mem.size()); + assert_eq!(1, mem.num_accessed_words()); assert_eq!(3, mem.trace_len()); - // write a value into address 0; clk = 4 - let value9 = [Felt::new(9), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr0, 4.into(), value9).unwrap(); - assert_eq!(value7, mem.get_value(ContextId::root(), addr1).unwrap()); - assert_eq!(3, mem.size()); + // write a value into address 3; clk = 4 + let addr3 = 3_u32; + let value9 = Felt::new(9); + mem.write(ContextId::root(), addr3.into(), 4.into(), value9).unwrap(); + assert_eq!(value9, mem.get_value(ContextId::root(), addr3).unwrap()); + assert_eq!(1, mem.num_accessed_words()); assert_eq!(4, mem.trace_len()); + // write a word into address 4; clk = 5 + let addr4 = 4_u32; + let word1234 = [ONE, 2_u32.into(), 3_u32.into(), 4_u32.into()]; + mem.write_word(ContextId::root(), addr4.into(), 5.into(), word1234).unwrap(); + assert_eq!(word1234, mem.get_word(ContextId::root(), addr4).unwrap().unwrap()); + assert_eq!(2, mem.num_accessed_words()); + assert_eq!(5, mem.trace_len()); + + // write a word into address 0; clk = 6 + let word5678: [Felt; 4] = [5_u32.into(), 6_u32.into(), 7_u32.into(), 8_u32.into()]; + mem.write_word(ContextId::root(), addr0.into(), 6.into(), word5678).unwrap(); + assert_eq!(word5678, mem.get_word(ContextId::root(), addr0).unwrap().unwrap()); + assert_eq!(2, mem.num_accessed_words()); + assert_eq!(6, mem.trace_len()); + // check generated trace and memory data provided to the ChipletsBus; rows should be sorted by // address and then clock cycle - let trace = build_trace(mem, 4); + let trace = build_trace(mem, 6); - // address 0 + // word 0 let mut prev_row = [ZERO; MEMORY_TRACE_WIDTH]; - let memory_access = MemoryAccess::new(ContextId::root(), addr0, 1.into(), value1); - prev_row = verify_memory_access(&trace, 0, MEMORY_WRITE, &memory_access, prev_row); - - let memory_access = MemoryAccess::new(ContextId::root(), addr0, 4.into(), value9); - prev_row = verify_memory_access(&trace, 1, MEMORY_WRITE, &memory_access, prev_row); - - // address 1 - let memory_access = MemoryAccess::new(ContextId::root(), addr1, 3.into(), value7); - prev_row = verify_memory_access(&trace, 2, MEMORY_WRITE, &memory_access, prev_row); - - // address 2 - let memory_access = MemoryAccess::new(ContextId::root(), addr2, 2.into(), value5); - verify_memory_access(&trace, 3, MEMORY_WRITE, &memory_access, prev_row); + let memory_access = MemoryAccess::new( + MemoryOperation::Write, + MemoryAccessType::Word, + ContextId::root(), + addr0.into(), + 1.into(), + word1, + ); + prev_row = verify_memory_access(&trace, 0, memory_access, prev_row); + + let memory_access = MemoryAccess::new( + MemoryOperation::Write, + MemoryAccessType::Element { addr_idx_in_word: 2 }, + ContextId::root(), + addr2.into(), + 2.into(), + [ONE, ZERO, value5, ZERO], + ); + prev_row = verify_memory_access(&trace, 1, memory_access, prev_row); + + let memory_access = MemoryAccess::new( + MemoryOperation::Write, + MemoryAccessType::Element { addr_idx_in_word: 1 }, + ContextId::root(), + addr1.into(), + 3.into(), + [ONE, value7, value5, ZERO], + ); + prev_row = verify_memory_access(&trace, 2, memory_access, prev_row); + + let memory_access = MemoryAccess::new( + MemoryOperation::Write, + MemoryAccessType::Element { addr_idx_in_word: 3 }, + ContextId::root(), + addr3.into(), + 4.into(), + [ONE, value7, value5, value9], + ); + prev_row = verify_memory_access(&trace, 3, memory_access, prev_row); + + let memory_access = MemoryAccess::new( + MemoryOperation::Write, + MemoryAccessType::Word, + ContextId::root(), + addr0.into(), + 6.into(), + word5678, + ); + prev_row = verify_memory_access(&trace, 4, memory_access, prev_row); + + // word 1 + let memory_access = MemoryAccess::new( + MemoryOperation::Write, + MemoryAccessType::Word, + ContextId::root(), + addr4.into(), + 5.into(), + word1234, + ); + verify_memory_access(&trace, 5, memory_access, prev_row); } +/// Tests that writing a word to an address that is not aligned with the word boundary results in an +/// error. #[test] -fn mem_write_read() { +fn mem_write_word_unaligned() { let mut mem = Memory::default(); - // write 1 into address 5; clk = 1 - let addr5 = 5; - let value1 = [ONE, ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr5, 1.into(), value1).unwrap(); - - // write 4 into address 2; clk = 2 - let addr2 = 2; - let value4 = [Felt::new(4), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr2, 2.into(), value4).unwrap(); - - // read a value from address 5; clk = 3 - mem.read(ContextId::root(), addr5, 3.into()).unwrap(); - - // write 2 into address 5; clk = 4 - let value2 = [Felt::new(2), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr5, 4.into(), value2).unwrap(); - - // read a value from address 2; clk = 5 - mem.read(ContextId::root(), addr2, 5.into()).unwrap(); - - // write 7 into address 2; clk = 6 - let value7 = [Felt::new(7), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), addr2, 6.into(), value7).unwrap(); - - // read a value from address 5; clk = 7 - mem.read(ContextId::root(), addr5, 7.into()).unwrap(); - - // read a value from address 2; clk = 8 - mem.read(ContextId::root(), addr2, 8.into()).unwrap(); - - // read a value from address 5; clk = 9 - mem.read(ContextId::root(), addr5, 9.into()).unwrap(); - - // check generated trace and memory data provided to the ChipletsBus; rows should be sorted by - // address and then clock cycle - let trace = build_trace(mem, 9); - - // address 2 - let mut prev_row = [ZERO; MEMORY_TRACE_WIDTH]; - let memory_access = MemoryAccess::new(ContextId::root(), addr2, 2.into(), value4); - prev_row = verify_memory_access(&trace, 0, MEMORY_WRITE, &memory_access, prev_row); - - let memory_access = MemoryAccess::new(ContextId::root(), addr2, 5.into(), value4); - prev_row = verify_memory_access(&trace, 1, MEMORY_COPY_READ, &memory_access, prev_row); - - let memory_access = MemoryAccess::new(ContextId::root(), addr2, 6.into(), value7); - prev_row = verify_memory_access(&trace, 2, MEMORY_WRITE, &memory_access, prev_row); - - let memory_access = MemoryAccess::new(ContextId::root(), addr2, 8.into(), value7); - prev_row = verify_memory_access(&trace, 3, MEMORY_COPY_READ, &memory_access, prev_row); - - // address 5 - let memory_access = MemoryAccess::new(ContextId::root(), addr5, 1.into(), value1); - prev_row = verify_memory_access(&trace, 4, MEMORY_WRITE, &memory_access, prev_row); - - let memory_access = MemoryAccess::new(ContextId::root(), addr5, 3.into(), value1); - prev_row = verify_memory_access(&trace, 5, MEMORY_COPY_READ, &memory_access, prev_row); - - let memory_access = MemoryAccess::new(ContextId::root(), addr5, 4.into(), value2); - prev_row = verify_memory_access(&trace, 6, MEMORY_WRITE, &memory_access, prev_row); - - let memory_access = MemoryAccess::new(ContextId::root(), addr5, 7.into(), value2); - prev_row = verify_memory_access(&trace, 7, MEMORY_COPY_READ, &memory_access, prev_row); - - let memory_access = MemoryAccess::new(ContextId::root(), addr5, 9.into(), value2); - verify_memory_access(&trace, 8, MEMORY_COPY_READ, &memory_access, prev_row); + // write a value into address 0; clk = 1 + let addr = ONE; + let word1 = [ONE, ZERO, ZERO, ZERO]; + let clk = 1.into(); + let ctx = ContextId::root(); + let ret = mem.write_word(ctx, addr, clk, word1); + + assert_matches!( + ret, + Err(ExecutionError::MemoryUnalignedWordAccess { addr: _, ctx: _, clk: _ }) + ); } +/// Tests that values written are properly read back. #[test] -fn mem_multi_context() { +fn mem_write_read() { let mut mem = Memory::default(); - - // write a value into ctx = ContextId::root(), addr = 0; clk = 1 - let value1 = [ONE, ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), 0, 1.into(), value1).unwrap(); - assert_eq!(value1, mem.get_value(ContextId::root(), 0).unwrap()); - assert_eq!(1, mem.size()); - assert_eq!(1, mem.trace_len()); - - // write a value into ctx = 3, addr = 1; clk = 4 - let value2 = [ZERO, ONE, ZERO, ZERO]; - mem.write(3.into(), 1, 4.into(), value2).unwrap(); - assert_eq!(value2, mem.get_value(3.into(), 1).unwrap()); - assert_eq!(2, mem.size()); - assert_eq!(2, mem.trace_len()); - - // read a value from ctx = 3, addr = 1; clk = 6 - let value = mem.read(3.into(), 1, 6.into()).unwrap(); - assert_eq!(value2, value); - assert_eq!(2, mem.size()); - assert_eq!(3, mem.trace_len()); - - // write a value into ctx = 3, addr = 0; clk = 7 - let value3 = [ZERO, ZERO, ONE, ZERO]; - mem.write(3.into(), 0, 7.into(), value3).unwrap(); - assert_eq!(value3, mem.get_value(3.into(), 0).unwrap()); - assert_eq!(3, mem.size()); - assert_eq!(4, mem.trace_len()); - - // read a value from ctx = 0, addr = 0; clk = 9 - let value = mem.read(ContextId::root(), 0, 9.into()).unwrap(); - assert_eq!(value1, value); - assert_eq!(3, mem.size()); - assert_eq!(5, mem.trace_len()); + let mut clk: RowIndex = 1.into(); + + // write [1,2,3,4] starting at address 0; clk = 1 + let word1234 = [ONE, 2_u32.into(), 3_u32.into(), 4_u32.into()]; + mem.write_word(ContextId::root(), ZERO, clk, word1234).unwrap(); + clk += 1; + + // read individual values from addresses 3,2,1,0; clk = 2,3,4,5 + let value_read = mem.read(ContextId::root(), 3_u32.into(), clk).unwrap(); + assert_eq!(value_read, 4_u32.into()); + clk += 1; + let value_read = mem.read(ContextId::root(), 2_u32.into(), clk).unwrap(); + assert_eq!(value_read, 3_u32.into()); + clk += 1; + let value_read = mem.read(ContextId::root(), 1_u32.into(), clk).unwrap(); + assert_eq!(value_read, 2_u32.into()); + clk += 1; + let value_read = mem.read(ContextId::root(), ZERO, clk).unwrap(); + assert_eq!(value_read, 1_u32.into()); + clk += 1; + + // read word from address 0; clk = 6 + let word_read = mem.read_word(ContextId::root(), ZERO, clk).unwrap(); + assert_eq!(word_read, word1234); + clk += 1; + + // write 42 into address 2; clk = 7 + mem.write(ContextId::root(), 2_u32.into(), clk, 42_u32.into()).unwrap(); + clk += 1; + + // read element from address 2; clk = 8 + let value_read = mem.read(ContextId::root(), 2_u32.into(), clk).unwrap(); + assert_eq!(value_read, 42_u32.into()); + clk += 1; + + // read word from address 0; clk = 9 + let word_read = mem.read_word(ContextId::root(), ZERO, clk).unwrap(); + assert_eq!(word_read, [ONE, 2_u32.into(), 42_u32.into(), 4_u32.into()]); + clk += 1; // check generated trace and memory data provided to the ChipletsBus; rows should be sorted by // address and then clock cycle - let trace = build_trace(mem, 5); + let trace = build_trace(mem, 9); + let mut clk: RowIndex = 1.into(); - // ctx = 0, addr = 0 + // address 2 let mut prev_row = [ZERO; MEMORY_TRACE_WIDTH]; - let memory_access = MemoryAccess::new(ContextId::root(), 0, 1.into(), value1); - prev_row = verify_memory_access(&trace, 0, MEMORY_WRITE, &memory_access, prev_row); - - let memory_access = MemoryAccess::new(ContextId::root(), 0, 9.into(), value1); - prev_row = verify_memory_access(&trace, 1, MEMORY_COPY_READ, &memory_access, prev_row); - - // ctx = 3, addr = 0 - let memory_access = MemoryAccess::new(3.into(), 0, 7.into(), value3); - prev_row = verify_memory_access(&trace, 2, MEMORY_WRITE, &memory_access, prev_row); - - // ctx = 3, addr = 1 - let memory_access = MemoryAccess::new(3.into(), 1, 4.into(), value2); - prev_row = verify_memory_access(&trace, 3, MEMORY_WRITE, &memory_access, prev_row); - - let memory_access = MemoryAccess::new(3.into(), 1, 6.into(), value2); - verify_memory_access(&trace, 4, MEMORY_COPY_READ, &memory_access, prev_row); + let memory_access = MemoryAccess::new( + MemoryOperation::Write, + MemoryAccessType::Word, + ContextId::root(), + ZERO, + clk, + word1234, + ); + prev_row = verify_memory_access(&trace, 0, memory_access, prev_row); + clk += 1; + + let memory_access = MemoryAccess::new( + MemoryOperation::Read, + MemoryAccessType::Element { addr_idx_in_word: 3 }, + ContextId::root(), + 3_u32.into(), + clk, + word1234, + ); + prev_row = verify_memory_access(&trace, 1, memory_access, prev_row); + clk += 1; + + let memory_access = MemoryAccess::new( + MemoryOperation::Read, + MemoryAccessType::Element { addr_idx_in_word: 2 }, + ContextId::root(), + 2_u32.into(), + clk, + word1234, + ); + prev_row = verify_memory_access(&trace, 2, memory_access, prev_row); + clk += 1; + + let memory_access = MemoryAccess::new( + MemoryOperation::Read, + MemoryAccessType::Element { addr_idx_in_word: 1 }, + ContextId::root(), + 1_u32.into(), + clk, + word1234, + ); + prev_row = verify_memory_access(&trace, 3, memory_access, prev_row); + clk += 1; + + let memory_access = MemoryAccess::new( + MemoryOperation::Read, + MemoryAccessType::Element { addr_idx_in_word: 0 }, + ContextId::root(), + ZERO, + clk, + word1234, + ); + prev_row = verify_memory_access(&trace, 4, memory_access, prev_row); + clk += 1; + + let memory_access = MemoryAccess::new( + MemoryOperation::Read, + MemoryAccessType::Word, + ContextId::root(), + ZERO, + clk, + word1234, + ); + prev_row = verify_memory_access(&trace, 5, memory_access, prev_row); + clk += 1; + + let memory_access = MemoryAccess::new( + MemoryOperation::Write, + MemoryAccessType::Element { addr_idx_in_word: 2 }, + ContextId::root(), + 2_u32.into(), + clk, + [ONE, 2_u32.into(), 42_u32.into(), 4_u32.into()], + ); + prev_row = verify_memory_access(&trace, 6, memory_access, prev_row); + clk += 1; + + let memory_access = MemoryAccess::new( + MemoryOperation::Read, + MemoryAccessType::Element { addr_idx_in_word: 2 }, + ContextId::root(), + 2_u32.into(), + clk, + [ONE, 2_u32.into(), 42_u32.into(), 4_u32.into()], + ); + prev_row = verify_memory_access(&trace, 7, memory_access, prev_row); + clk += 1; + + let memory_access = MemoryAccess::new( + MemoryOperation::Read, + MemoryAccessType::Word, + ContextId::root(), + ZERO, + clk, + [ONE, 2_u32.into(), 42_u32.into(), 4_u32.into()], + ); + verify_memory_access(&trace, 8, memory_access, prev_row); } #[test] fn mem_get_state_at() { let mut mem = Memory::default(); - // Write 1 into (ctx = 0, addr = 5) at clk = 1. - // This means that mem[5] = 1 at the beginning of clk = 2 - let value1 = [ONE, ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), 5, 1.into(), value1).unwrap(); + let addr_start: u32 = 40_u32; - // Write 4 into (ctx = 0, addr = 2) at clk = 2. - // This means that mem[2] = 4 at the beginning of clk = 3 - let value4 = [Felt::new(4), ZERO, ZERO, ZERO]; - mem.write(ContextId::root(), 2, 2.into(), value4).unwrap(); + // Write word starting at (ctx = 0, addr = 40) at clk = 1. + // This means that mem[40..43] is set at the beginning of clk = 2 + let word1234 = [ONE, 2_u32.into(), 3_u32.into(), 4_u32.into()]; + mem.write_word(ContextId::root(), addr_start.into(), 1.into(), word1234) + .unwrap(); - // write 7 into (ctx = 3, addr = 3) at clk = 4 - // This means that mem[3] = 7 at the beginning of clk = 4 - let value7 = [Felt::new(7), ZERO, ZERO, ZERO]; - mem.write(3.into(), 3, 4.into(), value7).unwrap(); + let word4567: [Felt; 4] = [4_u32.into(), 5_u32.into(), 6_u32.into(), 7_u32.into()]; + mem.write_word(ContextId::root(), addr_start.into(), 2.into(), word4567) + .unwrap(); // Check memory state at clk = 2 - assert_eq!(mem.get_state_at(ContextId::root(), 2.into()), vec![(5, value1)]); - assert_eq!(mem.get_state_at(3.into(), 2.into()), vec![]); + let clk: RowIndex = 2.into(); + assert_eq!( + mem.get_state_at(ContextId::root(), clk), + vec![ + (addr_start.into(), word1234[0]), + (u64::from(addr_start) + 1_u64, word1234[1]), + (u64::from(addr_start) + 2_u64, word1234[2]), + (u64::from(addr_start) + 3_u64, word1234[3]) + ] + ); + assert_eq!(mem.get_state_at(3.into(), clk), vec![]); // Check memory state at clk = 3 - assert_eq!(mem.get_state_at(ContextId::root(), 3.into()), vec![(2, value4), (5, value1)]); - assert_eq!(mem.get_state_at(3.into(), 3.into()), vec![]); - - // Check memory state at clk = 4 - assert_eq!(mem.get_state_at(ContextId::root(), 4.into()), vec![(2, value4), (5, value1)]); - assert_eq!(mem.get_state_at(3.into(), 4.into()), vec![]); - - // Check memory state at clk = 5 - assert_eq!(mem.get_state_at(ContextId::root(), 5.into()), vec![(2, value4), (5, value1)]); - assert_eq!(mem.get_state_at(3.into(), 5.into()), vec![(3, value7)]); + let clk: RowIndex = 3.into(); + assert_eq!( + mem.get_state_at(ContextId::root(), clk), + vec![ + (addr_start.into(), word4567[0]), + (u64::from(addr_start) + 1_u64, word4567[1]), + (u64::from(addr_start) + 2_u64, word4567[2]), + (u64::from(addr_start) + 3_u64, word4567[3]) + ] + ); + assert_eq!(mem.get_state_at(3.into(), clk), vec![]); } // HELPER STRUCT & FUNCTIONS @@ -304,19 +460,35 @@ fn mem_get_state_at() { /// Contains data representing a memory access. pub struct MemoryAccess { + operation: MemoryOperation, + access_type: MemoryAccessType, ctx: ContextId, addr: Felt, clk: Felt, - word: [Felt; 4], + word_values: [Felt; 4], } impl MemoryAccess { - pub fn new(ctx: ContextId, addr: u32, clk: RowIndex, word: Word) -> Self { + pub fn new( + operation: MemoryOperation, + access_type: MemoryAccessType, + ctx: ContextId, + addr: Felt, + clk: RowIndex, + word_values: Word, + ) -> Self { + if let MemoryAccessType::Element { addr_idx_in_word } = access_type { + let addr: u32 = addr.try_into().unwrap(); + assert_eq!(addr_idx_in_word as u32, addr % WORD_SIZE as u32); + } + Self { + operation, + access_type, ctx, - addr: Felt::from(addr), + addr, clk: Felt::from(clk), - word, + word_values, } } } @@ -339,29 +511,57 @@ fn read_trace_row(trace: &[Vec], step: usize) -> [Felt; MEMORY_TRACE_WIDTH } fn build_trace_row( - memory_access: &MemoryAccess, - op_selectors: Selectors, + memory_access: MemoryAccess, prev_row: [Felt; MEMORY_TRACE_WIDTH], ) -> [Felt; MEMORY_TRACE_WIDTH] { - let MemoryAccess { ctx, addr, clk, word: new_val } = *memory_access; + let MemoryAccess { + operation, + access_type, + ctx, + addr, + clk, + word_values, + } = memory_access; + + let (word, idx1, idx0) = { + let addr: u32 = addr.try_into().unwrap(); + let remainder = addr % WORD_SIZE as u32; + let word = Felt::from(addr - remainder); + + match remainder { + 0 => (word, ZERO, ZERO), + 1 => (word, ZERO, ONE), + 2 => (word, ONE, ZERO), + 3 => (word, ONE, ONE), + _ => unreachable!(), + } + }; let mut row = [ZERO; MEMORY_TRACE_WIDTH]; - row[0] = op_selectors[0]; - row[1] = op_selectors[1]; + row[IS_READ_COL_IDX] = match operation { + MemoryOperation::Read => MEMORY_READ, + MemoryOperation::Write => MEMORY_WRITE, + }; + row[IS_WORD_ACCESS_COL_IDX] = match access_type { + MemoryAccessType::Element { .. } => MEMORY_ACCESS_ELEMENT, + MemoryAccessType::Word => MEMORY_ACCESS_WORD, + }; row[CTX_COL_IDX] = ctx.into(); - row[ADDR_COL_IDX] = addr; + row[WORD_COL_IDX] = word; + row[IDX0_COL_IDX] = idx0; + row[IDX1_COL_IDX] = idx1; row[CLK_COL_IDX] = clk; - row[V_COL_RANGE.start] = new_val[0]; - row[V_COL_RANGE.start + 1] = new_val[1]; - row[V_COL_RANGE.start + 2] = new_val[2]; - row[V_COL_RANGE.start + 3] = new_val[3]; + row[V_COL_RANGE.start] = word_values[0]; + row[V_COL_RANGE.start + 1] = word_values[1]; + row[V_COL_RANGE.start + 2] = word_values[2]; + row[V_COL_RANGE.start + 3] = word_values[3]; if prev_row != [ZERO; MEMORY_TRACE_WIDTH] { let delta = if row[CTX_COL_IDX] != prev_row[CTX_COL_IDX] { row[CTX_COL_IDX] - prev_row[CTX_COL_IDX] - } else if row[ADDR_COL_IDX] != prev_row[ADDR_COL_IDX] { - row[ADDR_COL_IDX] - prev_row[ADDR_COL_IDX] + } else if row[WORD_COL_IDX] != prev_row[WORD_COL_IDX] { + row[WORD_COL_IDX] - prev_row[WORD_COL_IDX] } else { row[CLK_COL_IDX] - prev_row[CLK_COL_IDX] - ONE }; @@ -372,18 +572,24 @@ fn build_trace_row( row[D_INV_COL_IDX] = delta.inv(); } + if row[WORD_COL_IDX] == prev_row[WORD_COL_IDX] && row[CTX_COL_IDX] == prev_row[CTX_COL_IDX] { + row[FLAG_SAME_CONTEXT_AND_WORD] = ONE; + } else { + row[FLAG_SAME_CONTEXT_AND_WORD] = ZERO; + } + row } fn verify_memory_access( trace: &[Vec], row: u32, - op_selectors: Selectors, - memory_access: &MemoryAccess, + mem_access: MemoryAccess, prev_row: [Felt; MEMORY_TRACE_WIDTH], ) -> [Felt; MEMORY_TRACE_WIDTH] { - let expected_row = build_trace_row(memory_access, op_selectors, prev_row); - assert_eq!(expected_row, read_trace_row(trace, row as usize)); + let expected_row = build_trace_row(mem_access, prev_row); + let actual_row = read_trace_row(trace, row as usize); + assert_eq!(expected_row, actual_row); expected_row } diff --git a/processor/src/chiplets/mod.rs b/processor/src/chiplets/mod.rs index d2c8c13aae..c36dda1945 100644 --- a/processor/src/chiplets/mod.rs +++ b/processor/src/chiplets/mod.rs @@ -10,7 +10,6 @@ use super::{ crypto::MerklePath, utils, ChipletsTrace, ExecutionError, Felt, FieldElement, RangeChecker, TraceFragment, Word, CHIPLETS_WIDTH, EMPTY_WORD, ONE, ZERO, }; -use crate::system::ContextId; mod bitwise; use bitwise::Bitwise; @@ -44,7 +43,8 @@ mod tests; /// * 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 +/// - columns 1-16: execution trace of hash chiplet +/// - column 17: unused column 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. @@ -52,13 +52,12 @@ mod tests; /// - 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 +/// * 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 +/// - columns 3-17: execution trace of memory chiplet /// * 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. @@ -89,11 +88,11 @@ mod tests; /// | . | . | selectors | |-------------| /// | . | 0 | | |-------------| /// | . +---+---+-----------------------------------------------+-------------+ -/// | . | 1 | 0 | | |-------------| -/// | . | . | . | Memory chiplet | Memory chiplet |-------------| -/// | . | . | . | internal | 12 columns |-- Padding --| -/// | . | . | . | selectors | constraint degree 9 |-------------| -/// | . | . | 0 | | |-------------| +/// | . | 1 | 0 | |-------------| +/// | . | . | . | Memory chiplet |-------------| +/// | . | . | . | 15 columns |-- Padding --| +/// | . | . | . | constraint degree 9 |-------------| +/// | . | . | 0 | |-------------| /// | . + . |---+---+-------------------------------------------+-------------+ /// | . | . | 1 | 0 | | |-------------| /// | . | . | . | . | Kernel ROM | Kernel ROM chiplet |-------------| @@ -286,91 +285,14 @@ impl Chiplets { // MEMORY CHIPLET ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns a word located in memory at the specified context/address while recording the - /// memory access in the memory trace. - /// - /// If the specified address hasn't been previously written to, four ZERO elements are - /// returned. This effectively implies that memory is initialized to ZERO. - pub fn read_mem(&mut self, ctx: ContextId, addr: u32) -> Result { - // read the word from memory - self.memory.read(ctx, addr, self.clk) - } - - /// Returns two words read from consecutive addresses started with `addr` in the specified - /// context while recording memory accesses in the memory trace. - /// - /// If either of the accessed addresses hasn't been previously written to, ZERO elements are - /// returned. This effectively implies that memory is initialized to ZERO. - pub fn read_mem_double( - &mut self, - ctx: ContextId, - addr: u32, - ) -> Result<[Word; 2], ExecutionError> { - // read two words from memory: from addr and from addr + 1 - let addr2 = addr + 1; - Ok([self.memory.read(ctx, addr, self.clk)?, self.memory.read(ctx, addr2, self.clk)?]) - } - - /// Writes the provided word at the specified context/address. - pub fn write_mem( - &mut self, - ctx: ContextId, - addr: u32, - word: Word, - ) -> Result<(), ExecutionError> { - self.memory.write(ctx, addr, self.clk, word) - } - - /// Writes the provided element into the specified context/address leaving the remaining 3 - /// elements of the word previously stored at that address unchanged. - pub fn write_mem_element( - &mut self, - ctx: ContextId, - addr: u32, - value: Felt, - ) -> Result { - let old_word = self.memory.get_old_value(ctx, addr); - let new_word = [value, old_word[1], old_word[2], old_word[3]]; - - self.memory.write(ctx, addr, self.clk, new_word)?; - - Ok(old_word) + /// Returns a reference to the Memory chiplet. + pub fn memory(&self) -> &Memory { + &self.memory } - /// Writes the two provided words to two consecutive addresses in memory in the specified - /// context, starting at the specified address. - pub fn write_mem_double( - &mut self, - ctx: ContextId, - addr: u32, - words: [Word; 2], - ) -> Result<(), ExecutionError> { - let addr2 = addr + 1; - // write two words to memory at addr and addr + 1 - self.memory.write(ctx, addr, self.clk, words[0])?; - self.memory.write(ctx, addr2, self.clk, words[1]) - } - - /// Returns a word located at the specified context/address, or None if the address hasn't - /// been accessed previously. - /// - /// Unlike mem_read() which modifies the memory access trace, this method returns the value at - /// the specified address (if one exists) without altering the memory access trace. - pub fn get_mem_value(&self, ctx: ContextId, addr: u32) -> Option { - self.memory.get_value(ctx, addr) - } - - /// Returns the entire memory state for the specified execution context at the specified cycle. - /// The state is returned as a vector of (address, value) tuples, and includes addresses which - /// have been accessed at least once. - pub fn get_mem_state_at(&self, ctx: ContextId, clk: RowIndex) -> Vec<(u64, Word)> { - self.memory.get_state_at(ctx, clk) - } - - /// Returns current size of the memory (in words) across all execution contexts. - #[cfg(test)] - pub fn get_mem_size(&self) -> usize { - self.memory.size() + /// Returns a mutable reference to the Memory chiplet. + pub fn memory_mut(&mut self) -> &mut Memory { + &mut self.memory } // KERNEL ROM ACCESSORS @@ -469,7 +391,7 @@ impl Chiplets { // so they can be filled with the chiplet traces for (column_num, column) in trace.iter_mut().enumerate().skip(1) { match column_num { - 1 | 15..=17 => { + 1 => { // columns 1 and 15 - 17 are relevant only for the hasher hasher_fragment.push_column_slice(column, hasher.trace_len()); }, @@ -491,6 +413,19 @@ impl Chiplets { let rest = memory_fragment.push_column_slice(rest, memory.trace_len()); kernel_rom_fragment.push_column_slice(rest, kernel_rom.trace_len()); }, + 15 | 16 => { + // columns 15 and 16 are relevant only for the hasher and memory chiplets + let rest = hasher_fragment.push_column_slice(column, hasher.trace_len()); + // skip bitwise chiplet + let (_, rest) = rest.split_at_mut(bitwise.trace_len()); + memory_fragment.push_column_slice(rest, memory.trace_len()); + }, + 17 => { + // column 17 is relevant only for the memory chiplet + // skip the hasher and bitwise chiplets + let (_, rest) = column.split_at_mut(hasher.trace_len() + bitwise.trace_len()); + memory_fragment.push_column_slice(rest, memory.trace_len()); + }, _ => panic!("invalid column index"), } } diff --git a/processor/src/chiplets/tests.rs b/processor/src/chiplets/tests.rs index 89253f6916..28606b983b 100644 --- a/processor/src/chiplets/tests.rs +++ b/processor/src/chiplets/tests.rs @@ -51,8 +51,9 @@ fn bitwise_chiplet_trace() { #[test] fn memory_chiplet_trace() { // --- single memory operation with no stack manipulation ------------------------------------- + let addr = Felt::from(4_u32); let stack = [1, 2, 3, 4]; - let operations = vec![Operation::Push(Felt::new(2)), Operation::MStoreW]; + let operations = vec![Operation::Push(addr), Operation::MStoreW]; let (chiplets_trace, trace_len) = build_trace(&stack, operations, Kernel::default()); let memory_trace_len = 1; diff --git a/processor/src/debug.rs b/processor/src/debug.rs index ab908b5609..805b7f64a6 100644 --- a/processor/src/debug.rs +++ b/processor/src/debug.rs @@ -5,7 +5,7 @@ use alloc::{ use core::fmt; use miden_air::RowIndex; -use vm_core::{AssemblyOp, Operation, StackOutputs, Word}; +use vm_core::{AssemblyOp, Operation, StackOutputs}; use crate::{ range::RangeChecker, system::ContextId, Chiplets, ChipletsLengths, Decoder, ExecutionError, @@ -21,17 +21,15 @@ pub struct VmState { pub asmop: Option, pub fmp: Felt, pub stack: Vec, - pub memory: Vec<(u64, Word)>, + pub memory: Vec<(u64, Felt)>, } impl fmt::Display for VmState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let stack: Vec = self.stack.iter().map(|x| x.as_int()).collect(); - let memory: Vec<(u64, [u64; 4])> = - self.memory.iter().map(|x| (x.0, word_to_ints(&x.1))).collect(); write!( f, - "clk={}{}{}, fmp={}, stack={stack:?}, memory={memory:?}", + "clk={}{}{}, fmp={}, stack={stack:?}, memory={:?}", self.clk, match self.op { Some(op) => format!(", op={op}"), @@ -41,7 +39,8 @@ impl fmt::Display for VmState { Some(op) => format!(", {op}"), None => "".to_string(), }, - self.fmp + self.fmp, + self.memory ) } } @@ -166,7 +165,7 @@ impl VmStateIterator { asmop, fmp: self.system.get_fmp_at(self.clk), stack: self.stack.get_state_at(self.clk), - memory: self.chiplets.get_mem_state_at(ctx, self.clk), + memory: self.chiplets.memory().get_state_at(ctx, self.clk), }); self.clk -= 1; @@ -236,7 +235,7 @@ impl Iterator for VmStateIterator { asmop, fmp: self.system.get_fmp_at(self.clk), stack: self.stack.get_state_at(self.clk), - memory: self.chiplets.get_mem_state_at(ctx, self.clk), + memory: self.chiplets.memory().get_state_at(ctx, self.clk), })); self.clk += 1; @@ -245,12 +244,6 @@ impl Iterator for VmStateIterator { } } -// HELPER FUNCTIONS -// ================================================================================================ -fn word_to_ints(word: &Word) -> [u64; 4] { - [word[0].as_int(), word[1].as_int(), word[2].as_int(), word[3].as_int()] -} - /// Contains assembly instruction and operation index in the sequence corresponding to the specified /// AsmOp decorator. This index starts from 1 instead of 0. #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/processor/src/decoder/mod.rs b/processor/src/decoder/mod.rs index 2b7ee125ad..c7575e5b45 100644 --- a/processor/src/decoder/mod.rs +++ b/processor/src/decoder/mod.rs @@ -319,7 +319,10 @@ impl Process { let mem_addr = self.stack.get(0); // The callee hash is stored in memory, and the address is specified on the top of the // stack. - let callee_hash = self.read_mem_word(mem_addr)?; + let callee_hash = + self.chiplets + .memory_mut() + .read_word(self.system.ctx(), mem_addr, self.system.clk())?; let addr = self.chiplets.hash_control_block( EMPTY_WORD, @@ -350,7 +353,10 @@ impl Process { let mem_addr = self.stack.get(0); // The callee hash is stored in memory, and the address is specified on the top of the // stack. - let callee_hash = self.read_mem_word(mem_addr)?; + let callee_hash = + self.chiplets + .memory_mut() + .read_word(self.system.ctx(), mem_addr, self.system.clk())?; // Note: other functions end in "executing a Noop", which // 1. ensures trace capacity, diff --git a/processor/src/decoder/tests.rs b/processor/src/decoder/tests.rs index 664517b7a2..ef7c810b88 100644 --- a/processor/src/decoder/tests.rs +++ b/processor/src/decoder/tests.rs @@ -1289,14 +1289,14 @@ fn dyn_block() { // end // // begin - // # stack: [42, DIGEST] + // # stack: [40, DIGEST] // mstorew // push.42 // dynexec // end - const FOO_ROOT_NODE_ADDR: u64 = 42; - const PUSH_42_OP: Operation = Operation::Push(Felt::new(FOO_ROOT_NODE_ADDR)); + const FOO_ROOT_NODE_ADDR: u64 = 40; + const PUSH_40_OP: Operation = Operation::Push(Felt::new(FOO_ROOT_NODE_ADDR)); let mut mast_forest = MastForest::new(); @@ -1308,7 +1308,7 @@ fn dyn_block() { let mstorew_node = MastNode::new_basic_block(vec![Operation::MStoreW], None).unwrap(); let mstorew_node_id = mast_forest.add_node(mstorew_node.clone()).unwrap(); - let push_node = MastNode::new_basic_block(vec![PUSH_42_OP], None).unwrap(); + let push_node = MastNode::new_basic_block(vec![PUSH_40_OP], None).unwrap(); let push_node_id = mast_forest.add_node(push_node.clone()).unwrap(); let join_node = MastNode::new_join(mstorew_node_id, push_node_id, &mast_forest).unwrap(); @@ -1348,7 +1348,7 @@ fn dyn_block() { // starting second span let push_basic_block_addr = mstorew_basic_block_addr + EIGHT; check_op_decoding(&trace, 5, join_addr, Operation::Span, 2, 0, 0); - check_op_decoding(&trace, 6, push_basic_block_addr, PUSH_42_OP, 1, 0, 1); + check_op_decoding(&trace, 6, push_basic_block_addr, PUSH_40_OP, 1, 0, 1); check_op_decoding(&trace, 7, push_basic_block_addr, Operation::Noop, 0, 1, 1); check_op_decoding(&trace, 8, push_basic_block_addr, Operation::End, 0, 0, 0); // end inner join diff --git a/processor/src/errors.rs b/processor/src/errors.rs index 61d7319300..58009209e3 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -92,6 +92,14 @@ pub enum ExecutionError { NoMastForestWithProcedure { root_digest: Digest }, #[error("memory address cannot exceed 2^32 but was {0}")] MemoryAddressOutOfBounds(u64), + #[error( + "word memory access at address {addr} in context {ctx} is unaligned at clock cycle {clk}" + )] + MemoryUnalignedWordAccess { addr: u32, ctx: ContextId, clk: Felt }, + // Note: we need this version as well because to handle advice provider calls, which don't + // have access to the clock. + #[error("word access at memory address {addr} in context {ctx} is unaligned")] + MemoryUnalignedWordAccessNoClk { addr: u32, ctx: ContextId }, #[error("merkle path verification failed for value {value} at index {index} in the Merkle tree with root {root} (error code: {err_code})", value = to_hex(Felt::elements_as_bytes(value)), root = to_hex(root.as_bytes()), @@ -152,6 +160,8 @@ pub enum Ext2InttError { InputSizeTooBig(u64), #[error("address of the first input must be smaller than 2^32, but was {0}")] InputStartAddressTooBig(u64), + #[error("address of the first input is not word aligned: {0}")] + InputStartNotWordAligned(u64), #[error("output size ({0}) cannot be greater than the input size ({1})")] OutputSizeTooBig(usize, usize), #[error("output size must be greater than 0")] diff --git a/processor/src/host/debug.rs b/processor/src/host/debug.rs index 6812d5bc65..876e593662 100644 --- a/processor/src/host/debug.rs +++ b/processor/src/host/debug.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use std::{print, println}; use miden_air::RowIndex; -use vm_core::{DebugOptions, Word}; +use vm_core::{DebugOptions, Felt}; use super::ProcessState; use crate::system::ContextId; @@ -74,19 +74,22 @@ impl Printer { /// Prints the whole memory state at the cycle `clk` in context `ctx`. fn print_mem_all(&self, process: ProcessState) { let mem = process.get_mem_state(self.ctx); - let padding = - mem.iter().fold(0, |max, value| word_elem_max_len(Some(value.1)).max(max)) as usize; + let element_width = mem + .iter() + .map(|(_addr, value)| element_printed_width(Some(*value))) + .max() + .unwrap_or(0) as usize; println!("Memory state before step {} for the context {}:", self.clk, self.ctx); // print the main part of the memory (wihtout the last value) for (addr, value) in mem.iter().take(mem.len() - 1) { - print_mem_address(*addr as u32, Some(*value), false, false, padding); + print_mem_address(*addr as u32, Some(*value), false, false, element_width); } // print the last memory value if let Some((addr, value)) = mem.last() { - print_mem_address(*addr as u32, Some(*value), true, false, padding); + print_mem_address(*addr as u32, Some(*value), true, false, element_width); } } @@ -150,18 +153,21 @@ impl Printer { /// /// If `is_local` is true, the output addresses are formatted as decimal values, otherwise as hex /// strings. -fn print_interval(mem_interval: Vec<(u32, Option)>, is_local: bool) { - let padding = - mem_interval.iter().fold(0, |max, value| word_elem_max_len(value.1).max(max)) as usize; +fn print_interval(mem_interval: Vec<(u32, Option)>, is_local: bool) { + let element_width = mem_interval + .iter() + .map(|(_addr, value)| element_printed_width(*value)) + .max() + .unwrap_or(0) as usize; // print the main part of the memory (wihtout the last value) - for (addr, value) in mem_interval.iter().take(mem_interval.len() - 1) { - print_mem_address(*addr, *value, false, is_local, padding) + for (addr, mem_value) in mem_interval.iter().take(mem_interval.len() - 1) { + print_mem_address(*addr, *mem_value, false, is_local, element_width) } // print the last memory value if let Some((addr, value)) = mem_interval.last() { - print_mem_address(*addr, *value, true, is_local, padding); + print_mem_address(*addr, *value, true, is_local, element_width); } } @@ -171,27 +177,26 @@ fn print_interval(mem_interval: Vec<(u32, Option)>, is_local: bool) { /// string. fn print_mem_address( addr: u32, - value: Option, + mem_value: Option, is_last: bool, is_local: bool, - padding: usize, + element_width: usize, ) { - if let Some(value) = value { + if let Some(value) = mem_value { if is_last { if is_local { print!("└── {addr:>5}: "); } else { print!("└── {addr:#010x}: "); } - print_word(value, padding); - println!(); + println!("{:>width$}\n", value.as_int(), width = element_width); } else { if is_local { print!("├── {addr:>5}: "); } else { print!("├── {addr:#010x}: "); } - print_word(value, padding); + println!("{:>width$}", value.as_int(), width = element_width); } } else if is_last { if is_local { @@ -206,23 +211,10 @@ fn print_mem_address( } } -/// Prints the provided Word with specified padding. -fn print_word(value: Word, padding: usize) { - println!( - "[{:>width$}, {:>width$}, {:>width$}, {:>width$}]", - value[0].as_int(), - value[1].as_int(), - value[2].as_int(), - value[3].as_int(), - width = padding - ) -} - -/// Returns the maximum length among the word elements. -fn word_elem_max_len(word: Option) -> u32 { - if let Some(word) = word { - word.iter() - .fold(0, |max, value| (value.as_int().checked_ilog10().unwrap_or(1) + 1).max(max)) +/// Returns the number of digits required to print the provided element. +fn element_printed_width(element: Option) -> u32 { + if let Some(element) = element { + element.as_int().checked_ilog10().unwrap_or(1) + 1 } else { 0 } diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 2f186fd728..c1acc513a2 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -676,10 +676,18 @@ impl ProcessState<'_> { self.stack.get_state_at(self.system.clk()) } - /// Returns a word located at the specified context/address, or None if the address hasn't + /// Returns the element located at the specified context/address, or None if the address hasn't /// been accessed previously. - pub fn get_mem_value(&self, ctx: ContextId, addr: u32) -> Option { - self.chiplets.get_mem_value(ctx, addr) + pub fn get_mem_value(&self, ctx: ContextId, addr: u32) -> Option { + self.chiplets.memory().get_value(ctx, addr) + } + + /// Returns the batch of elements starting at the specified context/address. + /// + /// # Errors + /// - If the address is not word aligned. + pub fn get_mem_word(&self, ctx: ContextId, addr: u32) -> Result, ExecutionError> { + self.chiplets.memory().get_word(ctx, addr) } /// Returns the entire memory state for the specified execution context at the current clock @@ -687,8 +695,8 @@ impl ProcessState<'_> { /// /// The state is returned as a vector of (address, value) tuples, and includes addresses which /// have been accessed at least once. - pub fn get_mem_state(&self, ctx: ContextId) -> Vec<(u64, Word)> { - self.chiplets.get_mem_state_at(ctx, self.system.clk()) + pub fn get_mem_state(&self, ctx: ContextId) -> Vec<(u64, Felt)> { + self.chiplets.memory().get_state_at(ctx, self.system.clk()) } } diff --git a/processor/src/operations/comb_ops.rs b/processor/src/operations/comb_ops.rs index 498026f038..3a93a6809f 100644 --- a/processor/src/operations/comb_ops.rs +++ b/processor/src/operations/comb_ops.rs @@ -1,4 +1,4 @@ -use vm_core::{Felt, Operation, ONE, ZERO}; +use vm_core::{Felt, Operation, ZERO}; use crate::{ExecutionError, Process, QuadFelt}; @@ -35,7 +35,7 @@ impl Process { /// Output: /// /// +------+------+------+------+------+------+------+------+------+------+------+------+------+--------+--------+---+ - /// | T0 | T7 | T6 | T5 | T4 | T3 | T2 | T1 | p1' | p0' | r1' | r0' |x_addr|z_addr+1|a_addr+1| - | + /// | T0 | T7 | T6 | T5 | T4 | T3 | T2 | T1 | p1' | p0' | r1' | r0' |x_addr|z_addr+4|a_addr+4| - | /// +------+------+------+------+------+------+------+------+------+------+------+------+------+--------+--------+---+ /// /// @@ -91,9 +91,10 @@ impl Process { self.stack.set(11, r_new.to_base_elements()[0]); // --- update the memory pointers --------------------------------------------------------- + const FOUR: Felt = Felt::new(4); self.stack.set(12, self.stack.get(12)); - self.stack.set(13, self.stack.get(13) + ONE); - self.stack.set(14, self.stack.get(14) + ONE); + self.stack.set(13, self.stack.get(13) + FOUR); + self.stack.set(14, self.stack.get(14) + FOUR); // --- copy the rest of the stack --------------------------------------------------------- self.stack.copy_state(15); @@ -125,7 +126,7 @@ impl Process { fn get_randomness(&mut self) -> Result { let ctx = self.system.ctx(); let addr = self.stack.get(14); - let word = self.chiplets.read_mem(ctx, addr.as_int() as u32)?; + let word = self.chiplets.memory_mut().read_word(ctx, addr, self.system.clk())?; let a0 = word[0]; let a1 = word[1]; @@ -136,7 +137,7 @@ impl Process { fn get_ood_values(&mut self) -> Result<[QuadFelt; 2], ExecutionError> { let ctx = self.system.ctx(); let addr = self.stack.get(13); - let word = self.chiplets.read_mem(ctx, addr.as_int() as u32)?; + let word = self.chiplets.memory_mut().read_word(ctx, addr, self.system.clk())?; Ok([QuadFelt::new(word[0], word[1]), QuadFelt::new(word[2], word[3])]) } @@ -172,7 +173,7 @@ mod tests { use alloc::{borrow::ToOwned, vec::Vec}; use test_utils::{build_test, rand::rand_array, TRUNCATE_STACK_PROC}; - use vm_core::{Felt, FieldElement, Operation, StackInputs, ONE, ZERO}; + use vm_core::{Felt, FieldElement, Operation, StackInputs, ZERO}; use crate::{ContextId, DefaultHost, Process, QuadFelt}; @@ -203,9 +204,11 @@ mod tests { let tztgz = rand_array::(); process .chiplets - .write_mem( + .memory_mut() + .write_word( ctx, inputs[2].as_int().try_into().expect("Shouldn't fail by construction"), + process.system.clk(), tztgz, ) .unwrap(); @@ -213,9 +216,11 @@ mod tests { let a = rand_array::(); process .chiplets - .write_mem( + .memory_mut() + .write_word( ctx, inputs[1].as_int().try_into().expect("Shouldn't fail by construction"), + process.system.clk(), a, ) .unwrap(); @@ -267,9 +272,10 @@ mod tests { assert_eq!(r_new.to_base_elements()[0], stack_state[11]); // --- check that memory pointers were updated -------------------------------------------- + const FOUR: Felt = Felt::new(4); assert_eq!(inputs[12], stack_state[12]); - assert_eq!(inputs[13] + ONE, stack_state[13]); - assert_eq!(inputs[14] + ONE, stack_state[14]); + assert_eq!(inputs[13] + FOUR, stack_state[13]); + assert_eq!(inputs[14] + FOUR, stack_state[14]); // --- check that the helper registers were updated correctly ----------------------------- let helper_reg_expected = [tz0, tz1, tgz0, tgz1, a0, a1]; @@ -305,8 +311,8 @@ mod tests { # 5) Prepare stack ## a) Push pointers - push.10 # a_ptr - push.2 # z_ptr + push.40 # a_ptr + push.8 # z_ptr push.0 # x_ptr ## b) Push accumulators @@ -359,7 +365,7 @@ mod tests { // create the expected operand stack let mut expected = Vec::new(); // updated pointers - expected.extend_from_slice(&[ZERO, Felt::from(18_u8), Felt::from(10_u8), Felt::from(2_u8)]); + expected.extend_from_slice(&[ZERO, Felt::from(72_u8), Felt::from(40_u8), Felt::from(8_u8)]); // updated accumulators expected.extend_from_slice(&[ r.to_base_elements()[0], diff --git a/processor/src/operations/fri_ops.rs b/processor/src/operations/fri_ops.rs index cf67ef21cc..c656a62e35 100644 --- a/processor/src/operations/fri_ops.rs +++ b/processor/src/operations/fri_ops.rs @@ -5,7 +5,7 @@ use super::{super::QuadFelt, ExecutionError, Felt, Operation, Process}; // CONSTANTS // ================================================================================================ -const TWO: Felt = Felt::new(2); +const EIGHT: Felt = Felt::new(8); const TWO_INV: Felt = Felt::new(9223372034707292161); const DOMAIN_OFFSET: Felt = Felt::GENERATOR; @@ -31,7 +31,7 @@ impl Process { /// - Folds 4 query values (v0, v1), (v2, v3), (v4, v5), (v6, v7) into a single value (ne0, /// ne1). /// - Computes new value of the domain generator power: poe' = poe^4. - /// - Increments layer pointer (cptr) by 2. + /// - Increments layer pointer (cptr) by 8. /// - Checks that the previous folding was done correctly. /// - Shifts the stack to the left to move an item from the overflow table to stack position 15. /// @@ -100,7 +100,7 @@ impl Process { self.stack.set(7, ds[0]); self.stack.set(8, poe2); self.stack.set(9, f_tau); - self.stack.set(10, layer_ptr + TWO); + self.stack.set(10, layer_ptr + EIGHT); self.stack.set(11, poe4); self.stack.set(12, f_pos); self.stack.set(13, folded_value[1]); @@ -248,9 +248,9 @@ mod tests { use winter_utils::transpose_slice; use super::{ - ExtensionOf, Felt, FieldElement, Operation, Process, QuadFelt, StarkField, TWO, TWO_INV, + ExtensionOf, Felt, FieldElement, Operation, Process, QuadFelt, StarkField, TWO_INV, }; - use crate::DefaultHost; + use crate::{operations::fri_ops::EIGHT, DefaultHost}; #[test] fn fold4() { @@ -295,7 +295,7 @@ mod tests { assert_eq!(super::TAU2_INV, tau.square().inv()); assert_eq!(super::TAU3_INV, tau.cube().inv()); - assert_eq!(TWO.inv(), TWO_INV); + assert_eq!(Felt::new(2).inv(), TWO_INV); } #[test] @@ -304,7 +304,7 @@ mod tests { // we need 17 values because we also assume that the pointer to the last FRI layer will // be in the first position of the stack overflow table let mut inputs = rand_array::(); - inputs[7] = TWO; // domain segment must be < 4 + inputs[7] = Felt::new(2); // domain segment must be < 4 // when domain segment is 2, the 3rd query value and the previous value must be the same inputs[4] = inputs[13]; @@ -362,7 +362,7 @@ mod tests { // check poe, f_tau, layer_ptr, f_pos assert_eq!(stack_state[8], poe.square()); assert_eq!(stack_state[9], f_tau); - assert_eq!(stack_state[10], layer_ptr + TWO); + assert_eq!(stack_state[10], layer_ptr + EIGHT); assert_eq!(stack_state[11], poe.exp(4)); assert_eq!(stack_state[12], f_pos); diff --git a/processor/src/operations/io_ops.rs b/processor/src/operations/io_ops.rs index 96c98c0b81..9c6bc8782d 100644 --- a/processor/src/operations/io_ops.rs +++ b/processor/src/operations/io_ops.rs @@ -1,4 +1,6 @@ -use super::{ExecutionError, Felt, Operation, Process}; +use vm_core::WORD_SIZE; + +use super::{ExecutionError, Felt, Process}; use crate::{AdviceProvider, Host, Word}; // INPUT / OUTPUT OPERATIONS @@ -20,19 +22,26 @@ impl Process { // MEMORY READING AND WRITING // -------------------------------------------------------------------------------------------- - /// Loads a word (4 elements) from the specified memory address onto the stack. + /// Loads a word (4 elements) starting at the specified memory address onto the stack. /// /// The operation works as follows: /// - The memory address is popped off the stack. - /// - A word is retrieved from memory at the specified address. The memory is always initialized - /// to ZEROs, and thus, if the specified address has never been written to, four ZERO elements - /// are returned. + /// - A word is retrieved from memory starting at the specified address, which must be aligned + /// to a word boundary. The memory is always initialized to ZEROs, and thus, for any of the + /// four addresses which were not previously been written to, four ZERO elements are returned. /// - The top four elements of the stack are overwritten with values retrieved from memory. /// /// Thus, the net result of the operation is that the stack is shifted left by one item. + /// + /// # Errors + /// - Returns an error if the address is not aligned to a word boundary. pub(super) fn op_mloadw(&mut self) -> Result<(), ExecutionError> { // get the address from the stack and read the word from current memory context - let mut word = self.read_mem_word(self.stack.get(0))?; + let mut word = self.chiplets.memory_mut().read_word( + self.system.ctx(), + self.stack.get(0), + self.system.clk(), + )?; word.reverse(); // update the stack state @@ -44,68 +53,24 @@ impl Process { Ok(()) } - /// Loads the first element from the specified memory address onto the stack. + /// Loads the element from the specified memory address onto the stack. /// /// The operation works as follows: /// - The memory address is popped off the stack. - /// - A word is retrieved from memory at the specified address. The memory is always initialized - /// to ZEROs, and thus, if the specified address has never been written to, four ZERO elements - /// are returned. - /// - The first element of the word retrieved from memory is pushed to the top of the stack. - /// - /// The first 3 helper registers are filled with the elements of the word which were not pushed - /// to the stack. They are stored in stack order, with the last element of the word in helper - /// register 0. + /// - The element is retrieved from memory at the specified address. The memory is always + /// initialized to ZEROs, and thus, if the specified address has never been written to, the + /// ZERO element is returned. + /// - The element retrieved from memory is pushed to the top of the stack. pub(super) fn op_mload(&mut self) -> Result<(), ExecutionError> { - // get the address from the stack and read the word from memory - let mut word = self.read_mem_word(self.stack.get(0))?; - word.reverse(); + let element = self.chiplets.memory_mut().read( + self.system.ctx(), + self.stack.get(0), + self.system.clk(), + )?; - // update the stack state - self.stack.set(0, word[3]); + self.stack.set(0, element); self.stack.copy_state(1); - // write the 3 unused elements to the helpers so they're available for constraint evaluation - self.decoder.set_user_op_helpers(Operation::MLoad, &word[..3]); - - Ok(()) - } - - /// Loads two words from memory and replaces the top 8 elements of the stack with their - /// contents. - /// - /// The operation works as follows: - /// - The memory address of the first word is retrieved from 13th stack element (position 12). - /// - Two consecutive words, starting at this address, are loaded from memory. - /// - Elements of these words are written to the top 8 elements of the stack (element-wise, in - /// stack order). - /// - Memory address (in position 12) is incremented by 2. - /// - All other stack elements remain the same. - pub(super) fn op_mstream(&mut self) -> Result<(), ExecutionError> { - // get the address from position 12 on the stack - let ctx = self.system.ctx(); - let addr = Self::get_valid_address(self.stack.get(12))?; - - // load two words from memory - let words = self.chiplets.read_mem_double(ctx, addr)?; - - // replace the stack elements with the elements from memory (in stack order) - for (i, &mem_value) in words.iter().flat_map(|word| word.iter()).rev().enumerate() { - self.stack.set(i, mem_value); - } - - // copy over the next 4 elements - for i in 8..12 { - let stack_value = self.stack.get(i); - self.stack.set(i, stack_value); - } - - // increment the address by 2 - self.stack.set(12, Felt::from(addr + 2)); - - // copy over the rest of the stack - self.stack.copy_state(13); - Ok(()) } @@ -113,20 +78,24 @@ impl Process { /// /// The operation works as follows: /// - The memory address is popped off the stack. - /// - The top four stack items are saved into the specified memory address. The items are not - /// removed from the stack. + /// - The top four stack items are saved starting at the specified memory address, which must be + /// aligned on a word boundary. The items are not removed from the stack. /// /// Thus, the net result of the operation is that the stack is shifted left by one item. + /// + /// # Errors + /// - Returns an error if the address is not aligned to a word boundary. pub(super) fn op_mstorew(&mut self) -> Result<(), ExecutionError> { // get the address from the stack and build the word to be saved from the stack values - let ctx = self.system.ctx(); - let addr = Self::get_valid_address(self.stack.get(0))?; + let addr = self.stack.get(0); // build the word in memory order (reverse of stack order) let word = [self.stack.get(4), self.stack.get(3), self.stack.get(2), self.stack.get(1)]; - // write the word to memory and get the previous word - self.chiplets.write_mem(ctx, addr, word)?; + // write the word to memory + self.chiplets + .memory_mut() + .write_word(self.system.ctx(), addr, self.system.clk(), word)?; // reverse the order of the memory word & update the stack state for (i, &value) in word.iter().rev().enumerate() { @@ -141,28 +110,18 @@ impl Process { /// /// 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. + /// - The top stack element is saved at the specified memory address. 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. - /// - /// The first 3 helper registers are filled with the remaining elements of the word which were - /// previously stored in memory and not overwritten by the operation. They are stored in stack - /// order, with the last element at helper register 0. pub(super) fn op_mstore(&mut self) -> Result<(), ExecutionError> { // get the address and the value from the stack let ctx = self.system.ctx(); - let addr = Self::get_valid_address(self.stack.get(0))?; + let addr = self.stack.get(0); let value = self.stack.get(1); // write the value to the memory and get the previous word - let mut old_word = self.chiplets.write_mem_element(ctx, addr, value)?; - // put the retrieved word into stack order - old_word.reverse(); - - // write the 3 unused elements to the helpers so they're available for constraint evaluation - self.decoder.set_user_op_helpers(Operation::MStore, &old_word[..3]); + self.chiplets.memory_mut().write(ctx, addr, self.system.clk(), value)?; // update the stack state self.stack.shift_left(1); @@ -170,6 +129,54 @@ impl Process { Ok(()) } + /// Loads two words from memory and replaces the top 8 elements of the stack with their + /// contents. + /// + /// The operation works as follows: + /// - The memory address of the first word is retrieved from 13th stack element (position 12). + /// - Two consecutive words, starting at this address, are loaded from memory. + /// - Elements of these words are written to the top 8 elements of the stack (element-wise, in + /// stack order). + /// - Memory address (in position 12) is incremented by 8. + /// - All other stack elements remain the same. + /// + /// # Errors + /// - Returns an error if the address is not aligned to a word boundary. + pub(super) fn op_mstream(&mut self) -> Result<(), ExecutionError> { + const MEM_ADDR_STACK_IDX: usize = 12; + + let ctx = self.system.ctx(); + let clk = self.system.clk(); + let addr_first_word = self.stack.get(MEM_ADDR_STACK_IDX); + let addr_second_word = addr_first_word + Felt::from(WORD_SIZE as u32); + + // load two words from memory + let words = [ + self.chiplets.memory_mut().read_word(ctx, addr_first_word, clk)?, + self.chiplets.memory_mut().read_word(ctx, addr_second_word, clk)?, + ]; + + // replace the stack elements with the elements from memory (in stack order) + for (i, &mem_value) in words.iter().flat_map(|word| word.iter()).rev().enumerate() { + self.stack.set(i, mem_value); + } + + // copy over the next 4 elements + for i in 8..MEM_ADDR_STACK_IDX { + let stack_value = self.stack.get(i); + self.stack.set(i, stack_value); + } + + // increment the address by 8 (2 words) + self.stack + .set(MEM_ADDR_STACK_IDX, addr_first_word + Felt::from(WORD_SIZE as u32 * 2)); + + // copy over the rest of the stack + self.stack.copy_state(13); + + Ok(()) + } + /// Moves 8 elements from the advice stack to the memory, via the operand stack. /// /// The operation works as follows: @@ -178,18 +185,26 @@ impl Process { /// (position 12). /// - The two words are written to memory consecutively, starting at this address. /// - These words replace the top 8 elements of the stack (element-wise, in stack order). - /// - Memory address (in position 12) is incremented by 2. + /// - Memory address (in position 12) is incremented by 8. /// - All other stack elements remain the same. + /// + /// # Errors + /// - Returns an error if the address is not aligned to a word boundary. pub(super) fn op_pipe(&mut self, host: &mut impl Host) -> Result<(), ExecutionError> { + const MEM_ADDR_STACK_IDX: usize = 12; + // get the address from position 12 on the stack let ctx = self.system.ctx(); - let addr = Self::get_valid_address(self.stack.get(12))?; + let clk = self.system.clk(); + let addr_first_word = self.stack.get(MEM_ADDR_STACK_IDX); + let addr_second_word = addr_first_word + Felt::from(WORD_SIZE as u32); // pop two words from the advice stack let words = host.advice_provider_mut().pop_stack_dword(self.into())?; // write the words memory - self.chiplets.write_mem_double(ctx, addr, words)?; + self.chiplets.memory_mut().write_word(ctx, addr_first_word, clk, words[0])?; + self.chiplets.memory_mut().write_word(ctx, addr_second_word, clk, words[1])?; // replace the elements on the stack with the word elements (in stack order) for (i, &adv_value) in words.iter().flat_map(|word| word.iter()).rev().enumerate() { @@ -202,8 +217,9 @@ impl Process { self.stack.set(i, stack_value); } - // increment the address by 2 - self.stack.set(12, Felt::from(addr + 2)); + // increment the address by 8 (2 words) + self.stack + .set(MEM_ADDR_STACK_IDX, addr_first_word + Felt::from(WORD_SIZE as u32 * 2)); // copy over the rest of the stack self.stack.copy_state(13); @@ -241,30 +257,6 @@ impl Process { Ok(()) } - - // HELPER FUNCTIONS - // -------------------------------------------------------------------------------------------- - - /// Returns the memory word at address `addr` in the current context. - pub(crate) fn read_mem_word(&mut self, addr: Felt) -> Result { - let ctx = self.system.ctx(); - let mem_addr = Self::get_valid_address(addr)?; - let word_at_addr = self.chiplets.read_mem(ctx, mem_addr)?; - - Ok(word_at_addr) - } - - /// Checks that provided address is less than u32::MAX and returns it cast to u32. - /// - /// # Errors - /// Returns an error if the provided address is greater than u32::MAX. - fn get_valid_address(addr: Felt) -> Result { - let addr = addr.as_int(); - if addr > u32::MAX as u64 { - return Err(ExecutionError::MemoryAddressOutOfBounds(addr)); - } - Ok(addr as u32) - } } // TESTS @@ -316,11 +308,11 @@ mod tests { fn op_mloadw() { let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); - assert_eq!(0, process.chiplets.get_mem_size()); + assert_eq!(0, process.chiplets.memory().num_accessed_words()); - // push a word onto the stack and save it at address 1 + // push a word onto the stack and save it at address 4 let word = [1, 3, 5, 7].to_elements().try_into().unwrap(); - store_value(&mut process, 1, word, &mut host); + store_value(&mut process, 4, word, &mut host); // push four zeros onto the stack for _ in 0..4 { @@ -328,15 +320,18 @@ mod tests { } // push the address onto the stack and load the word - process.execute_op(Operation::Push(ONE), &mut host).unwrap(); + process.execute_op(Operation::Push(4_u32.into()), &mut host).unwrap(); process.execute_op(Operation::MLoadW, &mut host).unwrap(); let expected_stack = build_expected_stack(&[7, 5, 3, 1, 7, 5, 3, 1]); assert_eq!(expected_stack, process.stack.trace_state()); // check memory state - assert_eq!(1, process.chiplets.get_mem_size()); - assert_eq!(word, process.chiplets.get_mem_value(ContextId::root(), 1).unwrap()); + assert_eq!(1, process.chiplets.memory().num_accessed_words()); + assert_eq!( + word, + process.chiplets.memory().get_word(ContextId::root(), 4).unwrap().unwrap() + ); // --- calling MLOADW with address greater than u32::MAX leads to an error ---------------- process.execute_op(Operation::Push(Felt::new(u64::MAX / 2)), &mut host).unwrap(); @@ -351,22 +346,25 @@ mod tests { fn op_mload() { let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); - assert_eq!(0, process.chiplets.get_mem_size()); + assert_eq!(0, process.chiplets.memory().num_accessed_words()); - // push a word onto the stack and save it at address 2 + // push a word onto the stack and save it at address 4 let word = [1, 3, 5, 7].to_elements().try_into().unwrap(); - store_value(&mut process, 2, word, &mut host); + store_value(&mut process, 4, word, &mut host); // push the address onto the stack and load the element - process.execute_op(Operation::Push(Felt::new(2)), &mut host).unwrap(); + process.execute_op(Operation::Push(Felt::new(4)), &mut host).unwrap(); process.execute_op(Operation::MLoad, &mut host).unwrap(); let expected_stack = build_expected_stack(&[1, 7, 5, 3, 1]); assert_eq!(expected_stack, process.stack.trace_state()); // check memory state - assert_eq!(1, process.chiplets.get_mem_size()); - assert_eq!(word, process.chiplets.get_mem_value(ContextId::root(), 2).unwrap()); + assert_eq!(1, process.chiplets.memory().num_accessed_words()); + assert_eq!( + word, + process.chiplets.memory().get_word(ContextId::root(), 4).unwrap().unwrap() + ); // --- calling MLOAD with address greater than u32::MAX leads to an error ----------------- process.execute_op(Operation::Push(Felt::new(u64::MAX / 2)), &mut host).unwrap(); @@ -382,18 +380,24 @@ mod tests { let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); - // save two words into memory addresses 1 and 2 + // save two words into memory addresses 4 and 8 let word1 = [30, 29, 28, 27]; let word2 = [26, 25, 24, 23]; let word1_felts: Word = word1.to_elements().try_into().unwrap(); let word2_felts: Word = word2.to_elements().try_into().unwrap(); - store_value(&mut process, 1, word1_felts, &mut host); - store_value(&mut process, 2, word2_felts, &mut host); + store_value(&mut process, 4, word1_felts, &mut host); + store_value(&mut process, 8, word2_felts, &mut host); // check memory state - assert_eq!(2, process.chiplets.get_mem_size()); - assert_eq!(word1_felts, process.chiplets.get_mem_value(ContextId::root(), 1).unwrap()); - assert_eq!(word2_felts, process.chiplets.get_mem_value(ContextId::root(), 2).unwrap()); + assert_eq!(2, process.chiplets.memory().num_accessed_words()); + assert_eq!( + word1_felts, + process.chiplets.memory().get_word(ContextId::root(), 4).unwrap().unwrap() + ); + assert_eq!( + word2_felts, + process.chiplets.memory().get_word(ContextId::root(), 8).unwrap().unwrap() + ); // clear the stack for _ in 0..8 { @@ -402,11 +406,11 @@ mod tests { // arrange the stack such that: // - 101 is at position 13 (to make sure it is not overwritten) - // - 1 (the address) is at position 12 + // - 4 (the address) is at position 12 // - values 1 - 12 are at positions 0 - 11. Adding the first 8 of these values to the values // stored in memory should result in 35. process.execute_op(Operation::Push(Felt::new(101)), &mut host).unwrap(); - process.execute_op(Operation::Push(ONE), &mut host).unwrap(); + process.execute_op(Operation::Push(4_u32.into()), &mut host).unwrap(); for i in 1..13 { process.execute_op(Operation::Push(Felt::new(i)), &mut host).unwrap(); } @@ -417,8 +421,20 @@ mod tests { // the first 8 values should contain the values from memory. the next 4 values should remain // unchanged, and the address should be incremented by 2 (i.e., 1 -> 3). let stack_values = [ - word2[3], word2[2], word2[1], word2[0], word1[3], word1[2], word1[1], word1[0], 4, 3, - 2, 1, 3, 101, + word2[3], + word2[2], + word2[1], + word2[0], + word1[3], + word1[2], + word1[1], + word1[0], + 4, + 3, + 2, + 1, + 4 + 8, // initial address + 2 words + 101, // rest of stack ]; let expected_stack = build_expected_stack(&stack_values); assert_eq!(expected_stack, process.stack.trace_state()); @@ -428,7 +444,7 @@ mod tests { fn op_mstorew() { let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); - assert_eq!(0, process.chiplets.get_mem_size()); + assert_eq!(0, process.chiplets.memory().num_accessed_words()); // push the first word onto the stack and save it at address 0 let word1 = [1, 3, 5, 7].to_elements().try_into().unwrap(); @@ -439,21 +455,30 @@ mod tests { assert_eq!(expected_stack, process.stack.trace_state()); // check memory state - assert_eq!(1, process.chiplets.get_mem_size()); - assert_eq!(word1, process.chiplets.get_mem_value(ContextId::root(), 0).unwrap()); + assert_eq!(1, process.chiplets.memory().num_accessed_words()); + assert_eq!( + word1, + process.chiplets.memory().get_word(ContextId::root(), 0).unwrap().unwrap() + ); - // push the second word onto the stack and save it at address 3 + // push the second word onto the stack and save it at address 4 let word2 = [2, 4, 6, 8].to_elements().try_into().unwrap(); - store_value(&mut process, 3, word2, &mut host); + store_value(&mut process, 4, word2, &mut host); // check stack state let expected_stack = build_expected_stack(&[8, 6, 4, 2, 7, 5, 3, 1]); assert_eq!(expected_stack, process.stack.trace_state()); // check memory state - assert_eq!(2, process.chiplets.get_mem_size()); - assert_eq!(word1, process.chiplets.get_mem_value(ContextId::root(), 0).unwrap()); - assert_eq!(word2, process.chiplets.get_mem_value(ContextId::root(), 3).unwrap()); + assert_eq!(2, process.chiplets.memory().num_accessed_words()); + assert_eq!( + word1, + process.chiplets.memory().get_word(ContextId::root(), 0).unwrap().unwrap() + ); + assert_eq!( + word2, + process.chiplets.memory().get_word(ContextId::root(), 4).unwrap().unwrap() + ); // --- calling MSTOREW with address greater than u32::MAX leads to an error ---------------- process.execute_op(Operation::Push(Felt::new(u64::MAX / 2)), &mut host).unwrap(); @@ -468,7 +493,7 @@ mod tests { fn op_mstore() { let mut host = DefaultHost::default(); let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack(); - assert_eq!(0, process.chiplets.get_mem_size()); + assert_eq!(0, process.chiplets.memory().num_accessed_words()); // push new element onto the stack and save it as first element of the word on // uninitialized memory at address 0 @@ -481,16 +506,19 @@ mod tests { // check memory state let mem_0 = [element, ZERO, ZERO, ZERO]; - assert_eq!(1, process.chiplets.get_mem_size()); - assert_eq!(mem_0, process.chiplets.get_mem_value(ContextId::root(), 0).unwrap()); + assert_eq!(1, process.chiplets.memory().num_accessed_words()); + assert_eq!( + mem_0, + process.chiplets.memory().get_word(ContextId::root(), 0).unwrap().unwrap() + ); - // push the word onto the stack and save it at address 2 + // push the word onto the stack and save it at address 4 let word_2 = [1, 3, 5, 7].to_elements().try_into().unwrap(); - store_value(&mut process, 2, word_2, &mut host); + store_value(&mut process, 4, word_2, &mut host); // push new element onto the stack and save it as first element of the word at address 2 let element = Felt::new(12); - store_element(&mut process, 2, element, &mut host); + store_element(&mut process, 4, element, &mut host); // check stack state let expected_stack = build_expected_stack(&[12, 7, 5, 3, 1, 10]); @@ -498,8 +526,11 @@ mod tests { // check memory state to make sure the other 3 elements were not affected let mem_2 = [element, Felt::new(3), Felt::new(5), Felt::new(7)]; - assert_eq!(2, process.chiplets.get_mem_size()); - assert_eq!(mem_2, process.chiplets.get_mem_value(ContextId::root(), 2).unwrap()); + assert_eq!(2, process.chiplets.memory().num_accessed_words()); + assert_eq!( + mem_2, + process.chiplets.memory().get_word(ContextId::root(), 4).unwrap().unwrap() + ); // --- calling MSTORE with address greater than u32::MAX leads to an error ---------------- process.execute_op(Operation::Push(Felt::new(u64::MAX / 2)), &mut host).unwrap(); @@ -527,12 +558,12 @@ mod tests { // arrange the stack such that: // - 101 is at position 13 (to make sure it is not overwritten) - // - 1 (the address) is at position 12 + // - 4 (the address) is at position 12 // - values 1 - 12 are at positions 0 - 11. Replacing the first 8 of these values with the // values from the advice stack should result in 30 through 23 in stack order (with 23 at // stack[0]). process.execute_op(Operation::Push(Felt::new(101)), &mut host).unwrap(); - process.execute_op(Operation::Push(ONE), &mut host).unwrap(); + process.execute_op(Operation::Push(4_u32.into()), &mut host).unwrap(); for i in 1..13 { process.execute_op(Operation::Push(Felt::new(i)), &mut host).unwrap(); } @@ -541,15 +572,33 @@ mod tests { process.execute_op(Operation::Pipe, &mut host).unwrap(); // check memory state contains the words from the advice stack - assert_eq!(2, process.chiplets.get_mem_size()); - assert_eq!(word1_felts, process.chiplets.get_mem_value(ContextId::root(), 1).unwrap()); - assert_eq!(word2_felts, process.chiplets.get_mem_value(ContextId::root(), 2).unwrap()); + assert_eq!(2, process.chiplets.memory().num_accessed_words()); + assert_eq!( + word1_felts, + process.chiplets.memory().get_word(ContextId::root(), 4).unwrap().unwrap() + ); + assert_eq!( + word2_felts, + process.chiplets.memory().get_word(ContextId::root(), 8).unwrap().unwrap() + ); // the first 8 values should be the values from the advice stack. the next 4 values should // remain unchanged, and the address should be incremented by 2 (i.e., 1 -> 3). let stack_values = [ - word2[3], word2[2], word2[1], word2[0], word1[3], word1[2], word1[1], word1[0], 4, 3, - 2, 1, 3, 101, + word2[3], + word2[2], + word2[1], + word2[0], + word1[3], + word1[2], + word1[1], + word1[0], + 4, + 3, + 2, + 1, + 4 + 8, // initial address + 2 words + 101, // rest of stack ]; let expected_stack = build_expected_stack(&stack_values); assert_eq!(expected_stack, process.stack.trace_state()); diff --git a/processor/src/operations/sys_ops/sys_event_handlers.rs b/processor/src/operations/sys_ops/sys_event_handlers.rs index 13e4a796be..295ed7990d 100644 --- a/processor/src/operations/sys_ops/sys_event_handlers.rs +++ b/processor/src/operations/sys_ops/sys_event_handlers.rs @@ -6,7 +6,7 @@ use vm_core::{ merkle::{EmptySubtreeRoots, Smt, SMT_DEPTH}, }, sys_events::SystemEvent, - Felt, FieldElement, SignatureKind, Word, EMPTY_WORD, WORD_SIZE, ZERO, + Felt, FieldElement, SignatureKind, Word, WORD_SIZE, ZERO, }; use winter_prover::math::fft; @@ -64,7 +64,7 @@ impl Process { } } -/// Reads words from memory at the specified range and inserts them into the advice map under +/// Reads elements from memory at the specified range and inserts them into the advice map under /// the key `KEY` located at the top of the stack. /// /// Inputs: @@ -91,8 +91,8 @@ pub fn insert_mem_values_into_adv_map( let mut values = Vec::with_capacity(((end_addr - start_addr) as usize) * WORD_SIZE); for addr in start_addr..end_addr { - let mem_value = process.get_mem_value(ctx, addr).unwrap_or(EMPTY_WORD); - values.extend_from_slice(&mem_value); + let mem_value = process.get_mem_value(ctx, addr).unwrap_or(ZERO); + values.push(mem_value); } let key = process.get_stack_word(0); @@ -403,8 +403,8 @@ pub fn push_ext2_inv_result( /// Returns an error if: /// - `input_size` less than or equal to 1, or is not a power of 2. /// - `output_size` is 0 or is greater than the `input_size`. -/// - `input_ptr` is greater than 2^32. -/// - `input_ptr + input_size / 2` is greater than 2^32. +/// - `input_ptr` is greater than 2^32, or is not aligned on a word boundary. +/// - `input_ptr + input_size * 2` is greater than 2^32. pub fn push_ext2_intt_result( advice_provider: &mut impl AdviceProvider, process: ProcessState, @@ -422,11 +422,14 @@ pub fn push_ext2_intt_result( if input_start_ptr >= u32::MAX as u64 { return Err(Ext2InttError::InputStartAddressTooBig(input_start_ptr).into()); } + if input_start_ptr % WORD_SIZE as u64 != 0 { + return Err(Ext2InttError::InputStartNotWordAligned(input_start_ptr).into()); + } if input_size > u32::MAX as usize { return Err(Ext2InttError::InputSizeTooBig(input_size as u64).into()); } - let input_end_ptr = input_start_ptr + (input_size / 2) as u64; + let input_end_ptr = input_start_ptr + (input_size * 2) as u64; if input_end_ptr > u32::MAX as u64 { return Err(Ext2InttError::InputEndAddressTooBig(input_end_ptr).into()); } @@ -439,9 +442,9 @@ pub fn push_ext2_intt_result( } let mut poly = Vec::with_capacity(input_size); - for addr in (input_start_ptr as u32)..(input_end_ptr as u32) { + for addr in ((input_start_ptr as u32)..(input_end_ptr as u32)).step_by(4) { let word = process - .get_mem_value(process.ctx(), addr) + .get_mem_word(process.ctx(), addr)? .ok_or(Ext2InttError::UninitializedMemoryAddress(addr))?; poly.push(QuadFelt::new(word[0], word[1])); diff --git a/processor/src/trace/tests/chiplets/memory.rs b/processor/src/trace/tests/chiplets/memory.rs index 5415a38def..79d77ece73 100644 --- a/processor/src/trace/tests/chiplets/memory.rs +++ b/processor/src/trace/tests/chiplets/memory.rs @@ -1,11 +1,17 @@ use miden_air::{ trace::chiplets::{ - memory::{MEMORY_READ_LABEL, MEMORY_WRITE, MEMORY_WRITE_LABEL, NUM_ELEMENTS}, - MEMORY_ADDR_COL_IDX, MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, MEMORY_SELECTORS_COL_IDX, - MEMORY_V_COL_RANGE, + memory::{ + MEMORY_ACCESS_ELEMENT, MEMORY_ACCESS_WORD, MEMORY_READ, MEMORY_READ_ELEMENT_LABEL, + MEMORY_READ_WORD_LABEL, MEMORY_WRITE, MEMORY_WRITE_ELEMENT_LABEL, + MEMORY_WRITE_WORD_LABEL, + }, + MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, MEMORY_IDX0_COL_IDX, MEMORY_IDX1_COL_IDX, + MEMORY_IS_READ_COL_IDX, MEMORY_IS_WORD_ACCESS_COL_IDX, MEMORY_V_COL_RANGE, + MEMORY_WORD_COL_IDX, }, RowIndex, }; +use vm_core::WORD_SIZE; use super::{ build_trace_from_ops, rand_array, ExecutionTrace, Felt, FieldElement, Operation, Trace, Word, @@ -25,6 +31,8 @@ use super::{ #[test] #[allow(clippy::needless_range_loop)] fn b_chip_trace_mem() { + const FOUR: Felt = Felt::new(4); + let stack = [1, 2, 3, 4, 0]; let word = [ONE, Felt::new(2), Felt::new(3), Felt::new(4)]; let operations = vec![ @@ -33,14 +41,14 @@ fn b_chip_trace_mem() { Operation::Drop, Operation::Drop, Operation::Drop, - Operation::MLoad, // read the first value of the word - Operation::MovDn5, // put address 0 and space for a full word at top of stack - Operation::MLoadW, // load word from address 0 to stack - Operation::Push(ONE), // push a new value onto the stack - Operation::Push(ONE), // push a new address on to the stack - Operation::MStore, // store 1 at address 1 - Operation::Drop, // ensure the stack overflow table is empty - Operation::MStream, // read 2 words starting at address 0 + Operation::MLoad, // read the first value of the word + Operation::MovDn5, // put address 0 and space for a full word at top of stack + Operation::MLoadW, // load word from address 0 to stack + Operation::Push(ONE), // push a new value onto the stack + Operation::Push(FOUR), // push a new address on to the stack + Operation::MStore, // store 1 at address 4 + Operation::Drop, // ensure the stack overflow table is empty + Operation::MStream, // read 2 words starting at address 0 ]; let trace = build_trace_from_ops(operations, &stack); @@ -57,7 +65,8 @@ fn b_chip_trace_mem() { // The first memory request from the stack is sent when the `MStoreW` operation is executed, at // cycle 1, so the request is included in the next row. (The trace begins by executing `span`). - let value = build_expected_memory(&rand_elements, MEMORY_WRITE_LABEL, ZERO, ZERO, ONE, word); + let value = + build_expected_bus_word_msg(&rand_elements, MEMORY_WRITE_WORD_LABEL, ZERO, ZERO, ONE, word); let mut expected = value.inv(); assert_eq!(expected, b_chip[2]); @@ -68,8 +77,14 @@ fn b_chip_trace_mem() { // The next memory request from the stack is sent when `MLoad` is executed at cycle 6 and // included at row 7 - let value = - build_expected_memory(&rand_elements, MEMORY_READ_LABEL, ZERO, ZERO, Felt::new(6), word); + let value = build_expected_bus_element_msg( + &rand_elements, + MEMORY_READ_ELEMENT_LABEL, + ZERO, + ZERO, + Felt::new(6), + word[0], + ); expected *= value.inv(); assert_eq!(expected, b_chip[7]); @@ -84,52 +99,64 @@ fn b_chip_trace_mem() { // to the 5 memory operations (MStream requires 2 rows). // At cycle 8 `MLoadW` is requested by the stack and `MStoreW` is provided by memory - let value = - build_expected_memory(&rand_elements, MEMORY_READ_LABEL, ZERO, ZERO, Felt::new(8), word); + let value = build_expected_bus_word_msg( + &rand_elements, + MEMORY_READ_WORD_LABEL, + ZERO, + ZERO, + Felt::new(8), + word, + ); expected *= value.inv(); - expected *= build_expected_memory_from_trace(&trace, &rand_elements, 8.into()); + expected *= build_expected_bus_msg_from_trace(&trace, &rand_elements, 8.into()); assert_eq!(expected, b_chip[9]); // At cycle 9, `MLoad` is provided by memory. - expected *= build_expected_memory_from_trace(&trace, &rand_elements, 9.into()); + expected *= build_expected_bus_msg_from_trace(&trace, &rand_elements, 9.into()); assert_eq!(expected, b_chip[10]); // At cycle 10, `MLoadW` is provided by memory. - expected *= build_expected_memory_from_trace(&trace, &rand_elements, 10.into()); + expected *= build_expected_bus_msg_from_trace(&trace, &rand_elements, 10.into()); assert_eq!(expected, b_chip[11]); // At cycle 11, `MStore` is requested by the stack and the first read of `MStream` is provided // by the memory. - let value = build_expected_memory( + let value = build_expected_bus_element_msg( &rand_elements, - MEMORY_WRITE_LABEL, + MEMORY_WRITE_ELEMENT_LABEL, ZERO, - ONE, + FOUR, Felt::new(11), - [ONE, ZERO, ZERO, ZERO], + ONE, ); expected *= value.inv(); - expected *= build_expected_memory_from_trace(&trace, &rand_elements, 11.into()); + expected *= build_expected_bus_msg_from_trace(&trace, &rand_elements, 11.into()); assert_eq!(expected, b_chip[12]); // At cycle 12, `MStore` is provided by the memory - expected *= build_expected_memory_from_trace(&trace, &rand_elements, 12.into()); + expected *= build_expected_bus_msg_from_trace(&trace, &rand_elements, 12.into()); assert_eq!(expected, b_chip[13]); // At cycle 13, `MStream` is requested by the stack, and the second read of `MStream` is // provided by the memory. - let value1 = - build_expected_memory(&rand_elements, MEMORY_READ_LABEL, ZERO, ZERO, Felt::new(13), word); - let value2 = build_expected_memory( + let value1 = build_expected_bus_word_msg( &rand_elements, - MEMORY_READ_LABEL, + MEMORY_READ_WORD_LABEL, ZERO, - ONE, + ZERO, + Felt::new(13), + word, + ); + let value2 = build_expected_bus_word_msg( + &rand_elements, + MEMORY_READ_WORD_LABEL, + ZERO, + Felt::new(4), Felt::new(13), [ONE, ZERO, ZERO, ZERO], ); expected *= (value1 * value2).inv(); - expected *= build_expected_memory_from_trace(&trace, &rand_elements, 13.into()); + expected *= build_expected_bus_msg_from_trace(&trace, &rand_elements, 13.into()); assert_eq!(expected, b_chip[14]); // At cycle 14 the decoder requests the span hash. We set this as the inverse of the previously @@ -147,7 +174,25 @@ fn b_chip_trace_mem() { // TEST HELPERS // ================================================================================================ -fn build_expected_memory( +fn build_expected_bus_element_msg( + alphas: &[Felt], + op_label: u8, + ctx: Felt, + addr: Felt, + clk: Felt, + value: Felt, +) -> Felt { + assert!(op_label == MEMORY_READ_ELEMENT_LABEL || op_label == MEMORY_WRITE_ELEMENT_LABEL); + + alphas[0] + + alphas[1] * Felt::from(op_label) + + alphas[2] * ctx + + alphas[3] * addr + + alphas[4] * clk + + alphas[5] * value +} + +fn build_expected_bus_word_msg( alphas: &[Felt], op_label: u8, ctx: Felt, @@ -155,44 +200,69 @@ fn build_expected_memory( clk: Felt, word: Word, ) -> Felt { - let mut word_value = ZERO; - for i in 0..NUM_ELEMENTS { - word_value += alphas[i + 5] * word[i]; - } + assert!(op_label == MEMORY_READ_WORD_LABEL || op_label == MEMORY_WRITE_WORD_LABEL); alphas[0] + alphas[1] * Felt::from(op_label) + alphas[2] * ctx + alphas[3] * addr + alphas[4] * clk - + word_value + + alphas[5] * word[0] + + alphas[6] * word[1] + + alphas[7] * word[2] + + alphas[8] * word[3] } -fn build_expected_memory_from_trace( +fn build_expected_bus_msg_from_trace( trace: &ExecutionTrace, alphas: &[Felt], row: RowIndex, ) -> Felt { // get the memory access operation - let s0 = trace.main_trace.get_column(MEMORY_SELECTORS_COL_IDX)[row]; - let s1 = trace.main_trace.get_column(MEMORY_SELECTORS_COL_IDX + 1)[row]; - let op_label = if s0 == MEMORY_WRITE[0] { - debug_assert!(s1 == ZERO); - MEMORY_WRITE_LABEL + let read_write = trace.main_trace.get_column(MEMORY_IS_READ_COL_IDX)[row]; + let element_or_word = trace.main_trace.get_column(MEMORY_IS_WORD_ACCESS_COL_IDX)[row]; + let op_label = if read_write == MEMORY_WRITE { + if element_or_word == MEMORY_ACCESS_ELEMENT { + MEMORY_WRITE_ELEMENT_LABEL + } else { + MEMORY_WRITE_WORD_LABEL + } + } else if read_write == MEMORY_READ { + if element_or_word == MEMORY_ACCESS_ELEMENT { + MEMORY_READ_ELEMENT_LABEL + } else { + MEMORY_READ_WORD_LABEL + } } else { - MEMORY_READ_LABEL + panic!("invalid read_write value: {read_write}"); }; // get the memory access data let ctx = trace.main_trace.get_column(MEMORY_CTX_COL_IDX)[row]; - let addr = trace.main_trace.get_column(MEMORY_ADDR_COL_IDX)[row]; + let addr = { + let word = trace.main_trace.get_column(MEMORY_WORD_COL_IDX)[row]; + let idx1 = trace.main_trace.get_column(MEMORY_IDX1_COL_IDX)[row]; + let idx0 = trace.main_trace.get_column(MEMORY_IDX0_COL_IDX)[row]; + + word + idx1.mul_small(2) + idx0 + }; let clk = trace.main_trace.get_column(MEMORY_CLK_COL_IDX)[row]; // get the memory value - let mut word = [ZERO; NUM_ELEMENTS]; + let mut word = [ZERO; WORD_SIZE]; for (i, element) in word.iter_mut().enumerate() { *element = trace.main_trace.get_column(MEMORY_V_COL_RANGE.start + i)[row]; } - build_expected_memory(alphas, op_label, ctx, addr, clk, word) + if element_or_word == MEMORY_ACCESS_ELEMENT { + let idx1 = trace.main_trace.get_column(MEMORY_IDX1_COL_IDX)[row].as_int(); + let idx0 = trace.main_trace.get_column(MEMORY_IDX0_COL_IDX)[row].as_int(); + let idx = idx1 * 2 + idx0; + + build_expected_bus_element_msg(alphas, op_label, ctx, addr, clk, word[idx as usize]) + } else if element_or_word == MEMORY_ACCESS_WORD { + build_expected_bus_word_msg(alphas, op_label, ctx, addr, clk, word) + } else { + panic!("invalid element_or_word value: {element_or_word}"); + } } diff --git a/stdlib/asm/collections/mmr.masm b/stdlib/asm/collections/mmr.masm index 4ed948fc62..e18f2f7987 100644 --- a/stdlib/asm/collections/mmr.masm +++ b/stdlib/asm/collections/mmr.masm @@ -10,7 +10,7 @@ use.std::math::u64 #! Input: [pos, mmr_ptr, ...] #! Output: [N, ...] where `N` is the leaf and `R` is the MMR peak that owns the leaf. #! -#! Cycles: 115 +#! Cycles: 118 export.get # load the num_leaves of the MMR (2 cycles) dup.1 mem_load @@ -48,9 +48,9 @@ export.get swap u32assert u32popcnt # stack: [peak_count, relative_pos, depth, mmr_ptr, ...] - # compute `mmr_ptr + peak_count + 1` the target tree index (3 cycles) - movup.3 add add.1 - # stack: [mmr_ptr, relative_pos, depth, ...] + # compute `mmr_ptr + 4*peak_count + 4` the target tree index (6 cycles) + mul.4 movup.3 add add.4 + # stack: [peak_ptr, relative_pos, depth, ...] # load the target peak (6 cycles) padw movup.4 mem_loadw @@ -79,6 +79,8 @@ end #! Given the num_leaves of a MMR returns the num_peaks. #! +#! Implemented as counting the number of "1" bits in `num_leaves`. +#! #! Input: [num_leaves, ...] #! Output: [num_peaks, ...] #! Cycles: 69 @@ -93,18 +95,20 @@ end #! #! Input: [num_peaks, ...] #! Output: [len, ...] -#! Cycles: 17 +#! Cycles: 19 export.num_peaks_to_message_size # the peaks are padded to a minimum length of 16 (10 cycles) push.16 u32max # => [count_min, ...] - # when the number of peaks is greater than 16, then they are padded to an even number (7 cycles) - dup is_odd add + # when the number of peaks is greater than 16, then they are padded to an even number. + # we multiply by four because each peak is a word, and so is stored in 4 memory addresses. + # (9 cycles) + dup is_odd add mul.4 # => [even_count_min, ...] end -#! Load the MMR peak data based on its hash. +#! Writes the MMR who's peaks hash to `HASH` to the memory location pointed to by `mmr_ptr`. #! #! Input: [HASH, mmr_ptr, ...] #! Output: [...] @@ -113,11 +117,12 @@ end #! - HASH: is the MMR peak hash, the hash is expected to be padded to an even #! length and to have a minimum size of 16 elements #! - The advice map must contain a key with HASH, and its value is -#! `num_leaves || hash_data`, and hash_data is the data used to computed `HASH` -#! - mmt_ptr: the memory location where the MMR data will be written to, -#! starting with the MMR forest (its total leaves count) followed by its peaks +#! `[num_leaves, 0, 0 , 0] || hash_data`, and hash_data is the data used to computed `HASH` +#! - mmr_ptr: the memory location where the MMR data will be written to, +#! starting with the MMR forest (its total leaves count) followed by its peaks. +#! The address is expected to be word-aligned. #! -#! Cycles: 162 + 9 * extra_peak_pair cycles +#! Cycles: 164 + 9 * extra_peak_pair cycles #! where `extra_peak` is the number of peak pairs in addition to the first #! 16, i.e. `round_up((num_of_peaks - 16) / 2)` export.unpack @@ -141,12 +146,12 @@ export.unpack # => [state_size, HASH, mmr_ptr, ...] # compute the end address including the padding data and forest (3 cycles) - dup.5 add add.1 + dup.5 add add.4 # => [mmt_ptr_end, HASH, mmr_ptr, ...] # update the mmr_ptr to account for the size (2 cycles) - movup.5 add.1 - # => [mmr_ptr+1, mmt_ptr_end, HASH, ...] + movup.5 add.4 + # => [mmr_ptr+4, mmt_ptr_end, HASH, ...] # hash the first 16 words (28 cycles) padw padw padw @@ -158,7 +163,7 @@ export.unpack adv_pipe hperm adv_pipe hperm adv_pipe hperm - # => [C, B, A, mmr_ptr+17, mmt_ptr_end, HASH, ...] + # => [C, B, A, mmr_ptr+68, mmt_ptr_end, HASH, ...] # handle MMR with more than 16 elements (10 + 9 * words cycles) exec.mem::pipe_double_words_to_memory @@ -176,7 +181,7 @@ end #! #! Input: [mmr_ptr, ...] #! Output: [HASH, ...] -#! Cycles: 128 + 3 * num_peaks +#! Cycles: 130 + 3 * num_peaks export.pack # load num_leaves (2 cycles) dup mem_load @@ -186,12 +191,12 @@ export.pack exec.num_leaves_to_num_peaks # => [num_peaks, mmr_ptr, ...] - # compute the message size (18 cycles) + # compute the message size (19 cycles) exec.num_peaks_to_message_size # => [message_size, mmr_ptr, ...] # compute peaks_start and peaks_end (6 cycles) - dup.1 add.1 swap dup.1 add swap + dup.1 add.4 swap dup.1 add swap # => [peaks_start, peaks_end, mmr_ptr, ...] # hash the memory contents (25 + 3 * num_peaks) @@ -219,7 +224,7 @@ end #! #! Input: [EL, mmr_ptr, ...] #! Output: [...] -#! Cycles: 144 + 39 * peak_merges +#! Cycles: 147 + 39 * peak_merges export.add # get num_leaves (2 cycles) dup.4 mem_load @@ -232,8 +237,8 @@ export.add dup exec.num_leaves_to_num_peaks # [num_peaks, num_leaves, EL, mmr_ptr] (70 cycles) - # compute peaks_end (3 cycles) - movup.6 add add.1 + # compute peaks_end (6 cycles) + mul.4 movup.6 add add.4 # [mmr_end, num_leaves, EL] # find how many MMR peaks will be merged (41 cycles) @@ -258,7 +263,7 @@ export.add while.true # (39 cycles) # load peak (4 cycles) - dup.9 sub.1 mem_loadw + dup.9 sub.4 mem_loadw # => [PEAK, EL, -num_merges, mmr_end] # merge the nodes (17 cycles) @@ -270,8 +275,8 @@ export.add # => [PAD, EL', -num_merges, mmr_end] # update control (7 cycles) - swapw.2 add.1 swap sub.1 swap swapw.2 - # => [PAD, EL', -num_merges+1, mmr_end-1] + swapw.2 add.1 swap sub.4 swap swapw.2 + # => [PAD, EL', -num_merges+1, mmr_end-4] # check loop condition (5 cycles) dup.8 neq.0 @@ -280,7 +285,7 @@ export.add # drop padding (4 cycles) dropw - # =>: [EL, -num_merges+1, mmr_end-1] + # =>: [EL, -num_merges+1, mmr_end-4] # save the new peak (2 cycles) movup.5 mem_storew diff --git a/stdlib/asm/crypto/dsa/ecdsa/secp256k1.masm b/stdlib/asm/crypto/dsa/ecdsa/secp256k1.masm index 20b7837316..7fba59328d 100644 --- a/stdlib/asm/crypto/dsa/ecdsa/secp256k1.masm +++ b/stdlib/asm/crypto/dsa/ecdsa/secp256k1.masm @@ -25,31 +25,31 @@ use.std::math::secp256k1::group #! If verification fails, program execution will be aborted. #! #! See https://github.com/itzmeanjan/secp256k1/blob/37b339db3e03d24c2977399eb8896ef515ebb09b/ecdsa/verify.py#L11-L45 -export.verify.24 +export.verify.96 # cache pub_key loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw - loc_storew.2 + loc_storew.8 dropw - loc_storew.3 + loc_storew.12 dropw - loc_storew.4 + loc_storew.16 dropw - loc_storew.5 + loc_storew.20 dropw # cache h - loc_storew.6 + loc_storew.24 dropw - loc_storew.7 + loc_storew.28 dropw # cache r - loc_storew.8 + loc_storew.32 dropw - loc_storew.9 + loc_storew.36 dropw # Only s lives on stack @@ -64,45 +64,45 @@ export.verify.24 push.0.0.0.0.0.0.0.0 # load h - loc_loadw.7 + loc_loadw.28 swapw - loc_loadw.6 + loc_loadw.24 # compute h * s^-1 exec.scalar_field::mul exec.scalar_field::from_mont # cache h * s^-1 - loc_storew.6 + loc_storew.24 swapw - loc_storew.7 + loc_storew.28 # load r - loc_loadw.9 + loc_loadw.36 swapw - loc_loadw.8 + loc_loadw.32 # compute r * s^-1 exec.scalar_field::mul exec.scalar_field::from_mont # cache r * s^-1 - loc_storew.10 + loc_storew.40 dropw - loc_storew.11 + loc_storew.44 dropw - locaddr.17 - locaddr.16 - locaddr.15 - locaddr.14 - locaddr.13 - locaddr.12 + locaddr.68 + locaddr.64 + locaddr.60 + locaddr.56 + locaddr.52 + locaddr.48 push.0.0.0.0.0.0.0.0 - loc_loadw.7 + loc_loadw.28 swapw - loc_loadw.6 + loc_loadw.24 # compute G * ((h * s^-1) mod N) = P0 exec.group::gen_mul @@ -111,47 +111,47 @@ export.verify.24 movup.4 mem_loadw - loc_storew.12 + loc_storew.48 movup.4 mem_loadw - loc_storew.13 + loc_storew.52 movup.4 mem_loadw - loc_storew.14 + loc_storew.56 movup.4 mem_loadw - loc_storew.15 + loc_storew.60 movup.4 mem_loadw - loc_storew.16 + loc_storew.64 movup.4 mem_loadw - loc_storew.17 + loc_storew.68 dropw - locaddr.23 - locaddr.22 - locaddr.21 - locaddr.20 - locaddr.19 - locaddr.18 + locaddr.92 + locaddr.88 + locaddr.84 + locaddr.80 + locaddr.76 + locaddr.72 push.0.0.0.0.0.0.0.0 - loc_loadw.11 + loc_loadw.44 swapw - loc_loadw.10 + loc_loadw.40 - locaddr.5 + locaddr.20 + locaddr.16 + locaddr.12 + locaddr.8 locaddr.4 - locaddr.3 - locaddr.2 - locaddr.1 locaddr.0 # compute pkey * ((r * s^-1) mod N) = P1 @@ -165,45 +165,45 @@ export.verify.24 movup.4 mem_loadw - loc_storew.1 + loc_storew.4 movup.4 mem_loadw - loc_storew.2 + loc_storew.8 movup.4 mem_loadw - loc_storew.3 + loc_storew.12 movup.4 mem_loadw - loc_storew.4 + loc_storew.16 movup.4 mem_loadw - loc_storew.5 + loc_storew.20 dropw - locaddr.23 - locaddr.22 - locaddr.21 - locaddr.20 - locaddr.19 - locaddr.18 + locaddr.92 + locaddr.88 + locaddr.84 + locaddr.80 + locaddr.76 + locaddr.72 + + locaddr.68 + locaddr.64 + locaddr.60 + locaddr.56 + locaddr.52 + locaddr.48 - locaddr.17 + locaddr.20 locaddr.16 - locaddr.15 - locaddr.14 - locaddr.13 locaddr.12 - - locaddr.5 + locaddr.8 locaddr.4 - locaddr.3 - locaddr.2 - locaddr.1 locaddr.0 # compute P0 + P1 = P2 @@ -217,33 +217,33 @@ export.verify.24 movup.4 mem_loadw - loc_storew.1 + loc_storew.4 movup.4 mem_loadw - loc_storew.2 + loc_storew.8 movup.4 mem_loadw - loc_storew.3 + loc_storew.12 movup.4 mem_loadw - loc_storew.4 + loc_storew.16 movup.4 mem_loadw - loc_storew.5 + loc_storew.20 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 swapw - loc_loadw.4 + loc_loadw.16 exec.base_field::inv push.0.0.0.0.0.0.0.0 - loc_loadw.1 + loc_loadw.4 swapw loc_loadw.0 @@ -252,9 +252,9 @@ export.verify.24 exec.base_field::from_mont push.0.0.0.0.0.0.0.0 - loc_loadw.9 + loc_loadw.36 swapw - loc_loadw.8 + loc_loadw.32 # compute r ( in radix-2^32 form ) exec.scalar_field::from_mont diff --git a/stdlib/asm/crypto/dsa/rpo_falcon512.masm b/stdlib/asm/crypto/dsa/rpo_falcon512.masm index 637f490bf9..e142df9ac4 100644 --- a/stdlib/asm/crypto/dsa/rpo_falcon512.masm +++ b/stdlib/asm/crypto/dsa/rpo_falcon512.masm @@ -67,7 +67,7 @@ end #! Takes as input a message digest, a nonce of size 40 bytes represented as 8 field elements #! and a pointer. The procedure absorbs MSG and NONCE into a fresh RPO state and squeezes the #! coefficients of a polynomial c representing the hash-to-point of (MSG || NONCE). The coefficients -#! are then saved in the memory region [c_ptr, c_ptr + 128). +#! are then saved in the memory region [c_ptr, c_ptr + 512). #! This implementation of the `hash_to_point` procedure avoids the rejection-sampling step #! required in the per-the-spec algorithm by using the observation on page 31 in #! https://falcon-sign.info/falcon.pdf @@ -75,32 +75,32 @@ end #! Input: [c_ptr, MSG, NONCE1, NONCE0, ...] #! Output: [...] #! -#! Cycles: 1327 -export.hash_to_point.2 +#! Cycles: ~1400 +export.hash_to_point.8 # Move pointer out of the way movdn.12 # Store MSG for later absorption - loc_storew.1 dropw + loc_storew.4 dropw # Absorb the nonce padw movdnw.2 hperm # Absorb the message - swapw loc_loadw.1 swapw hperm + swapw loc_loadw.4 swapw hperm # Squeeze the coefficients and save them repeat.63 swapw dup.12 mem_storew - swapw dup.12 add.2 swap.13 add.1 + swapw dup.12 add.8 swap.13 add.4 mem_storew hperm end # Save the last remaining coefficients - dup.12 add.1 mem_storew dropw + dup.12 add.4 mem_storew dropw movup.8 mem_storew dropw # Clean up the stack @@ -112,21 +112,21 @@ end # ============================================================================================= #! For an element `tau := (tau0, tau1)` in the quadratic extension field, computes all its powers -#! `tau^i` for `i = 0,..., 512` and stores them in the memory region `[tau_ptr, tau_ptr + 513)`. -#! The procedure returns `tau_ptr + 513`. +#! `tau^i` for `i = 0,..., 512` and stores them in the memory region `[tau_ptr, tau_ptr + 513*4)`. +#! The procedure returns `tau_ptr + 513*4`. #! #! Input: [tau1, tau0, tau_ptr, ...] -#! Output: [tau_ptr + 513, ...] +#! Output: [tau_ptr + 513*4, ...] #! -#! Cycles: 8323 +#! Cycles: ~8900 export.powers_of_tau # 1) Save tau^0 i.e. (0, 1) push.1 push.0.0.0 - dup.6 add.1 swap.7 + dup.6 add.4 swap.7 mem_storew drop drop - #=> [0, 1, tau1, tau0, tau_ptr+1, ...] + #=> [0, 1, tau1, tau0, tau_ptr+4, ...] # 2) Compute tau^i repeat.512 @@ -134,27 +134,27 @@ export.powers_of_tau movup.3 movup.3 - dup.6 add.1 swap.7 mem_storew + dup.6 add.4 swap.7 mem_storew drop drop end dropw - #=> [tau_ptr + 513, ...] + #=> [tau_ptr + 513*4, ...] end -#! Sets the memory region `[ptr, ptr + 512)` to zero. The pointer c_ptr := ptr + 512 is returned +#! Sets the memory region `[ptr, ptr + 512*4)` to zero. The pointer c_ptr := ptr + 512*4 is returned #! to be used to store the hash-to-point polynomial of the message later on. #! #! Input: [ptr, ...] #! Output: [...] #! -#! Cycles: 2607 +#! Cycles: ~3100 export.set_to_zero padw repeat.512 - dup.4 add.1 swap.5 + dup.4 add.4 swap.5 mem_storew end dropw @@ -173,10 +173,10 @@ end #! the incremented pointer. #! #! Input: [ptr, PK, ...] -#! Output: [tau1, tau0, ptr + 512 ...] +#! Output: [tau1, tau0, ptr + 512*4 ...] #! #! Cycles: 5049 -export.load_h_s2_and_product.1 +export.load_h_s2_and_product.4 # 1) Store PK for later comparison movdn.4 @@ -228,13 +228,13 @@ export.load_h_s2_and_product.1 # 6) Return the challenge point and the incremented pointer exec.rpo::squeeze_digest drop drop - #=> [tau1, tau0, ptr + 512] + #=> [tau1, tau0, ptr + 512*4] end #! Checks that pi == h * s2 in Z_Q[x] by evaluating both sides at a random point. #! The procedure takes as input a pointer h_ptr to h. The other two polynomials -#! are located at h_ptr + 128, for s2, and h_ptr + 256, for pi. The procedure takes -#! also a pointer zeros_ptr to a region of memory [zeros_ptr, zeros_ptr + 1024) +#! are located at h_ptr + 512, for s2, and h_ptr + 1024, for pi. The procedure takes +#! also a pointer zeros_ptr to a region of memory [zeros_ptr, zeros_ptr + 512*4) #! and a pointer tau_ptr to powers of the random point we are evaluating at stored #! as [a_i, b_i, x, x] where (a_i, b_i) := tau^i for i in [0, 1023]. #! The procedure returns () if the check passes, otherwise it raises an exception @@ -244,7 +244,7 @@ end #! Output: [...] #! #! Cycles: 2504 -export.probabilistic_product.4 +export.probabilistic_product.16 # 1) Save the pointers push.0 movdn.3 loc_storew.0 @@ -266,13 +266,13 @@ export.probabilistic_product.4 end # Save the evaluation h(tau) - swapdw loc_storew.1 + swapdw loc_storew.4 dropw #=> [X, X, X, ...] # 3) Compute the evaluation of the s2 polynomial at the random challenge loc_loadw.0 - add.128 + add.512 #=> [s2_ptr, zeros_ptr, tau_ptr, 0, X, X, ...] # Accumulator to compute s2(tau) @@ -289,7 +289,7 @@ export.probabilistic_product.4 end # Save the evaluation of s2(tau) - swapdw loc_storew.2 + swapdw loc_storew.8 dropw #=> [X, X, X, ...] @@ -301,7 +301,7 @@ export.probabilistic_product.4 # Setup the pointers loc_loadw.0 - add.256 + add.1024 #=> [pi_ptr, zeros_ptr, tau_ptr, 0, X, X, ...] # Accumulator to compute pi(tau) @@ -320,10 +320,10 @@ export.probabilistic_product.4 # where (ev0, ev1) := pi1(tau) # Save pi_1(tau) - swapw.2 loc_storew.3 + swapw.2 loc_storew.12 # Setup the pointers - swapw.3 loc_loadw.0 add.384 + swapw.3 loc_loadw.0 add.1536 # Accumulator to compute pi2(tau) swapw dropw padw @@ -349,7 +349,7 @@ export.probabilistic_product.4 #=> [a1, a0, X, x, x, X, ...] # Compute (res0, res1) := pi(tau) - swapw loc_loadw.3 drop drop + swapw loc_loadw.12 drop drop ext2add #=> [res1, res0, x, x, X, ...] @@ -358,11 +358,11 @@ export.probabilistic_product.4 ## a) Load h(tau) swapw - loc_loadw.1 + loc_loadw.4 ## b) Load s2(tau) push.0.0 - loc_loadw.2 + loc_loadw.8 ## c) compute the product drop drop @@ -484,21 +484,21 @@ end #! Input: [pi_ptr, ...] #! Output: [norm_sq(s1), ...] #! -#! Cycles: 58888 +#! Cycles: 59000 export.compute_s1_norm_sq repeat.128 # 1) Load the next 4 * 3 coefficients # load c_i padw - dup.4 add.1281 + dup.4 add.5124 mem_loadw # load pi_{i+512} padw - dup.8 add.128 + dup.8 add.512 mem_loadw - # load pi_i + # load pi_4 padw dup.12 mem_loadw @@ -545,7 +545,7 @@ export.compute_s1_norm_sq swap # 6) Increment the pointer - add.1 + add.4 end # Sum up the squared norm of all the coefficients of s1 @@ -561,11 +561,11 @@ end #! Input: [s2_ptr, ...] #! Output: [norm_sq(s2), ...] #! -#! Cycles: 13322 +#! Cycles: ~13500 export.compute_s2_norm_sq repeat.128 padw - dup.4 add.1 swap.5 + dup.4 add.4 swap.5 mem_loadw repeat.4 @@ -592,8 +592,8 @@ end #! Input: [PK, MSG, ...] #! Output: [...] #! -#! Cycles: ~ 92029 -export.verify.1665 +#! Cycles: ~ 90400 +export.verify.6660 # 1) Generate a Falcon signature using the secret key associated to PK on message MSG. adv.push_sig.rpo_falcon512 @@ -631,8 +631,8 @@ export.verify.1665 # 5) Check that we indeed have pi := h * s2 in Z_Q[x] by checking that pi(tau) = h(tau) * s2(tau) # where tau is a random (Fiat-Shamir) challenge resulting from hashing h, s2 and pi. - locaddr.512 # tau_ptr - locaddr.1025 # z_ptr + locaddr.2048 # tau_ptr + locaddr.4100 # z_ptr locaddr.0 # h ptr #=> [h_ptr, zeros_ptr, tau_ptr, ...] @@ -641,7 +641,7 @@ export.verify.1665 # 6) Compute the squared norm of s1 := c - h * s2 (in Z_q[x]/(phi)) - locaddr.256 + locaddr.1024 #=> [pi_ptr, ...] exec.compute_s1_norm_sq @@ -649,7 +649,7 @@ export.verify.1665 # 7) Compute the squared norm of s2 - locaddr.128 + locaddr.512 #=> [s2_ptr, norm_sq(s1), ...] exec.compute_s2_norm_sq diff --git a/stdlib/asm/crypto/elgamal_ecgfp5.masm b/stdlib/asm/crypto/elgamal_ecgfp5.masm index d3b51ba2bc..0a2b6f0c83 100644 --- a/stdlib/asm/crypto/elgamal_ecgfp5.masm +++ b/stdlib/asm/crypto/elgamal_ecgfp5.masm @@ -3,7 +3,7 @@ use.std::math::ecgfp5::group #! Generates the public key, point H #! the private key is expected as input and is a 319-bit random #! number of 10 32-bit limbs. -export.gen_privatekey.8 +export.gen_privatekey.32 exec.group::gen_mul end @@ -14,7 +14,7 @@ end #! [r0, r1, ..., r9] #! Final stack state #! [Ca_x0, ..., C_x4, Ca_y0, ..., Ca_y4, Ca_inf] -export.encrypt_ca.8 +export.encrypt_ca.32 exec.group::gen_mul end @@ -28,7 +28,7 @@ end #! [H_x0, ..., H_x4, H_y0, ..., H_y4, H_inf, r0, r1, ..., M_x0, ..., M_x4, M_y0, ..., M_y4, M_inf,] #! Final stack state #! [Cb_x0, ..., Cb_x4, Cb_y0, ..., Cb_y4, Cb_inf] -export.encrypt_cb.20 +export.encrypt_cb.80 exec.group::mul exec.group::add end @@ -40,7 +40,7 @@ end #! #! Final stack state #! [C'a_x0, ..., C'a_x4, C'a_y0, ..., C'a_y4, C'a_inf] -export.remask_ca.5 +export.remask_ca.20 exec.group::gen_mul exec.group::add end @@ -52,7 +52,7 @@ end #! #! Final stack state #! [C'b_x0, ..., C'b_x4, C'b_y0, ..., C'b_y4, C'b_inf] -export.remask_cb.14 +export.remask_cb.56 exec.group::mul exec.group::add end diff --git a/stdlib/asm/crypto/fri/ext2fri.masm b/stdlib/asm/crypto/fri/ext2fri.masm index dc558d1c44..bc2effaccc 100644 --- a/stdlib/asm/crypto/fri/ext2fri.masm +++ b/stdlib/asm/crypto/fri/ext2fri.masm @@ -7,7 +7,7 @@ #! remaining part of word i.e. a3, a2 will be consumed during immediate next iteration #! of computing β which involves invocation of accumulate_for_odd_index. #! -#! Input: [ω, ν1, ν0, τ1, τ0, q_ptr - 1, ...] +#! Input: [ω, ν1, ν0, τ1, τ0, q_ptr - 4, ...] #! Output: [a3, a2, ν1', ν0', τ1, τ0, q_ptr, ...] #! #! Cycles: 54 @@ -24,11 +24,11 @@ proc.accumulate_for_even_index movdn.2 mul - # load from q_ptr + 1 + # load from q_ptr + 4 # # notice, first increment the memory address and then load from it. movup.6 - add.1 + add.4 movdn.6 push.0.0.0 dup.9 @@ -102,9 +102,9 @@ end #! #! Cycles: 2802 proc.compute_beta_64 - # decrement starting address by 1, because we first increment and then load onto the stack + # decrement starting address by 4, because we first increment and then load onto the stack movup.2 - sub.1 + sub.4 movdn.2 push.0.0 # accumulator for β @@ -393,7 +393,7 @@ end proc.compute_beta_32 # decrement starting address by 1, because we first increment and then load onto the stack movup.2 - sub.1 + sub.4 movdn.2 push.0.0 # accumulator for β @@ -556,37 +556,37 @@ end #! #! Cycles: 114 proc.compute_alpha_64 - padw dup.6 sub.1 swap.7 + padw dup.6 sub.4 swap.7 mem_loadw - #=> [a11, a10, a01, a00, τ1, τ0, p_ptr-1, ...] + #=> [a11, a10, a01, a00, τ1, τ0, p_ptr-4, ...] dup.5 dup.5 ext2mul ext2add - #=> [acc1, acc0, τ1, τ0, p_ptr-1, ...] + #=> [acc1, acc0, τ1, τ0, p_ptr-4, ...] - movup.4 dup sub.1 movdn.5 + movup.4 dup sub.4 movdn.5 padw movup.4 mem_loadw - #=> [a11, a10, a01, a00, acc1, acc0, τ1, τ0, p_ptr-1, ...] + #=> [a11, a10, a01, a00, acc1, acc0, τ1, τ0, p_ptr-4, ...] movup.5 movup.5 dup.7 dup.7 - #=> [τ1, τ0, acc1, acc0, a11, a10, a01, a00, τ1, τ0, p_ptr-1, ...] + #=> [τ1, τ0, acc1, acc0, a11, a10, a01, a00, τ1, τ0, p_ptr-4, ...] ext2mul ext2add - #=> [acc1, acc0, a01, a00, τ1, τ0, p_ptr-1, ...] + #=> [acc1, acc0, a01, a00, τ1, τ0, p_ptr-4, ...] dup.5 dup.5 ext2mul ext2add - #=> [acc1, acc0, τ1, τ0, p_ptr-1, ...] + #=> [acc1, acc0, τ1, τ0, p_ptr-4, ...] - movup.4 dup sub.1 movdn.5 + movup.4 dup sub.4 movdn.5 padw movup.4 mem_loadw - #=> [a11, a10, a01, a00, acc1, acc0, τ1, τ0, p_ptr-1, ...] + #=> [a11, a10, a01, a00, acc1, acc0, τ1, τ0, p_ptr-4, ...] movup.5 movup.5 dup.7 dup.7 - #=> [τ1, τ0, acc1, acc0, a11, a10, a01, a00, τ1, τ0, p_ptr-1, ...] + #=> [τ1, τ0, acc1, acc0, a11, a10, a01, a00, τ1, τ0, p_ptr-4, ...] ext2mul ext2add #=> [acc1, acc0, a01, a00, τ1, τ0, p_ptr-1, ...] @@ -624,7 +624,7 @@ end #! #! Cycles: 47 proc.compute_alpha_32 - padw dup.6 sub.1 swap.7 + padw dup.6 sub.4 swap.7 mem_loadw dup.5 dup.5 ext2mul @@ -667,7 +667,7 @@ export.verify_remainder_64 #=> [β1, β0, τ1, τ0, q_ptr, ...] # Pointer to the last word of the remainder polynomial for Horner evaluation. - movup.4 add.4 + movup.4 add.16 #=> [p_ptr, β1, β0, τ1, τ0, ...] # We need to multiply τ by the domain offset before evaluation. @@ -714,7 +714,7 @@ export.verify_remainder_32 #=> [β1, β0, τ1, τ0, q_ptr, ...] # Pointer to the last word of the remainder polynomial for Horner evaluation. - movup.4 add.2 + movup.4 add.8 #=> [p_ptr, β1, β0, τ1, τ0, ...] # We need to multiply τ by the domain offset before evaluation. diff --git a/stdlib/asm/crypto/fri/frie2f4.masm b/stdlib/asm/crypto/fri/frie2f4.masm index d3ae69c261..da054ed154 100644 --- a/stdlib/asm/crypto/fri/frie2f4.masm +++ b/stdlib/asm/crypto/fri/frie2f4.masm @@ -6,8 +6,8 @@ #! g being the initial domain generator. #! TODO: This pre-processing function should in fact compute d_size and t_depth for each C #! starting from the original domain size. -export.preprocess.4 - locaddr.3 +export.preprocess.16 + locaddr.12 adv_push.1 #[num_queries, query_ptr, g, ..] sub.1 push.0.0.0.0 @@ -15,13 +15,13 @@ export.preprocess.4 while.true adv_loadw #[Q, num_queries, ptr, ..] dup.5 #[ptr, Q, num_queries, ptr,..] - u32wrapping_add.1 #[ptr+1, Q, num_queries, ptr, ..] - swap.6 #[ptr, Q, num_queries, ptr+1, ..] - mem_storew #[Q, num_queries, ptr+1, ..] + u32wrapping_add.4 #[ptr+4, Q, num_queries, ptr, ..] + swap.6 #[ptr, Q, num_queries, ptr+4, ..] + mem_storew #[Q, num_queries, ptr+4, ..] dup.4 - sub.1 #[num_queries-1, Q, num_queries, ptr+1, ..] - swap.5 #[num_queries, Q, num_queries-1, ptr+1, ..] - neq.0 #[?, Q, num_queries-1, ptr+1, ..] + sub.1 #[num_queries-1, Q, num_queries, ptr+4, ..] + swap.5 #[num_queries, Q, num_queries-1, ptr+4, ..] + neq.0 #[?, Q, num_queries-1, ptr+4, ..] end #=> [X, x, layer_ptr, g] @@ -42,7 +42,7 @@ export.preprocess.4 while.true adv_loadw dup.5 - u32wrapping_add.1 + u32wrapping_add.4 swap.6 mem_storew dup.4 @@ -68,7 +68,7 @@ export.preprocess.4 while.true adv_loadw dup.5 - u32wrapping_add.1 + u32wrapping_add.4 swap.6 mem_storew dup.4 @@ -80,24 +80,24 @@ export.preprocess.4 dropw drop drop swap - locaddr.3 + locaddr.12 #=> [query_ptr, layer_ptr, remainder_ptr, g] end #! Checks that, for a query with index p at layer i, the folding procedure to create layer (i + 1) -#! was performed correctly. This also advances layer_ptr by 2 to point to the next query layer. +#! was performed correctly. This also advances layer_ptr by 8 to point to the next query layer. #! #! Input: [layer_ptr, layer_ptr, poe, p, e1, e0, layer_ptr, rem_ptr, x, x, x, x, x, x, x, x, ...] -#! Output: [layer_ptr + 2, layer_ptr + 2, poe^4, f_pos, ne1, ne0, layer_ptr + 2, rem_ptr, x, x, x, x, x, x, x, x, ...] +#! Output: [layer_ptr+8, layer_ptr+8, poe^4, f_pos, ne1, ne0, layer_ptr+8, rem_ptr, x, x, x, x, x, x, x, x, ...] #! #! Cycles: 76 -export.verify_query_layer.3 +export.verify_query_layer.12 # load layer commitment C as well as [a0, a1, t_depth, d_size] (7 cycles) swapdw movup.8 - add.1 - mem_loadw # load [a0, a1, t_depth, d_size] from layer_ptr + 1 + add.4 + mem_loadw # load [a0, a1, t_depth, d_size] from layer_ptr + 4 swapw movup.8 mem_loadw # load C from layer_ptr @@ -118,9 +118,9 @@ export.verify_query_layer.3 # => [V, C, f_pos, d_seg, poe, e1, e0, a1, a0, layer_ptr, rem_ptr, ...] # where f_pos = p % d_size and d_seg = p / 4 - # unhash V and save the pre-image in locaddr.0 and locaddr.1; we don't clear values of C + # unhash V and save the pre-image in locaddr.0 and locaddr.4; we don't clear values of C # because adv_pipe overwrites the first 8 elements of the stack (15 cycles) - locaddr.1 + locaddr.4 movdn.4 push.0.0.0.0 swapw @@ -141,26 +141,27 @@ export.verify_query_layer.3 assert_eq # load (v7, ..v0) from memory (8 cycles) - loc_loadw.1 + loc_loadw.4 swapw - loc_loadw.2 + loc_loadw.8 # => [v7, ..., v0, f_pos, d_seg, poe, e1, e0, a1, a0, layer_ptr, rem_ptr, ...] # fold by 4 (1 cycle) fri_ext2fold4 - # => [x, x, x, x, x, x, x, x, x, x, layer_ptr + 2, poe^4, f_pos, ne1, ne0, rem_ptr, ...] + # => [x, x, x, x, x, x, x, x, x, x, layer_ptr + 8, poe^4, f_pos, ne1, ne0, rem_ptr, ...] # prepare for next iteration (10 cycles) swapdw - dup.2 - movdn.7 - drop - drop - dup - dup.7 - dup.1 - neq - # => [?, layer_ptr + 2, layer_ptr + 2, poe^4, f_pos, ne1, ne0, layer_ptr + 2, rem_ptr, x, x, x, x, x, x, x, x, ...] + # => [x, x, layer_ptr + 8, poe^4, f_pos, ne1, ne0, rem_ptr, x, x, x, x, x, x, x, x, ...] + dup.2 # [layer_ptr+8, x, x, layer_ptr+8, poe^4, f_pos, ne1, ne0, rem_ptr, ] + movdn.7 # [x, x, layer_ptr+8, poe^4, f_pos, ne1, ne0, layer_ptr+8, rem_ptr, ...] + drop + drop # [layer_ptr+8, poe^4, f_pos, ne1, ne0, layer_ptr+8, rem_ptr, ...] + dup # [layer_ptr+8, layer_ptr+8, poe^4, f_pos, ne1, ne0, layer_ptr+8, rem_ptr, ...] + dup.7 # [rem_ptr, layer_ptr+8, layer_ptr+8, poe^4, f_pos, ne1, ne0, layer_ptr+8, rem_ptr, ...] + dup.1 # [layer_ptr+8, rem_ptr, layer_ptr+8, layer_ptr+8, poe^4, f_pos, ne1, ne0, layer_ptr+8, rem_ptr, ...] + neq + # => [?, layer_ptr+8, layer_ptr+8, poe^4, f_pos, ne1, ne0, layer_ptr+8, rem_ptr, x, x, x, x, x, x, x, x, ...] end #! Verifies one FRI query. @@ -175,7 +176,7 @@ end #! layer. #! - rem_ptr is the memory address of the remainder codeword. #! -#! Cycles: 40 + num_layers * 76 +#! Cycles: 42 + num_layers * 76 export.verify_query # prepare stack to be in a form that leverages the fri_ext2fold4 instruction output stack state @@ -197,17 +198,18 @@ export.verify_query end # => [rem_ptr, rem_ptr, poe^(2^n), f_pos, ne1, ne0, rem_ptr, rem_ptr, x, x, x, x, x, x, x, x, ...] - # check that remainder[f_pos] == (ne0, ne1) + # check that rem_ptr[f_pos] == (ne0, ne1) # Since each memory address contains two extension field elements, we have to determine which # of the two elements we should compare against. (7 cycles) - movup.3 - push.2 - u32divmod # f_pos must be a u32 value - movdn.4 - dup.1 + movup.3 # [f_pos, rem_ptr, rem_ptr, poe^(2^n), ne1, ne0, rem_ptr, rem_ptr, ...] + push.2 # [2, f_pos, rem_ptr, rem_ptr, poe^(2^n), ne1, ne0, rem_ptr, rem_ptr, ...] + u32divmod # [f_pos%2, f_pos/2, rem_ptr, rem_ptr, poe^(2^n), ne1, ne0, rem_ptr, rem_ptr, ...] + movdn.4 # [f_pos/2, rem_ptr, rem_ptr, poe^(2^n), f_pos%2, ne1, ne0, rem_ptr, rem_ptr, ...] + mul.4 # [f_pos*2, rem_ptr, rem_ptr, poe^(2^n), f_pos%2, ne1, ne0, rem_ptr, rem_ptr, ...] dup.1 - add + dup.1 # [f_pos*2, rem_ptr, f_pos*2, rem_ptr, rem_ptr, poe^(2^n), f_pos%2, ne1, ne0, rem_ptr, rem_ptr, ...] + add # [rem_ptr + f_pos*2, f_pos*2, rem_ptr, rem_ptr, poe^(2^n), f_pos%2, ne1, ne0, rem_ptr, rem_ptr, ...] # => [rem_ptr + offset, x, x, x, x, ?, ne1, ne0, rem_ptr, rem_ptr, x, x, x, x, x, x, x, x, ..] mem_loadw @@ -239,7 +241,7 @@ end #! to g^p with g being the initial FRI domain generator. p is the query index at the first layer #! and (e0, e1) is an extension field element corresponding to the value of the first layer at index p. #! - layer_ptr is a pointer to the first layer commitment denoted throughout the code by C. -#! layer_ptr + 1 points to the first [alpha0, alpha1, t_depth, d_size] where d_size is the size +#! layer_ptr + 4 points to the first [alpha0, alpha1, t_depth, d_size] where d_size is the size #! of initial domain divided by 4, t_depth is the depth of the Merkle tree commitment to the #! first layer and (alpha0, alpha1) is the first challenge used in folding the first layer. #! Both t_depth and d_size are expected to be smaller than 2^32. Otherwise, the result of @@ -255,8 +257,8 @@ end #! 1. rem_ptr - 1 points to the last (alpha0, alpha1, t_depth, d_size) tuple. #! 2. layer_ptr - 1 points to the last (e0, e1, p, poe) tuple. #! -#! Cycles: 7 + 4 + num_queries * (40 + num_layers * 76 + 26) -export.verify.1 +#! Cycles: 7 + 4 + num_queries * (42 + num_layers * 76 + 26) +export.verify.4 # store [query_ptr, layer_ptr, rem_ptr, g] to keep track of all queries # (3 cycles) @@ -282,12 +284,12 @@ export.verify.1 # => [x, x, x, x, x, x, x, x, x, x, g, ...] dropw drop drop drop loc_loadw.0 # load [query_ptr, layer_ptr, rem_ptr, g] - add.1 - loc_storew.0 # store [query_ptr + 1, layer_ptr, rem_ptr, g] + add.4 + loc_storew.0 # store [query_ptr + 4, layer_ptr, rem_ptr, g] dup dup.2 neq - #=> [?, query_ptr + 1, layer_ptr, rem_ptr, g, ...] + #=> [?, query_ptr + 4, layer_ptr, rem_ptr, g, ...] end #=> [X, ..] diff --git a/stdlib/asm/crypto/fri/helper.masm b/stdlib/asm/crypto/fri/helper.masm index bf7b4ffeb5..c97dfa1033 100644 --- a/stdlib/asm/crypto/fri/helper.masm +++ b/stdlib/asm/crypto/fri/helper.masm @@ -101,24 +101,25 @@ export.load_fri_layer_commitments neq while.true swapw # [Y, num_layers, ptr_layer, y, y, ...] - adv_loadw # [Com, num_layers, ptr_layer, y, y, ...] + adv_loadw # [COM, num_layers, ptr_layer, y, y, ...] # Save FRI layer commitment dup.5 - add.1 + add.4 swap.6 mem_storew - #=> [Com, num_layers, ptr_layer + 1, y, y, ...] + #=> [COM, num_layers, ptr_layer + 4, y, y, ...] # Reseed exec.random_coin::reseed - # => [num_layers, ptr_layer + 1, y, y, ...] + # => [num_layers, ptr_layer + 4, y, y, ...] push.0.0.0.0 exec.random_coin::get_rate_1 + #=> [R1, ZERO, num_layers, ptr_layer + 4, y, y, ... ] push.0.0 exec.constants::tmp5 mem_loadw - # => [lde_size, log2(lde_size), lde_generator, 0, a1, a0, Y, num_layers, ptr_layer + 1, y, y, ...] + # => [lde_size, log2(lde_size), lde_generator, 0, a1, a0, Y, num_layers, ptr_layer + 4, y, y, ...] # Compute and save to memory new lde_size and its new logarithm div.4 @@ -132,15 +133,15 @@ export.load_fri_layer_commitments movup.2 drop swapw dropw - # => [lde_size, log2(lde_size), a1, a0, num_layers, ptr_layer + 1, y, y, Y, ...] + # => [lde_size, log2(lde_size), a1, a0, num_layers, ptr_layer + 4, y, y, Y, ...] # Save [lde_size, log2(lde_size), a1, a0] in memory next to the layer commitment dup.5 - add.1 + add.4 swap.6 mem_storew swapw - # => [num_layers, ptr_layer + 2, y, y, lde_size, log2(lde_size), a1, a0, Y] + # => [num_layers, ptr_layer + 8, y, y, lde_size, log2(lde_size), a1, a0, Y] # Decrement the FRI layer counter sub.1 @@ -170,17 +171,21 @@ export.load_and_verify_remainder push.0.0.0.0 adv_loadw exec.constants::tmp7 mem_storew + #=> [COM, ...] # Reseed with remainder commitment exec.random_coin::reseed + #=> [...] # adv_pipe the remainder codeword ## Get the length of remainder - exec.constants::tmp6 mem_loadw + padw exec.constants::tmp6 mem_loadw ## Compute the correct remainder pointer using length of remainder exec.constants::fri_com_ptr + #=> [fri_com_ptr, num_fri_layers, remainder_size, lde_size, lde_size] + swap - mul.2 + mul.8 add ## Store for later use exec.constants::tmp8 mem_storew @@ -205,12 +210,12 @@ export.load_and_verify_remainder # coefficients first. adv_loadw dup.12 - add.16 + add.64 mem_storew swapw adv_loadw dup.12 - add.17 + add.68 mem_storew hperm # => [Y, Remainder_poly_com, Y, ptr_remainder, remainder_size, y, y] @@ -246,23 +251,23 @@ export.load_and_verify_remainder # coefficients first. adv_loadw dup.12 - add.32 + add.128 mem_storew swapw adv_loadw dup.12 - add.33 + add.132 mem_storew hperm adv_loadw dup.12 - add.34 + add.136 mem_storew swapw adv_loadw dup.12 - add.35 + add.140 mem_storew hperm # => [Y, Remainder_poly_com, Y, ptr_remainder, remainder_size, y, y] diff --git a/stdlib/asm/crypto/hashes/blake3.masm b/stdlib/asm/crypto/hashes/blake3.masm index 326023d1c6..1290b68df6 100644 --- a/stdlib/asm/crypto/hashes/blake3.masm +++ b/stdlib/asm/crypto/hashes/blake3.masm @@ -177,7 +177,7 @@ end #! [state0, state1, state2, state3, state4, state5, state6, state7, state8, state9, state10, state11, state12, state13, state14, state15] #! #! i.e. whole blake3 state is placed on stack ( in order ). -proc.columnar_mixing.1 +proc.columnar_mixing.4 swapw.2 swapw @@ -416,7 +416,7 @@ end #! [state0, state1, state2, state3, state4, state5, state6, state7, state8, state9, state10, state11, state12, state13, state14, state15] #! #! i.e. whole blake3 state is placed on stack ( in order ). -proc.diagonal_mixing.1 +proc.diagonal_mixing.4 swapw.2 swapw @@ -658,24 +658,24 @@ end #! #! i.e. mixed state matrix lives in memory addresses {state0_3_addr, state4_7_addr, state8_11_addr, state12_15_addr}, #! which were provided, on stack top, while invoking this routine. -proc.round.5 +proc.round.20 loc_storew.0 exec.columnar_mixing - loc_storew.1 + loc_storew.4 dropw - loc_storew.2 + loc_storew.8 dropw - loc_storew.3 + loc_storew.12 dropw - loc_storew.4 + loc_storew.16 dropw + locaddr.16 + locaddr.12 + locaddr.8 locaddr.4 - locaddr.3 - locaddr.2 - locaddr.1 exec.diagonal_mixing @@ -721,7 +721,7 @@ end #! i.e. 7 -round mixed state matrix lives in memory addresses {state0_3_addr, state4_7_addr, state8_11_addr, state12_15_addr}, #! which were provided, on stack top, while invoking this routine. So updated state matrix can be read by caller routine, by reading #! the content of memory addresses where state was provided as routine input. -proc.compress.1 +proc.compress.4 loc_storew.0 dropw @@ -757,10 +757,10 @@ end #! [dig0, dig1, dig2, dig3, dig4, dig5, dig6, dig7, ...] #! #! dig`i` -> 32 -bit digest word | i ∈ [0, 8) -export.hash_2to1.4 - locaddr.3 - locaddr.2 - locaddr.1 +export.hash_2to1.16 + locaddr.12 + locaddr.8 + locaddr.4 locaddr.0 exec.initialize_2to1 @@ -769,19 +769,19 @@ export.hash_2to1.4 # block ( = 64 -bytes ) because what we're doing here is 2-to-1 hashing i.e. 64 -bytes # input being converted to 32 -bytes output - locaddr.3 - locaddr.2 - locaddr.1 + locaddr.12 + locaddr.8 + locaddr.4 locaddr.0 exec.compress push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -801,15 +801,15 @@ end #! [dig0, dig1, dig2, dig3, dig4, dig5, dig6, dig7, ...] #! #! dig`i` -> 32 -bit digest word | i ∈ [0, 8) -export.hash_1to1.4 +export.hash_1to1.16 # Pad 32 -bytes input message with zero bytes to make # 64 -bytes, which is processed same as 2-to-1 hashing push.0.0.0.0.0.0.0.0 swapdw - locaddr.3 - locaddr.2 - locaddr.1 + locaddr.12 + locaddr.8 + locaddr.4 locaddr.0 exec.initialize_1to1 @@ -817,19 +817,19 @@ export.hash_1to1.4 # Note, chunk compression routine needs to compress only one chunk with one message # block ( = 64 -bytes ), which is obtained by padding 32 -bytes input. - locaddr.3 - locaddr.2 - locaddr.1 + locaddr.12 + locaddr.8 + locaddr.4 locaddr.0 exec.compress push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 diff --git a/stdlib/asm/crypto/hashes/keccak256.masm b/stdlib/asm/crypto/hashes/keccak256.masm index a342ee646d..c9547b7d24 100644 --- a/stdlib/asm/crypto/hashes/keccak256.masm +++ b/stdlib/asm/crypto/hashes/keccak256.masm @@ -14,8 +14,8 @@ #! Whole keccak-p[1600, 24] state can be represented using fifty u32 elements i.e. 13 absolute memory addresses #! s.t. last two elements of 12 -th ( when indexed from zero ) memory address are zeroed. #! -#! Consecutive memory addresses can be computed by repeated application of `add.1`. -proc.theta.3 +#! Consecutive memory addresses can be computed by repeated application of `add.4`. +proc.theta.12 dup locaddr.0 mem_store @@ -34,7 +34,7 @@ proc.theta.3 drop movup.2 - add.2 + add.8 # bring S[10], S[11] dup @@ -56,7 +56,7 @@ proc.theta.3 swap movup.2 - add.3 + add.12 # bring S[20], S[21] dup @@ -80,7 +80,7 @@ proc.theta.3 swap movup.2 - add.2 + add.8 # bring S[30], S[31] dup @@ -102,7 +102,7 @@ proc.theta.3 swap movup.2 - add.3 + add.12 # bring S[40], S[41] push.0.0.0.0 @@ -140,7 +140,7 @@ proc.theta.3 drop movup.2 - add.3 + add.12 # bring S[12], S[13] dup @@ -164,7 +164,7 @@ proc.theta.3 swap movup.2 - add.2 + add.8 # bring S[22], S[23] dup @@ -186,7 +186,7 @@ proc.theta.3 swap movup.2 - add.3 + add.12 # bring S[32], S[33] dup @@ -210,7 +210,7 @@ proc.theta.3 swap movup.2 - add.2 + add.8 # bring S[42], S[43] push.0.0.0.0 @@ -235,7 +235,7 @@ proc.theta.3 # stack = [c0, c1, c2, c3] - locaddr.1 + locaddr.4 mem_storew dropw @@ -243,7 +243,7 @@ proc.theta.3 locaddr.0 mem_load - add.1 + add.4 # bring S[4], S[5] dup @@ -257,7 +257,7 @@ proc.theta.3 drop movup.2 - add.2 + add.8 # bring S[14], S[15] dup @@ -279,7 +279,7 @@ proc.theta.3 swap movup.2 - add.3 + add.12 # bring S[24], S[25] dup @@ -303,7 +303,7 @@ proc.theta.3 swap movup.2 - add.2 + add.8 # bring S[34], S[35] dup @@ -325,7 +325,7 @@ proc.theta.3 swap movup.2 - add.3 + add.12 # bring S[44], S[45] push.0.0.0.0 @@ -352,7 +352,7 @@ proc.theta.3 locaddr.0 mem_load - add.1 + add.4 # bring S[6], S[7] dup @@ -364,7 +364,7 @@ proc.theta.3 drop movup.2 - add.3 + add.12 # bring S[16], S[17] dup @@ -388,7 +388,7 @@ proc.theta.3 swap movup.2 - add.2 + add.8 # bring S[26], S[27] dup @@ -410,7 +410,7 @@ proc.theta.3 swap movup.2 - add.3 + add.12 # bring S[36], S[37] dup @@ -434,7 +434,7 @@ proc.theta.3 swap movup.2 - add.2 + add.8 # bring S[46], S[47] push.0.0.0.0 @@ -459,7 +459,7 @@ proc.theta.3 # stack = [c4, c5, c6, c7] - locaddr.2 + locaddr.8 mem_storew dropw @@ -467,7 +467,7 @@ proc.theta.3 locaddr.0 mem_load - add.2 + add.8 # bring S[8], S[9] dup @@ -481,7 +481,7 @@ proc.theta.3 drop movup.2 - add.2 + add.8 # bring S[18], S[19] dup @@ -503,7 +503,7 @@ proc.theta.3 swap movup.2 - add.3 + add.12 # bring S[28], S[29] dup @@ -527,7 +527,7 @@ proc.theta.3 swap movup.2 - add.2 + add.8 # bring S[38], S[39] dup @@ -549,7 +549,7 @@ proc.theta.3 swap movup.2 - add.3 + add.12 # bring S[48], S[49] push.0.0.0.0 @@ -573,11 +573,11 @@ proc.theta.3 # stack = [c8, c9] - locaddr.2 + locaddr.8 push.0.0.0.0 movup.4 mem_loadw - locaddr.1 + locaddr.4 push.0.0.0.0 movup.4 mem_loadw @@ -675,7 +675,7 @@ proc.theta.3 mem_storew dropw - add.1 + add.4 # compute state[4..8) @@ -706,7 +706,7 @@ proc.theta.3 mem_storew dropw - add.1 + add.4 # compute state[8..12) @@ -737,7 +737,7 @@ proc.theta.3 mem_storew dropw - add.1 + add.4 # compute state[12..16) @@ -768,7 +768,7 @@ proc.theta.3 mem_storew dropw - add.1 + add.4 # compute state[16..20) @@ -799,7 +799,7 @@ proc.theta.3 mem_storew dropw - add.1 + add.4 # compute state[20..24) @@ -830,7 +830,7 @@ proc.theta.3 mem_storew dropw - add.1 + add.4 # compute state[24..28) @@ -861,7 +861,7 @@ proc.theta.3 mem_storew dropw - add.1 + add.4 # compute state[28..32) @@ -892,7 +892,7 @@ proc.theta.3 mem_storew dropw - add.1 + add.4 # compute state[32..36) @@ -923,7 +923,7 @@ proc.theta.3 mem_storew dropw - add.1 + add.4 # compute state[36..40) @@ -954,7 +954,7 @@ proc.theta.3 mem_storew dropw - add.1 + add.4 # compute state[40..44) @@ -985,7 +985,7 @@ proc.theta.3 mem_storew dropw - add.1 + add.4 # compute state[44..48) @@ -1016,7 +1016,7 @@ proc.theta.3 mem_storew dropw - add.1 + add.4 # compute state[48..50) @@ -1054,8 +1054,8 @@ end #! Whole keccak-p[1600, 24] state can be represented using fifty u32 elements i.e. 13 absolute memory addresses #! s.t. last two elements of 12 -th ( when indexed from zero ) memory address are zeroed. #! -#! Consecutive memory addresses can be computed by repeated application of `add.1`. -proc.rho.1 +#! Consecutive memory addresses can be computed by repeated application of `add.4`. +proc.rho.4 dup locaddr.0 mem_store @@ -1071,7 +1071,7 @@ proc.rho.1 movup.4 dup - add.1 + add.4 movdn.5 mem_storew @@ -1093,7 +1093,7 @@ proc.rho.1 movup.4 dup - add.1 + add.4 movdn.5 mem_storew @@ -1114,7 +1114,7 @@ proc.rho.1 movup.4 dup - add.1 + add.4 movdn.5 mem_storew @@ -1136,7 +1136,7 @@ proc.rho.1 movup.4 dup - add.1 + add.4 movdn.5 mem_storew @@ -1157,7 +1157,7 @@ proc.rho.1 movup.4 dup - add.1 + add.4 movdn.5 mem_storew @@ -1178,7 +1178,7 @@ proc.rho.1 movup.4 dup - add.1 + add.4 movdn.5 mem_storew @@ -1199,7 +1199,7 @@ proc.rho.1 movup.4 dup - add.1 + add.4 movdn.5 mem_storew @@ -1220,7 +1220,7 @@ proc.rho.1 movup.4 dup - add.1 + add.4 movdn.5 mem_storew @@ -1241,7 +1241,7 @@ proc.rho.1 movup.4 dup - add.1 + add.4 movdn.5 mem_storew @@ -1262,7 +1262,7 @@ proc.rho.1 movup.4 dup - add.1 + add.4 movdn.5 mem_storew @@ -1284,7 +1284,7 @@ proc.rho.1 movup.4 dup - add.1 + add.4 movdn.5 mem_storew @@ -1305,7 +1305,7 @@ proc.rho.1 movup.4 dup - add.1 + add.4 movdn.5 mem_storew @@ -1339,13 +1339,13 @@ end #! Whole keccak-p[1600, 24] state can be represented using fifty u32 elements i.e. 13 absolute memory addresses #! s.t. last two elements of 12 -th ( when indexed from zero ) memory address are zeroed. #! -#! Consecutive memory addresses can be computed by repeated application of `add.1`. -proc.pi.14 +#! Consecutive memory addresses can be computed by repeated application of `add.4`. +proc.pi.56 dup locaddr.0 mem_store - locaddr.1 + locaddr.4 swap push.0.0.0.0 @@ -1366,12 +1366,12 @@ proc.pi.14 movdn.3 dup.5 - add.5 + add.20 mem_storew # place state[4..8) to desired location(s) movup.4 - add.1 + add.4 movdn.4 dup.4 @@ -1382,19 +1382,19 @@ proc.pi.14 movdn.3 dup.7 - add.10 + add.40 mem_storew drop drop dup.5 - add.2 + add.8 mem_storew # place state[8..12) to desired location(s) movup.4 - add.1 + add.4 movdn.4 dup.4 @@ -1403,7 +1403,7 @@ proc.pi.14 push.0.0 dup.7 - add.7 + add.28 mem_storew movup.2 @@ -1415,12 +1415,12 @@ proc.pi.14 movdn.3 dup.5 - add.8 + add.32 mem_storew # place state[12..16) to desired location(s) movup.4 - add.1 + add.4 movdn.4 dup.4 @@ -1440,7 +1440,7 @@ proc.pi.14 mem_storew dup.7 - add.5 + add.20 mem_loadw movup.2 @@ -1449,19 +1449,19 @@ proc.pi.14 drop dup.5 - add.5 + add.20 mem_storew # place state[16..20) to desired location(s) movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw dup.5 - add.10 + add.40 push.0.0.0.0 movup.4 mem_loadw @@ -1472,7 +1472,7 @@ proc.pi.14 drop dup.7 - add.10 + add.40 mem_storew dropw @@ -1482,19 +1482,19 @@ proc.pi.14 movdn.3 dup.5 - add.3 + add.12 mem_storew # place state[20..24) to desired location(s) movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw dup.5 - add.3 + add.12 push.0.0.0.0 movup.4 mem_loadw @@ -1505,11 +1505,11 @@ proc.pi.14 drop dup.7 - add.3 + add.12 mem_storew dup.7 - add.8 + add.32 mem_loadw movup.2 @@ -1518,12 +1518,12 @@ proc.pi.14 drop dup.5 - add.8 + add.32 mem_storew # place state[24..28) to desired location(s) movup.4 - add.1 + add.4 movdn.4 dup.4 @@ -1534,7 +1534,7 @@ proc.pi.14 movdn.3 dup.7 - add.1 + add.4 mem_storew drop @@ -1543,24 +1543,24 @@ proc.pi.14 movdn.3 dup.5 - add.6 + add.24 mem_storew # place state[28..32) to desired location(s) movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw dup.5 - add.11 + add.44 mem_storew # place state[32..36) to desired location(s) movup.4 - add.1 + add.4 movdn.4 dup.4 @@ -1571,7 +1571,7 @@ proc.pi.14 movdn.3 dup.7 - add.4 + add.16 mem_storew drop @@ -1580,19 +1580,19 @@ proc.pi.14 movdn.3 dup.5 - add.9 + add.36 mem_storew # place state[36..40) to desired location(s) movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw dup.5 - add.1 + add.4 push.0.0.0.0 movup.4 mem_loadw @@ -1603,11 +1603,11 @@ proc.pi.14 drop dup.7 - add.1 + add.4 mem_storew dup.7 - add.6 + add.24 mem_loadw movup.2 @@ -1616,19 +1616,19 @@ proc.pi.14 drop dup.5 - add.6 + add.24 mem_storew # place state[40..44) to desired location(s) movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw dup.5 - add.7 + add.28 push.0.0.0.0 movup.4 mem_loadw @@ -1639,7 +1639,7 @@ proc.pi.14 movup.3 dup.7 - add.7 + add.28 mem_storew dropw @@ -1649,19 +1649,19 @@ proc.pi.14 movdn.3 dup.5 - add.12 + add.48 mem_storew # place state[44..48) to desired location(s) movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw dup.5 - add.4 + add.16 push.0.0.0.0 movup.4 mem_loadw @@ -1672,11 +1672,11 @@ proc.pi.14 drop dup.7 - add.4 + add.16 mem_storew dup.7 - add.9 + add.36 mem_loadw movup.2 @@ -1685,19 +1685,19 @@ proc.pi.14 drop dup.5 - add.9 + add.36 mem_storew # place state[48..50) to desired location(s) movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw dup.5 - add.2 + add.8 push.0.0.0.0 movup.4 mem_loadw @@ -1708,7 +1708,7 @@ proc.pi.14 movdn.3 dup.7 - add.2 + add.8 mem_storew drop @@ -1729,11 +1729,11 @@ proc.pi.14 mem_storew movup.4 - add.1 + add.4 movdn.4 movup.5 - add.1 + add.4 movdn.5 end @@ -1758,8 +1758,8 @@ end #! Whole keccak-p[1600, 24] state can be represented using fifty u32 elements i.e. 13 absolute memory addresses #! s.t. last two elements of 12 -th ( when indexed from zero ) memory address are zeroed. #! -#! Consecutive memory addresses can be computed by repeated application of `add.1`. -proc.chi.4 +#! Consecutive memory addresses can be computed by repeated application of `add.4`. +proc.chi.16 dup locaddr.0 mem_store @@ -1779,7 +1779,7 @@ proc.chi.4 swap movup.2 - add.1 + add.4 dup movdn.3 @@ -1815,7 +1815,7 @@ proc.chi.4 movup.3 movup.3 - locaddr.1 + locaddr.4 mem_storew dup.4 @@ -1830,7 +1830,7 @@ proc.chi.4 swap movup.2 - add.1 + add.4 dup movdn.3 @@ -1857,7 +1857,7 @@ proc.chi.4 movup.3 movup.4 - sub.2 + sub.8 push.0.0.0.0 movup.4 mem_loadw @@ -1877,7 +1877,7 @@ proc.chi.4 movup.7 movup.7 - locaddr.2 + locaddr.8 mem_storew dropw @@ -1901,7 +1901,7 @@ proc.chi.4 dup.4 mem_loadw - locaddr.1 + locaddr.4 push.0.0.0.0 movup.4 mem_loadw @@ -1928,13 +1928,13 @@ proc.chi.4 mem_storew movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw - locaddr.2 + locaddr.8 push.0.0.0.0 movup.4 mem_loadw @@ -1961,7 +1961,7 @@ proc.chi.4 mem_storew movup.4 - add.1 + add.4 movdn.4 dup.4 @@ -1979,7 +1979,7 @@ proc.chi.4 # process state[10..20) movup.4 - add.1 + add.4 movdn.4 dup.4 @@ -2001,11 +2001,11 @@ proc.chi.4 swap push.0.0 - locaddr.1 + locaddr.4 mem_storew movup.6 - add.1 + add.4 dup movdn.7 @@ -2044,11 +2044,11 @@ proc.chi.4 movup.3 movup.3 - locaddr.2 + locaddr.8 mem_storew movup.6 - sub.2 + sub.8 dup movdn.7 @@ -2077,7 +2077,7 @@ proc.chi.4 movup.3 movup.4 - add.1 + add.4 push.0.0.0.0 movup.4 mem_loadw @@ -2105,19 +2105,19 @@ proc.chi.4 movup.3 movup.3 - locaddr.3 + locaddr.12 mem_storew locaddr.0 mem_load - add.2 + add.8 dup movdn.5 mem_loadw push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 movup.4 u32xor @@ -2141,14 +2141,14 @@ proc.chi.4 mem_storew movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 movup.4 u32xor @@ -2172,14 +2172,14 @@ proc.chi.4 mem_storew movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 movup.4 u32xor @@ -2204,7 +2204,7 @@ proc.chi.4 # process state[20..30) movup.4 - add.1 + add.4 movdn.4 dup.4 @@ -2219,7 +2219,7 @@ proc.chi.4 swap movup.2 - add.1 + add.4 movdn.2 dup.2 @@ -2255,10 +2255,10 @@ proc.chi.4 movup.3 movup.3 - loc_storew.1 + loc_storew.4 movup.6 - add.1 + add.4 movdn.6 dup.6 @@ -2288,7 +2288,7 @@ proc.chi.4 swap movup.4 - sub.2 + sub.8 movdn.4 dup.4 @@ -2317,7 +2317,7 @@ proc.chi.4 movup.7 movup.7 - loc_storew.2 + loc_storew.8 dropw u32not @@ -2336,13 +2336,13 @@ proc.chi.4 movdn.3 movdn.3 - loc_storew.3 + loc_storew.12 dup.4 mem_loadw push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 movup.4 u32xor @@ -2366,14 +2366,14 @@ proc.chi.4 mem_storew movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 movup.4 u32xor @@ -2397,14 +2397,14 @@ proc.chi.4 mem_storew movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 movup.4 u32xor @@ -2429,7 +2429,7 @@ proc.chi.4 # process state[30..40) movup.4 - add.1 + add.4 movdn.4 dup.4 @@ -2451,10 +2451,10 @@ proc.chi.4 swap push.0.0 - loc_storew.1 + loc_storew.4 movup.6 - add.1 + add.4 movdn.6 dup.6 @@ -2499,10 +2499,10 @@ proc.chi.4 movup.3 movup.3 - loc_storew.2 + loc_storew.8 movup.6 - sub.2 + sub.8 movdn.6 dup.6 @@ -2530,7 +2530,7 @@ proc.chi.4 swap movup.4 - add.1 + add.4 movdn.4 dup.4 @@ -2561,17 +2561,17 @@ proc.chi.4 movup.3 movup.3 - loc_storew.3 + loc_storew.12 movup.4 - sub.1 + sub.4 movdn.4 dup.4 mem_loadw push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 movup.4 u32xor @@ -2595,14 +2595,14 @@ proc.chi.4 mem_storew movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 movup.4 u32xor @@ -2626,14 +2626,14 @@ proc.chi.4 mem_storew movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 movup.4 u32xor @@ -2658,7 +2658,7 @@ proc.chi.4 # process state[40..50) movup.4 - add.1 + add.4 movdn.4 dup.4 @@ -2668,7 +2668,7 @@ proc.chi.4 drop movup.2 - add.1 + add.4 movdn.2 dup.2 @@ -2715,10 +2715,10 @@ proc.chi.4 movup.3 movup.3 - loc_storew.1 + loc_storew.4 movup.6 - add.1 + add.4 movdn.6 dup.6 @@ -2748,7 +2748,7 @@ proc.chi.4 swap movup.4 - sub.2 + sub.8 movdn.4 dup.4 @@ -2777,7 +2777,7 @@ proc.chi.4 movup.7 movup.7 - loc_storew.2 + loc_storew.8 dropw u32not @@ -2796,13 +2796,13 @@ proc.chi.4 movdn.3 movdn.3 - loc_storew.3 + loc_storew.12 dup.4 mem_loadw push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 movup.4 u32xor @@ -2826,14 +2826,14 @@ proc.chi.4 mem_storew movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 movup.4 u32xor @@ -2857,14 +2857,14 @@ proc.chi.4 mem_storew movup.4 - add.1 + add.4 movdn.4 dup.4 mem_loadw push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 movup.4 u32xor @@ -2947,7 +2947,7 @@ end #! Whole keccak-p[1600, 24] state can be represented using fifty u32 elements i.e. 13 absolute memory addresses #! s.t. last two elements of 12 -th ( when indexed from zero ) memory address are zeroed. #! -#! Consecutive memory addresses can be computed by repeated application of `add.1`. +#! Consecutive memory addresses can be computed by repeated application of `add.4`. #! #! See https://github.com/itzmeanjan/merklize-sha/blob/1d35aae9da7fed20127489f362b4bc93242a516c/include/sha3.hpp#L325-L340 proc.round @@ -2977,7 +2977,7 @@ end #! Whole keccak-p[1600, 24] state can be represented using fifty u32 elements i.e. 13 absolute memory addresses #! s.t. last two elements of 12 -th ( when indexed from zero ) memory address are zeroed. #! -#! Consecutive memory addresses can be computed by repeated application of `add.1`. +#! Consecutive memory addresses can be computed by repeated application of `add.4`. #! #! See https://github.com/itzmeanjan/merklize-sha/blob/1d35aae9da7fed20127489f362b4bc93242a516c/include/sha3.hpp#L379-L427 proc.keccak_p @@ -3357,7 +3357,7 @@ end #! [state_addr, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, ...] #! #! Note, state_addr is the starting absolute memory address where keccak-p[1600, 24] state -#! is kept. Consecutive addresses can be computed by repeated application of `add.1` instruction. +#! is kept. Consecutive addresses can be computed by repeated application of `add.4` instruction. #! #! Final stack state : #! @@ -3381,7 +3381,7 @@ proc.to_state_array mem_storew dropw - add.1 + add.4 end push.0.0.0.1 @@ -3389,56 +3389,56 @@ proc.to_state_array mem_storew dropw - add.1 + add.4 push.0.0.0.0 dup.4 mem_storew dropw - add.1 + add.4 push.0.0.0.0 dup.4 mem_storew dropw - add.1 + add.4 push.0.0.0.0 dup.4 mem_storew dropw - add.1 + add.4 push.0.0.2147483648.0 dup.4 mem_storew dropw - add.1 + add.4 push.0.0.0.0 dup.4 mem_storew dropw - add.1 + add.4 push.0.0.0.0 dup.4 mem_storew dropw - add.1 + add.4 push.0.0.0.0 dup.4 mem_storew dropw - add.1 + add.4 push.0.0.0.0 movup.4 @@ -3485,7 +3485,7 @@ end #! [oword0, oword1, oword2, oword3, oword4, oword5, oword6, oword7, ... ] #! #! See https://github.com/itzmeanjan/merklize-sha/blob/1d35aae9da7fed20127489f362b4bc93242a516c/include/keccak_256.hpp#L232-L257 -export.hash.13 +export.hash.52 # prapare keccak256 state from input message locaddr.0 exec.to_state_array @@ -3496,7 +3496,7 @@ export.hash.13 # prapare keccak256 digest from state push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.to_digest diff --git a/stdlib/asm/crypto/hashes/rpo.masm b/stdlib/asm/crypto/hashes/rpo.masm index 1035fa464d..f6a132e27a 100644 --- a/stdlib/asm/crypto/hashes/rpo.masm +++ b/stdlib/asm/crypto/hashes/rpo.masm @@ -34,7 +34,7 @@ end #! Hashes the memory `start_addr` to `end_addr` given an RPO state specified by 3 words. #! -#! This requires that `end_addr = start_addr + 2n` where n = {0, 1, 2 ...}, otherwise the procedure +#! This requires that `end_addr = start_addr + 8n` where n = {0, 1, 2 ...}, otherwise the procedure #! will enter an infinite loop. #! #! Input: [C, B, A, start_addr, end_addr, ...] @@ -56,24 +56,25 @@ end #! Hashes the memory `start_addr` to `end_addr`, handles odd number of elements. #! #! Requires `start_addr ≤ end_addr`, `end_addr` is not inclusive. +#! Requires `start_addr` and `end_addr` to be word-aligned. #! #! Input: [start_addr, end_addr, ...] #! Output: [H, ...] #! #! Cycles: -#! - even words: 49 cycles + 3 * words -#! - odd words: 61 cycles + 3 * words +#! - even words: 53 cycles + 3 * words +#! - odd words: 65 cycles + 3 * words #! where `words` is the `start_addr - end_addr - 1` export.hash_memory_words # enforce `start_addr ≤ end_addr` dup.1 dup.1 u32assert2 u32gte assert - # figure out if the range is for an odd number of words (9 cycles) - dup.1 dup.1 sub is_odd + # figure out if the range is for an odd number of words (11 cycles) + dup.1 dup.1 sub div.4 is_odd # => [is_odd, start_addr, end_addr, ...] - # make the start/end range even (4 cycles) - movup.2 dup.1 sub + # make the start/end range even (6 cycles) + movup.2 dup.1 mul.4 sub # => [end_addr, is_odd, start_addr, ...] # move start_addr to the right stack position (1 cycles) @@ -134,7 +135,7 @@ export.hash_memory # => [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 + mul.8 dup.2 add movup.2 # => [ptr, end_addr, num_elements%8] # get the capacity element which is equal to num_elements%8 diff --git a/stdlib/asm/crypto/hashes/sha256.masm b/stdlib/asm/crypto/hashes/sha256.masm index 7b627c1114..6cce695ddd 100644 --- a/stdlib/asm/crypto/hashes/sha256.masm +++ b/stdlib/asm/crypto/hashes/sha256.masm @@ -233,12 +233,12 @@ end #! - msg0 through msg15 are the 64 -bytes input message (in terms of 16 SHA256 words) #! See https://github.com/itzmeanjan/merklize-sha/blob/8a2c006/include/sha2.hpp#L89-L113 #! & https://github.com/itzmeanjan/merklize-sha/blob/8a2c006/include/sha2_256.hpp#L148-L187 ( loop body execution ) -proc.prepare_message_schedule_and_consume.4 +proc.prepare_message_schedule_and_consume.16 loc_storew.0 - loc_storew.2 + loc_storew.8 dropw - loc_storew.1 - loc_storew.3 + loc_storew.4 + loc_storew.12 dropw dup.15 @@ -282,7 +282,7 @@ proc.prepare_message_schedule_and_consume.4 push.0x428a2f98 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.consume_message_word # consume msg[0] @@ -301,7 +301,7 @@ proc.prepare_message_schedule_and_consume.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw dup.15 @@ -343,7 +343,7 @@ proc.prepare_message_schedule_and_consume.4 push.0x3956c25b push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.consume_message_word # consume msg[4] @@ -362,7 +362,7 @@ proc.prepare_message_schedule_and_consume.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw dup.6 @@ -400,7 +400,7 @@ proc.prepare_message_schedule_and_consume.4 push.0xd807aa98 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.consume_message_word # consume msg[8] @@ -419,7 +419,7 @@ proc.prepare_message_schedule_and_consume.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw movupw.3 @@ -461,7 +461,7 @@ proc.prepare_message_schedule_and_consume.4 push.0x72be5d74 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.consume_message_word # consume msg[12] @@ -480,7 +480,7 @@ proc.prepare_message_schedule_and_consume.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw movupw.3 @@ -522,7 +522,7 @@ proc.prepare_message_schedule_and_consume.4 push.0xe49b69c1 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.consume_message_word # consume msg[16] @@ -541,7 +541,7 @@ proc.prepare_message_schedule_and_consume.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw movupw.3 @@ -583,7 +583,7 @@ proc.prepare_message_schedule_and_consume.4 push.0x2de92c6f push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.consume_message_word # consume msg[20] @@ -602,7 +602,7 @@ proc.prepare_message_schedule_and_consume.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw movupw.3 @@ -644,7 +644,7 @@ proc.prepare_message_schedule_and_consume.4 push.0x983e5152 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.consume_message_word # consume msg[24] @@ -663,7 +663,7 @@ proc.prepare_message_schedule_and_consume.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw movupw.3 @@ -705,7 +705,7 @@ proc.prepare_message_schedule_and_consume.4 push.0xc6e00bf3 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.consume_message_word # consume msg[28] @@ -724,7 +724,7 @@ proc.prepare_message_schedule_and_consume.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw movupw.3 @@ -766,7 +766,7 @@ proc.prepare_message_schedule_and_consume.4 push.0x27b70a85 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.consume_message_word # consume msg[32] @@ -785,7 +785,7 @@ proc.prepare_message_schedule_and_consume.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw movupw.3 @@ -827,7 +827,7 @@ proc.prepare_message_schedule_and_consume.4 push.0x650a7354 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.consume_message_word # consume msg[36] @@ -846,7 +846,7 @@ proc.prepare_message_schedule_and_consume.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw movupw.3 @@ -888,7 +888,7 @@ proc.prepare_message_schedule_and_consume.4 push.0xa2bfe8a1 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.consume_message_word # consume msg[40] @@ -907,7 +907,7 @@ proc.prepare_message_schedule_and_consume.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw movupw.3 @@ -949,7 +949,7 @@ proc.prepare_message_schedule_and_consume.4 push.0xd192e819 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.consume_message_word # consume msg[44] @@ -968,7 +968,7 @@ proc.prepare_message_schedule_and_consume.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw movupw.2 @@ -979,7 +979,7 @@ proc.prepare_message_schedule_and_consume.4 push.0x19a4c116 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 exec.consume_message_word # consume msg[48] @@ -1057,10 +1057,10 @@ proc.prepare_message_schedule_and_consume.4 exec.consume_message_word # consume msg[63] push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 repeat.8 movup.8 @@ -1563,57 +1563,60 @@ end #! #! Input: [addr, len, ...] #! Output: [dig0, dig1, dig2, dig3, dig4, dig5, dig6, dig7, ...] -export.hash_memory.12 +export.hash_memory.48 # loc.0 (input address) loc_store.0 # loc.1 (input length) - loc_store.1 + loc_store.4 # loc.2 (padded length): input_length + (55 - input_length) % 64 + 9 - push.55 loc_load.1 u32wrapping_sub push.63 u32and - loc_load.1 u32assert2 u32overflowing_add assertz u32assert u32overflowing_add.9 assertz loc_store.2 + push.55 loc_load.4 u32wrapping_sub push.63 u32and + loc_load.4 u32assert2 u32overflowing_add assertz u32assert u32overflowing_add.9 assertz loc_store.8 - # loc.3 (last memory address in padding): input_address + padded_length / 16 - 1 - loc_load.2 u32assert u32div.16 loc_load.0 u32wrapping_add u32wrapping_sub.1 loc_store.3 + # loc.3 (last word address in padding): input_address + (padded_length / 4) - 4 + loc_load.8 u32assert u32div.4 loc_load.0 u32wrapping_add u32wrapping_sub.4 loc_store.12 # loc.4 (u32 aligned padding byte): 0x80000000 >> ((input_length % 4) * 8) - loc_load.1 u32assert u32mod.4 u32assert u32overflowing_mul.8 assertz push.0x80000000 swap u32shr loc_store.4 + loc_load.4 u32assert u32mod.4 u32assert u32overflowing_mul.8 assertz push.0x80000000 swap u32shr loc_store.16 # loc.5 (memory offset of first padding byte): (input_length / 4) % 4 - loc_load.1 u32assert u32div.4 u32mod.4 loc_store.5 + loc_load.4 u32assert u32div.4 u32mod.4 loc_store.20 + + # loc.6 (memory address of first padding byte): input_address + (len / 16) * 4 + # Note: (len /16) * 4 is *not* the same as (len / 4), due to the division being a division + floor operation + loc_load.0 loc_load.4 u32assert u32div.16 u32assert2 mul.4 u32overflowing_add assertz loc_store.24 - # loc.6 (memory address of first padding byte): input_address + (len / 16) - loc_load.0 loc_load.1 u32assert u32div.16 u32assert2 u32overflowing_add assertz loc_store.6 # loc.7 (number of remaining 512-bit blocks to consume): padded_length / 64 - loc_load.2 u32assert u32div.64 loc_store.7 + loc_load.8 u32assert u32div.64 loc_store.28 # Set the first byte after the message to 0x80 - padw loc_load.6 mem_loadw loc_store.8 loc_store.9 loc_store.10 loc_store.11 - locaddr.8 loc_load.5 u32wrapping_add dup mem_load loc_load.4 u32wrapping_add swap mem_store - loc_load.11 loc_load.10 loc_load.9 loc_load.8 loc_load.6 mem_storew dropw + padw loc_load.24 mem_loadw loc_store.32 loc_store.36 loc_store.40 loc_store.44 + # Note: We have to `mul.4` here because locals are spread 4 addresses apart. + locaddr.32 loc_load.20 mul.4 u32wrapping_add dup mem_load loc_load.16 u32wrapping_add swap mem_store + loc_load.44 loc_load.40 loc_load.36 loc_load.32 loc_load.24 mem_storew dropw # Set message length in bits at end of padding - padw loc_load.3 mem_loadw - movup.3 drop loc_load.1 u32assert u32overflowing_mul.8 assertz movdn.3 - loc_load.3 mem_storew dropw + padw loc_load.12 mem_loadw + movup.3 drop loc_load.4 u32assert u32overflowing_mul.8 assertz movdn.3 + loc_load.12 mem_storew dropw # Sha256 init push.0x5be0cd19.0x1f83d9ab.0x9b05688c.0x510e527f push.0xa54ff53a.0x3c6ef372.0xbb67ae85.0x6a09e667 # Consume sha256 blocks - loc_load.7 u32assert neq.0 + loc_load.28 u32assert neq.0 while.true - padw loc_load.0 u32assert u32overflowing_add.3 assertz mem_loadw movdnw.2 - padw loc_load.0 u32assert u32overflowing_add.2 assertz mem_loadw movdnw.2 - padw loc_load.0 u32assert u32overflowing_add.1 assertz mem_loadw movdnw.2 + padw loc_load.0 u32assert u32overflowing_add.12 assertz mem_loadw movdnw.2 + padw loc_load.0 u32assert u32overflowing_add.8 assertz mem_loadw movdnw.2 + padw loc_load.0 u32assert u32overflowing_add.4 assertz mem_loadw movdnw.2 padw loc_load.0 u32assert u32overflowing_add.0 assertz mem_loadw movdnw.2 exec.prepare_message_schedule_and_consume - loc_load.0 u32assert u32overflowing_add.4 assertz loc_store.0 - loc_load.7 u32assert u32overflowing_sub.1 assertz dup loc_store.7 + loc_load.0 u32assert u32overflowing_add.16 assertz loc_store.0 + loc_load.28 u32assert u32overflowing_sub.1 assertz dup loc_store.28 u32assert neq.0 end end diff --git a/stdlib/asm/crypto/stark/constants.masm b/stdlib/asm/crypto/stark/constants.masm index 8529917593..e6ff602f13 100644 --- a/stdlib/asm/crypto/stark/constants.masm +++ b/stdlib/asm/crypto/stark/constants.masm @@ -5,9 +5,17 @@ const.ROOT_UNITY=7277203076849721926 const.DOMAIN_OFFSET=7 const.DOMAIN_OFFSET_INV=2635249152773512046 -const.NUM_CONSTRAINT_COMPOSITION_COEF_MULTIPLIED_BY_TWO_ROUNDED_UP_TO_FOUR=224 -const.NUM_DEEP_COMPOSITION_COEF_MULTIPLIED_BY_TWO_ROUNDED_UP_TO_FOUR=88 +# Number of coefficients corresponds to the number of boundary + transition constraints +# (including auxiliary constraints) +const.NUM_CONSTRAINT_COMPOSITION_COEF_ROUNDED_UP_TO_FOUR=232 + +# Number of coefficients corresponds to "number of main & aux columns" + 8, +# where "8" is the number of columns needed to store the constraint composition polynomial. +const.NUM_DEEP_COMPOSITION_COEF_ROUNDED_UP_TO_FOUR=88 + +# Number of random extension field coefficients related to the auxiliary trace (i.e. the alphas) +const.NUM_AUX_TRACE_COEFS=16 # MEMORY POINTERS # ================================================================================================= @@ -19,35 +27,37 @@ const.TRACE_DOMAIN_GENERATOR_PTR=4294799999 const.PUBLIC_INPUTS_PTR=4294800000 # OOD Frames -# (70 + 7) * 2 * 2 Felt for current and next trace rows and 8 * 2 Felt for constraint composition -# polynomials. Total memory slots required: ((70 + 7) * 2 * 2 + 8 * 2) / 4 = 81 +# (71 + 7) * 2 * 2 Felt for current and next trace rows and 8 * 2 Felt for constraint composition +# polynomials. Memory slots: +# OOD_TRACE_PTR: (71 + 7) * 2 * 2 = 312 +# OOD_CONSTRAINT_EVALS_PTR: 8 * 2 = 16 const.OOD_TRACE_PTR=4294900000 -const.OOD_CONSTRAINT_EVALS_PTR=4294900077 +const.OOD_CONSTRAINT_EVALS_PTR=4294900312 # Current trace row -# 70 Felt for main portion of trace, 7 * 2 Felt for auxiliary portion of trace and 8 * 2 Felt for +# 71 Felt for main portion of trace, 7 * 2 Felt for auxiliary portion of trace and 8 * 2 Felt for # constraint composition polynomials. Since we store these with the padding to make each of the -# three portions a multiple of 8, the number of slots required is (72 + 16 + 16) / 4 = 26 -const.CURRENT_TRACE_ROW_PTR=4294900100 +# three portions a multiple of 8, the number of slots required is 72 + 16 + 16 = 104 +const.CURRENT_TRACE_ROW_PTR=4294900400 # Random elements -# There are are currently 16 ExtFelt for a total of 32 Felt. Thus the number of slots required is 8. -const.AUX_RAND_ELEM_PTR=4294900150 +# There are are currently 16 ExtFelt for a total of 32 Felt. Thus the number of memory slots required is 32. +const.AUX_RAND_ELEM_PTR=4294900600 -# We need 2 Felt for each constraint. We take 2800 slots as an upper bound -const.COMPOSITION_COEF_PTR=4294900200 +# We need 2 Felt for each constraint. We take 112000 slots as an upper bound +const.COMPOSITION_COEF_PTR=4294900800 # We need 2 Felt for each trace column and each of the 8 constraint composition columns. We thus need -# (70 + 7 + 8) * 2 Felt i.e. 43 memory slots. +# (71 + 7 + 8) * 2 = 172 Felt i.e. 172 memory slots. # Note that there is a cap on the number of such coefficients so that the memory region allocated for # these coefficients does not overlap with the memory region storing the FRI queries. -# This cap is of a 100 coefficients which is equivalent to 50 memory slots. This gives 150 memory +# This cap is of a 100 coefficients which is equivalent to 200 memory slots. This gives 600 memory # slots for all of the FRI queries i.e., 150 FRI queries. -const.DEEP_RAND_CC_PTR=4294903000 +const.DEEP_RAND_CC_PTR=4294912000 # FRI # -# (FRI_COM_PTR - 150) ---| +# (FRI_COM_PTR - 600) ---| # . # . | <- FRI queries # . @@ -55,53 +65,53 @@ const.DEEP_RAND_CC_PTR=4294903000 # . # . | <- FRI layer commitments and folding challenges # . -# (FRI_COM_PTR + 32) ---| +# (FRI_COM_PTR + 128) ---| # . # . | <- Remainder codeword and polynomial # . -# (FRI_COM_PTR + 66-1) ---| +# (FRI_COM_PTR + 264-1) ---| # -# For each FRI layer, we need 2 memory slots, one for storing the FRI layer commitment and one for +# For each FRI layer, we need 8 memory slots, one for storing the FRI layer commitment and one for # storing the word [a0, a1, log2(lde_size), lde_size] where a := (a0, a1) is the folding randomness # and lde_size is the size of the LDE domain. Since we are using a folding factor of 4 and the # maximal degree of the remainder polynomial that we allow is 7, an upper limit of 16 FRI layers is -# ample and the number of memory slots we thus allocate for this is 32. Moreover, we allocate -# an additional 32 slots for the remainder codeword and 2 for the remainder polynomial. These are +# ample and the number of memory slots we thus allocate for this is 128. Moreover, we allocate +# an additional 128 slots for the remainder codeword and 8 for the remainder polynomial. These are # expected to be laid out right after the FRI commitments. -# The total number of slots thus becomes 66. -const.FRI_COM_PTR=4294903200 +# The total number of slots thus becomes 264. +const.FRI_COM_PTR=4294912800 # Commitment to main, auxiliary and composition polynomials traces -const.MAIN_TRACE_COM_PTR=4294903300 -const.AUX_TRACE_COM_PTR=4294903301 -const.COMPOSITION_POLY_COM_PTR=4294903302 +const.MAIN_TRACE_COM_PTR=4294913200 +const.AUX_TRACE_COM_PTR=4294913204 +const.COMPOSITION_POLY_COM_PTR=4294913208 # Instant-specific constants -const.LDE_SIZE_PTR=4294903303 -const.Z_PTR=4294903304 -const.NUM_QUERIES_PTR=4294903305 -const.TRACE_LENGTH_PTR=4294903306 -const.TRACE_LENGTH_LOG_PTR=4294903307 -const.GRINDING_FACTOR_PTR=4294903308 +const.LDE_SIZE_PTR=4294913212 +const.Z_PTR=4294913216 +const.NUM_QUERIES_PTR=4294913220 +const.TRACE_LENGTH_PTR=4294913224 +const.TRACE_LENGTH_LOG_PTR=4294913228 +const.GRINDING_FACTOR_PTR=4294913232 # RPO capacity initialization words -const.ZERO_WORD_PTR=4294903309 +const.ZERO_WORD_PTR=4294913236 # State of RPO-based random coin -const.C_PTR=4294903311 -const.R1_PTR=4294903312 -const.R2_PTR=4294903313 +const.C_PTR=4294913244 +const.R1_PTR=4294913248 +const.R2_PTR=4294913252 # Address used for storing temporary values: -const.TMP1=4294903315 -const.TMP2=4294903316 -const.TMP3=4294903317 -const.TMP4=4294903318 -const.TMP5=4294903319 -const.TMP6=4294903320 -const.TMP7=4294903321 -const.TMP8=4294903322 -const.TMP9=4294903323 +const.TMP1=4294913256 +const.TMP2=4294913260 +const.TMP3=4294913264 +const.TMP4=4294913268 +const.TMP5=4294913272 +const.TMP6=4294913276 +const.TMP7=4294913280 +const.TMP8=4294913284 +const.TMP9=4294913288 @@ -112,35 +122,35 @@ const.TMP9=4294903323 # | TRACE_DOMAIN_GENERATOR_PTR | 4294799999 | # | PUBLIC_INPUTS_PTR | 4294800000 | # | OOD_TRACE_PTR | 4294900000 | -# | OOD_CONSTRAINT_EVALS_PTR | 4294900077 | -# | CURRENT_TRACE_ROW_PTR | 4294900100 | -# | AUX_RAND_ELEM_PTR | 4294900150 | -# | COMPOSITION_COEF_PTR | 4294900200 | -# | DEEP_RAND_CC_PTR | 4294903000 | -# | FRI_COM_PTR | 4294903200 | -# | MAIN_TRACE_COM_PTR | 4294903300 | -# | AUX_TRACE_COM_PTR | 4294903301 | -# | COMPOSITION_POLY_COM_PTR | 4294903302 | -# | LDE_SIZE_PTR | 4294903303 | -# | Z_PTR | 4294903304 | -# | NUM_QUERIES_PTR | 4294903305 | -# | TRACE_LENGTH_PTR | 4294903306 | -# | TRACE_LENGTH_LOG_PTR | 4294903307 | -# | GRINDING_FACTOR_PTR | 4294903308 | -# | ZERO_WORD_PTR | 4294903309 | -# | ZERO_ZERO_ZERO_ONE_PTR | 4294903310 | -# | C_PTR | 4294903311 | -# | R1_PTR | 4294903312 | -# | R2_PTR | 4294903313 | -# | TMP1 | 4294903315 | -# | TMP2 | 4294903316 | -# | TMP3 | 4294903317 | -# | TMP4 | 4294903318 | -# | TMP5 | 4294903319 | -# | TMP6 | 4294903320 | -# | TMP7 | 4294903321 | -# | TMP8 | 4294903322 | -# | TMP9 | 4294903323 | +# | OOD_CONSTRAINT_EVALS_PTR | 4294900312 | +# | CURRENT_TRACE_ROW_PTR | 4294900400 | +# | AUX_RAND_ELEM_PTR | 4294900600 | +# | COMPOSITION_COEF_PTR | 4294900800 | +# | DEEP_RAND_CC_PTR | 4294912000 | +# | FRI_COM_PTR | 4294912800 | +# | MAIN_TRACE_COM_PTR | 4294913200 | +# | AUX_TRACE_COM_PTR | 4294913204 | +# | COMPOSITION_POLY_COM_PTR | 4294913208 | +# | LDE_SIZE_PTR | 4294913212 | +# | Z_PTR | 4294913216 | +# | NUM_QUERIES_PTR | 4294913220 | +# | TRACE_LENGTH_PTR | 4294913224 | +# | TRACE_LENGTH_LOG_PTR | 4294913228 | +# | GRINDING_FACTOR_PTR | 4294913232 | +# | ZERO_WORD_PTR | 4294913236 | +# | ZERO_ZERO_ZERO_ONE_PTR | 4294913240 | +# | C_PTR | 4294913244 | +# | R1_PTR | 4294913248 | +# | R2_PTR | 4294913252 | +# | TMP1 | 4294913256 | +# | TMP2 | 4294913260 | +# | TMP3 | 4294913264 | +# | TMP4 | 4294913268 | +# | TMP5 | 4294913272 | +# | TMP6 | 4294913276 | +# | TMP7 | 4294913280 | +# | TMP8 | 4294913284 | +# | TMP9 | 4294913288 | # +------------------------------------------+-------------------------+ # ACCESSORS @@ -150,6 +160,10 @@ export.root_unity push.ROOT_UNITY end +export.num_aux_trace_coefs + push.NUM_AUX_TRACE_COEFS +end + # Procedure to push the trace domain generator address to the stack. # # Input: [...] @@ -168,11 +182,11 @@ export.domain_offset_inv end export.num_constraint_composition_coef_multiplied_by_two_and_rounded_up_to_4 - push.NUM_CONSTRAINT_COMPOSITION_COEF_MULTIPLIED_BY_TWO_ROUNDED_UP_TO_FOUR + push.NUM_CONSTRAINT_COMPOSITION_COEF_ROUNDED_UP_TO_FOUR end -export.num_deep_composition_coef_multiplied_by_two_and_rounded_up_to_4 - push.NUM_DEEP_COMPOSITION_COEF_MULTIPLIED_BY_TWO_ROUNDED_UP_TO_FOUR +export.num_deep_composition_coef_rounded_up_to_4 + push.NUM_DEEP_COMPOSITION_COEF_ROUNDED_UP_TO_FOUR end export.public_inputs_ptr diff --git a/stdlib/asm/crypto/stark/deep_queries.masm b/stdlib/asm/crypto/stark/deep_queries.masm index 41c721c066..c36f7a701a 100644 --- a/stdlib/asm/crypto/stark/deep_queries.masm +++ b/stdlib/asm/crypto/stark/deep_queries.masm @@ -22,7 +22,7 @@ use.std::crypto::stark::constants #! \/ #! #! +-------+-------+-------+-------+-------+-------+-------+-------+------+------+------+------+------+--------+--------+-----+ -#! | T31 | T30 | T21 | T20 | T11 | T10 | T01 | T00 | p1' | p0' | r1' | r0' |x_addr|z_addr+1|a_addr+b| - | +#! | T31 | T30 | T21 | T20 | T11 | T10 | T01 | T00 | p1' | p0' | r1' | r0' |x_addr|z_addr+4|a_addr+b| - | #! +-------+-------+-------+-------+-------+-------+-------+-------+------+------+------+------+------+--------+--------------+ #! #! @@ -47,12 +47,12 @@ export.combine_aux # 2) Get a_addr and update it. This is done here before it becomes inaccessible. # Update a_addr - dup.14 add.1 swap.15 + dup.14 add.4 swap.15 #=> [a_addr, T01, T00, T31, T30, T21, T20, T11, T10, p1, p0, r1, r0, x_addr, z_addr, a_addr', 0] # 3) Load i-th OOD frame portion. This assumes that the OOD frame has been serialized with `current` and `next` rows interleaved. # This also updates the z_addr pointer. - dup.14 add.1 swap.15 + dup.14 add.4 swap.15 padw movup.4 mem_loadw #=> [Tgz1, Tgz0, Tz1, Tz0, a_addr, T01, T00, T31, T30, T21, T20, T11, T10, p1, p0, r1, r0, x_addr, z_addr', a_addr', 0] @@ -144,6 +144,8 @@ proc.load_query_row ## Get main trace commitment and use it to get the leaf movdn.3 movdn.2 + #=> [y, y, depth, index, query_ptr] + push.0.0 exec.constants::main_trace_com_ptr mem_loadw #=>[R, depth, index, query_ptr, ...] @@ -158,12 +160,18 @@ proc.load_query_row exec.constants::current_trace_row_ptr swapw #=>[R, ptr, y, y, y, depth, index, query_ptr, ...] + + # Set the first element of the capacity to the number of main trace columns (modulo 8) exec.constants::zero_word mem_loadw - add.6 + add.7 swap.3 + + # Set R1 and R2 to whatever (they will be overwritten by adv_pipe) padw padw - #=> [Y, Y, 0, 0, 0, 1, ptr, y, y, y] + #=> [Y, Y, 0, 0, 0, 7, ptr, y, y, y] + + # Read the first 64 main trace columns in - the last 7 will be handled separately repeat.8 adv_pipe hperm end @@ -176,10 +184,10 @@ proc.load_query_row dropw adv_push.1 adv_push.1 - push.0 + adv_push.1 push.0 ## Store the last 2 main segment columns - dup.12 add.1 mem_storew + dup.12 add.4 mem_storew ## Final hperm hperm @@ -198,7 +206,7 @@ proc.load_query_row #=> [Y, ptr, y, y, y, depth, index, query_ptr, ...] ## increment ptr to account for the last two words we loaded from the advice tape - swapw add.2 swapw + swapw add.8 swapw # Aux trace part @@ -225,7 +233,7 @@ proc.load_query_row push.0.0 ## Store the last aux segment column - dup.12 add.1 mem_storew + dup.12 add.4 mem_storew ## Final hperm hperm @@ -243,7 +251,7 @@ proc.load_query_row #=> [Y, ptr, y, y, y, depth, index, query_ptr, ...] ##increment ptr to account for column 9 and an additional +1 for the all zero word - swapw add.2 swapw + swapw add.8 swapw # Constraint composition trace part @@ -349,7 +357,7 @@ proc.combine_main_trace_columns end mem_stream - repeat.6 + repeat.7 rcomb_base end end @@ -473,6 +481,7 @@ export.compute_deep_composition_polynomial_queries exec.constants::fri_com_ptr dup.1 # =>[query_ptr, query_end_ptr, query_ptr...] + # Store the pointers to: # 1. random values for computing DEEP polynomial # 2. OOD evaluation frame @@ -505,6 +514,7 @@ export.compute_deep_composition_polynomial_queries push.1 while.true + # I) # # Load the (main, aux, constraint)-traces rows associated with the current query and get @@ -580,16 +590,16 @@ export.compute_deep_composition_polynomial_queries ## b) Store [eval0, eval1, index, poe] ## ## Cycles: 5 - dup.4 add.1 swap.5 + dup.4 add.4 swap.5 mem_storew - #=> [poe, index, eval1, eval0, query_ptr+1, query_end_ptr, query_ptr, ...] + #=> [poe, index, eval1, eval0, query_ptr+4, query_end_ptr, query_ptr, ...] ## c) Prepare stack for next iteration ## ## Cycles: 4 dup.5 dup.5 neq - #=> [?, query_ptr+1, query_end_ptr, ...] + #=> [?, query_ptr+4, query_end_ptr, ...] end dropw drop drop end diff --git a/stdlib/asm/crypto/stark/ood_frames.masm b/stdlib/asm/crypto/stark/ood_frames.masm index b162d71b77..d6e3988550 100644 --- a/stdlib/asm/crypto/stark/ood_frames.masm +++ b/stdlib/asm/crypto/stark/ood_frames.masm @@ -9,34 +9,32 @@ use.std::crypto::hashes::rpo #! Output: [OOD_FRAME_HASH, ...] #! Cycles: 100 export.load_evaluation_frame - # We have 70 main trace columns and 7 aux trace columns for a total of 154 base field elements + # We have 71 main trace columns and 7 aux trace columns for a total of 156 base field elements # per row. Since we have two rows, i.e. current and next, the total number of field elements - # making up the OOD evaluation frame is: - # 324 = 38 * 8 + 4 + # making up the OOD evaluation frame is 156*2 = 312. We will be reading felts in 39 batches of 8 + # using `adv_pipe`: 312 = 39 * 8. # The elements are stored from the stack as (a1_1, a1_0, a0_1, a0_0) where a0 is from the # current row and a1 from the next row. exec.constants::ood_trace_ptr + #=> [ood_trace_ptr ] - push.4.0.0.0 + # Note: the first word is the capacity, where its first element is initialized with the number of elements to hash MODULO 8. + push.0.0.0.0 padw padw - repeat.38 + #=> [ZERO, ZERO, 0, 0, 0, 4, ood_trace_ptr] + repeat.39 adv_pipe hperm end - - # Load the last remaining word and pad with 1 followed by three 0 - adv_loadw - dup.12 mem_storew - swapw - exec.constants::zero_word mem_loadw - hperm + #=> [R1, R2, C, ood_trace_ptr+312] dropw swapw dropw movup.4 drop + #=> [R2] end #! Loads OOD constraint composition polynomial evaluation columns into memory and reseeds the random @@ -61,18 +59,18 @@ export.load_constraint_evaluations dropw dup.1 dup.1 push.0.0 - exec.constants::ood_constraint_evals_ptr add.1 + exec.constants::ood_constraint_evals_ptr add.4 mem_storew # Load value_2 and value_3 adv_loadw dup.3 dup.3 push.0.0 - exec.constants::ood_constraint_evals_ptr add.2 + exec.constants::ood_constraint_evals_ptr add.8 mem_storew dropw dup.1 dup.1 push.0.0 - exec.constants::ood_constraint_evals_ptr add.3 + exec.constants::ood_constraint_evals_ptr add.12 mem_storew dropw @@ -81,12 +79,12 @@ export.load_constraint_evaluations # Load value_4 and value_5 adv_loadw dup.3 dup.3 push.0.0 - exec.constants::ood_constraint_evals_ptr add.4 + exec.constants::ood_constraint_evals_ptr add.16 mem_storew dropw dup.1 dup.1 push.0.0 - exec.constants::ood_constraint_evals_ptr add.5 + exec.constants::ood_constraint_evals_ptr add.20 mem_storew dropw @@ -95,12 +93,12 @@ export.load_constraint_evaluations # Load value_6 and value_7 adv_loadw dup.3 dup.3 push.0.0 - exec.constants::ood_constraint_evals_ptr add.6 + exec.constants::ood_constraint_evals_ptr add.24 mem_storew dropw dup.1 dup.1 push.0.0 - exec.constants::ood_constraint_evals_ptr add.7 + exec.constants::ood_constraint_evals_ptr add.28 mem_storew dropw @@ -127,36 +125,38 @@ export.compute_Hz # => [0, 0, v0_1, v0_0, ptr, ...] # Load value_1 - push.0.0 dup.6 add.1 mem_loadw + push.0.0 dup.6 add.4 mem_loadw # => [0, 0, v1_1, v1_0, v0_1, v0_0, ptr, ...] # Load value_2 - push.0.0 dup.8 add.2 mem_loadw + push.0.0 dup.8 add.8 mem_loadw # => [0, 0, v2_1, v2_0, v1_1, v1_0, v0_1, v0_0, ptr, ...] # Load value_3 - push.0.0 dup.10 add.3 mem_loadw + push.0.0 dup.10 add.12 mem_loadw # => [0, 0, v3_1, v3_0, v2_1, v2_0, v1_1, v1_0, v0_1, v0_0, ptr, ...] # Load value_4 - push.0.0 dup.12 add.4 mem_loadw + push.0.0 dup.12 add.16 mem_loadw # => [0, 0, v4_1, v4_0, v3_1, v3_0, v2_1, v2_0, v1_1, v1_0, v0_1, v0_0, ptr, ...] # Load value_5 - push.0.0 movup.14 movdn.4 dup.4 add.5 mem_loadw + push.0.0 movup.14 movdn.4 dup.4 add.20 mem_loadw # => [0, 0, v5_1, v5_0, ptr, v4_1, v4_0, v3_1, v3_0, v2_1, v2_0, v1_1, v1_0, v0_1, v0_0, ptr, ...] # Load value_6 - push.0.0 dup.6 add.6 mem_loadw + push.0.0 dup.6 add.24 mem_loadw # => [0, 0, v6_1, v6_0, v5_1, v5_0, ptr, v4_1, v4_0, v3_1, v3_0, v2_1, v2_0, v1_1, v1_0, v0_1, v0_0, ptr, ...] # Load value_7 - push.0.0 movup.8 add.7 mem_loadw + push.0.0 movup.8 add.28 mem_loadw # => [0, 0, v7_1, v7_0, v6_1, v6_0, v5_1, v5_0, ptr, v4_1, v4_0, v3_1, v3_0, v2_1, v2_0, v1_1, v1_0, v0_1, v0_0, ptr, ...] ## Load z^N where N is the length of the execution trace push.0.0 exec.constants::z_ptr mem_loadw + # => [(z1, z0)^N, z1, z0, v7_1, v7_0, v6_1, v6_0, v5_1, v5_0, ptr, v4_1, v4_0, v3_1, v3_0, v2_1, v2_0, v1_1, v1_0, v0_1, v0_0, ptr, ...] + movup.2 drop movup.2 drop # => [z1, z0, value_7, ... ,value_0] diff --git a/stdlib/asm/crypto/stark/public_inputs.masm b/stdlib/asm/crypto/stark/public_inputs.masm index a837288d3e..af8151d87b 100644 --- a/stdlib/asm/crypto/stark/public_inputs.masm +++ b/stdlib/asm/crypto/stark/public_inputs.masm @@ -5,7 +5,7 @@ use.std::crypto::stark::constants #! Load the public inputs in memory starting from the address referenced by `public_inputs_ptr`. #! In parallel, compute the hash of the public inputs being loaded. The hashing starts with #! capacity registers of the hash function set to `C` resulting from hashing the proof context. -#! The ouptut D is the digest of the hashing. +#! The output D is the digest of the hashing. #! #! Input: [public_inputs_ptr, C] #! Output: [D] diff --git a/stdlib/asm/crypto/stark/random_coin.masm b/stdlib/asm/crypto/stark/random_coin.masm index 63fe259c9f..5489e4115f 100644 --- a/stdlib/asm/crypto/stark/random_coin.masm +++ b/stdlib/asm/crypto/stark/random_coin.masm @@ -1,3 +1,5 @@ +#! Disclaimer: most of the procedures in this file assume that the input pointers are word-aligned. + use.std::crypto::stark::constants use.std::crypto::stark::utils use.std::crypto::hashes::rpo @@ -172,9 +174,9 @@ export.init_seed ## 4. number of auxiliary random values ## 5. trace length (this is already on the stack) - ## main segment width is 70 and there are 1 auxiliary segments + ## main segment width is 71 and there are 1 auxiliary segments ## of width 7 using 16 random extension field elements - push.0x46010710 + push.0x47010710 ## field modulus bytes (2 field elements) push.0x01 # lower half of the modulus push.0xffffffff # upper half of the modulus @@ -233,10 +235,12 @@ end # ============================================================================================= #! Generates a `num_tuples` tuples of random field elements and stores them in memory -#! starting from address `dest_ptr`. Each memory address holds two tuples. +#! starting from address `dest_ptr`. Each tuple uses 8 memory slots. #! TODO: Generalize by keeping track of something similar to the `output` variable in `RpoRandomCoin` #! so that we keep track of already used randomness and know when there is a need to apply `hperm`. #! +#! `dest_ptr` must be word-aligned. +#! #! Input: [dest_ptr, num_tuples, ...] #! Output: [...] #! @@ -257,14 +261,16 @@ proc.generate_random_coefficients dup.5 mem_storew exec.get_rate_2 - dup.9 add.1 mem_storew + dup.9 add.4 mem_storew #=> [R2, R1, loop_ctr, dest_ptr, 0, 0, ..] exec.get_capacity swapdw + #=> [R1, loop_ctr, dest_ptr, 0, 0, C, R2 ..] swapw - swap add.2 swap - #=> [loop_ctr, dest_ptr, 0, 0, R1, C, R2, ..] + #=> [loop_ctr, dest_ptr, 0, 0, R1, C, R2 ..] + swap add.8 swap + #=> [loop_ctr, dest_ptr+8, 0, 0, R1, C, R2, ..] add.1 dup neq.0 @@ -273,8 +279,8 @@ proc.generate_random_coefficients swapw.3 hperm #=> [R2, R1, C, loop_ctr, dest_ptr, x, x, ...] - # save R2 to mem[dest+1]; we use dup.13 here because it takes only 1 cycle - dup.13 add.1 mem_storew + # save R2 to mem[dest+4]; we use dup.13 here because it takes only 1 cycle + dup.13 add.4 mem_storew #=> [R2, R1, C, loop_ctr, dest_ptr, x, x, ...] # save R1 to mem[dest] @@ -285,8 +291,8 @@ proc.generate_random_coefficients swapw.3 #=> [loop_ctr, dest_ptr, x, x, R1, C, R2, ...] - swap add.2 swap - #=> [loop_ctr, dest_ptr+2, x, x, R1, C, R2, ...] + swap add.8 swap + #=> [loop_ctr, dest_ptr+8, x, x, R1, C, R2, ...] add.1 dup #=> [loop_ctr+1, loop_ctr+1, dest_ptr+2, x, x, R1, C, R2, ...] @@ -306,10 +312,12 @@ proc.generate_random_coefficients end #! Generates a `num_tuples` tuples of random field elements and stores them in memory -#! starting from address `dest_ptr`. Each memory address holds one tuple. +#! starting from address `dest_ptr`. Each memory word holds one tuple, e.g. `[0, 0, t0, t1]`. #! TODO: Generalize by keeping track of something similar to the `output` variable in `RpoRandomCoin` #! so that we keep track of already used randomness and know when there is a need to apply `hperm`. #! +#! `dest_ptr` must be word-aligned. +#! #! Input: [dest_ptr, num_tuples, ...] #! Output: [...] #! @@ -333,33 +341,45 @@ proc.generate_random_coefficients_pad dup.4 push.0.0 dup.4 - mem_storew - #=> [0, 0, a01, a00, dest_ptr, a11, a10, a01, a00, loop_ctr, dest_ptr, x, x, ...] + #=> [dest_ptr, 0, 0, a01, a00, dest_ptr, a11, a10, a01, a00, loop_ctr, dest_ptr, x, x, ...] + + mem_storew dropw + #=> [dest_ptr, a11, a10, a01, a00, loop_ctr, dest_ptr, x, x, ...] - dropw dup.2 dup.2 push.0.0 - movup.4 add.1 mem_storew + #=> [0, 0, a11, a10, dest_ptr, a11, a10, a01, a00, loop_ctr, dest_ptr, x, x, ...] + + movup.4 add.4 mem_storew #=> [0, 0, a11, a10, a11, a10, a01, a00, loop_ctr, dest_ptr, x, x, ...] exec.constants::r2_ptr mem_loadw - dup.9 add.4 swap.10 - #=> [dest_ptr, a31, a30, a21, a20, a11, a10, a01, a00, loop_ctr, dest_ptr+4, x, x, ...] + #=> [a31, a30, a21, a20, a11, a10, a01, a00, loop_ctr, dest_ptr, x, x, ...] + + dup.9 add.16 swap.10 + #=> [dest_ptr, a31, a30, a21, a20, a11, a10, a01, a00, loop_ctr, dest_ptr+16, x, x, ...] dup.4 dup.4 push.0.0 - dup.4 add.2 - mem_storew - #=> [0, 0, a21, a20, dest_ptr, a31, a30, a21, a20, a11, a10, a01, a00, loop_ctr, dest_ptr+4, x, x, ...] - dropw + #=> [0, 0, a21, a20, dest_ptr, a31, a30, a21, a20, a11, a10, a01, a00, loop_ctr, dest_ptr+16, x, x, ...] + + dup.4 add.8 + mem_storew dropw + #=> [dest_ptr, a31, a30, a21, a20, a11, a10, a01, a00, loop_ctr, dest_ptr+16, x, x, ...] + dup.2 dup.2 push.0.0 - movup.4 add.3 mem_storew - #=> [0, 0, a31, a30, a31, a30, a21, a20, a11, a10, a01, a00, loop_ctr, dest_ptr+4, x, x, ...] + #=> [0, 0, a31, a30, dest_ptr, a31, a30, a21, a20, a11, a10, a01, a00, loop_ctr, dest_ptr+16, x, x, ...] + + movup.4 add.12 mem_storew + #=> [0, 0, a31, a30, a31, a30, a21, a20, a11, a10, a01, a00, loop_ctr, dest_ptr+16, x, x, ...] exec.constants::c_ptr mem_loadw + #=> [C, a31, a30, a21, a20, a11, a10, a01, a00, loop_ctr, dest_ptr+16, x, x, ...] + swapdw + #=> [a11, a10, a01, a00, loop_ctr, dest_ptr+16, x, x, C, a31, a30, a21, a20, ...] swapw - #=> [loop_ctr, dest_ptr, 0, 0, R1, C, R2, ..] + #=> [loop_ctr, dest_ptr+16, x, x, R1, C, R2, ..] add.1 dup neq.0 @@ -368,36 +388,44 @@ proc.generate_random_coefficients_pad swapw.3 hperm #=> [R2, R1, C, loop_ctr, dest_ptr, x, x, ...] - # save R2 to mem[dest+1]; we use dup.13 here because it takes only 1 cycle + # save R2 to mem[dest+4]; we use dup.13 here because it takes only 1 cycle dup.13 dup.4 dup.4 push.0.0 - dup.4 add.2 - mem_storew - #=> [0, 0, a21, a20, dest_ptr, a31, a30, a21, a20, a11, a10, a01, a00, C, loop_ctr, dest_ptr, x, x, ...] - dropw + #=> [0, 0, a31, a30, dest_ptr, R2, R1, C, loop_ctr, dest_ptr, x, x, ...] + + dup.4 add.8 + mem_storew dropw + #=> [dest_ptr, a31, a30, a21, a20, a11, a10, a01, a00, C, loop_ctr, dest_ptr, x, x, ...] + dup.2 dup.2 push.0.0 - movup.4 add.3 mem_storew - #=> [0, 0, a31, a30, a31, a30, a21, a20, a11, a10, a01, a00, C, loop_ctr, dest_ptr, x, x, ...] + #=> [0, 0, a31, a30, dest_ptr, a31, a30, a21, a20, a11, a10, a01, a00, C, loop_ctr, dest_ptr, x, x, ...] + + movup.4 add.12 mem_storew dropw + #=> [a31, a30, a21, a20, a11, a10, a01, a00, C, loop_ctr, dest_ptr, x, x, ...] # save R1 to mem[dest] - dropw swapw dup.13 + #=> [dest_ptr, a11, a10, a01, a00, a31, a30, a21, a20, C, loop_ctr, dest_ptr, x, x, ...] + dup.4 dup.4 push.0.0 - dup.4 - mem_storew #=> [0, 0, a01, a00, dest_ptr, a11, a10, a01, a00, a31, a30, a21, a20, C, loop_ctr, dest_ptr, x, x, ...] - dropw + + dup.4 + mem_storew dropw + #=> [dest_ptr, a11, a10, a01, a00, a31, a30, a21, a20, C, loop_ctr, dest_ptr, x, x, ...] + dup.2 dup.2 push.0.0 - movup.4 add.1 mem_storew - #=> [0, 0, a01, a00, a11, a10, a01, a00, a31, a30, a21, a20, C, loop_ctr, dest_ptr, x, x, ...] + #=> [0, 0, a11, a10, dest_ptr, a11, a10, a01, a00, a31, a30, a21, a20, C, loop_ctr, dest_ptr, x, x, ...] + + movup.4 add.4 mem_storew dropw + #=> [a11, a10, a01, a00, a31, a30, a21, a20, C, loop_ctr, dest_ptr, x, x, ...] # reshuffle and update destination pointer and loop counter - dropw swapw swapw.3 #=> [loop_ctr, dest_ptr, x, x, R1, C, R2, ...] - swap add.4 swap + swap add.16 swap #=> [loop_ctr, dest_ptr+2, x, x, R1, C, R2, ...] add.1 dup @@ -405,7 +433,7 @@ proc.generate_random_coefficients_pad neq.0 end - + # Save the new state of the random coin dropw exec.constants::r1_ptr mem_storew @@ -418,20 +446,19 @@ proc.generate_random_coefficients_pad end #! Draw a list of random extension field elements related to the auxiliary trace and store the list -#! in memory from `aux_rand_elem_ptr` to `aux_rand_elem_ptr + 8 - 1` +#! in memory from `aux_rand_elem_ptr` to `aux_rand_elem_ptr + 32 - 4` #! #! Input: [aux_rand_elem_ptr, ...] #! Output: [...] #! Cycles: 159 export.generate_aux_randomness - - push.16 swap + exec.constants::num_aux_trace_coefs swap exec.generate_random_coefficients #=> [...] end #! Draw constraint composition random coefficients and save them into memory in the region from -#! `compos_coef_ptr` `compos_coef_ptr + 112 - 1` as `(r1_1, r1_0, r0_1, r0_0)` +#! `compos_coef_ptr` `compos_coef_ptr + NUM_COEFFS - 1` as `(r1_1, r1_0, r0_1, r0_0)` #! #! Input: [compos_coef_ptr, ...] #! Output: [...] @@ -447,16 +474,16 @@ end #! Draw deep composition polynomial random coefficients and save them into memory in the region from #! `deep_rand_coef_ptr` to `deep_rand_coef_ptr + 89 - 1` as `(0, 0, r0_1, r0_0)` #! The number of coefficients is equal to: -#! 1. (70 + 7) * 2 Felt for the main and auxiliary traces. +#! 1. (71 + 7) * 2 Felt for the main and auxiliary traces. #! 2. 8 * 2 Felt for constraint polynomial. -#! Total: 85 tuples of type (Felt, Felt) +#! Total: 86 tuples of type (Felt, Felt) #! #! Input: [deep_rand_coef_ptr, ...] #! Output: [...] #! Cycles: 1624 export.generate_deep_composition_random_coefficients - # note that 88 is the next number after 85 divisible by 4 - exec.constants::num_deep_composition_coef_multiplied_by_two_and_rounded_up_to_4 + # note that 88 is the next number after 86 divisible by 4 + exec.constants::num_deep_composition_coef_rounded_up_to_4 swap exec.generate_random_coefficients_pad #=> [...] @@ -468,7 +495,7 @@ end #! Generate the OOD challenge point `z = (z0, z1)` and compute `z^N` where N is #! the trace length. The resulting word `[(z_1, z_0)^N, z1, z0]` is stored in the -#! global memory address `exec.z_ptr` reservedfor it. +#! global memory address `exec.z_ptr` reserved for it. #! #! Input: [X, ...] #! Output: [...] @@ -508,11 +535,11 @@ end # ============================================================================================= # Helper function for generating a list of indices that takes a word of random felts and saves -# to memory region referenced by `ptr` 4 random integers in the range 0..=(mask+1). +# to memory region (referenced by `ptr`) 4 random integers in the range 0..=(mask+1). # `depth` is saved next to each of the 4 integers for use in subsequent steps. # # Input: [R, ptr, mask, depth, ...] -# Output:[...] +# Output:[R, ptr+16, mask, depth, ...] # # Cycles: 100 proc.generate_four_integers @@ -525,22 +552,22 @@ proc.generate_four_integers push.0 movdn.3 # [r, depth, r0_hi, 0, R1, ptr, mask, depth, ...] # Store and update pointer - dup.8 add.1 swap.9 # [ptr, r, depth, r0_hi, 0, R1, ptr + 1, mask, depth, ...] + dup.8 add.4 swap.9 # [ptr, r, depth, r0_hi, 0, R1, ptr + 4, mask, depth, ...] mem_storew - dropw # [R1, ptr + 1, mask, depth, ...] + dropw # [R1, ptr + 4, mask, depth, ...] # Get the second random felt - dup.2 # [r1, R1, ptr, mask, depth, ...] - u32split swap # [r1_lo, r1_hi, R1, ptr, mask, depth, ...] - dup.7 # [mask, r1_lo, r1_hi, R1, ptr, mask, depth, ...] - u32and # [r, r1_hi, R1, ptr, mask, depth, ...] - dup.8 swap # [r, depth, r1_hi, R1, ptr, mask, depth, ...] - push.0 movdn.3 # [r, depth, r1_hi, 0, R1, ptr, mask, depth, ...] + dup.2 # [r1, R1, ptr+4, mask, depth, ...] + u32split swap # [r1_lo, r1_hi, R1, ptr+4, mask, depth, ...] + dup.7 # [mask, r1_lo, r1_hi, R1, ptr+4, mask, depth, ...] + u32and # [r, r1_hi, R1, ptr+4, mask, depth, ...] + dup.8 swap # [r, depth, r1_hi, R1, ptr+4, mask, depth, ...] + push.0 movdn.3 # [r, depth, r1_hi, 0, R1, ptr+4, mask, depth, ...] # Store and update pointer - dup.8 add.1 swap.9 # [ptr, r, depth, r1_hi, 0, R1, ptr + 1, mask, depth, ...] + dup.8 add.4 swap.9 # [ptr, r, depth, r1_hi, 0, R1, ptr+8, mask, depth, ...] mem_storew - dropw # [R1, ptr + 1, mask, depth, ...] + dropw # [R1, ptr + 8, mask, depth, ...] # Get the third random felt dup.1 @@ -551,7 +578,7 @@ proc.generate_four_integers push.0 movdn.3 # Store and update pointer - dup.8 add.1 swap.9 + dup.8 add.4 swap.9 mem_storew dropw @@ -564,7 +591,7 @@ proc.generate_four_integers push.0 movdn.3 # Store and update pointer - dup.8 add.1 swap.9 + dup.8 add.4 swap.9 mem_storew dropw end @@ -577,7 +604,7 @@ end # `depth` is saved next to each of the 3 integers for use in subsequent steps. # # Input: [R, ptr, mask, depth, ...] -# Output:[R, ptr + 3, mask, depth, ...] +# Output:[R, ptr + 12, mask, depth, ...] # # Cycles: 75 proc.generate_three_integers @@ -590,9 +617,9 @@ proc.generate_three_integers push.0 movdn.3 # [r, depth, r0_hi, 0, R1, ptr, mask, depth, ...] # Store and update pointer - dup.8 add.1 swap.9 # [ptr, r, depth, r0_hi, 0, R1, ptr + 1, mask, depth, ...] + dup.8 add.4 swap.9 # [ptr, r, depth, r0_hi, 0, R1, ptr + 4, mask, depth, ...] mem_storew - dropw # [R1, ptr + 1, mask, depth, ...] + dropw # [R1, ptr + 4, mask, depth, ...] # Get the second random felt dup.1 # [r1, R1, ptr, mask, depth, ...] @@ -603,9 +630,9 @@ proc.generate_three_integers push.0 movdn.3 # [r, depth, r1_hi, 0, R1, ptr, mask, depth, ...] # Store and update pointer - dup.8 add.1 swap.9 # [ptr, r, depth, r1_hi, 0, R1, ptr + 1, mask, depth, ...] + dup.8 add.4 swap.9 # [ptr, r, depth, r1_hi, 0, R1, ptr + 4, mask, depth, ...] mem_storew - dropw # [R1, ptr + 1, mask, depth, ...] + dropw # [R1, ptr + 4, mask, depth, ...] # Get the third random felt dup.0 @@ -616,7 +643,7 @@ proc.generate_three_integers push.0 movdn.3 # Store and update pointer - dup.8 add.1 swap.9 + dup.8 add.4 swap.9 mem_storew dropw end @@ -652,18 +679,22 @@ export.generate_list_indices # Load the first half of the rate portion of the state of the random coin. We discard the first # element as it is used for PoW and use the remaining the 3. exec.get_rate_1 + #=> [R1, query_ptr, mask, depth, num_queries] exec.generate_three_integers + #=> [R1, query_ptr+12, mask, depth, num_queries] # Load the second half of the rate portion of the state of the random coin. exec.constants::r2_ptr mem_loadw + #=> [R2, query_ptr+12, mask, depth, num_queries] exec.generate_four_integers - #=> [R2, query_ptr, mask, depth, num_queries, ...] + #=> [R2, query_ptr+26, mask, depth, num_queries, ...] # Squeeze exec.constants::c_ptr mem_loadw exec.get_rate_1 exec.get_rate_2 hperm + #=> [R2', R1, C, query_ptr+26, mask, depth, num_queries, ...] # Save the new state exec.constants::r2_ptr mem_storew @@ -674,7 +705,7 @@ export.generate_list_indices # => [C, R1] exec.constants::c_ptr mem_storew dropw - #=> [R1, query_ptr, mask, depth, num_queries, ...] + #=> [R1, query_ptr+26, mask, depth, num_queries, ...] # Use `num_queries` to iterate. @@ -682,50 +713,63 @@ export.generate_list_indices ## Subtract the 7 elements we have already generated above. movup.7 push.7 sub + #=> [num_queries-7, R1, query_ptr+26, mask, depth, ...] ## Divide by 8 to get the number of iterations u32assert u32divmod.8 - #=> [remainder, quotient, X, query_ptr, mask, depth, ...] + #=> [num_queries_remainder, num_queries_quotient, X, query_ptr+26, mask, depth, ...] ## Save remainder for later use movdn.8 + #=> [num_queries_quotient, X, query_ptr+26, mask, depth, num_queries_remainder, ...] ## Use `quotient` to iterate dup movdn.8 + #=> [num_queries_quotient, X, query_ptr+26, mask, depth, num_queries_quotient, num_queries_remainder, ...] + push.0 neq while.true + #=> [X, query_ptr', mask, depth, num_remaining_iterations, remainder, ...] + exec.generate_four_integers + #=> [X, query_ptr'+16, mask, depth, num_remaining_iterations, remainder, ...] exec.constants::r2_ptr mem_loadw exec.generate_four_integers - #=> [R2, query_ptr, mask, depth, num_queries, ...] + #=> [R2, query_ptr'+32, mask, depth, num_remaining_iterations, remainder, ...] # Squeeze exec.constants::c_ptr mem_loadw exec.get_rate_1 exec.get_rate_2 hperm + #=> [R2, R1, C, query_ptr'+32, mask, depth, num_remaining_iterations, remainder, ...] # Save the new state exec.constants::r2_ptr mem_storew dropw - # => [R1, C] + #=> [R1, C, query_ptr'+32, mask, depth, num_remaining_iterations, remainder, ...] exec.constants::r1_ptr mem_storew swapw - # => [C, R1] + #=> [C, R1, query_ptr'+32, mask, depth, num_remaining_iterations, remainder, ...] exec.constants::c_ptr mem_storew dropw - #=> [R1, query_ptr, mask, depth, num_remaining_iterations, remainder, ...] + #=> [R1, query_ptr'+32, mask, depth, num_remaining_iterations, remainder, ...] movup.7 sub.1 dup movdn.8 + #=> [num_remaining_iterations-1, R1, query_ptr'+32, mask, depth, num_remaining_iterations-1, remainder, ...] + push.0 neq end - + #=> [R1, query_ptr', mask, depth, 0, remainder, ...] ## Use remainder + ## Note: we rename the `remainder` variable to `num_queries`, as it now indicates the number of + ## queries left. ### Put the remaining number of queries to generate in the appropriate stack position movup.8 movdn.7 + #=> [R1, query_ptr', mask, depth, num_queries, ...] ### Load the second half of the rate portion of the state of the random coin. padw exec.constants::r2_ptr mem_loadw @@ -733,22 +777,27 @@ export.generate_list_indices ### Iterate over remainder dup.11 sub.1 swap.12 + #=> [num_queries, R2, R1, query_ptr, mask, depth, num_queries-1, ...] + neq.0 while.true + #=> [R2, R1, query_ptr, mask, depth, num_queries, ...] movup.7 - u32split swap # [r0_lo, r0_hi, R2, r3, r2, r1, ptr, mask, depth, ...] - dup.10 # [mask, r0_lo, r0_hi, R2, r3, r2, r1, ptr, mask, depth, ...] - u32and # [r, r0_hi, R2, r3, r2, r1, ptr, mask, depth, ...] - dup.11 swap # [r, depth, r0_hi, R2, r3, r2, r1, ptr, mask, depth, ...] - push.0 movdn.3 # [r, depth, r0_hi, 0, R2, r3, r2, r1, ptr, mask, depth, ...] + u32split swap # [r0_lo, r0_hi, R2, r3, r2, r1, ptr, mask, depth, num_queries, ...] + dup.10 # [mask, r0_lo, r0_hi, R2, r3, r2, r1, ptr, mask, depth, num_queries, ...] + u32and # [r, r0_hi, R2, r3, r2, r1, ptr, mask, depth, num_queries, ...] + dup.11 swap # [r, depth, r0_hi, R2, r3, r2, r1, ptr, mask, depth, num_queries, ...] + push.0 movdn.3 # [r, depth, r0_hi, 0, R2, r3, r2, r1, ptr, mask, depth, num_queries, ...] # Store and update pointer - dup.11 add.1 swap.12 # [ptr, r, depth, r0_hi, 0, R2, r3, r2, r1, ptr + 1, mask, depth, ...] + dup.11 add.4 swap.12 # [ptr, r, depth, r0_hi, 0, R2, r3, r2, r1, ptr + 4, mask, depth, num_queries, ...] mem_storew - drop drop drop # [x, R2, r3, r2, r1, ptr + 1, mask, depth, ...] + drop drop drop # [x, R2, r3, r2, r1, ptr + 1, mask, depth, num_queries, ...] dup.11 sub.1 swap.12 + #=> [num_queries, x, R2, r3, r2, r1, ptr + 1, mask, depth, num_queries-1, ...] push.0 neq end + #=> [R2, R1, query_ptr, mask, depth, 0, ...] dropw dropw dropw drop end @@ -772,6 +821,7 @@ export.check_pow # Load Capacity portion exec.get_capacity + #=> [C, mask, ...] # Load first half of rate portion and add pow witness to first element of rate exec.get_rate_1 diff --git a/stdlib/asm/crypto/stark/verifier.masm b/stdlib/asm/crypto/stark/verifier.masm index 0183fe2a78..fb587b38ce 100644 --- a/stdlib/asm/crypto/stark/verifier.masm +++ b/stdlib/asm/crypto/stark/verifier.masm @@ -14,9 +14,9 @@ use.std::crypto::stark::utils #! - The maximal allowed degree of the remainder polynomial is 7. #! - The public inputs are composed of the input and output stacks, of fixed size equal to 16. #! - There are two trace segments, main and auxiliary. It is assumed that the main trace segment -#! is 70 columns wide while the auxiliary trace segment is 7 columns wide. +#! is 71 columns wide while the auxiliary trace segment is 7 columns wide. #! - The OOD evaluation frame is composed of two interleaved rows, current and next, each composed -#! of 70 elements representing the main trace portion and 7 elements for the auxiliary trace one. +#! of 71 elements representing the main trace portion and 7 elements for the auxiliary trace one. #! - To boost soundness, the protocol is run on a quadratic extension field and this means that #! the OOD evaluation frame is composed of elements in a quadratic extension field i.e. tuples. #! Similarly, elements of the auxiliary trace are quadratic extension field elements. The random @@ -215,7 +215,9 @@ export.verify # the first layer commitment and total number of queries. exec.constants::fri_com_ptr exec.constants::number_queries_ptr mem_load - dup movdn.2 + dup movdn.2 mul.4 + #=> [num_queries*4, fri_com_ptr, num_queries, ...] + sub #=> [query_ptr, num_queries, ...] @@ -226,7 +228,7 @@ export.verify exec.random_coin::generate_list_indices #=> [query_ptr, ...] - # Compute deep compostion polynomial queries + # Compute deep composition polynomial queries # # Cycles: 24 + num_queries * 445 #=> [query_ptr, ...] diff --git a/stdlib/asm/math/ecgfp5/group.masm b/stdlib/asm/math/ecgfp5/group.masm index ddecc813cd..209a1d6b50 100644 --- a/stdlib/asm/math/ecgfp5/group.masm +++ b/stdlib/asm/math/ecgfp5/group.masm @@ -229,32 +229,32 @@ end #! #! Read point addition section ( on page 8 ) of https://ia.cr/2022/274 #! For reference implementation see https://github.com/pornin/ecgfp5/blob/ce059c6/python/ecGFp5.py#L1228-L1255 -export.add.10 +export.add.40 loc_storew.0 dropw - loc_store.1 # cached x1 + loc_store.4 # cached x1 - loc_storew.2 + loc_storew.8 dropw - loc_store.3 # cached y1 + loc_store.12 # cached y1 - loc_store.4 # cached inf1 + loc_store.16 # cached inf1 - loc_storew.5 + loc_storew.20 dropw - loc_store.6 # cached x2 + loc_store.24 # cached x2 - loc_storew.7 + loc_storew.28 dropw - loc_store.8 # cached y2 + loc_store.32 # cached y2 - loc_store.9 # cached inf2 + loc_store.36 # cached inf2 - loc_load.6 + loc_load.24 push.0.0.0.0 - loc_loadw.5 # bring x2 + loc_loadw.20 # bring x2 - loc_load.1 + loc_load.4 push.0.0.0.0 loc_loadw.0 # bring x1 @@ -262,7 +262,7 @@ export.add.10 dup if.true - loc_load.1 + loc_load.4 push.0.0.0.0 loc_loadw.0 # bring x1 @@ -278,13 +278,13 @@ export.add.10 add.263 swap else - loc_load.3 + loc_load.12 push.0.0.0.0 - loc_loadw.2 # bring y1 + loc_loadw.8 # bring y1 - loc_load.8 + loc_load.32 push.0.0.0.0 - loc_loadw.7 # bring y2 + loc_loadw.28 # bring y2 exec.base_field::sub end # = λ0 @@ -292,22 +292,22 @@ export.add.10 dup.5 if.true - loc_load.3 + loc_load.12 push.0.0.0.0 - loc_loadw.2 # bring y1 + loc_loadw.8 # bring y1 repeat.5 movup.4 mul.2 end else - loc_load.1 + loc_load.4 push.0.0.0.0 loc_loadw.0 # bring x1 - loc_load.6 + loc_load.24 push.0.0.0.0 - loc_loadw.5 # bring x2 + loc_loadw.20 # bring x2 exec.base_field::sub end # = λ1 @@ -324,11 +324,11 @@ export.add.10 exec.base_field::square # = λ^2 - loc_load.6 + loc_load.24 push.0.0.0.0 - loc_loadw.5 # bring x2 + loc_loadw.20 # bring x2 - loc_load.1 + loc_load.4 push.0.0.0.0 loc_loadw.0 # bring x1 @@ -344,7 +344,7 @@ export.add.10 dup.4 end - loc_load.1 + loc_load.4 push.0.0.0.0 loc_loadw.0 # bring x1 @@ -356,9 +356,9 @@ export.add.10 exec.base_field::mul - loc_load.3 + loc_load.12 push.0.0.0.0 - loc_loadw.2 # bring y1 + loc_loadw.8 # bring y1 repeat.5 movup.9 @@ -368,13 +368,13 @@ export.add.10 movup.10 - loc_load.3 + loc_load.12 push.0.0.0.0 - loc_loadw.2 # bring y1 + loc_loadw.8 # bring y1 - loc_load.8 + loc_load.32 push.0.0.0.0 - loc_loadw.7 # bring y2 + loc_loadw.28 # bring y2 exec.base_field::neq @@ -384,11 +384,11 @@ export.add.10 # finalize selection of y3 - loc_load.8 + loc_load.32 push.0.0.0.0 - loc_loadw.7 # bring y2 + loc_loadw.28 # bring y2 - loc_load.4 # bring inf1 + loc_load.16 # bring inf1 if.true repeat.5 @@ -401,11 +401,11 @@ export.add.10 end end - loc_load.3 + loc_load.12 push.0.0.0.0 - loc_loadw.2 # bring y1 + loc_loadw.8 # bring y1 - loc_load.9 # bring inf2 + loc_load.36 # bring inf2 if.true repeat.5 @@ -424,11 +424,11 @@ export.add.10 movup.10 end - loc_load.6 + loc_load.24 push.0.0.0.0 - loc_loadw.5 # bring x2 + loc_loadw.20 # bring x2 - loc_load.4 # bring inf1 + loc_load.16 # bring inf1 if.true repeat.5 @@ -441,11 +441,11 @@ export.add.10 end end - loc_load.1 + loc_load.4 push.0.0.0.0 loc_loadw.0 # bring x1 - loc_load.9 # bring inf2 + loc_load.36 # bring inf2 if.true repeat.5 @@ -461,12 +461,12 @@ export.add.10 # finalize selection of inf3 movup.10 - loc_load.9 # bring inf2 - loc_load.4 # bring inf1 + loc_load.36 # bring inf2 + loc_load.16 # bring inf1 cdrop - loc_load.4 # bring inf1 - loc_load.9 # bring inf2 + loc_load.16 # bring inf1 + loc_load.36 # bring inf2 cdrop movdn.10 @@ -492,27 +492,27 @@ end #! #! Read point addition section ( on page 8 ) of https://ia.cr/2022/274 #! For reference implementation see https://github.com/pornin/ecgfp5/blob/ce059c6/python/ecGFp5.py#L1270-L1280 -export.double.5 +export.double.20 loc_storew.0 dropw - loc_store.1 # cached x + loc_store.4 # cached x - loc_storew.2 + loc_storew.8 dropw - loc_store.3 # cached y + loc_store.12 # cached y - loc_store.4 # cached inf + loc_store.16 # cached inf - loc_load.3 + loc_load.12 push.0.0.0.0 - loc_loadw.2 # bring y + loc_loadw.8 # bring y repeat.5 movup.4 mul.2 end # compute λ1 - loc_load.1 + loc_load.4 push.0.0.0.0 loc_loadw.0 # bring x @@ -530,7 +530,7 @@ export.double.5 exec.base_field::div # compute λ - loc_load.1 + loc_load.4 push.0.0.0.0 loc_loadw.0 # bring x @@ -550,7 +550,7 @@ export.double.5 dup.4 end - loc_load.1 + loc_load.4 push.0.0.0.0 loc_loadw.0 # bring x @@ -562,9 +562,9 @@ export.double.5 exec.base_field::mul - loc_load.3 + loc_load.12 push.0.0.0.0 - loc_loadw.2 # bring y + loc_loadw.8 # bring y repeat.5 movup.9 @@ -576,7 +576,7 @@ export.double.5 movup.9 end - loc_load.4 + loc_load.16 movdn.10 end @@ -604,31 +604,31 @@ end #! Point b = (x', y' inf') | b = e * a #! #! See https://github.com/itzmeanjan/secp256k1/blob/cbbe199/point.py#L174-L186 for source of inpiration. -export.mul.10 +export.mul.40 loc_storew.0 dropw - loc_store.1 # cached base_x + loc_store.4 # cached base_x - loc_storew.2 + loc_storew.8 dropw - loc_store.3 # cached base_y + loc_store.12 # cached base_y - loc_store.4 # cached base_inf + loc_store.16 # cached base_inf push.0.0.0.0 - loc_storew.5 + loc_storew.20 dropw push.0 - loc_store.6 # initialize and cache res_x + loc_store.24 # initialize and cache res_x push.0.0.0.0 - loc_storew.7 + loc_storew.28 dropw push.0 - loc_store.8 # initialize and cache res_y + loc_store.32 # initialize and cache res_y push.1 - loc_store.9 # initialize and cache res_inf + loc_store.36 # initialize and cache res_inf repeat.10 repeat.32 @@ -638,49 +638,49 @@ export.mul.10 if.true # bring base - loc_load.4 + loc_load.16 - loc_load.3 + loc_load.12 push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 - loc_load.1 + loc_load.4 push.0.0.0.0 loc_loadw.0 # bring res - loc_load.9 + loc_load.36 - loc_load.8 + loc_load.32 push.0.0.0.0 - loc_loadw.7 + loc_loadw.28 - loc_load.6 + loc_load.24 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 exec.add # write back res - loc_storew.5 + loc_storew.20 dropw - loc_store.6 + loc_store.24 - loc_storew.7 + loc_storew.28 dropw - loc_store.8 + loc_store.32 - loc_store.9 + loc_store.36 end # bring base - loc_load.4 + loc_load.16 - loc_load.3 + loc_load.12 push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 - loc_load.1 + loc_load.4 push.0.0.0.0 loc_loadw.0 @@ -689,13 +689,13 @@ export.mul.10 # write back base loc_storew.0 dropw - loc_store.1 + loc_store.4 - loc_storew.2 + loc_storew.8 dropw - loc_store.3 + loc_store.12 - loc_store.4 + loc_store.16 u32shr.1 end @@ -704,15 +704,15 @@ export.mul.10 end # bring res - loc_load.9 + loc_load.36 - loc_load.8 + loc_load.32 push.0.0.0.0 - loc_loadw.7 + loc_loadw.28 - loc_load.6 + loc_load.24 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 end #! Given a 319 -bit scalar ( say e ) on stack, this routine computes elliptic curve point @@ -741,29 +741,29 @@ end #! Point b = (x, y, inf) | b = e * G #! #! See https://github.com/itzmeanjan/secp256k1/blob/cbbe199/point.py#L174-L186 for source of inpiration. -export.gen_mul.8 +export.gen_mul.32 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw push.0.0 movup.3 movup.3 - loc_storew.2 + loc_storew.8 dropw # cache 319 -bit scalar, having 10 limbs, each of 32 -bit width push.0.0.0.0.0 - loc_storew.3 + loc_storew.12 dropw - loc_store.4 # initialize and cache res_x + loc_store.16 # initialize and cache res_x push.0.0.0.0.0 - loc_storew.5 + loc_storew.20 dropw - loc_store.6 # initialize and cache res_y + loc_store.24 # initialize and cache res_y push.1 - loc_store.7 # initialize and cache res_inf + loc_store.28 # initialize and cache res_inf push.0.8623373087021137817.12280941316336150722.16498106075800537106.17561351883262171669.14191612778916076371.18286383079848041790.5863339128417000744.11451759428629996034.14179124258460441178.4768624678334228939 push.0.16248535820734672314.12843058077554438509.14604603115652880441.1861370543582612103.12413761366396265525.4176022593458990041.3883454194479152564.14208568149880154517.30496475895846659.10372352552633189742 @@ -1105,28 +1105,28 @@ export.gen_mul.8 if.true # base already on stack top # bring res from memory - loc_load.7 + loc_load.28 - loc_load.6 + loc_load.24 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 - loc_load.4 + loc_load.16 push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 exec.add # write back res - loc_storew.3 + loc_storew.12 dropw - loc_store.4 + loc_store.16 - loc_storew.5 + loc_storew.20 dropw - loc_store.6 + loc_store.24 - loc_store.7 + loc_store.28 else dropw dropw @@ -1147,7 +1147,7 @@ export.gen_mul.8 end push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 loc_storew.0 dropw end @@ -1155,7 +1155,7 @@ export.gen_mul.8 repeat.2 # process last 2 limbs of scalar repeat.32 # process each of last two 32 -bit limbs push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 dup push.1 @@ -1164,34 +1164,34 @@ export.gen_mul.8 u32shr.1 - loc_storew.2 + loc_storew.8 dropw if.true # base already on stack top # bring res from memory - loc_load.7 + loc_load.28 - loc_load.6 + loc_load.24 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 - loc_load.4 + loc_load.16 push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 exec.add # write back res - loc_storew.3 + loc_storew.12 dropw - loc_store.4 + loc_store.16 - loc_storew.5 + loc_storew.20 dropw - loc_store.6 + loc_store.24 - loc_store.7 + loc_store.28 else dropw dropw @@ -1201,24 +1201,24 @@ export.gen_mul.8 end push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 drop push.0 movdn.3 - loc_storew.2 + loc_storew.8 dropw end # bring res back to stack - loc_load.7 + loc_load.28 - loc_load.6 + loc_load.24 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 - loc_load.4 + loc_load.16 push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 end diff --git a/stdlib/asm/math/ecgfp5/scalar_field.masm b/stdlib/asm/math/ecgfp5/scalar_field.masm index 2572fb3cdd..01c40872fd 100644 --- a/stdlib/asm/math/ecgfp5/scalar_field.masm +++ b/stdlib/asm/math/ecgfp5/scalar_field.masm @@ -251,19 +251,19 @@ end #! [r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, ...] #! #! Adapted from equivalent Rust implementation https://github.com/itzmeanjan/miden/blob/6a611e693601577864da3e43e745525b83c0030d/miden/tests/integration/stdlib/math/ext5_scalar.rs#L92-L132 -export.mont_mul.8 +export.mont_mul.32 dup loc_store.0 - loc_storew.1 + loc_storew.4 dropw - loc_storew.2 + loc_storew.8 dropw push.0.0 movup.3 movup.3 - loc_storew.3 + loc_storew.12 dropw # cached (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) # when i = 0 @@ -272,15 +272,15 @@ export.mont_mul.8 loc_load.0 u32wrapping_mul u32wrapping_mul.91978719 # more about this literal constant - loc_store.4 # https://github.com/itzmeanjan/miden/blob/e7038e45865a7032a0629346921a77010e82862d/miden/tests/integration/stdlib/math/ext5_scalar.rs#L46-L54 + loc_store.16 # https://github.com/itzmeanjan/miden/blob/e7038e45865a7032a0629346921a77010e82862d/miden/tests/integration/stdlib/math/ext5_scalar.rs#L46-L54 # cached f push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 dup.4 u32overflowing_mul - loc_load.4 + loc_load.16 u32overflowing_mul.2492202977 swap movup.3 @@ -299,7 +299,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.3893352854 swap movup.3 @@ -313,7 +313,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -325,7 +325,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.3609501852 swap movup.3 @@ -338,12 +338,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 swap drop movup.3 swap - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -355,7 +355,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.3901250617 swap movup.3 @@ -368,16 +368,16 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.2 drop movup.3 movdn.2 - loc_storew.5 + loc_storew.20 dropw push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 movup.5 movup.5 movup.2 @@ -389,7 +389,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.3484943929 swap movup.3 @@ -402,10 +402,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.3 drop - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -417,7 +417,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.2147483622 swap movup.3 @@ -431,7 +431,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -443,7 +443,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.22 swap movup.3 @@ -456,12 +456,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swap drop movup.3 swap - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -473,7 +473,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.2147483633 swap movup.3 @@ -486,16 +486,16 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.2 drop movup.3 movdn.2 - loc_storew.6 + loc_storew.24 dropw push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 movup.3 movup.3 drop @@ -512,7 +512,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.2147483655 swap movup.3 @@ -525,10 +525,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.3 drop - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -540,7 +540,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.2147483645 swap movup.3 @@ -558,7 +558,7 @@ export.mont_mul.8 movup.4 u32wrapping_add swap - loc_storew.7 + loc_storew.28 dropw # when i = 1 @@ -567,15 +567,15 @@ export.mont_mul.8 loc_load.0 u32wrapping_mul push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 dup movup.5 u32wrapping_add u32wrapping_mul.91978719 - loc_store.4 + loc_store.16 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 dup.8 u32overflowing_mul swap @@ -584,7 +584,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.2492202977 swap movup.3 @@ -607,7 +607,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3893352854 swap movup.3 @@ -621,7 +621,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -637,7 +637,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3609501852 swap movup.3 @@ -650,12 +650,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 swap drop movup.3 swap - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -671,7 +671,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3901250617 swap movup.3 @@ -684,18 +684,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.2 drop movup.3 movdn.2 - loc_storew.5 + loc_storew.20 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swapw - loc_loadw.2 + loc_loadw.8 movup.9 movup.9 @@ -712,7 +712,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3484943929 swap movup.3 @@ -725,10 +725,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.3 drop - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -744,7 +744,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483622 swap movup.3 @@ -758,7 +758,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -774,7 +774,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.22 swap movup.3 @@ -787,12 +787,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swap drop movup.3 swap - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -808,7 +808,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483633 swap movup.3 @@ -821,18 +821,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.2 drop movup.3 movdn.2 - loc_storew.6 + loc_storew.24 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.7 + loc_loadw.28 swapw - loc_loadw.3 + loc_loadw.12 movup.3 movup.3 drop @@ -857,7 +857,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483655 swap movup.3 @@ -870,10 +870,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.3 drop - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -889,7 +889,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483645 swap movup.3 @@ -906,7 +906,7 @@ export.mont_mul.8 push.0.0 movup.3 movup.3 - loc_storew.7 + loc_storew.28 dropw drop @@ -916,15 +916,15 @@ export.mont_mul.8 loc_load.0 u32wrapping_mul push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 dup movup.5 u32wrapping_add u32wrapping_mul.91978719 - loc_store.4 + loc_store.16 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 dup.8 u32overflowing_mul swap @@ -933,7 +933,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.2492202977 swap movup.3 @@ -956,7 +956,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3893352854 swap movup.3 @@ -970,7 +970,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -986,7 +986,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3609501852 swap movup.3 @@ -999,12 +999,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 swap drop movup.3 swap - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -1020,7 +1020,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3901250617 swap movup.3 @@ -1033,18 +1033,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.2 drop movup.3 movdn.2 - loc_storew.5 + loc_storew.20 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swapw - loc_loadw.2 + loc_loadw.8 movup.9 movup.9 @@ -1061,7 +1061,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3484943929 swap movup.3 @@ -1074,10 +1074,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.3 drop - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -1093,7 +1093,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483622 swap movup.3 @@ -1107,7 +1107,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -1123,7 +1123,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.22 swap movup.3 @@ -1136,12 +1136,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swap drop movup.3 swap - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -1157,7 +1157,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483633 swap movup.3 @@ -1170,18 +1170,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.2 drop movup.3 movdn.2 - loc_storew.6 + loc_storew.24 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.7 + loc_loadw.28 swapw - loc_loadw.3 + loc_loadw.12 movup.3 movup.3 drop @@ -1206,7 +1206,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483655 swap movup.3 @@ -1219,10 +1219,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.3 drop - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -1238,7 +1238,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483645 swap movup.3 @@ -1255,7 +1255,7 @@ export.mont_mul.8 push.0.0 movup.3 movup.3 - loc_storew.7 + loc_storew.28 dropw drop @@ -1265,15 +1265,15 @@ export.mont_mul.8 loc_load.0 u32wrapping_mul push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 dup movup.5 u32wrapping_add u32wrapping_mul.91978719 - loc_store.4 + loc_store.16 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 dup.8 u32overflowing_mul swap @@ -1282,7 +1282,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.2492202977 swap movup.3 @@ -1305,7 +1305,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3893352854 swap movup.3 @@ -1319,7 +1319,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -1335,7 +1335,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3609501852 swap movup.3 @@ -1348,12 +1348,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 swap drop movup.3 swap - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -1369,7 +1369,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3901250617 swap movup.3 @@ -1382,18 +1382,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.2 drop movup.3 movdn.2 - loc_storew.5 + loc_storew.20 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swapw - loc_loadw.2 + loc_loadw.8 movup.9 movup.9 @@ -1410,7 +1410,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3484943929 swap movup.3 @@ -1423,10 +1423,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.3 drop - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -1442,7 +1442,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483622 swap movup.3 @@ -1456,7 +1456,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -1472,7 +1472,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.22 swap movup.3 @@ -1485,12 +1485,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swap drop movup.3 swap - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -1506,7 +1506,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483633 swap movup.3 @@ -1519,18 +1519,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.2 drop movup.3 movdn.2 - loc_storew.6 + loc_storew.24 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.7 + loc_loadw.28 swapw - loc_loadw.3 + loc_loadw.12 movup.3 movup.3 drop @@ -1555,7 +1555,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483655 swap movup.3 @@ -1568,10 +1568,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.3 drop - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -1587,7 +1587,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483645 swap movup.3 @@ -1604,7 +1604,7 @@ export.mont_mul.8 push.0.0 movup.3 movup.3 - loc_storew.7 + loc_storew.28 dropw drop @@ -1614,15 +1614,15 @@ export.mont_mul.8 loc_load.0 u32wrapping_mul push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 dup movup.5 u32wrapping_add u32wrapping_mul.91978719 - loc_store.4 + loc_store.16 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 dup.8 u32overflowing_mul swap @@ -1631,7 +1631,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.2492202977 swap movup.3 @@ -1654,7 +1654,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3893352854 swap movup.3 @@ -1668,7 +1668,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -1684,7 +1684,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3609501852 swap movup.3 @@ -1697,12 +1697,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 swap drop movup.3 swap - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -1718,7 +1718,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3901250617 swap movup.3 @@ -1731,18 +1731,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.2 drop movup.3 movdn.2 - loc_storew.5 + loc_storew.20 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swapw - loc_loadw.2 + loc_loadw.8 movup.9 movup.9 @@ -1759,7 +1759,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3484943929 swap movup.3 @@ -1772,10 +1772,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.3 drop - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -1791,7 +1791,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483622 swap movup.3 @@ -1805,7 +1805,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -1821,7 +1821,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.22 swap movup.3 @@ -1834,12 +1834,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swap drop movup.3 swap - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -1855,7 +1855,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483633 swap movup.3 @@ -1868,18 +1868,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.2 drop movup.3 movdn.2 - loc_storew.6 + loc_storew.24 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.7 + loc_loadw.28 swapw - loc_loadw.3 + loc_loadw.12 movup.3 movup.3 drop @@ -1904,7 +1904,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483655 swap movup.3 @@ -1917,10 +1917,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.3 drop - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -1936,7 +1936,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483645 swap movup.3 @@ -1953,7 +1953,7 @@ export.mont_mul.8 push.0.0 movup.3 movup.3 - loc_storew.7 + loc_storew.28 dropw drop @@ -1963,15 +1963,15 @@ export.mont_mul.8 loc_load.0 u32wrapping_mul push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 dup movup.5 u32wrapping_add u32wrapping_mul.91978719 - loc_store.4 + loc_store.16 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 dup.8 u32overflowing_mul swap @@ -1980,7 +1980,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.2492202977 swap movup.3 @@ -2003,7 +2003,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3893352854 swap movup.3 @@ -2017,7 +2017,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -2033,7 +2033,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3609501852 swap movup.3 @@ -2046,12 +2046,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 swap drop movup.3 swap - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -2067,7 +2067,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3901250617 swap movup.3 @@ -2080,18 +2080,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.2 drop movup.3 movdn.2 - loc_storew.5 + loc_storew.20 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swapw - loc_loadw.2 + loc_loadw.8 movup.9 movup.9 @@ -2108,7 +2108,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3484943929 swap movup.3 @@ -2121,10 +2121,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.3 drop - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -2140,7 +2140,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483622 swap movup.3 @@ -2154,7 +2154,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -2170,7 +2170,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.22 swap movup.3 @@ -2183,12 +2183,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swap drop movup.3 swap - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -2204,7 +2204,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483633 swap movup.3 @@ -2217,18 +2217,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.2 drop movup.3 movdn.2 - loc_storew.6 + loc_storew.24 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.7 + loc_loadw.28 swapw - loc_loadw.3 + loc_loadw.12 movup.3 movup.3 drop @@ -2253,7 +2253,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483655 swap movup.3 @@ -2266,10 +2266,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.3 drop - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -2285,7 +2285,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483645 swap movup.3 @@ -2302,7 +2302,7 @@ export.mont_mul.8 push.0.0 movup.3 movup.3 - loc_storew.7 + loc_storew.28 dropw drop @@ -2312,15 +2312,15 @@ export.mont_mul.8 loc_load.0 u32wrapping_mul push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 dup movup.5 u32wrapping_add u32wrapping_mul.91978719 - loc_store.4 + loc_store.16 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 dup.8 u32overflowing_mul swap @@ -2329,7 +2329,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.2492202977 swap movup.3 @@ -2352,7 +2352,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3893352854 swap movup.3 @@ -2366,7 +2366,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -2382,7 +2382,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3609501852 swap movup.3 @@ -2395,12 +2395,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 swap drop movup.3 swap - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -2416,7 +2416,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3901250617 swap movup.3 @@ -2429,18 +2429,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.2 drop movup.3 movdn.2 - loc_storew.5 + loc_storew.20 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swapw - loc_loadw.2 + loc_loadw.8 movup.9 movup.9 @@ -2457,7 +2457,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3484943929 swap movup.3 @@ -2470,10 +2470,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.3 drop - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -2489,7 +2489,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483622 swap movup.3 @@ -2503,7 +2503,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -2519,7 +2519,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.22 swap movup.3 @@ -2532,12 +2532,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swap drop movup.3 swap - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -2553,7 +2553,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483633 swap movup.3 @@ -2566,18 +2566,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.2 drop movup.3 movdn.2 - loc_storew.6 + loc_storew.24 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.7 + loc_loadw.28 swapw - loc_loadw.3 + loc_loadw.12 movup.3 movup.3 drop @@ -2602,7 +2602,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483655 swap movup.3 @@ -2615,10 +2615,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.3 drop - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -2634,7 +2634,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483645 swap movup.3 @@ -2651,7 +2651,7 @@ export.mont_mul.8 push.0.0 movup.3 movup.3 - loc_storew.7 + loc_storew.28 dropw drop @@ -2661,15 +2661,15 @@ export.mont_mul.8 loc_load.0 u32wrapping_mul push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 dup movup.5 u32wrapping_add u32wrapping_mul.91978719 - loc_store.4 + loc_store.16 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 dup.8 u32overflowing_mul swap @@ -2678,7 +2678,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.2492202977 swap movup.3 @@ -2701,7 +2701,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3893352854 swap movup.3 @@ -2715,7 +2715,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -2731,7 +2731,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3609501852 swap movup.3 @@ -2744,12 +2744,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 swap drop movup.3 swap - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -2765,7 +2765,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3901250617 swap movup.3 @@ -2778,18 +2778,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.2 drop movup.3 movdn.2 - loc_storew.5 + loc_storew.20 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swapw - loc_loadw.2 + loc_loadw.8 movup.9 movup.9 @@ -2806,7 +2806,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3484943929 swap movup.3 @@ -2819,10 +2819,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.3 drop - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -2838,7 +2838,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483622 swap movup.3 @@ -2852,7 +2852,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -2868,7 +2868,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.22 swap movup.3 @@ -2881,12 +2881,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swap drop movup.3 swap - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -2902,7 +2902,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483633 swap movup.3 @@ -2915,18 +2915,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.2 drop movup.3 movdn.2 - loc_storew.6 + loc_storew.24 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.7 + loc_loadw.28 swapw - loc_loadw.3 + loc_loadw.12 movup.3 movup.3 drop @@ -2951,7 +2951,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483655 swap movup.3 @@ -2964,10 +2964,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.3 drop - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -2983,7 +2983,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483645 swap movup.3 @@ -3000,7 +3000,7 @@ export.mont_mul.8 push.0.0 movup.3 movup.3 - loc_storew.7 + loc_storew.28 dropw drop @@ -3010,15 +3010,15 @@ export.mont_mul.8 loc_load.0 u32wrapping_mul push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 dup movup.5 u32wrapping_add u32wrapping_mul.91978719 - loc_store.4 + loc_store.16 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 dup.8 u32overflowing_mul swap @@ -3027,7 +3027,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.2492202977 swap movup.3 @@ -3050,7 +3050,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3893352854 swap movup.3 @@ -3064,7 +3064,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -3080,7 +3080,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3609501852 swap movup.3 @@ -3093,12 +3093,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 swap drop movup.3 swap - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -3114,7 +3114,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3901250617 swap movup.3 @@ -3127,18 +3127,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.2 drop movup.3 movdn.2 - loc_storew.5 + loc_storew.20 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swapw - loc_loadw.2 + loc_loadw.8 movup.9 movup.9 @@ -3155,7 +3155,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3484943929 swap movup.3 @@ -3168,10 +3168,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.3 drop - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -3187,7 +3187,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483622 swap movup.3 @@ -3201,7 +3201,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -3217,7 +3217,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.22 swap movup.3 @@ -3230,12 +3230,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swap drop movup.3 swap - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -3251,7 +3251,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483633 swap movup.3 @@ -3264,18 +3264,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.2 drop movup.3 movdn.2 - loc_storew.6 + loc_storew.24 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.7 + loc_loadw.28 swapw - loc_loadw.3 + loc_loadw.12 movup.3 movup.3 drop @@ -3300,7 +3300,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483655 swap movup.3 @@ -3313,10 +3313,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.3 drop - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -3332,7 +3332,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483645 swap movup.3 @@ -3349,7 +3349,7 @@ export.mont_mul.8 push.0.0 movup.3 movup.3 - loc_storew.7 + loc_storew.28 dropw drop @@ -3359,15 +3359,15 @@ export.mont_mul.8 loc_load.0 u32wrapping_mul push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 dup movup.5 u32wrapping_add u32wrapping_mul.91978719 - loc_store.4 + loc_store.16 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 dup.8 u32overflowing_mul swap @@ -3376,7 +3376,7 @@ export.mont_mul.8 movup.2 u32wrapping_add - loc_load.4 + loc_load.16 u32overflowing_mul.2492202977 swap movup.3 @@ -3399,7 +3399,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3893352854 swap movup.3 @@ -3413,7 +3413,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -3429,7 +3429,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3609501852 swap movup.3 @@ -3442,12 +3442,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 swap drop movup.3 swap - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -3463,7 +3463,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3901250617 swap movup.3 @@ -3476,18 +3476,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.2 drop movup.3 movdn.2 - loc_storew.5 + loc_storew.20 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swapw - loc_loadw.2 + loc_loadw.8 movup.9 movup.9 @@ -3504,7 +3504,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.3484943929 swap movup.3 @@ -3517,10 +3517,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.3 drop - loc_storew.5 + loc_storew.20 dropw movup.2 @@ -3536,7 +3536,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483622 swap movup.3 @@ -3550,7 +3550,7 @@ export.mont_mul.8 swap push.0.0.0 movup.3 - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -3566,7 +3566,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.22 swap movup.3 @@ -3579,12 +3579,12 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 swap drop movup.3 swap - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -3600,7 +3600,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483633 swap movup.3 @@ -3613,18 +3613,18 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.2 drop movup.3 movdn.2 - loc_storew.6 + loc_storew.24 dropw push.0.0.0.0.0.0.0.0 - loc_loadw.7 + loc_loadw.28 swapw - loc_loadw.3 + loc_loadw.12 movup.3 movup.3 drop @@ -3649,7 +3649,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483655 swap movup.3 @@ -3662,10 +3662,10 @@ export.mont_mul.8 u32wrapping_add3 swap push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 movup.3 drop - loc_storew.6 + loc_storew.24 dropw movup.2 @@ -3681,7 +3681,7 @@ export.mont_mul.8 movup.3 u32wrapping_add3 - loc_load.4 + loc_load.16 u32overflowing_mul.2147483645 swap movup.3 @@ -3698,7 +3698,7 @@ export.mont_mul.8 push.0.0 movup.3 movup.3 - loc_storew.7 + loc_storew.28 dropw drop @@ -3706,11 +3706,11 @@ export.mont_mul.8 # # this will be used when executing https://github.com/itzmeanjan/miden/blob/6a611e693601577864da3e43e745525b83c0030d/miden/tests/integration/stdlib/math/ext5_scalar.rs#L131 push.0.0.0.0.0.0.0.0.0.0.0.0 - loc_loadw.7 + loc_loadw.28 swapw - loc_loadw.6 + loc_loadw.24 movupw.2 - loc_loadw.5 + loc_loadw.20 movup.11 movup.11 drop @@ -3723,11 +3723,11 @@ export.mont_mul.8 # # this will be used when executing https://github.com/itzmeanjan/miden/blob/6a611e693601577864da3e43e745525b83c0030d/miden/tests/integration/stdlib/math/ext5_scalar.rs#L130 push.0.0.0.0.0.0.0.0.0.0.0.0 - loc_loadw.7 + loc_loadw.28 swapw - loc_loadw.6 + loc_loadw.24 movupw.2 - loc_loadw.5 + loc_loadw.20 movup.11 movup.11 drop @@ -3803,28 +3803,28 @@ end #! Note, if input operand is 0, then multiplicative inverse can't be computed, which is why output result is also 0. #! #! Adapted from equivalent Rust implementation https://github.com/itzmeanjan/miden/blob/6a611e693601577864da3e43e745525b83c0030d/miden/tests/integration/stdlib/math/ext5_scalar.rs#L162-L176 -export.inv.6 +export.inv.24 # cache result initial value 1 ( in Montgomery form ) push.0.0.4.4294967281.29.4294967251.50.1620046732.787433356.1370930886.803228882.3605528638 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw - loc_storew.2 + loc_storew.8 dropw # compute Montgomery form of base and cache it # # note, base ( i.e. input operand ) is expected in radix-2^32 form exec.to_mont - loc_storew.3 + loc_storew.12 dropw - loc_storew.4 + loc_storew.16 dropw push.0.0 movup.3 movup.3 - loc_storew.5 + loc_storew.20 dropw push.2492202975.3893352854.3609501852.3901250617.3484943929.2147483622.22.2147483633.2147483655.2147483645 @@ -3833,9 +3833,9 @@ export.inv.6 repeat.32 # bring res back to stack push.0.0.0.0.0.0.0.0.0.0.0.0 - loc_loadw.2 + loc_loadw.8 swapw - loc_loadw.1 + loc_loadw.4 movupw.2 loc_loadw.0 movup.11 @@ -3848,12 +3848,12 @@ export.inv.6 # write res back to memory loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw push.0.0 movup.3 movup.3 - loc_storew.2 + loc_storew.8 dropw dup @@ -3861,11 +3861,11 @@ export.inv.6 if.true # bring base back to stack push.0.0.0.0.0.0.0.0.0.0.0.0 - loc_loadw.5 + loc_loadw.20 swapw - loc_loadw.4 + loc_loadw.16 movupw.2 - loc_loadw.3 + loc_loadw.12 movup.11 movup.11 drop @@ -3873,9 +3873,9 @@ export.inv.6 # bring res back to stack push.0.0.0.0.0.0.0.0.0.0.0.0 - loc_loadw.2 + loc_loadw.8 swapw - loc_loadw.1 + loc_loadw.4 movupw.2 loc_loadw.0 movup.11 @@ -3888,12 +3888,12 @@ export.inv.6 # write res back to memory loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw push.0.0 movup.3 movup.3 - loc_storew.2 + loc_storew.8 dropw end @@ -3905,9 +3905,9 @@ export.inv.6 # bring res back to stack push.0.0.0.0.0.0.0.0.0.0.0.0 - loc_loadw.2 + loc_loadw.8 swapw - loc_loadw.1 + loc_loadw.4 movupw.2 loc_loadw.0 movup.11 diff --git a/stdlib/asm/math/secp256k1/base_field.masm b/stdlib/asm/math/secp256k1/base_field.masm index 66b2d7f76f..524da5895c 100644 --- a/stdlib/asm/math/secp256k1/base_field.masm +++ b/stdlib/asm/math/secp256k1/base_field.masm @@ -250,10 +250,10 @@ end #! while computed c[0..8] will also be in Montgomery form. #! #! See https://github.com/itzmeanjan/secp256k1/blob/6e5e654823a073add7d62b21ed88e9de9bb06869/field/base_field_utils.py#L101-L222 -export.mul.2 +export.mul.8 loc_storew.0 swapw - loc_storew.1 + loc_storew.4 swapw exec.u256xu32 @@ -274,7 +274,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -284,7 +284,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -294,7 +294,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -304,7 +304,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -314,7 +314,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -324,7 +324,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -334,7 +334,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -577,18 +577,18 @@ end #! inverse can't be computed, which is why output result is also 0. #! #! See https://github.com/itzmeanjan/secp256k1/blob/37b339db3e03d24c2977399eb8896ef515ebb09b/field/base_field.py#L114-L132 -export.inv.4 +export.inv.16 # cache result initial value ( = 1, in Montgomery form ) push.0.0.0.0.0.0.1.977 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw # cache base - loc_storew.2 + loc_storew.8 dropw - loc_storew.3 + loc_storew.12 dropw push.4294966317.4294967294.4294967295.4294967295.4294967295.4294967295.4294967295.4294967295 @@ -596,7 +596,7 @@ export.inv.4 repeat.8 repeat.32 push.0.0.0.0.0.0.0.0 - loc_loadw.1 + loc_loadw.4 swapw loc_loadw.0 @@ -604,7 +604,7 @@ export.inv.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw dup @@ -612,13 +612,13 @@ export.inv.4 if.true push.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 - loc_loadw.3 + loc_loadw.12 swapw - loc_loadw.2 + loc_loadw.8 swapdw - loc_loadw.1 + loc_loadw.4 swapw loc_loadw.0 @@ -626,7 +626,7 @@ export.inv.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw end @@ -637,7 +637,7 @@ export.inv.4 end push.0.0.0.0.0.0.0.0 - loc_loadw.1 + loc_loadw.4 swapw loc_loadw.0 end diff --git a/stdlib/asm/math/secp256k1/group.masm b/stdlib/asm/math/secp256k1/group.masm index d92994f713..cc94611465 100644 --- a/stdlib/asm/math/secp256k1/group.masm +++ b/stdlib/asm/math/secp256k1/group.masm @@ -35,7 +35,7 @@ use.std::math::secp256k1::base_field #! Stack at end of execution of routine looks like #! #! [x3_addr[0..4], x3_addr[4..8], y3_addr[0..4], y3_addr[4..8], z3_addr[0..4], z3_addr[4..8]] -export.double.12 +export.double.48 dup.3 push.0.0.0.0 movup.4 @@ -52,7 +52,7 @@ export.double.12 loc_storew.0 swapw - loc_storew.1 + loc_storew.4 swapw # cache t0 dupw.1 @@ -70,9 +70,9 @@ export.double.12 exec.base_field::add # = z3 - loc_storew.2 + loc_storew.8 dropw - loc_storew.3 + loc_storew.12 dropw # cache z3 dup.5 @@ -95,9 +95,9 @@ export.double.12 exec.base_field::mul # = t1 - loc_storew.4 + loc_storew.16 dropw - loc_storew.5 + loc_storew.20 dropw # cache t1 dup.5 @@ -119,61 +119,61 @@ export.double.12 exec.base_field::mul # = t2 - loc_storew.6 + loc_storew.24 swapw - loc_storew.7 # cache t2 + loc_storew.28 # cache t2 swapw push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 - loc_loadw.2 # = z3 + loc_loadw.8 # = z3 exec.base_field::mul # = x3 - loc_storew.8 + loc_storew.32 dropw - loc_storew.9 + loc_storew.36 dropw # cache x3 push.0.0.0.0 - loc_loadw.7 + loc_loadw.28 push.0.0.0.0 - loc_loadw.6 # = t2 + loc_loadw.24 # = t2 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 # = t0 exec.base_field::add # = y3 - loc_storew.10 + loc_storew.40 dropw - loc_storew.11 + loc_storew.44 dropw # cache y3 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 push.0.0.0.0 - loc_loadw.4 # = t1 + loc_loadw.16 # = t1 push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 - loc_loadw.2 # = z3 + loc_loadw.8 # = z3 exec.base_field::mul # = z3 - loc_storew.2 + loc_storew.8 dropw - loc_storew.3 + loc_storew.12 dropw # cache z3 push.0.0.0.0 - loc_loadw.7 + loc_loadw.28 push.0.0.0.0 - loc_loadw.6 # = t2 + loc_loadw.24 # = t2 dupw.1 dupw.1 # repeated t2 @@ -181,14 +181,14 @@ export.double.12 exec.base_field::add # = t1 push.0.0.0.0 - loc_loadw.7 + loc_loadw.28 push.0.0.0.0 - loc_loadw.6 # = t2 + loc_loadw.24 # = t2 exec.base_field::add # = t2 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 # = t0 @@ -196,26 +196,26 @@ export.double.12 loc_storew.0 swapw - loc_storew.1 + loc_storew.4 swapw # cache t0 push.0.0.0.0 - loc_loadw.11 + loc_loadw.44 push.0.0.0.0 - loc_loadw.10 # = y3 + loc_loadw.40 # = y3 exec.base_field::mul # = y3 push.0.0.0.0 - loc_loadw.9 + loc_loadw.36 push.0.0.0.0 - loc_loadw.8 # = x3 + loc_loadw.32 # = x3 exec.base_field::add # = y3 - loc_storew.10 + loc_storew.40 dropw - loc_storew.11 + loc_storew.44 dropw # cache y3 dup.3 @@ -239,7 +239,7 @@ export.double.12 exec.base_field::mul # = t1 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 # = t0 @@ -250,9 +250,9 @@ export.double.12 exec.base_field::add # = x3 - loc_storew.8 + loc_storew.32 dropw - loc_storew.9 + loc_storew.36 dropw # cache x3 dropw @@ -261,42 +261,42 @@ export.double.12 dup push.0.0.0.0 - loc_loadw.8 + loc_loadw.32 movup.4 mem_storew dropw # write x3[0..4] to memory dup.1 push.0.0.0.0 - loc_loadw.9 + loc_loadw.36 movup.4 mem_storew dropw # write x3[4..8] to memory dup.2 push.0.0.0.0 - loc_loadw.10 + loc_loadw.40 movup.4 mem_storew dropw # write y3[0..4] to memory dup.3 push.0.0.0.0 - loc_loadw.11 + loc_loadw.44 movup.4 mem_storew dropw # write y3[4..8] to memory dup.4 push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 movup.4 mem_storew dropw # write z3[0..4] to memory dup.5 push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 movup.4 mem_storew dropw # write z3[4..8] to memory @@ -333,7 +333,7 @@ end #! Stack at end of execution of routine looks like #! #! [x3_addr[0..4], x3_addr[4..8], y3_addr[0..4], y3_addr[4..8], z3_addr[0..4], z3_addr[4..8]] -export.add.16 +export.add.64 dup.6 dup.8 @@ -360,7 +360,7 @@ export.add.16 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw # cache t0 dup.8 @@ -387,9 +387,9 @@ export.add.16 exec.base_field::mul # = t1 - loc_storew.2 + loc_storew.8 dropw - loc_storew.3 + loc_storew.12 dropw # cache t1 dup.10 @@ -416,9 +416,9 @@ export.add.16 exec.base_field::mul # = t2 - loc_storew.4 + loc_storew.16 dropw - loc_storew.5 + loc_storew.20 dropw # cache t2 dup.2 @@ -445,9 +445,9 @@ export.add.16 exec.base_field::add # = t3 - loc_storew.6 + loc_storew.24 dropw - loc_storew.7 + loc_storew.28 dropw # cache t3 dup.8 @@ -476,39 +476,39 @@ export.add.16 exec.base_field::add # = t4 push.0.0.0.0 - loc_loadw.7 + loc_loadw.28 push.0.0.0.0 - loc_loadw.6 # t3 loaded back + loc_loadw.24 # t3 loaded back exec.base_field::mul # = t3 - loc_storew.6 + loc_storew.24 dropw - loc_storew.7 + loc_storew.28 dropw # cache t3 push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 - loc_loadw.2 # t1 loaded back + loc_loadw.8 # t1 loaded back push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 # t0 loaded back exec.base_field::add # = t4 push.0.0.0.0 - loc_loadw.7 + loc_loadw.28 push.0.0.0.0 - loc_loadw.6 # t3 loaded back + loc_loadw.24 # t3 loaded back exec.base_field::sub # = t3 - loc_storew.6 + loc_storew.24 dropw - loc_storew.7 + loc_storew.28 dropw # cache t3 dup.2 @@ -535,9 +535,9 @@ export.add.16 exec.base_field::add # = t4 - loc_storew.8 + loc_storew.32 dropw - loc_storew.9 + loc_storew.36 dropw # cache t4 dup.11 @@ -568,39 +568,39 @@ export.add.16 exec.base_field::add # = x3 push.0.0.0.0 - loc_loadw.9 + loc_loadw.36 push.0.0.0.0 - loc_loadw.8 # t4 loaded back + loc_loadw.32 # t4 loaded back exec.base_field::mul # = t4 - loc_storew.8 + loc_storew.32 dropw - loc_storew.9 + loc_storew.36 dropw # cache t4 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 push.0.0.0.0 - loc_loadw.4 # t2 loaded back + loc_loadw.16 # t2 loaded back push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 - loc_loadw.2 # t1 loaded back + loc_loadw.8 # t1 loaded back exec.base_field::add # = x3 push.0.0.0.0 - loc_loadw.9 + loc_loadw.36 push.0.0.0.0 - loc_loadw.8 # t4 loaded back + loc_loadw.32 # t4 loaded back exec.base_field::sub # = t4 - loc_storew.8 + loc_storew.32 dropw - loc_storew.9 + loc_storew.36 dropw # cache t4 dup.4 @@ -627,9 +627,9 @@ export.add.16 exec.base_field::add # = x3 - loc_storew.10 + loc_storew.40 dropw - loc_storew.11 + loc_storew.44 dropw # cache x3 dup.10 @@ -658,43 +658,43 @@ export.add.16 exec.base_field::add # = y3 push.0.0.0.0 - loc_loadw.11 + loc_loadw.44 push.0.0.0.0 - loc_loadw.10 # x3 loaded back + loc_loadw.40 # x3 loaded back exec.base_field::mul # = x3 - loc_storew.10 + loc_storew.40 dropw - loc_storew.11 + loc_storew.44 dropw # cache x3 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 push.0.0.0.0 - loc_loadw.4 # t2 loaded back + loc_loadw.16 # t2 loaded back push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 # t0 loaded back exec.base_field::add # = y3 push.0.0.0.0 - loc_loadw.11 + loc_loadw.44 push.0.0.0.0 - loc_loadw.10 # x3 loaded back + loc_loadw.40 # x3 loaded back exec.base_field::sub # = y3 - loc_storew.12 + loc_storew.48 dropw - loc_storew.13 + loc_storew.52 dropw # cache y3 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 # t0 loaded back @@ -703,13 +703,13 @@ export.add.16 exec.base_field::add # = x3 - loc_storew.10 + loc_storew.40 swapw - loc_storew.11 + loc_storew.44 swapw # cache x3 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 # t0 loaded back @@ -717,153 +717,153 @@ export.add.16 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw # cache t0 push.0.0.0.0 push.0.0.21.20517 # b3 on stack top push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 push.0.0.0.0 - loc_loadw.4 # t2 loaded back + loc_loadw.16 # t2 loaded back exec.base_field::mul # = t2 - loc_storew.4 + loc_storew.16 swapw - loc_storew.5 + loc_storew.20 swapw # cache t2 push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 - loc_loadw.2 # t1 loaded back + loc_loadw.8 # t1 loaded back exec.base_field::add # = z3 - loc_storew.14 + loc_storew.56 dropw - loc_storew.15 + loc_storew.60 dropw # cache z3 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 push.0.0.0.0 - loc_loadw.4 # t2 loaded back + loc_loadw.16 # t2 loaded back push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 - loc_loadw.2 # t1 loaded back + loc_loadw.8 # t1 loaded back exec.base_field::sub # = t1 - loc_storew.2 + loc_storew.8 dropw - loc_storew.3 + loc_storew.12 dropw # cache t1 push.0.0.0.0 push.0.0.21.20517 # b3 on stack top push.0.0.0.0 - loc_loadw.13 + loc_loadw.52 push.0.0.0.0 - loc_loadw.12 # y3 loaded back + loc_loadw.48 # y3 loaded back exec.base_field::mul # = y3 - loc_storew.12 + loc_storew.48 swapw - loc_storew.13 + loc_storew.52 swapw # cache y3 push.0.0.0.0 - loc_loadw.9 + loc_loadw.36 push.0.0.0.0 - loc_loadw.8 # t4 loaded back + loc_loadw.32 # t4 loaded back exec.base_field::mul # = x3 - loc_storew.10 + loc_storew.40 dropw - loc_storew.11 + loc_storew.44 dropw # cache x3 push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 - loc_loadw.2 # t1 loaded back + loc_loadw.8 # t1 loaded back push.0.0.0.0 - loc_loadw.7 + loc_loadw.28 push.0.0.0.0 - loc_loadw.6 # t3 loaded back + loc_loadw.24 # t3 loaded back exec.base_field::mul # = t2 push.0.0.0.0 - loc_loadw.11 + loc_loadw.44 push.0.0.0.0 - loc_loadw.10 # x3 loaded back + loc_loadw.40 # x3 loaded back exec.base_field::neg exec.base_field::add # = x3 - loc_storew.10 + loc_storew.40 dropw - loc_storew.11 + loc_storew.44 dropw # cache x3 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 # t0 loaded back push.0.0.0.0 - loc_loadw.13 + loc_loadw.52 push.0.0.0.0 - loc_loadw.12 # y3 loaded back + loc_loadw.48 # y3 loaded back exec.base_field::mul # = y3 - loc_storew.12 + loc_storew.48 dropw - loc_storew.13 + loc_storew.52 dropw # cache y3 push.0.0.0.0 - loc_loadw.15 + loc_loadw.60 push.0.0.0.0 - loc_loadw.14 # z3 loaded back + loc_loadw.56 # z3 loaded back push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 - loc_loadw.2 # t1 loaded back + loc_loadw.8 # t1 loaded back exec.base_field::mul # = t1 push.0.0.0.0 - loc_loadw.13 + loc_loadw.52 push.0.0.0.0 - loc_loadw.12 # y3 loaded back + loc_loadw.48 # y3 loaded back exec.base_field::add # = y3 - loc_storew.12 + loc_storew.48 dropw - loc_storew.13 + loc_storew.52 dropw # cache y3 push.0.0.0.0 - loc_loadw.7 + loc_loadw.28 push.0.0.0.0 - loc_loadw.6 # t3 loaded back + loc_loadw.24 # t3 loaded back push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 # t0 loaded back @@ -871,31 +871,31 @@ export.add.16 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw # cache t0 push.0.0.0.0 - loc_loadw.9 + loc_loadw.36 push.0.0.0.0 - loc_loadw.8 # t4 loaded back + loc_loadw.32 # t4 loaded back push.0.0.0.0 - loc_loadw.15 + loc_loadw.60 push.0.0.0.0 - loc_loadw.14 # z3 loaded back + loc_loadw.56 # z3 loaded back exec.base_field::mul # = z3 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 # t0 loaded back exec.base_field::add # = z3 - loc_storew.14 + loc_storew.56 dropw - loc_storew.15 + loc_storew.60 dropw # cache z3 dropw @@ -903,37 +903,37 @@ export.add.16 dropw push.0.0.0.0 - loc_loadw.10 + loc_loadw.40 dup.4 mem_storew dropw # write x3[0..4] to memory push.0.0.0.0 - loc_loadw.11 + loc_loadw.44 dup.5 mem_storew dropw # write x3[4..8] to memory push.0.0.0.0 - loc_loadw.12 + loc_loadw.48 dup.6 mem_storew dropw # write y3[0..4] to memory push.0.0.0.0 - loc_loadw.13 + loc_loadw.52 dup.7 mem_storew dropw # write y3[4..8] to memory push.0.0.0.0 - loc_loadw.14 + loc_loadw.56 dup.8 mem_storew dropw # write z3[0..4] to memory push.0.0.0.0 - loc_loadw.15 + loc_loadw.60 dup.9 mem_storew dropw # write z3[4..8] to memory @@ -977,7 +977,7 @@ end #! #! If base point being multiplied is secp256k1 curve generator point, one should use `gen_point` routine, #! which is almost 2x faster ! -export.mul.18 +export.mul.72 # initialize `base` push.0.0.0.0 @@ -987,41 +987,41 @@ export.mul.18 movup.4 mem_loadw - loc_storew.1 + loc_storew.4 movup.4 mem_loadw - loc_storew.2 + loc_storew.8 movup.4 mem_loadw - loc_storew.3 + loc_storew.12 movup.4 mem_loadw - loc_storew.4 + loc_storew.16 movup.4 mem_loadw - loc_storew.5 + loc_storew.20 dropw # initialize `res` ( with group identity ) # See https://github.com/itzmeanjan/secp256k1/blob/d23ea7d/point.py#L40-L45 push.0.0.0.0 - loc_storew.6 - loc_storew.7 + loc_storew.24 + loc_storew.28 dropw push.0.0.1.977 - loc_storew.8 + loc_storew.32 dropw push.0.0.0.0 - loc_storew.9 + loc_storew.36 - loc_storew.10 - loc_storew.11 + loc_storew.40 + loc_storew.44 dropw @@ -1033,27 +1033,27 @@ export.mul.18 if.true # res = base + res - locaddr.17 - locaddr.16 - locaddr.15 - locaddr.14 - locaddr.13 - locaddr.12 + locaddr.68 + locaddr.64 + locaddr.60 + locaddr.56 + locaddr.52 + locaddr.48 # res - locaddr.11 - locaddr.10 - locaddr.9 - locaddr.8 - locaddr.7 - locaddr.6 + locaddr.44 + locaddr.40 + locaddr.36 + locaddr.32 + locaddr.28 + locaddr.24 # base - locaddr.5 + locaddr.20 + locaddr.16 + locaddr.12 + locaddr.8 locaddr.4 - locaddr.3 - locaddr.2 - locaddr.1 locaddr.0 exec.add @@ -1063,45 +1063,45 @@ export.mul.18 movup.4 mem_loadw - loc_storew.6 + loc_storew.24 movup.4 mem_loadw - loc_storew.7 + loc_storew.28 movup.4 mem_loadw - loc_storew.8 + loc_storew.32 movup.4 mem_loadw - loc_storew.9 + loc_storew.36 movup.4 mem_loadw - loc_storew.10 + loc_storew.40 movup.4 mem_loadw - loc_storew.11 + loc_storew.44 dropw end # base = base + base - locaddr.17 - locaddr.16 - locaddr.15 - locaddr.14 - locaddr.13 - locaddr.12 + locaddr.68 + locaddr.64 + locaddr.60 + locaddr.56 + locaddr.52 + locaddr.48 # base - locaddr.5 + locaddr.20 + locaddr.16 + locaddr.12 + locaddr.8 locaddr.4 - locaddr.3 - locaddr.2 - locaddr.1 locaddr.0 exec.double @@ -1115,23 +1115,23 @@ export.mul.18 movup.4 mem_loadw - loc_storew.1 + loc_storew.4 movup.4 mem_loadw - loc_storew.2 + loc_storew.8 movup.4 mem_loadw - loc_storew.3 + loc_storew.12 movup.4 mem_loadw - loc_storew.4 + loc_storew.16 movup.4 mem_loadw - loc_storew.5 + loc_storew.20 dropw @@ -1144,27 +1144,27 @@ export.mul.18 # write resulting point to provided output memory addresses push.0.0.0.0 - loc_loadw.6 + loc_loadw.24 dup.4 mem_storew - loc_loadw.7 + loc_loadw.28 dup.5 mem_storew - loc_loadw.8 + loc_loadw.32 dup.6 mem_storew - loc_loadw.9 + loc_loadw.36 dup.7 mem_storew - loc_loadw.10 + loc_loadw.40 dup.8 mem_storew - loc_loadw.11 + loc_loadw.44 dup.9 mem_storew @@ -1205,33 +1205,33 @@ end #! #! Note, this routine is a specialised instantiation of secp256k1 point multiplication, where we know what the base #! point is, so we enjoy faster computation ( because all point doublings can be precomputed, saving us 256 point doublings ! ). -export.gen_mul.20 +export.gen_mul.80 # identity point of group (0, 1, 0) in projective coordinate # see https://github.com/itzmeanjan/secp256k1/blob/d23ea7d/point.py#L40-L45 push.0.0.0.0 loc_storew.0 dropw push.0.0.0.0 - loc_storew.1 + loc_storew.4 dropw # init & cache res_X push.0.0.1.977 - loc_storew.2 + loc_storew.8 dropw push.0.0.0.0 - loc_storew.3 + loc_storew.12 dropw # init & cache res_Y push.0.0.0.0 - loc_storew.4 + loc_storew.16 dropw push.0.0.0.0 - loc_storew.5 + loc_storew.20 dropw # init & cache res_Z - loc_storew.18 + loc_storew.72 dropw - loc_storew.19 + loc_storew.76 dropw # push (2^255)G into stack @@ -3286,48 +3286,48 @@ export.gen_mul.20 repeat.4 repeat.32 push.0.0.0.0 - loc_loadw.18 + loc_loadw.72 dup push.1 u32and movdn.4 u32shr.1 - loc_storew.18 + loc_storew.72 dropw if.true - loc_storew.12 + loc_storew.48 dropw - loc_storew.13 + loc_storew.52 dropw - loc_storew.14 + loc_storew.56 dropw - loc_storew.15 + loc_storew.60 dropw - loc_storew.16 + loc_storew.64 dropw - loc_storew.17 + loc_storew.68 dropw - locaddr.11 - locaddr.10 - locaddr.9 - locaddr.8 - locaddr.7 - locaddr.6 - - locaddr.17 + locaddr.44 + locaddr.40 + locaddr.36 + locaddr.32 + locaddr.28 + locaddr.24 + + locaddr.68 + locaddr.64 + locaddr.60 + locaddr.56 + locaddr.52 + locaddr.48 + + locaddr.20 locaddr.16 - locaddr.15 - locaddr.14 - locaddr.13 locaddr.12 - - locaddr.5 + locaddr.8 locaddr.4 - locaddr.3 - locaddr.2 - locaddr.1 locaddr.0 exec.add @@ -3335,20 +3335,20 @@ export.gen_mul.20 drop drop - loc_loadw.6 + loc_loadw.24 loc_storew.0 - loc_loadw.7 - loc_storew.1 + loc_loadw.28 + loc_storew.4 - loc_loadw.8 - loc_storew.2 - loc_loadw.9 - loc_storew.3 + loc_loadw.32 + loc_storew.8 + loc_loadw.36 + loc_storew.12 - loc_loadw.10 - loc_storew.4 - loc_loadw.11 - loc_storew.5 + loc_loadw.40 + loc_storew.16 + loc_loadw.44 + loc_storew.20 dropw else @@ -3359,15 +3359,15 @@ export.gen_mul.20 end push.0.0.0.0 - loc_loadw.18 + loc_loadw.72 movdn.3 - loc_storew.18 + loc_storew.72 dropw end push.0.0.0.0 - loc_loadw.19 - loc_storew.18 + loc_loadw.76 + loc_storew.72 dropw end @@ -3380,35 +3380,35 @@ export.gen_mul.20 dup.1 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 movup.4 mem_storew dropw # write x[4..8] to memory dup.2 push.0.0.0.0 - loc_loadw.2 + loc_loadw.8 movup.4 mem_storew dropw # write y[0..4] to memory dup.3 push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 movup.4 mem_storew dropw # write y[4..8] to memory dup.4 push.0.0.0.0 - loc_loadw.4 + loc_loadw.16 movup.4 mem_storew dropw # write z[0..4] to memory dup.5 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.4 mem_storew dropw # write z[4..8] to memory diff --git a/stdlib/asm/math/secp256k1/scalar_field.masm b/stdlib/asm/math/secp256k1/scalar_field.masm index 4e5a7cc44a..a94c4bdd55 100644 --- a/stdlib/asm/math/secp256k1/scalar_field.masm +++ b/stdlib/asm/math/secp256k1/scalar_field.masm @@ -250,10 +250,10 @@ end #! while computed c[0..8] will also be in Montgomery form. #! #! See https://github.com/itzmeanjan/secp256k1/blob/6e5e654823a073add7d62b21ed88e9de9bb06869/field/scalar_field_utils.py#L101-L225 -export.mul.2 +export.mul.8 loc_storew.0 swapw - loc_storew.1 + loc_storew.4 swapw exec.u256xu32 @@ -274,7 +274,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -284,7 +284,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -294,7 +294,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -304,7 +304,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -314,7 +314,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -324,7 +324,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -334,7 +334,7 @@ export.mul.2 movup.9 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 push.0.0.0.0 loc_loadw.0 @@ -437,18 +437,18 @@ end #! inverse can't be computed, which is why output result is also 0. #! #! See https://github.com/itzmeanjan/secp256k1/blob/37b339db3e03d24c2977399eb8896ef515ebb09b/field/scalar_field.py#L118-L136 -export.inv.4 +export.inv.16 # cache result initial value ( = 1, in Montgomery form ) push.0.0.0.1.1162945305.1354194884.1076732275.801750719 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw # cache base - loc_storew.2 + loc_storew.8 dropw - loc_storew.3 + loc_storew.12 dropw push.3493216575.3218235020.2940772411.3132021990.4294967294.4294967295.4294967295.4294967295 @@ -456,7 +456,7 @@ export.inv.4 repeat.8 repeat.32 push.0.0.0.0.0.0.0.0 - loc_loadw.1 + loc_loadw.4 swapw loc_loadw.0 @@ -464,7 +464,7 @@ export.inv.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw dup @@ -472,13 +472,13 @@ export.inv.4 if.true push.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 - loc_loadw.3 + loc_loadw.12 swapw - loc_loadw.2 + loc_loadw.8 swapdw - loc_loadw.1 + loc_loadw.4 swapw loc_loadw.0 @@ -486,7 +486,7 @@ export.inv.4 loc_storew.0 dropw - loc_storew.1 + loc_storew.4 dropw end @@ -497,7 +497,7 @@ export.inv.4 end push.0.0.0.0.0.0.0.0 - loc_loadw.1 + loc_loadw.4 swapw loc_loadw.0 end diff --git a/stdlib/asm/math/u256.masm b/stdlib/asm/math/u256.masm index 7598009cd3..6038ab940c 100644 --- a/stdlib/asm/math/u256.masm +++ b/stdlib/asm/math/u256.masm @@ -246,31 +246,31 @@ end #! Stack transition looks as follows: #! [b7, b6, b5, b4, b3, b2, b1, b0, a7, a6, a5, a4, a3, a2, a1, a0, ...] -> [c7, c6, c5, c4, c3, c2, c1, c0, ...] #! where c = (a * b) % 2^256, and a0, b0, and c0 are least significant 32-bit limbs of a, b, and c respectively. -export.mul_unsafe.6 +export.mul_unsafe.24 # Memory storing setup loc_storew.0 dropw # b[5-8] at 0 - loc_storew.1 + loc_storew.4 # b[0-4] at 1 push.0 dropw # b[0] at top of stack, followed by a[0-7] movdn.8 - loc_storew.2 + loc_storew.8 # a[0-4] at 2 swapw - loc_storew.3 + loc_storew.12 # a[5-8] at 3 padw - loc_storew.4 - loc_storew.5 + loc_storew.16 + loc_storew.20 # p at 4 and 5 # b[0] dropw swapw push.0.0.0.0 - loc_loadw.4 + loc_loadw.16 movdnw.2 movup.12 @@ -279,10 +279,10 @@ export.mul_unsafe.6 movdn.9 movdn.9 swapw - loc_storew.4 + loc_storew.16 dropw push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 swapw movup.9 movup.9 @@ -315,21 +315,21 @@ export.mul_unsafe.6 exec.mulstep drop - loc_storew.5 + loc_storew.20 dropw # b[1] push.0.0.0.0 - loc_loadw.4 + loc_loadw.16 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.7 dropw push.0.0.0.0 - loc_loadw.3 push.0.0.0.0 - loc_loadw.2 # load the xs + loc_loadw.12 push.0.0.0.0 + loc_loadw.8 # load the xs push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 movup.2 movdn.3 push.0 dropw # only need b[1] @@ -341,15 +341,15 @@ export.mul_unsafe.6 swapw movdn.3 push.0.0.0.0 - loc_loadw.4 + loc_loadw.16 push.0 dropw # only need p[0] movdn.3 # save p[0-3] to memory, not needed any more - loc_storew.4 + loc_storew.16 dropw push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.3 drop swapw @@ -379,22 +379,22 @@ export.mul_unsafe.6 drop swap drop - loc_storew.5 + loc_storew.20 dropw # b[2] push.0.0.0.0 - loc_loadw.4 + loc_loadw.16 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.7 movup.7 dropw push.0.0.0.0 - loc_loadw.3 push.0.0.0.0 - loc_loadw.2 # load the xs + loc_loadw.12 push.0.0.0.0 + loc_loadw.8 # load the xs push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 swap movdn.3 push.0 dropw # only need b[1] @@ -407,15 +407,15 @@ export.mul_unsafe.6 movdn.3 movdn.3 push.0.0.0.0 - loc_loadw.4 + loc_loadw.16 drop drop movdn.3 movdn.3 - loc_storew.4 + loc_storew.16 dropw push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.3 movup.3 drop @@ -440,23 +440,23 @@ export.mul_unsafe.6 swap drop movdn.3 drop drop drop - loc_storew.5 + loc_storew.20 dropw # b[3] push.0.0.0.0 - loc_loadw.4 + loc_loadw.16 push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movup.7 movup.7 movup.7 dropw push.0.0.0.0 - loc_loadw.3 push.0.0.0.0 - loc_loadw.2 + loc_loadw.12 push.0.0.0.0 + loc_loadw.8 push.0.0.0.0 - loc_loadw.1 + loc_loadw.4 movdn.3 push.0 dropw @@ -468,14 +468,14 @@ export.mul_unsafe.6 swapw movup.3 push.0.0.0.0 - loc_loadw.4 + loc_loadw.16 drop movup.3 - loc_storew.4 + loc_storew.16 dropw push.0.0.0.0 - loc_loadw.5 + loc_loadw.20 movdn.3 push.0 dropw swapw @@ -494,8 +494,8 @@ export.mul_unsafe.6 # b[4] push.0.0.0.0 - loc_loadw.3 push.0.0.0.0 - loc_loadw.2 # load the xs + loc_loadw.12 push.0.0.0.0 + loc_loadw.8 # load the xs # OPTIM: don't need a[4-7], but can't use mulstep4 if we don't load push.0.0.0.0 @@ -507,7 +507,7 @@ export.mul_unsafe.6 # b[5] push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 loc_loadw.0 movup.2 movdn.3 @@ -537,7 +537,7 @@ export.mul_unsafe.6 # b[6] push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 loc_loadw.0 swap @@ -561,7 +561,7 @@ export.mul_unsafe.6 # b[7] push.0.0.0.0 - loc_loadw.3 + loc_loadw.12 push.0.0.0.0 loc_loadw.0 @@ -576,6 +576,6 @@ export.mul_unsafe.6 drop drop drop push.0.0.0.0 - loc_loadw.4 + loc_loadw.16 swapw end diff --git a/stdlib/asm/mem.masm b/stdlib/asm/mem.masm index b420ab944a..6cd7495378 100644 --- a/stdlib/asm/mem.masm +++ b/stdlib/asm/mem.masm @@ -4,10 +4,12 @@ use.std::crypto::hashes::rpo #! Copies `n` words from `read_ptr` to `write_ptr`. #! +#! `read_ptr` and `write_ptr` *must be* word-aligned. +#! #! Stack transition looks as follows: #! [n, read_ptr, write_ptr, ...] -> [...] #! cycles: 15 + 16n -export.memcopy +export.memcopy_words # The loop variable is changed with an add instead of sub because the former # uses one fewer cycle. So here the counter is negated. (1 cycles) # stack: [-n, read_ptr, write_ptr, ...] @@ -37,11 +39,11 @@ export.memcopy # stack: [-n, read_ptr, write_ptr, x, 0, 0, 0, 0, ...] swapw - # stack: [-n+1, read_ptr+1, write_ptr+1, x, 0, 0, 0, 0, ...] + # stack: [-n+1, read_ptr+4, write_ptr+4, x, 0, 0, 0, 0, ...] # update counters (9 cycles) - add.1 movup.3 movup.3 add.1 movup.3 add.1 movup.3 + add.1 movup.3 movup.3 add.4 movup.3 add.4 movup.3 - # stack: [0, 0, 0, 0, -n+1, read_ptr+1, write_ptr+1, x, ...] + # stack: [0, 0, 0, 0, -n+1, read_ptr+4, write_ptr+4, x, ...] swapw dup.4 neq.0 # while(n!=0) (3 cycles) @@ -61,7 +63,7 @@ end #! - The words C, B, and A are the RPO hasher state #! - A is the capacity #! - C,B are the rate portion of the state -#! - The value `words = end_ptr - write_ptr` must be positive and even +#! - The value `words = end_ptr - write_ptr` must be positive and a multiple of 8 #! #! Cycles: 10 + 9 * word_pairs export.pipe_double_words_to_memory.0 @@ -86,19 +88,19 @@ end #! Input: [num_words, write_ptr, ...] #! Output: [C, B, A, write_ptr', ...] #! Cycles: -#! even num_words: 41 + 9 * num_words / 2 -#! odd num_words: 58 + 9 * round_down(num_words / 2) +#! even num_words: 43 + 9 * num_words / 2 +#! odd num_words: 60 + 9 * round_down(num_words / 2) export.pipe_words_to_memory.0 # check if there is an odd number of words (6 cycles) dup is_odd # => [is_odd, num_words, write_ptr, ...] - # copy is_odd, it defines if last last word requires padding (2 cycles) + # copy is_odd, it defines if last word requires padding (2 cycles) dup movdn.3 # => [is_odd, num_words, write_ptr, needs_padding, ...] - # compute `end_ptr` with an even number of words (5 cycles) - sub dup.1 add swap + # compute `end_ptr` with an even number of words (7 cycles) + sub mul.4 dup.1 add swap # => [write_ptr, end_ptr, needs_padding, ...] # Prepare the capacity word. We use the padding rule which sets the first capacity @@ -132,8 +134,8 @@ export.pipe_words_to_memory.0 # - get the memory address that B' should be saved to # - update the write_ptr to point to the next address (4 cycles) - movup.8 dup.0 add.1 movdn.5 - # => [write_ptr, B', write_ptr+1, A, ...] + movup.8 dup.0 add.4 movdn.5 + # => [write_ptr, B', write_ptr+4, A, ...] # save data to memory (1 cycles) mem_storew diff --git a/stdlib/asm/sys.masm b/stdlib/asm/sys.masm index e4d4cf55d7..ad0d273b40 100644 --- a/stdlib/asm/sys.masm +++ b/stdlib/asm/sys.masm @@ -7,7 +7,7 @@ #! Output: Stack with only the original top 16 elements. #! #! Cycles: 17 + 11 * overflow_words, where `overflow_words` is the number of words needed to drop. -export.truncate_stack.1 +export.truncate_stack.4 # save the first word to memory and bring elements to be dropped to the top of the stack loc_storew.0 dropw movupw.3 # => [X, B, C, D, ...] diff --git a/stdlib/docs/mem.md b/stdlib/docs/mem.md index 84fdb8134e..f88f914f00 100644 --- a/stdlib/docs/mem.md +++ b/stdlib/docs/mem.md @@ -2,7 +2,7 @@ ## std::mem | Procedure | Description | | ----------- | ------------- | -| memcopy | Copies `n` words from `read_ptr` to `write_ptr`.

Stack transition looks as follows:
[n, read_ptr, write_ptr, ...] -> [...]
cycles: 15 + 16n
| +| memcopy_words | Copies `n` words from `read_ptr` to `write_ptr`, both of which must be word-aligned.

Stack transition looks as follows:
[n, read_ptr, write_ptr, ...] -> [...]
cycles: 15 + 16n
| | pipe_double_words_to_memory | Copies an even number of words from the advice_stack to memory.

Input: [C, B, A, write_ptr, end_ptr, ...]
Output: [C, B, A, write_ptr, ...]

Where:
- The words C, B, and A are the RPO hasher state
- A is the capacity
- C,B are the rate portion of the state
- The value `words = end_ptr - write_ptr` must be positive and even

Cycles: 10 + 9 * word_pairs
| | pipe_words_to_memory | Copies an arbitrary number of words from the advice stack to memory

Input: [num_words, write_ptr, ...]
Output: [C, B, A, write_ptr', ...]
Cycles:
even num_words: 41 + 9 * num_words / 2
odd num_words: 58 + 9 * round_down(num_words / 2)
| | pipe_preimage_to_memory | Moves an arbitrary number of words from the advice stack to memory and asserts it matches the commitment.

Input: [num_words, write_ptr, COM, ...]
Output: [write_ptr', ...]
Cycles:
even num_words: 62 + 9 * num_words / 2
odd num_words: 79 + 9 * round_down(num_words / 2)
| diff --git a/stdlib/tests/collections/mmr.rs b/stdlib/tests/collections/mmr.rs index 5d9156c3de..cf9f8e12e9 100644 --- a/stdlib/tests/collections/mmr.rs +++ b/stdlib/tests/collections/mmr.rs @@ -42,21 +42,21 @@ fn test_num_peaks_to_message_size() { "; // minimum size is 16 - build_test!(hash_size, &[1]).expect_stack(&[16]); - build_test!(hash_size, &[2]).expect_stack(&[16]); - build_test!(hash_size, &[3]).expect_stack(&[16]); - build_test!(hash_size, &[4]).expect_stack(&[16]); - build_test!(hash_size, &[7]).expect_stack(&[16]); - build_test!(hash_size, &[11]).expect_stack(&[16]); - build_test!(hash_size, &[16]).expect_stack(&[16]); + build_test!(hash_size, &[1]).expect_stack(&[16 * 4]); + build_test!(hash_size, &[2]).expect_stack(&[16 * 4]); + build_test!(hash_size, &[3]).expect_stack(&[16 * 4]); + build_test!(hash_size, &[4]).expect_stack(&[16 * 4]); + build_test!(hash_size, &[7]).expect_stack(&[16 * 4]); + build_test!(hash_size, &[11]).expect_stack(&[16 * 4]); + build_test!(hash_size, &[16]).expect_stack(&[16 * 4]); // after that, size is round to the next even number - build_test!(hash_size, &[17]).expect_stack(&[18]); - build_test!(hash_size, &[18]).expect_stack(&[18]); - build_test!(hash_size, &[19]).expect_stack(&[20]); - build_test!(hash_size, &[20]).expect_stack(&[20]); - build_test!(hash_size, &[21]).expect_stack(&[22]); - build_test!(hash_size, &[22]).expect_stack(&[22]); + build_test!(hash_size, &[17]).expect_stack(&[18 * 4]); + build_test!(hash_size, &[18]).expect_stack(&[18 * 4]); + build_test!(hash_size, &[19]).expect_stack(&[20 * 4]); + build_test!(hash_size, &[20]).expect_stack(&[20 * 4]); + build_test!(hash_size, &[21]).expect_stack(&[22 * 4]); + build_test!(hash_size, &[22]).expect_stack(&[22 * 4]); } #[test] @@ -75,7 +75,7 @@ fn test_mmr_get_single_peak() -> Result<(), MerkleError> { begin push.{num_leaves} push.1000 mem_store # leaves count - adv_push.4 push.1001 mem_storew dropw # MMR single peak + adv_push.4 push.1004 mem_storew dropw # MMR single peak push.1000 push.{pos} exec.mmr::get @@ -135,8 +135,8 @@ fn test_mmr_get_two_peaks() -> Result<(), MerkleError> { begin push.{num_leaves} push.1000 mem_store # leaves count - adv_push.4 push.1001 mem_storew dropw # MMR first peak - adv_push.4 push.1002 mem_storew dropw # MMR second peak + adv_push.4 push.1004 mem_storew dropw # MMR first peak + adv_push.4 push.1008 mem_storew dropw # MMR second peak push.1000 push.{pos} exec.mmr::get @@ -187,7 +187,7 @@ fn test_mmr_tree_with_one_element() -> Result<(), MerkleError> { begin push.{num_leaves} push.1000 mem_store # leaves count - adv_push.4 push.1001 mem_storew dropw # MMR first peak + adv_push.4 push.1004 mem_storew dropw # MMR first peak push.1000 push.{pos} exec.mmr::get @@ -213,9 +213,9 @@ fn test_mmr_tree_with_one_element() -> Result<(), MerkleError> { begin push.{num_leaves} push.1000 mem_store # leaves count - adv_push.4 push.1001 mem_storew dropw # MMR first peak - adv_push.4 push.1002 mem_storew dropw # MMR second peak - adv_push.4 push.1003 mem_storew dropw # MMR third peak + adv_push.4 push.1004 mem_storew dropw # MMR first peak + adv_push.4 push.1008 mem_storew dropw # MMR second peak + adv_push.4 push.1012 mem_storew dropw # MMR third peak push.1000 push.{pos} exec.mmr::get @@ -235,13 +235,13 @@ fn test_mmr_unpack() { let number_of_leaves: u64 = 0b10101; // 3 peaks, 21 leaves // The hash data is not the same as the peaks, it is padded to 16 elements - let hash_data: [[Felt; 4]; 16] = [ + let peaks: [[Felt; 4]; 16] = [ // 3 peaks. These hashes are invalid, we can't produce data for any of these peaks (only // for testing) [ZERO, ZERO, ZERO, ONE], [ZERO, ZERO, ZERO, Felt::new(2)], [ZERO, ZERO, ZERO, Felt::new(3)], - // Padding, the MMR is padded to a minimum length o 16 + // Padding, the MMR is padded to a minimum length of 16 EMPTY_WORD, EMPTY_WORD, EMPTY_WORD, @@ -256,10 +256,10 @@ fn test_mmr_unpack() { EMPTY_WORD, EMPTY_WORD, ]; - let hash = hash_elements(&hash_data.concat()); + let peaks_hash = hash_elements(&peaks.concat()); // Set up the VM stack with the MMR hash, and its target address - let mut stack = felt_slice_to_ints(&*hash); + let mut stack = felt_slice_to_ints(&*peaks_hash); let mmr_ptr = 1000_u32; stack.insert(0, mmr_ptr as u64); @@ -268,13 +268,13 @@ fn test_mmr_unpack() { let advice_stack = &[]; let store = MerkleStore::new(); - let mut map_data: Vec = Vec::with_capacity(hash_data.len() + 1); - map_data.extend_from_slice(&[number_of_leaves.try_into().unwrap(), ZERO, ZERO, ZERO]); - map_data.extend_from_slice(&hash_data.as_slice().concat()); + let mut mmr_mem_repr: Vec = Vec::with_capacity(peaks.len() + 1); + mmr_mem_repr.extend_from_slice(&[number_of_leaves.try_into().unwrap(), ZERO, ZERO, ZERO]); + mmr_mem_repr.extend_from_slice(&peaks.as_slice().concat()); let advice_map: &[(RpoDigest, Vec)] = &[ // Under the MMR key is the number_of_leaves, followed by the MMR peaks, and any padding - (hash, map_data), + (peaks_hash, mmr_mem_repr), ]; let source = " @@ -356,8 +356,7 @@ fn test_mmr_unpack_invalid_hash() { fn test_mmr_unpack_large_mmr() { let number_of_leaves: u64 = 0b11111111111111111; // 17 peaks - // The hash data is not the same as the peaks, it is padded to 16 elements - let hash_data: [[Felt; 4]; 18] = [ + let peaks: [[Felt; 4]; 18] = [ // These hashes are invalid, we can't produce data for any of these peaks (only for // testing) [ZERO, ZERO, ZERO, ONE], @@ -380,10 +379,10 @@ fn test_mmr_unpack_large_mmr() { [ZERO, ZERO, ZERO, Felt::new(17)], EMPTY_WORD, ]; - let hash = hash_elements(&hash_data.concat()); + let peaks_hash = hash_elements(&peaks.concat()); // Set up the VM stack with the MMR hash, and its target address - let mut stack = felt_slice_to_ints(&*hash); + let mut stack = felt_slice_to_ints(&*peaks_hash); let mmr_ptr = 1000_u32; stack.insert(0, mmr_ptr as u64); @@ -392,13 +391,13 @@ fn test_mmr_unpack_large_mmr() { let advice_stack = &[]; let store = MerkleStore::new(); - let mut map_data: Vec = Vec::with_capacity(hash_data.len() + 1); - map_data.extend_from_slice(&[number_of_leaves.try_into().unwrap(), ZERO, ZERO, ZERO]); - map_data.extend_from_slice(&hash_data.as_slice().concat()); + let mut mmr_mem_repr: Vec = Vec::with_capacity(peaks.len() + 1); + mmr_mem_repr.extend_from_slice(&[number_of_leaves.try_into().unwrap(), ZERO, ZERO, ZERO]); + mmr_mem_repr.extend_from_slice(&peaks.as_slice().concat()); let advice_map: &[(RpoDigest, Vec)] = &[ // Under the MMR key is the number_of_leaves, followed by the MMR peaks, and any padding - (hash, map_data), + (peaks_hash, mmr_mem_repr), ]; let source = " @@ -497,8 +496,8 @@ fn test_mmr_pack() { begin push.3.1000 mem_store # num_leaves, 2 peaks - push.1.1001 mem_store # peak1 - push.2.1002 mem_store # peak2 + push.1.1004 mem_store # peak1 + push.2.1008 mem_store # peak2 push.1000 exec.mmr::pack @@ -587,7 +586,7 @@ fn test_mmr_two() { } #[test] -fn test_mmr_large() { +fn test_add_mmr_large() { let mmr_ptr = 1000; let source = format!( " diff --git a/stdlib/tests/crypto/falcon.rs b/stdlib/tests/crypto/falcon.rs index 5c41c90052..233ad9a7e6 100644 --- a/stdlib/tests/crypto/falcon.rs +++ b/stdlib/tests/crypto/falcon.rs @@ -24,35 +24,38 @@ const Q: u64 = (M - 1) / 2; const N: usize = 512; const J: u64 = (N * M as usize * M as usize) as u64; -const PROBABILISTIC_PRODUCT_SOURCE: &str = " +#[test] +fn test_set_to_zero() { + let source = " use.std::crypto::dsa::rpo_falcon512 begin - #=> [PK, ...] - mem_load.0 - #=> [h_ptr, PK, ...] + # write bytes in the first and last addresses of the region to be zeroed + push.1.2.3.4 mem_storew.1000 dropw + push.1.2.3.4 mem_storew.3044 dropw - exec.rpo_falcon512::load_h_s2_and_product - #=> [tau1, tau0, tau_ptr, ...] - - exec.rpo_falcon512::powers_of_tau - #=> [zeros_ptr, ...] + # This address should be untouched + push.1.2.3.4 mem_storew.3048 dropw + push.1000 exec.rpo_falcon512::set_to_zero - #=> [c_ptr, ...] - drop - #=> [...] + # Assert that output pointer is 1000 + 4 * 512 = 3048 + push.3048 assert_eq + end + "; - push.512 # tau_ptr - push.1025 # z_ptr - push.0 # h ptr + let expected_memory = { + let mut memory = vec![0_u64; N * 4]; + // addresses [3048, 3052) (not zeroed) + memory.extend_from_slice(&[1, 2, 3, 4]); - #=> [h_ptr, zeros_ptr, tau_ptr, ...] + memory + }; - exec.rpo_falcon512::probabilistic_product - end - "; + let test = build_test!(source, &[]); + test.expect_stack_and_memory(&[], 1000_u32, &expected_memory); +} #[test] fn test_falcon512_norm_sq() { @@ -119,10 +122,40 @@ fn test_falcon512_powers_of_tau() { let stack_init = [tau_ptr.into(), tau_0, tau_1]; let test = build_test!(source, &stack_init); - let expected_stack = &[>::into(tau_ptr) + N as u64 + 1]; + let expected_stack = &[u64::from(tau_ptr) + (N as u64 + 1_u64) * 4]; test.expect_stack_and_memory(expected_stack, tau_ptr, &expected_memory); } +const PROBABILISTIC_PRODUCT_SOURCE: &str = " + use.std::crypto::dsa::rpo_falcon512 + + begin + #=> [PK, ...] + mem_load.0 + #=> [h_ptr, PK, ...] + + exec.rpo_falcon512::load_h_s2_and_product + #=> [tau1, tau0, tau_ptr, ...] + + exec.rpo_falcon512::powers_of_tau + #=> [zeros_ptr, ...] + + exec.rpo_falcon512::set_to_zero + #=> [c_ptr, ...] + + drop + #=> [...] + + push.2048 # tau_ptr + push.4100 # zeroes_ptr (tau_ptr + 4 * 513) + push.0 # h ptr + + #=> [h_ptr, zeros_ptr, tau_ptr, ...] + + exec.rpo_falcon512::probabilistic_product + end + "; + #[test] fn test_falcon512_probabilistic_product() { // Create two random polynomials and multiply them. @@ -174,7 +207,7 @@ fn test_falcon512_probabilistic_product_failure() { expect_exec_error_matches!( test, ExecutionError::FailedAssertion{ clk, err_code, err_msg } - if clk == RowIndex::from(17490) && err_code == 0 && err_msg.is_none() + if clk == RowIndex::from(18843) && err_code == 0 && err_msg.is_none() ); } diff --git a/stdlib/tests/crypto/fri/remainder.rs b/stdlib/tests/crypto/fri/remainder.rs index 33746d2af6..24f732e5c4 100644 --- a/stdlib/tests/crypto/fri/remainder.rs +++ b/stdlib/tests/crypto/fri/remainder.rs @@ -17,7 +17,7 @@ fn test_decorator_ext2intt(in_poly_len: usize, blowup: usize) { assert!((blowup > 0) && blowup.is_power_of_two()); let eval_len = in_poly_len * blowup; - let eval_mem_req = (eval_len * 2) / 4; + let eval_mem_req = eval_len * 2; let out_mem_req = (in_poly_len * 2) / 4; let poly = rand_vector::(in_poly_len); @@ -43,7 +43,7 @@ fn test_decorator_ext2intt(in_poly_len: usize, blowup: usize) { dup.4 mem_storew dropw - sub.1 + sub.4 end drop @@ -70,7 +70,7 @@ fn test_decorator_ext2intt(in_poly_len: usize, blowup: usize) { end ", eval_mem_req, - eval_mem_req - 1, + eval_mem_req - 4, eval_mem_req, eval_len, in_poly_len, @@ -98,14 +98,14 @@ fn test_verify_remainder_64() { " use.std::crypto::fri::ext2fri - proc.helper.36 - locaddr.35 + proc.helper.144 + locaddr.140 repeat.36 movdn.4 dup.4 mem_storew dropw - sub.1 + sub.4 end drop @@ -141,14 +141,14 @@ fn test_verify_remainder_32() { " use.std::crypto::fri::ext2fri - proc.helper.18 - locaddr.17 + proc.helper.72 + locaddr.68 repeat.18 movdn.4 dup.4 mem_storew dropw - sub.1 + sub.4 end drop diff --git a/stdlib/tests/crypto/keccak256.rs b/stdlib/tests/crypto/keccak256.rs index 8991c87def..3c179bfb73 100644 --- a/stdlib/tests/crypto/keccak256.rs +++ b/stdlib/tests/crypto/keccak256.rs @@ -80,13 +80,13 @@ fn keccak256_2_to_1_hash() { fn to_stack(i_digest: &[u8], stack: &mut [u64]) { for i in 0..(i_digest.len() >> 3) { // byte array ( = 8 -bytes ) to little endian 64 -bit unsigned integer - let word = (i_digest[(i << 3) + 7] as u64) << 56 - | (i_digest[(i << 3) + 6] as u64) << 48 - | (i_digest[(i << 3) + 5] as u64) << 40 - | (i_digest[(i << 3) + 4] as u64) << 32 - | (i_digest[(i << 3) + 3] as u64) << 24 - | (i_digest[(i << 3) + 2] as u64) << 16 - | (i_digest[(i << 3) + 1] as u64) << 8 + let word = ((i_digest[(i << 3) + 7] as u64) << 56) + | ((i_digest[(i << 3) + 6] as u64) << 48) + | ((i_digest[(i << 3) + 5] as u64) << 40) + | ((i_digest[(i << 3) + 4] as u64) << 32) + | ((i_digest[(i << 3) + 3] as u64) << 24) + | ((i_digest[(i << 3) + 2] as u64) << 16) + | ((i_digest[(i << 3) + 1] as u64) << 8) | (i_digest[i << 3] as u64); // split into higher/ lower bits of u64 diff --git a/stdlib/tests/crypto/rpo.rs b/stdlib/tests/crypto/rpo.rs index f3879ce22a..391d5e1118 100644 --- a/stdlib/tests/crypto/rpo.rs +++ b/stdlib/tests/crypto/rpo.rs @@ -55,7 +55,7 @@ fn test_hash_empty() { use.std::crypto::hashes::rpo begin - push.1002 # end address + push.1008 # end address push.1000 # start address exec.rpo::hash_memory_words @@ -107,7 +107,7 @@ fn test_single_iteration() { # insert 1 to memory push.1.1000 mem_store - push.1002 # end address + push.1008 # end address push.1000 # start address exec.rpo::hash_memory_words @@ -138,7 +138,7 @@ fn test_hash_one_word() { begin push.1.1000 mem_store # push data to memory - push.1001 # end address + push.1004 # end address push.1000 # start address exec.rpo::hash_memory_words @@ -159,9 +159,9 @@ fn test_hash_even_words() { begin push.1.0.0.0.1000 mem_storew dropw - push.0.1.0.0.1001 mem_storew dropw + push.0.1.0.0.1004 mem_storew dropw - push.1002 # end address + push.1008 # end address push.1000 # start address exec.rpo::hash_memory_words @@ -187,10 +187,10 @@ fn test_hash_odd_words() { begin push.1.0.0.0.1000 mem_storew dropw - push.0.1.0.0.1001 mem_storew dropw - push.0.0.1.0.1002 mem_storew dropw + push.0.1.0.0.1004 mem_storew dropw + push.0.0.1.0.1008 mem_storew dropw - push.1003 # end address + push.1012 # end address push.1000 # start address exec.rpo::hash_memory_words @@ -217,9 +217,9 @@ fn test_absorb_double_words_from_memory() { begin push.1.0.0.0.1000 mem_storew dropw - push.0.1.0.0.1001 mem_storew dropw + push.0.1.0.0.1004 mem_storew dropw - push.1002 # end address + push.1008 # end address push.1000 # start address padw padw padw # hasher state exec.rpo::absorb_double_words_from_memory @@ -237,8 +237,8 @@ fn test_absorb_double_words_from_memory() { ]).into_iter().map(|e| e.as_int()).collect(); // start and end addr - even_hash.push(1002); - even_hash.push(1002); + even_hash.push(1008); + even_hash.push(1008); build_test!(even_words, &[]).expect_stack(&even_hash); } @@ -250,11 +250,11 @@ fn test_squeeze_digest() { begin push.1.0.0.0.1000 mem_storew dropw - push.0.1.0.0.1001 mem_storew dropw - push.0.0.1.0.1002 mem_storew dropw - push.0.0.0.1.1003 mem_storew dropw + push.0.1.0.0.1004 mem_storew dropw + push.0.0.1.0.1008 mem_storew dropw + push.0.0.0.1.1012 mem_storew dropw - push.1004 # end address + push.1016 # end address push.1000 # start address padw padw padw # hasher state exec.rpo::absorb_double_words_from_memory @@ -275,8 +275,8 @@ fn test_squeeze_digest() { ]).into_iter().map(|e| e.as_int()).collect(); // start and end addr - even_hash.push(1004); - even_hash.push(1004); + even_hash.push(1016); + even_hash.push(1016); build_test!(even_words, &[]).expect_stack(&even_hash); } @@ -289,7 +289,7 @@ fn test_hash_memory() { begin push.1.2.3.4.1000 mem_storew dropw - push.5.0.0.0.1001 mem_storew dropw + push.5.0.0.0.1004 mem_storew dropw push.11 push.5.1000 @@ -315,7 +315,7 @@ fn test_hash_memory() { begin push.1.2.3.4.1000 mem_storew dropw - push.5.6.7.8.1001 mem_storew dropw + push.5.6.7.8.1004 mem_storew dropw push.11 push.8.1000 @@ -341,9 +341,9 @@ fn test_hash_memory() { 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.5.6.7.8.1004 mem_storew dropw + push.9.10.11.12.1008 mem_storew dropw + push.13.14.15.0.1012 mem_storew dropw push.11 push.15.1000 diff --git a/stdlib/tests/crypto/sha256.rs b/stdlib/tests/crypto/sha256.rs index e6a371b9c3..e7df08311c 100644 --- a/stdlib/tests/crypto/sha256.rs +++ b/stdlib/tests/crypto/sha256.rs @@ -38,15 +38,15 @@ fn sha256_hash_memory() { # mem.2 - length in felts mem_load.1 u32assert u32overflowing_add.3 assertz u32assert u32div.4 mem_store.2 - # Load input data into memory address 10000, 10001, ... + # Load input data into memory address 10000, 10004, ... mem_load.2 u32assert neq.0 while.true mem_load.0 mem_storew dropw - mem_load.0 u32assert u32overflowing_add.1 assertz mem_store.0 + mem_load.0 u32assert u32overflowing_add.4 assertz mem_store.0 mem_load.2 u32assert u32overflowing_sub.1 assertz dup mem_store.2 u32assert neq.0 end - # Compute hash of memory address 10000, 10001, ... + # Compute hash of memory address 10000, 10004, ... mem_load.1 push.10000 exec.sha256::hash_memory diff --git a/stdlib/tests/math/secp256k1/group.rs b/stdlib/tests/math/secp256k1/group.rs index 9837028184..28832fc8c4 100644 --- a/stdlib/tests/math/secp256k1/group.rs +++ b/stdlib/tests/math/secp256k1/group.rs @@ -15,44 +15,44 @@ fn test_secp256k1_point_doubling(src: Point, dst: Point) { # Given a point of secp256k1 elliptic curve, this routine first computes # point doubling of that point in projective coordinate & then asserts # each coordinate limb-by-limb for ensuring correctness. - proc.point_doubling_test_wrapper.12 + proc.point_doubling_test_wrapper.48 # push X -coordinate to memory push.{}.{}.{}.{} loc_storew.0 dropw push.{}.{}.{}.{} - loc_storew.1 + loc_storew.4 dropw # push Y -coordinate to memory push.{}.{}.{}.{} - loc_storew.2 + loc_storew.8 dropw push.{}.{}.{}.{} - loc_storew.3 + loc_storew.12 dropw # push Z -coordinate to memory push.{}.{}.{}.{} - loc_storew.4 + loc_storew.16 dropw push.{}.{}.{}.{} - loc_storew.5 + loc_storew.20 dropw # input/ output memory addresses for point doubling purpose - locaddr.11 - locaddr.10 - locaddr.9 + locaddr.44 + locaddr.40 + locaddr.36 + locaddr.32 + locaddr.28 + locaddr.24 + + locaddr.20 + locaddr.16 + locaddr.12 locaddr.8 - locaddr.7 - locaddr.6 - - locaddr.5 locaddr.4 - locaddr.3 - locaddr.2 - locaddr.1 locaddr.0 # elliptic curve point doubling @@ -211,75 +211,75 @@ fn test_secp256k1_point_addition(src0: Point, src1: Point, dst: Point) { # Given two points of secp256k1 elliptic curve ( twice ), this routine first computes # point addition of them in projective coordinate & then asserts each coordinate # limb-by-limb for ensuring correctness. - proc.point_addition_test_wrapper.18 + proc.point_addition_test_wrapper.72 # push X1 -coordinate to memory push.{}.{}.{}.{} loc_storew.0 dropw push.{}.{}.{}.{} - loc_storew.1 + loc_storew.4 dropw # push Y1 -coordinate to memory push.{}.{}.{}.{} - loc_storew.2 + loc_storew.8 dropw push.{}.{}.{}.{} - loc_storew.3 + loc_storew.12 dropw # push Z1 -coordinate to memory push.{}.{}.{}.{} - loc_storew.4 + loc_storew.16 dropw push.{}.{}.{}.{} - loc_storew.5 + loc_storew.20 dropw # push X2 -coordinate to memory push.{}.{}.{}.{} - loc_storew.6 + loc_storew.24 dropw push.{}.{}.{}.{} - loc_storew.7 + loc_storew.28 dropw # push Y2 -coordinate to memory push.{}.{}.{}.{} - loc_storew.8 + loc_storew.32 dropw push.{}.{}.{}.{} - loc_storew.9 + loc_storew.36 dropw # push Z2 -coordinate to memory push.{}.{}.{}.{} - loc_storew.10 + loc_storew.40 dropw push.{}.{}.{}.{} - loc_storew.11 + loc_storew.44 dropw # input/ output memory addresses for point doubling purpose - locaddr.17 + locaddr.68 + locaddr.64 + locaddr.60 + locaddr.56 + locaddr.52 + locaddr.48 + + locaddr.44 + locaddr.40 + locaddr.36 + locaddr.32 + locaddr.28 + locaddr.24 + + locaddr.20 locaddr.16 - locaddr.15 - locaddr.14 - locaddr.13 locaddr.12 - - locaddr.11 - locaddr.10 - locaddr.9 locaddr.8 - locaddr.7 - locaddr.6 - - locaddr.5 locaddr.4 - locaddr.3 - locaddr.2 - locaddr.1 locaddr.0 # elliptic curve point addition @@ -465,12 +465,12 @@ fn test_secp256k1_point_multiplication(src_point: Point, scalar: FieldElement, d # the EC point with provided scalar and then asserts for correctness with known answer. proc.point_multiplication_test_wrapper.12 # resulting point - locaddr.11 - locaddr.10 - locaddr.9 - locaddr.8 - locaddr.7 - locaddr.6 + locaddr.44 + locaddr.40 + locaddr.36 + locaddr.32 + locaddr.28 + locaddr.24 # scalar push.{}.{}.{}.{} @@ -482,30 +482,30 @@ fn test_secp256k1_point_multiplication(src_point: Point, scalar: FieldElement, d dropw push.{}.{}.{}.{} - loc_storew.1 + loc_storew.4 dropw push.{}.{}.{}.{} - loc_storew.2 + loc_storew.8 dropw push.{}.{}.{}.{} - loc_storew.3 + loc_storew.12 dropw push.{}.{}.{}.{} - loc_storew.4 + loc_storew.16 dropw push.{}.{}.{}.{} - loc_storew.5 + loc_storew.20 dropw - locaddr.5 + locaddr.20 + locaddr.16 + locaddr.12 + locaddr.8 locaddr.4 - locaddr.3 - locaddr.2 - locaddr.1 locaddr.0 # elliptic curve point multiplication @@ -671,14 +671,14 @@ fn test_secp256k1_generator_multiplication(scalar: FieldElement, point: Point) { # Given a 256 -bit scalar in radix-2^32 form ( i.e. 8 limbs, each of 32 -bit width ), # this routine first multiplies the secp256k1 generator point with provided scalar and # then asserts for correctness with known answer. - proc.generator_multiplication_test_wrapper.12 + proc.generator_multiplication_test_wrapper.48 # resulting point - locaddr.11 - locaddr.10 - locaddr.9 - locaddr.8 - locaddr.7 - locaddr.6 + locaddr.44 + locaddr.40 + locaddr.36 + locaddr.32 + locaddr.28 + locaddr.24 # scalar push.{}.{}.{}.{} diff --git a/stdlib/tests/mem/mod.rs b/stdlib/tests/mem/mod.rs index 8cfe9e4226..9d3fbbb906 100644 --- a/stdlib/tests/mem/mod.rs +++ b/stdlib/tests/mem/mod.rs @@ -5,7 +5,7 @@ use test_utils::{ }; #[test] -fn test_memcopy() { +fn test_memcopy_words() { use miden_stdlib::StdLibrary; let source = " @@ -13,12 +13,12 @@ fn test_memcopy() { begin push.0.0.0.1.1000 mem_storew dropw - push.0.0.1.0.1001 mem_storew dropw - push.0.0.1.1.1002 mem_storew dropw - push.0.1.0.0.1003 mem_storew dropw - push.0.1.0.1.1004 mem_storew dropw + push.0.0.1.0.1004 mem_storew dropw + push.0.0.1.1.1008 mem_storew dropw + push.0.1.0.0.1012 mem_storew dropw + push.0.1.0.1.1016 mem_storew dropw - push.2000.1000.5 exec.mem::memcopy + push.2000.1000.5 exec.mem::memcopy_words end "; @@ -38,86 +38,86 @@ fn test_memcopy() { process.execute(&program, &mut host).unwrap(); assert_eq!( - process.chiplets.get_mem_value(ContextId::root(), 1000), + process.chiplets.memory().get_word(ContextId::root(), 1000).unwrap(), Some([ZERO, ZERO, ZERO, ONE]), "Address 1000" ); assert_eq!( - process.chiplets.get_mem_value(ContextId::root(), 1001), + process.chiplets.memory().get_word(ContextId::root(), 1004).unwrap(), Some([ZERO, ZERO, ONE, ZERO]), - "Address 1001" + "Address 1004" ); assert_eq!( - process.chiplets.get_mem_value(ContextId::root(), 1002), + process.chiplets.memory().get_word(ContextId::root(), 1008).unwrap(), Some([ZERO, ZERO, ONE, ONE]), - "Address 1002" + "Address 1008" ); assert_eq!( - process.chiplets.get_mem_value(ContextId::root(), 1003), + process.chiplets.memory().get_word(ContextId::root(), 1012).unwrap(), Some([ZERO, ONE, ZERO, ZERO]), - "Address 1003" + "Address 1012" ); assert_eq!( - process.chiplets.get_mem_value(ContextId::root(), 1004), + process.chiplets.memory().get_word(ContextId::root(), 1016).unwrap(), Some([ZERO, ONE, ZERO, ONE]), - "Address 1004" + "Address 1016" ); assert_eq!( - process.chiplets.get_mem_value(ContextId::root(), 2000), + process.chiplets.memory().get_word(ContextId::root(), 2000).unwrap(), Some([ZERO, ZERO, ZERO, ONE]), "Address 2000" ); assert_eq!( - process.chiplets.get_mem_value(ContextId::root(), 2001), + process.chiplets.memory().get_word(ContextId::root(), 2004).unwrap(), Some([ZERO, ZERO, ONE, ZERO]), - "Address 2001" + "Address 2004" ); assert_eq!( - process.chiplets.get_mem_value(ContextId::root(), 2002), + process.chiplets.memory().get_word(ContextId::root(), 2008).unwrap(), Some([ZERO, ZERO, ONE, ONE]), - "Address 2002" + "Address 2008" ); assert_eq!( - process.chiplets.get_mem_value(ContextId::root(), 2003), + process.chiplets.memory().get_word(ContextId::root(), 2012).unwrap(), Some([ZERO, ONE, ZERO, ZERO]), - "Address 2003" + "Address 2012" ); assert_eq!( - process.chiplets.get_mem_value(ContextId::root(), 2004), + process.chiplets.memory().get_word(ContextId::root(), 2016).unwrap(), Some([ZERO, ONE, ZERO, ONE]), - "Address 2004" + "Address 2016" ); } #[test] fn test_pipe_double_words_to_memory() { - let mem_addr = 1000; + let start_addr = 1000; + let end_addr = 1008; let source = format!( " use.std::mem use.std::sys begin - push.1002 # end_addr - push.{} # write_addr + push.{end_addr} + push.{start_addr} padw padw padw # hasher state exec.mem::pipe_double_words_to_memory exec.sys::truncate_stack - end", - mem_addr + end" ); let operand_stack = &[]; let data = &[1, 2, 3, 4, 5, 6, 7, 8]; let mut expected_stack = felt_slice_to_ints(&build_expected_perm(&[0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8])); - expected_stack.push(1002); + expected_stack.push(end_addr); build_test!(source, operand_stack, &data).expect_stack_and_memory( &expected_stack, - mem_addr, + start_addr, data, ); } @@ -146,7 +146,7 @@ fn test_pipe_words_to_memory() { let operand_stack = &[]; let data = &[1, 2, 3, 4]; let mut expected_stack = felt_slice_to_ints(&build_expected_hash(data)); - expected_stack.push(1001); + expected_stack.push(1004); build_test!(one_word, operand_stack, &data).expect_stack_and_memory( &expected_stack, mem_addr, @@ -174,7 +174,7 @@ fn test_pipe_words_to_memory() { let operand_stack = &[]; let data = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; let mut expected_stack = felt_slice_to_ints(&build_expected_hash(data)); - expected_stack.push(1003); + expected_stack.push(1012); build_test!(three_words, operand_stack, &data).expect_stack_and_memory( &expected_stack, mem_addr, @@ -205,7 +205,7 @@ fn test_pipe_preimage_to_memory() { advice_stack.reverse(); advice_stack.extend(data); build_test!(three_words, operand_stack, &advice_stack).expect_stack_and_memory( - &[1003], + &[1012], mem_addr, data, ); diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index 2d048db53f..d232e7b0cb 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -23,6 +23,7 @@ pub use processor::{ }; #[cfg(not(target_family = "wasm"))] use proptest::prelude::{Arbitrary, Strategy}; +use prover::utils::range; pub use prover::{prove, MemAdviceProvider, MerkleTreeVC, ProvingOptions}; pub use test_case::test_case; pub use verifier::{verify, AcceptableOptions, VerifierError}; @@ -70,7 +71,7 @@ pub const U32_BOUND: u64 = u32::MAX as u64 + 1; /// A source code of the `truncate_stack` procedure. pub const TRUNCATE_STACK_PROC: &str = " -proc.truncate_stack.1 +proc.truncate_stack.4 loc_storew.0 dropw movupw.3 sdepth neq.16 while.true @@ -221,7 +222,7 @@ impl Test { pub fn expect_stack_and_memory( &self, final_stack: &[u64], - mut mem_start_addr: u32, + mem_start_addr: u32, expected_mem: &[u64], ) { // compile the program @@ -243,21 +244,22 @@ impl Test { process.execute(&program, &mut host).unwrap(); // validate the memory state - for data in expected_mem.chunks(WORD_SIZE) { - // Main memory is zeroed by default, use zeros as a fallback when unwrap to make testing - // easier + for (addr, mem_value) in + (range(mem_start_addr as usize, expected_mem.len())).zip(expected_mem.iter()) + { let mem_state = process .chiplets - .get_mem_value(ContextId::root(), mem_start_addr) - .unwrap_or(EMPTY_WORD); - - let mem_state = felt_slice_to_ints(&mem_state); + .memory() + .get_value(ContextId::root(), addr as u32) + .unwrap_or(ZERO); assert_eq!( - data, mem_state, + *mem_value, + mem_state.as_int(), "Expected memory [{}] => {:?}, found {:?}", - mem_start_addr, data, mem_state + addr, + mem_value, + mem_state ); - mem_start_addr += 1; } // validate the stack states diff --git a/test-utils/src/test_builders.rs b/test-utils/src/test_builders.rs index 2f8ea1f79d..661fdade14 100644 --- a/test-utils/src/test_builders.rs +++ b/test-utils/src/test_builders.rs @@ -18,7 +18,7 @@ macro_rules! build_op_test { ($op_str:expr) => {{ let source = format!(" -proc.truncate_stack.1 +proc.truncate_stack.4 loc_storew.0 dropw movupw.3 sdepth neq.16 while.true @@ -35,7 +35,7 @@ begin {} exec.truncate_stack end", }}; ($op_str:expr, $($tail:tt)+) => {{ let source = format!(" -proc.truncate_stack.1 +proc.truncate_stack.4 loc_storew.0 dropw movupw.3 sdepth neq.16 while.true From 29ef9259dcd80e0cff9d83b1820bdfad70914e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Laferri=C3=A8re?= Date: Tue, 14 Jan 2025 09:37:14 -0500 Subject: [PATCH 37/39] docs: update docs for element-addressable memory (#1616) * docs: update docs for element-addressable memory * PR fixes * docs(mem): update all constraints with memory flags * `c` -> `ctx` * docs: PR fixes * fix: merge 8 -> 4 constraints together * more PR fixes * PR fixes * PR fixes --- air/src/constraints/chiplets/memory/mod.rs | 44 +++--- core/src/mast/serialization/tests.rs | 3 + docs/src/assets/design/chiplets/chiplets.png | Bin 124152 -> 239279 bytes docs/src/assets/design/chiplets/chiplets.zip | Bin 1297525 -> 0 bytes .../memory/memory_miden_vm_layout.png | Bin 26095 -> 48561 bytes .../design/stack/crypto_ops/RCOMBBASE.png | Bin 84858 -> 178860 bytes docs/src/assets/design/stack/io_ops/MLOAD.png | Bin 64554 -> 66152 bytes .../src/assets/design/stack/io_ops/MSTORE.png | Bin 64718 -> 67892 bytes .../assets/design/stack/io_ops/MSTREAM.png | Bin 988232 -> 155425 bytes docs/src/assets/design/vm_trace.png | Bin 1747532 -> 679034 bytes docs/src/assets/design/vm_trace.zip | Bin 143521 -> 0 bytes docs/src/design/chiplets/main.md | 34 +++-- docs/src/design/chiplets/memory.md | 144 ++++++++++++------ docs/src/design/stack/crypto_ops.md | 6 +- docs/src/design/stack/io_ops.md | 61 +++----- .../user_docs/assembly/code_organization.md | 4 +- .../user_docs/assembly/execution_contexts.md | 10 +- docs/src/user_docs/assembly/io_operations.md | 22 +-- processor/src/chiplets/memory/mod.rs | 2 + stdlib/asm/crypto/stark/constants.masm | 2 +- stdlib/docs/mem.md | 2 +- 21 files changed, 195 insertions(+), 139 deletions(-) delete mode 100644 docs/src/assets/design/chiplets/chiplets.zip delete mode 100644 docs/src/assets/design/vm_trace.zip diff --git a/air/src/constraints/chiplets/memory/mod.rs b/air/src/constraints/chiplets/memory/mod.rs index 729dd3138c..d4c93c1bc7 100644 --- a/air/src/constraints/chiplets/memory/mod.rs +++ b/air/src/constraints/chiplets/memory/mod.rs @@ -20,7 +20,7 @@ mod tests; // ================================================================================================ /// The number of constraints on the management of the memory chiplet. -pub const NUM_CONSTRAINTS: usize = 22; +pub const NUM_CONSTRAINTS: usize = 18; /// The degrees of constraints on the management of the memory chiplet. All constraint degrees are /// increased by 3 due to the selectors for the memory chiplet. pub const CONSTRAINT_DEGREES: [usize; NUM_CONSTRAINTS] = [ @@ -29,8 +29,7 @@ pub const CONSTRAINT_DEGREES: [usize; NUM_CONSTRAINTS] = [ 8, // Enforce values in ctx, word, clk transition correctly. 7, // Enforce the correct value for the f_scw flag. 9, 9, 9, 9, // Constrain the values in the first row of the chiplet. - 9, 9, 9, 9, // Constrain the values in non-first rows, new word or context is started. - 9, 9, 9, 9, // Constrain the values in non-first rows, same word or context. + 9, 9, 9, 9, // Constrain the values in all rows of the chiplet except the first. ]; // MEMORY TRANSITION CONSTRAINTS @@ -152,8 +151,7 @@ fn enforce_flag_same_context_and_word( result: &mut [E], memory_flag_not_last_row: E, ) -> usize { - result[0] = memory_flag_not_last_row - * (frame.f_scw_next() - binary_not(frame.n0() + frame.not_n0() * frame.n1())); + result[0] = memory_flag_not_last_row * (frame.f_scw_next() - frame.not_n0() * frame.not_n1()); 1 } @@ -211,21 +209,27 @@ fn enforce_values( result[2] = memory_flag_first_row * c2 * frame.v_next(2); result[3] = memory_flag_first_row * c3 * frame.v_next(3); - // non-first row, new word address or context constraints: when row' is a new word address/ctx, - // and v'[i] is not written to, then v'[i] must be 0. - result[4] = memory_flag_no_last * binary_not(frame.f_scw_next()) * c0 * frame.v_next(0); - result[5] = memory_flag_no_last * binary_not(frame.f_scw_next()) * c1 * frame.v_next(1); - result[6] = memory_flag_no_last * binary_not(frame.f_scw_next()) * c2 * frame.v_next(2); - result[7] = memory_flag_no_last * binary_not(frame.f_scw_next()) * c3 * frame.v_next(3); - - // non-first row, same word address and context constraints: when row' is in the same word - // address/ctx, and v'[i] is not written to, then v'[i] must be equal to v[i]. - result[8] = memory_flag_no_last * frame.f_scw_next() * c0 * (frame.v_next(0) - frame.v(0)); - result[9] = memory_flag_no_last * frame.f_scw_next() * c1 * (frame.v_next(1) - frame.v(1)); - result[10] = memory_flag_no_last * frame.f_scw_next() * c2 * (frame.v_next(2) - frame.v(2)); - result[11] = memory_flag_no_last * frame.f_scw_next() * c3 * (frame.v_next(3) - frame.v(3)); - - 12 + // non-first row constraints: if v[i] is not written to, + // - (f_scw' = 1) then its value needs to be copied over from the previous row, + // - (f_scw' = 0) then its value needs to be set to 0. + result[4] = memory_flag_no_last + * c0 + * (frame.f_scw_next() * (frame.v_next(0) - frame.v(0)) + + binary_not(frame.f_scw_next()) * frame.v_next(0)); + result[5] = memory_flag_no_last + * c1 + * (frame.f_scw_next() * (frame.v_next(1) - frame.v(1)) + + binary_not(frame.f_scw_next()) * frame.v_next(1)); + result[6] = memory_flag_no_last + * c2 + * (frame.f_scw_next() * (frame.v_next(2) - frame.v(2)) + + binary_not(frame.f_scw_next()) * frame.v_next(2)); + result[7] = memory_flag_no_last + * c3 + * (frame.f_scw_next() * (frame.v_next(3) - frame.v(3)) + + binary_not(frame.f_scw_next()) * frame.v_next(3)); + + 8 } // MEMORY FRAME EXTENSION TRAIT diff --git a/core/src/mast/serialization/tests.rs b/core/src/mast/serialization/tests.rs index 4962f99599..ec5441af05 100644 --- a/core/src/mast/serialization/tests.rs +++ b/core/src/mast/serialization/tests.rs @@ -356,6 +356,9 @@ fn mast_forest_invalid_node_id() { // Hydrate a forest larger than the first to get an overflow MastNodeId let mut overflow_forest = MastForest::new(); + // Note: clippy wants us to use `next_back()` instead of `last()`, but `next_back()` makes the + // test fail. + #[allow(clippy::double_ended_iterator_last)] let overflow = (0..=3) .map(|_| overflow_forest.add_block(vec![Operation::U32div], None).unwrap()) .last() diff --git a/docs/src/assets/design/chiplets/chiplets.png b/docs/src/assets/design/chiplets/chiplets.png index e61ac4468beb1c9793b7c26d9d8c8e92b7190f6d..d78a8ea407a101ff5116f6d3b4e90dd0f889d1c4 100644 GIT binary patch literal 239279 zcmbTecRbbo8$a&YiX1H=9LFj`M)o*1l~G8Mk-ZLOZ%5XV>`}-}X2{;7W$(QXmAxY) zMBmrZ{kcC*-_Q5=$M3!$55#$o*Y&!t>-oIaD_C7sk>V8XDLgzp3S}j^yLfnna(H+o zN~FZ#H=>0MpTReX!(Bxgyn?RFi{KyXruUW2R8;Udz|W+3_>i-B1lW&&|M7w>@d%H9 z#=~O+-|_GWQX&8SYbySK{z@p9O7NebNtCc3gglsMz<#yVyzh8l<+iA?9g_QziJg%t zw=427_5*n0uA<;aq^aX0m@Cr8)`0pDf9gPt|!i`XWX%;b) z*b6M298akrPIqG7y<>q;L4%)Wqu@$VC>0gdHx?%S^d0IcA-%oAQ>Duvt`qQ2dDnK< zyG-U5tQBZD{=2?J_#z<`fd|3)Vu&U*qe-vt)57`f z@rP1b5eWa&5Zup$zXYnHP%S-4_M*@6@JV5)lV1qsDvAH^M=TJMqRBB=vp)XyX;K(? zfc0N5^#AVx3Y2=`i`VhjNq?>XKC)|<_Gu3+$ntR!%e1U{v*>)S=~v&RetW~=c*FM1 zvn#H8PJ>$EjsCgqaT<-z{$2B{FYw5V=*- zakhEpB@Mq>Gj&d*Z4%$GSB^bw+&BPn?FolXC3!+EssF zjNhu)AD?mCbzhntSafA*u(d1>GhOZ9F9Baq$4i<#C@Rw$W4?8daY`ae{2m)J$*pX4 ze|$%6_l-}G(zHh45q<0tm+7yD-@CtcI{S@_)0X#jclI)uHN)brHmthebC)P^a=G<- z){+4;BwmeKtyCGeth4dHI=qwBUp?@;xpcK$rMghpLm;%1+DNaxXR4f{TFqzNIBBS) zZ>e6qp`>rbYtd;6X}POooj+?^vntryb~e7Tq0ZsyfM*D?1<)jBvXRox5 z@oc+TXYsYIt4eK3z1b@2Ic(!t4-;E^oL5-yK9qKIH@|u**S0T1Id(|;E9s|eJ*&TI z$}w*JmJCx{35APO`@dV^s}>w~k>yb&|Iyxe_@}umQ$4;0P_J+gF;k(90@1&o@c670 zlyr&a$20O-%RD97WM1BUUBa@mLYAbGW@#fyA?av8)F_uV&AUd;PB!wK_D1fu$JRox zs<&B^(@-R%n8#e9&!Kf5-D#fM$IO@q?u&19A(6WczYo{Gh482n`ejJlhiz3PhrgW? zOv+UItvX-gRvgwUS*Ngh#2v+MH=5L^b;o?{ql@nNQX&uMVQTDPb!w+iMz>pB?c!#P zJnbKwlsPp5t1WQZ=h}7_9kTR6izl^}AFNgnjasKj*bbL$wm(T^t4p3-yQd(E94cQa zkt!Fjk+Rk^Z*$p)kIXH`l#hES8VvPsJC4U0?^N0@OJauXoy5GvyS)=LJF3Gv`%GJo zFwWzhhhHgu9^c}w%6OGtua~Ksw3MlmI7I8w=Bm+@>Q^i`82zJLYN~EtVx7z?@k~Ms z-fet#`&+@2X7J@rX7&d@0(h?*-dyhF^rIGwh$c4o`aUab=y#E}He%d|1QMPY*6u$y>&o##yG54Fn_d@i*OW{T}(Poe{1)y&_qI z9A;ICU*&V_cXHvlwfo`_xy-41FY=e5TI4p|t+acwc5zj8QFQgiucV8KSsJvXjG}sx ze6v%-wYMfq-TIa5-1_H~U#J>ah~;OE_gqcFqzI|CIO_NeHXsx8-nPf-nbavUtm=x| zxz}h^iyyqXc38CNlyX$wY?4F-A$&&GVoYz+da!O;q%+yc>f<2!``3^B_$z#LtaSy_ z6o>iK`ZX9!2@E&Qi!q*WrV@2r(>fTt@J_MyTGje@1{P@nI#SMhJA)F=p&YQNz%xbp zkYK3kwlz(?Z;blk07^+?J7|ey78ar1c*GPhM7j81-$|^2I1-F`a~m-eA$#V@rcH;> z;j%%#Ve>7Xx|tdC&6(DeB?BadAC$F~w^&(nk z^&`4&CJN6d)?Tmf+)&912V%CpJpKp(j{lR4^EgX^6(UAa=m}9c#MjRIN8%cqIjlu@ z6_F+O1w}A%N0EN;6mh_U1;5%jd9Qt23s)-RrwJN-R+B&_n9?)p6CmYVf3D}r{oYaX zgm6V@hxoRsih11D8Z9KUv<1(WU)BUqjYCi7#O{IU3UuHW8sGfFT84-)CA>h&VGAPdjMuUAmu zhU^>NapAy|7m*GyrTS7#RSOs_JfNyyMf28;#UOPxA(84y93gz9dp9mId08$IWbn%Y zH2!cwp7FoR21PU~rml3Hp=abz-n_GWi%uRPr&emvuZ&Sap_GK^!~P45SiIjRJx{-G zRi9b^r9iQ{Fs|p~9YVQZqzNiq>#|3^;odWBs;?HpMCM+a(e=M^rh_gCC|f>e#p$v=s9jNgu)kbb zbGS2Zm$A%Kw-pubBHw)dO)IgSjDY|%{aR5v68-+QqJ{`A9U;8Xg9s!R^gKQ6sS-Nx zIj(E&UUpU2T$5kUNU9eA^}Nz`xi5@fTX$T3ehsR4$`W#|7_IyGJrQQ1_cf-8&Ti$5 z@E5$@Mgqzh38{b|*4JfDS+vE{zys76X-=-fu!YF6r_eW+X+AsS;0}=-$Ks<@ncu(W zrE2%NdSq2CdBxjRFT6g>W;7S_7V~{7+;s^WUGt{o`tqDx$a_I zYUu20c@^7MSor*ANThyB@4SDuV|88T*>Ff^damfjb}oK;OW}Xn5mpHZxKpR!rTe*b zHj_s#ai7PBi`b}1?#*;)PB(>GOK5KY`qAw|Dfu(C=J(M7=7D+AtmJ-gc=;Qt?~MeQ zy4}g~@`hac{jZUm+kW@8^pe^JQ^^^>sLHG;p0ph43s=O>^z-VC+7q`5mC8@s#rH;E zV{a{Jw(pzi^@gYF;~B9IdiRC2VB>?8lJJ^eo9*Wg za>sfM{bLyS(_6>QkOUPSGZ&cYPl}oR87l1KvoMDnj0V>$45+O7y+7Ge^2@oq>bcFv z#$R=w^4cvoM6<=5-obj~n$_~eHLJt=Z_hX4|5k5AE~R3A6L=+r5b9fc4c>ZD;;mwev~L z{@!kIWN#$atssip1a>#mdQVj4Hr7eSNG6@fQ8_O*b-{=<2$gMoyxX@yO{5va)$)s4 z2o}w(XLnc}T0g4sRLw+v>dG%4<0wipXZEu01cWhxU3XDHRP42*H0!AzK`8X9uhPAf zxUg``x7g2xp3z#wD;VnbnhfnIEUm5Qr1h<(;rBs9O#rpNa1l>;p#G->XMnoZI*WF| zXU_x$p_JZaz5L6e`4$`V(%<5f&8UvPjmYp@r4X8k5H8*svt}>s-Fvm&CiRO2KLq> z^?M{DUU&LS$*_PYHvaVBiytLh*tNQ?zTj1Hy1QM<>`5_7)kpiw?B#i5q)xsumMP`F zG;q`Ntsy6k0=Oc!h_v?+-YYIrW$`GbrqMp%T8XU>r+G#?A1)8;d*t7ma{mDssj&8B zaqvr~yZ0h~;e?NfCxDrlgz|XAS`onIH+MN2row~xm|#&)SX(4?fD^iP+3i~}``K|f z!84)@sdB}gLQ@l1_Nu&+XZ6t=m7Duox60mkNxn_O4wHckrG8g{#lpQx3?466<|>KI zEOH}&(7Mmwx)v$SXW5tW$+<9;v!L<=2AD#y$-!!cfl*Bx#UWno9yHE!YHP7yu~=v@ zFp~J))00ak9k`Y{oeGCg(040Ghs+dK<&|k=Wu|dF=YXBf)nICS0AS+W=ZBsfpMzIz z=`=ELKLHL*4cC z%%$=G{Cko(9q?c1Smns=02kkjahp#H_DT{29?ivNy>6H7jL*UNl&Xz(-6JW4)^Mzr zB)?p-=19=V_x=cna=`em>8or-qm)Rt)x4H8kc!S@mh8ivJ&!kA*=zdMq$DDDL5Q3( zZk3mR_8@oCFi>gmLrnO|yO3%!8#X)es$9;_tk}Dyb^0(#%G~)TK75qh_I0SB{W-SV zN|)$Km{E;W^`4St%t4c2NeNEW>r#z)2X{R$1^{lYIuQww@TmW6R5~~J)R09Hcu_6Q8 z@V%`psnz-e%sSgndy}2dfo=tG>gB72?m?kf?&)dY+uI?yyr15g8}5ape2uB3BO4$= z5O02?ln8t)M_L=~wQ0ZV_^!lL?6Wuf!i9v<^W8X54@?_p;R-kKOOAZI{V7KSAbVhj zJXjt9;&vK%QQ$`dx1=h^ArvnOw?e+c{ag=-Tq1vcoOzg82z>7XHl|n`&`|5Vy#Wla z`Xi;(M3T?ZUVJm9=mM-_xuC|3AB@|8dhcLb-&ar(#1t&Wj3buKRKE zX(EGG+!gv1oV_+e3>>>958jS@tk*dVO4&{b?@&U1O;(+? zI<&9N5p=YoJ2?5qc)qzrZO{EyTsfMa*bh10E)Fba?zWf6*;alfW`7@M z+df>sa|6q$1Q^I81p})Q)!P;0ya$k>kaAu3zbGcaOkgU;{9@A$U=Qs}y~vR7$*Jh$3>ltZDMywpoMV_6(*B9*5yzg7tyuC)`r*j+DQX%V%#?qLnPhD^n8ZLc~AY zGcnk0(5uf_PLP15^B6{Wr?rBVJ~5g*`Cx;kgj zg{VWky*Y|VKsiX|+fHzLJYVk%Jt(C}R3N?Dh1RJ;Gq-UN5ayzkvQ=m0FV8pvc%yuT z@hW5dQoa1w%cWXE=tGXBp&9v0Ww$POUshLu0a~P)+)T?`5B}M;y7RhkZ#S$y0-bQ>1%SvDv|LmyR_R?(I52X~> zhPP6}n95ddygVFI9_*~nev((hdi)XbPa=|-WDq8Eqo0xMcC<953fLZzoQzX$lJ(D>h2sZ32f^w8e$bNw;3ZR)j=U@K@(u~*mZA!(80aFh ztCsFL{l7$w)mbu&kqzuFZB>Mc%IX50oql^mJxJmFw(NbEbEV!n6*+G%2x^H+D<7X7!bgLXR3+UX&4ySq+43!-#$sF@bEd*%`TfXYornX9xL!Z zq91#TQj#*D&>ORrLg++@o`kx2p}KpRJKb`@3_-w*$Qau81Nl_WkX zr~?`EH4p~6@QwKV+Gxgv@YG*2mz}bVJ-%VPW7n})jNggIH6BtXbrWdev`7v>8t5uR zAYb|^^w!7Vassa!VCY7`KbCfmL7|r4$Z>63;Oh1EYwKq!LY_OUx)vxMu^s4{V$!%s z(XPo{VwIF$eBz0HH>$i*HXb!*q@L=_FN}as{=$9dGOn(Lpy<^;i?6*Udi~)hxc1xF zd#?`aOzdS6H98zsLp%WjgMoMHVf?t4$^5YhP|O29k7w^%<@i+nof8d`RR6v3Y$T&qU zq#B3brY~K&h~ucxHxbR!@=c-6;@T`en$QaZY%$6JQ%=2lN}WSpcK-4b3!zS@6(rw+6 zXSuKM<6H~V`OmeX7nwc;Qb9MwuR1?w{1K4D2piY$E{MBXuzib3r7TU8qy;w{6$5<- zGJ2A28W}2#KPvgv42)D|5uc`4T8Q$g%O1w?8obw-a~QtePDYA{ntKhZp)f7mm!b8*qGS6s)Z_ty-ro)F77x_FxOH;b>W?EV02{XrfE z>L;v&%bg|bpa7#HMYuU3!I08antkGn-@Pz9^&1&Att*Vj_;>{Ti&EN z^)t+A)wQiaXe7Y*Z2};Sma$(xc>d^|$I{O5hn*~glq%$q$jq^x=i+5>oaBYFFCTyAX}<>=~0qFX*)ZLnQA9u~I-HArk%j5G#&L z?}MBXJlCoNPGbHfe*H8vh>_*%-RkIu6=??G1HwXp1~DcDEhOeLrm9mvvkNNop7P8% znJk15600CcHr)Cyz?2e>W=9fp>nZR`mUE52)V2s-BmjEo4J&Ne&@j9uhzDJ-i z@nG`eI1Ft!S?d(h1<;GY{lW;P`Z+zD|E*MZ>VS{@t5i@ZO$;aVAmy>zeALfYDPxlV zz=q?ld(bF)##u{L*gQ+V-K6+z^qd|9`xI6{&~gm@hon+#9Z%!fYzx;Hx`B7oS8I}M zK0s6!mPbMW2j@wXlqjG+1&NfD@D;!b|A`e4&E{@mCVsGbw;w~j;fq+Iy+A4X<{{K% zeBYLtT2_aGP$`>R(YIV*c6F_QJl2|4K@EXStBcUR_%;;~4NEd7+naHGBz*)zs=0*ES)$Nl~Tw zhnf&7RmaL(fWZM#2*3E+#mah;0V8})gGbu9*Gx{Gn$=~*^4~p&v!-M!Byy!kSDsj$ z$5B%C*MGd_ihHOE!?RpkO7wlxsgO@|rV>|E<9IuumbnzlROPtJV@Bxc8CT&G_M=Lv2@Z;Y^l>FwX4NgJr|N% z+$58`0Bs#MxjReML#YC^Ws1@>SuzqA%@!0PQyY;gcEbln>rCU9$Kb~%9^k)TQ9Mwb zgF+LVe9F%e+!_~oU3U@;uD?9i^B}=Gz3%820((TH=$VS8yqNtoOW+(K@%1vVi%xzX zg&_LJ4{3P*xYzT`;u$$$=bwHIjN!>HM1LKPDQI0K$+rIR?9AAH{sZ~JJJ)c83C+hs zc4mcfo`u**76mh`FN?iEyJVYS02@NA52}VSR3Mw42Lbxc<~iKRV%%w26T=)eC0>h)5jn(~g0FW*XQ z*)q|yT=DkoJFh2ha^}x~u9xY#6d&)33Hx>}ee>Q^K;VXQ3#ZGn3HEUHB9jQK7xaP> z#(BeCawtMbD&&B+!u`gWX~gM!VM9V-3Kh_-kw$THf~*;b|{f|Sk=QyW;Uw}p(} zm3qB*^Tj!WKg;8{aJS^b@g8~taL}2mb!%H67C0u{56SF9I{eAb=}0k*(kkat-_&?d z3bHkXsGGR3s&uTaBG|X8wK`f%uQqLJ*qd2kNo7m`BHAIC$O*+MK!IK-X~1GKrbUW2 zfJ;cWqOy8Lr-hIc6KY|9S5Tj0ntl3n1qNKFN#{9!+Z|W3ry%qxvs~j?6{y`)exU{` zsghasx85+OW>XCh;7|9Kj+A2KjDT%W~DUU*^<+v@KyKzUX?xGW57nFXK*n9Br8(Y_>)dLdSA(7*ONK6B`6#v^gA`{^o=gi(_o~wpPJH` zdyvJLfJJ4G)he4@ih3j)uAZs~94pzecNelt-aNRWf{hvu-B|&rJNDwGM+}`axIy^! z?^yFooFrBxX2_j8a~7IIQ(LgK1?&g$$=d>eh~CfuPFzgte;(i^{dmB&27~syA?n5* zSRix1Yh>fH0JbSyr@>CL?=Pcc!y4Tp^07(JL(cyK7!eD4&?4jej$T0pe8zy(feKlv;VqYD2&v=_Uk%^6k|Z$JU|@)hom zg*cd)n{bXb9qWb3HGzMdmX0%P+47BHpFNuu1xddpR>XDP&_ zP^|>rWUg~>g+R24wVwRp(}O~cf9Z(yA4(}&PA3q3Y5yE3n-NFYA|ni2$k<Aj95U#qLZRtL1_-~c>p-D zJ|}i5_KN5J)?)rd-w^1Xi@B0B_STAKNoE6?A8I5xNIWGNjgy#zqNN+LaSA!ox|jm% z5Cq%=Ip6B?!~lWPM&!H;IgdkQNw)&9mX=wu?A1F;OG{(*>J}m+FnUn#!k_)ykzU^I zRK&Gr!yZEH7Swr3bB)hB}a;OUHK(84kW$=JJ`^tlFHB21{0wS5@Vq>{7u-{_0HY0Ge zFqhBfZhY0{iwv(A2C26l8k#H(Ks_+^ti}_=ZIYb{jg+A5pm0 zdx_-_q4Q(*GdU}3fJNoxtxJ#h%P5Mx3=>YZZ7uJ;#+>vV*YD%k8|x)XmT%}qn=9za zgz~;Vg%%(aQ*s)oybU0ESdmBbe?*7J`H1mtX$Q+{DZs9TdmnKJUAk;+A$v*XCT^ar z?U<>*m~gIp$hj|7AsV><4Z!JdgL6oUcZ@I9sc-7CN;oeBaI_{^$%eDpUpi@tP^`ZW z-_*=rG`+^UT%E+e4$Ha|q;7m0#Cxt~BcSIG(|@`wXFmrw3yH?2OEMPC<7Y8U9^}Tg z_piy8GXrMZ5=P&pZd|zmQ`Zw67QioRVl3C7FMp!E~76J(7 z`?R0q(8BB4_>$AiD(&QvJ9+QNN*TAOK)Pw_+WuuhSf8BnHXNC5`&Z@9=-hW2W=kD1 z^aAxnHBY(swuc8P`n<4Tk->8i8l8%W0==InP5lc!gx(IcbsXBi6I@k1+yzm}hMo7J z2xyR;_!Y475KZ|GI5D6n z?%%{*YDSsQl8b!xKX**Ahx9#GE&Sjr-O%7eaY2s@cf?Loe)2?A_a)_iK!%P$$?w~v zUqiZfRanSXZv2{)m28hKj4H0}M~#7sTgwBl{46${br`yWsK-%qcPP^#_ZM~pE3DqU z;x$%Su6RYNw|gypb4Cz$*UgmX!h(Fl6SUq*n?p7Bko|lf3DWL`6N5aJUYnGB1)|xv zM(Y8dT^*;{Y~W0#4z|HYDmG2c}y0!3g`4yvS%&n(oy1|ktDQ? zthXD;VXO*DPdCT+^Bwt*(X928&+X~P7v*My-93B&oQmaJ>MWBGCt`mJv_P+d^-Sw6 zd))PXA@pogN{B-#&9}pj^B?BZ^%5Y0&lK%(JXG0EbjN+CYmgM)s7%luz-@^f3*TR- zSxsTJIkT!hayO`^G}9bSULjeB8-YJNy>RIPK3w#i%sEFrz)|nS+Q2VTuvmLfdMUg8DcB%K2Cr}jkz%4Z=^zA z`&;@LGWUrw|0|Wi5jZP}8V}1l+TL$}7gj`!4BVdR!RZLTBnc5ySRV9H8J9!MEaeqC zD>As56tb0vrGSnQ+i;1U$94^e&IMb%Y|j9ZW>%Lf52k@1x(-?3)-SF*q$4npNRV9W z_nH@*+7Kmrq3@n^us__ehV49$EwnJAyT+jt*h!5Ix6J8UsvA%g=#nI;tIpdt90WZtMK_pPBEKYsE*CaTCfT=LUBtxN z2GPy~8`=`Mp;76LO33jt1BI{u%Ofi{nm^C9Tt0(-2--r)_{ah;J9O~o2W55fHeXg{ z$gbX<^r+Fbt@^C;@;Xx_C}M>>b&LPFZDeqPZir2854bKFM>O9n<3Lhv$jQBd^`ziC z_Z0FoIT~n5xUZHC>rmb}J(%9>9hDX$V<~!q zWS&{+91_8z8&Te)*TDr>ie$Kjbayw#9a4*>+3pmex$j zQ|p1YLZgpht5ZOzjx8!rj*~s#6B;1HNJ@O<%Z+*~G z#k%>G^YdX&pU7!7@eMinIH<5K%rNC);_@pdt5?65TK13aXcaMIEf3v1N^`?1M%VI9 z?8Hl6u||2=iAP; z8%=boW>F*ZJ6cuQ#>Z@nYV~0(%Cs#m2}P;>UOsuPgo-7~TW>V)zx8hByt=FQBXp!;k^aN@D-?lj}?5tSrSCzejou)7M3CMy!Dx9F4@n#p+} zrin0d91ZmEC}fv3iT`;2UVoU%u+;PUSCS{=)<#1hsoXAecYIB;A-l$(I3h(ICv(0v8aQuy%Ol(2(qM8Fup_{l!mxm z2JR?UftJrP?0is}fC~M<8=E(w-R8Y#loeOpM*)-az8ti-((iI|tFX)ln7L_>!LY7b zDT7nLt^p9LBBNKS*f%uuM22#-PLS!;&y8se$bt#DO&x`S?$wHCkvD1=Y2f>iu;&4D zxF{b=3UE>YNe-d<9EGQxRPo88ZgFae&FgK8U`5CIJ0arX6ll3g%>JaVhDXbJCjuY(QpS=^%^RL~+?f?|f{&tkAzN1SvwH2Zernf>JiCcfPX2ljn_ zx6PSP&Ng(5c&fcS@&MJx$TnGt63(C(NQ6f@{>uA)J%2@1A(sOt zqG1gKT_v&`^k60>1w0D>{LXg#;*v~onn5YcZT5o|={7;Ph_L zV~3T%(%o`B;J(|OGnanZQ`LjkgtfONUG~^c$#jY@fWi;5J($%#eAJm6?S{$Xei49C@kLF1-Q<-L-9=kq-P~IiewtVP>yYi*Ul5{Ou+H1fj(&2zLE$U zS%Kra;~b-aYe)pw)U^>NV5HCyI{+uMIpNT)V$=LCjn_|o`XN4?!Q2TXX4fV?){(QW zfHPnSD8;`-*3@UXM-FH+-@v^WspHx2G_NhkM9tAkH9`}QKg#`3lR9){4-Y{-Q%Uk z{o9lWds)9vinXT!Uo<%v0K5^_DicMWay`Jv2FCkfb^am{+!ZESuRT=+OIyEg!5!y7 zC6F9=7}A3K8svp2Vi8ZGCHL&ya_fSsIeRcQkzlK^t4j~U_9|1hYK@2W(#I_A=1G1A zv^M(|)<({t2L_w(FM)(lI=ke>SKi1V5M4iewVg+_s({nvlyLDo&9apaO*<7-Ia$Oy zTKuj}9?)oHInd}3B&M8q$j}dIY~x*uo8v>ImVnwI;O2s-Zd|kz2T#$hwi*_5sjFfx z_exVmT&OGJm&?7R zZA7C1!jGLVjv4lCeLfmQnCN8Jlrs_KdU(yw=XP+7&FR$0lXw99#3~>`rq^~dv;rz} zbiNmXqarX3AgXw#DCHR$3>n}oEH!ygRtG~gbS^Bh_^+h_iE$@x{ssEK2v>yr*x4bG zHu>tt8vq65d7^C6zhcH65&l1fYlj&mPV#SGd!Py5;^LhZkHbky5KY?NnJ=bR2Eon% zedd#sT7yD-!gqc?*MHj#ZDJjO<$J9=ofDi37xj6%IzCaX*|-NP`RQd@;VdDwkVwE4 zZmdS{z}Mul8>5p5WfuVgMW4}n5cu#p=i7Hr>G;i_Cju3k0Of|I zVa9RCD8wp0pRsF%CViiCZBP;07StUka*kQplmq!|dN+fQ-THY*O?CCg8I|6btsF$7tZ&!>XY; zi^s!fB7DhoR;I{-W`eJOG}A5|*l2c+oL7kSN8A`FkjT+YHrdQV#|-T_bm~SI610}o zWV26V9dIMD+%Jb{T+l$kh#^3vDFMD@=n-sQb4}3W2kHH0kQGUGSH{iA!+N+4n=Muq zqdf$;_~oLwi%pNiEKAQov~x?tBB;DHl1HlY{Z(_>S47dk2a`D&a&_|UnBO#CgfcA* zwwNN`oLpf~5WcAQvqym~4&4&-$?i*6cenc0YApLRs{m)LCZc}$ZoA~u*;|#puM^3x z`d6Pzj~_}5xvN&bQoIZZZZiH5FQ}-D5=NaQR(SZbpu@q`8AI|WhXxL&qEf^Ft;c9- z>kSrFZ4wWGft}`1MyYz^gxoX!wDSs1?YDbFGB{?uxrE9=kh7#zgDcueU$vqSpfdo0=a;ziOSJ*jbbBsnu3!{ z2KBo)CJX>p^UHNf{r**M z*ka$#HC)YV2HNA*9F=V&LG!l9*#m5Eybh>OT4tk^G`pd_T^lv8P)ST6?Rs?>ZjZY? zq(4s$@XF474PtaYSgWy1w>-zL>PY?YvF|MsS(aWf5Vtz%IvpyvKXtg~QYLwe<+yJI zt?=P&v5o8L5fs|Rj1SktkqUqU^tWm=^sg0jsasoG`gtBrH0G6i^C?nA20~q8>*9L! zdt8txv9(HLlbYNM*5}eeb3M^{(`V`((j?>M>X9Y*w@q-9htMlb>f7X0WY-@)`FtVx zVzOhOvMrY8tn`Nhe$j7ABscT(%?th9*8Mpl&_t=hO5F}%eW?q?jzX4wELpu*!{JuS zwAM9Ni{i1kuN(P?{eU5zqrGk)Hd~)al5FqqevBOh*n!3gzyOlmZaW|CR5UngWT~#Y zihqKDH?9RW8Ya;@+BDc^P4L02@h8pnNe{1J0~0+hk3nN|w5ur#?_d5)0AH&c8ZYi< zpI!*cy|>KNr+rBnEb1)hdKDQ}GAiio$2-b~^{rjB${Cha(clrZU#YIv=*K?{ern$F z^6MIy@w-RXpQ+*ih5!wN`*Pc@=wRhWYI78fA;>H{#YIpzM!KeKML zjWekO9)JDO)3w|g7pz9|%W*`~evnxW;u;9x^MCG$I~tX(2VR~pj_!t)X5JA;!`9l) z!-%(Cx2~IjSjUon%qWCwEDA)vBFF=o)pvqSZCJZZI^A3Ji2HRLW&8D-KAcblk*W$w zV!Atf$!<1Q+j$D?ql43OkkqfQU{sZ_h10!ZJKv6VQ{S;y{!T;epkN#dJ=juY>nB*$ zRsRmz(aRczt1YCOSB|lRZ}KfO433!uqdyoB?k>cEX&}LXs=0*rRgiR81M#y31|F>n z8O(bhJ-AOExitIcs0P8Djgj%j=+tXE(t=!7T94?UOOawSl?dL{$!;LN$zx3X1Al9*KsrkcfBz6a4<4% zl|+}qWK`3Pn6x@?P{A*kKsuJOJX3p7>cz~-Q5fGN>hCn?EOltKv8>hOU)JiJeg8*m zEGx<~?$iMeZ}UAm3j?oG-)Hc_JZq^A+ngswKEqY3x2~dRa0-+V>UDC+a6&#r>RVJ; zh-s~e)j;;WC!mw-ite>NelVtQlIl3d+FP|T-CWcATCs-LxTzAPsY*5a6n2+MlxV4B zdz#9#?p)$HNdqB-`$~W!v_`)%zU8FMusuclZ{9ZVDspGk+y$HcO9Xa@$;~TRx@h3J zVa37mfCJ1n;z!ZryqAR%qPb;rw*1U5nrSTuZ0q+zaA9WN?5)^%ChTnMa+as%U~FU1 z8&TbxxJ=)Z=vkN(7+|;+RDJHLu353yfqUWMJCFcxLyhdjTn)k*HbEg8Nc}I7P(gEc zY(RXRQ=a!x!@VDjqLY3g?;g#}I;j|-oB^0)w@o&$IVhJvsY-1MzK(@VKTfi9$5Bo{ zW^55>>m1lJ?c*&AK`HT8WIK$~V53M=(YMsdc)k{#OGwERfmW>WGaR?E0Diu*eF28| zl~Vz?*qtg0l4PYL1aOe$^FIK{xa>UfMA2nXOIwTN{+-s)2GgdUyajoqc0-Jam@FYB z4u!_^Jvxt;fZ2Z_W3L1muDSpZ;LNH)HmW1PxQ!#$m~w`k3LpNcfNuE`DCbx{IOnuq z8eV!*04NYfizY`u83YLzolb0hBBrx$F*EV(YBk7oogLRg`qMhtx8K$>&wg)joXC{U zxInC&jdRunbV1Dbr8&A58@hvrVU?GXn=Q=2t0O{SgReh6<52bGQd8tzT%BekGCG46 z|MmSNQUDtfkJm?tIlj%~Hf;yhhkMsQ{KAHoIdW9a(QLR8wHH$=f!kEmQsY$EhG%;= z$Dwh!#67LvvRKe5lu@ov=_)TTWs`QlPx@u%__0+1nMduIGE{bccT$HeQA9z(au&Z37lkO zJzoFP1vEQ~i`m}+-HsAV+T&xC`q?eD#9Jmq$J(x}W3j@TfC?quLbSfXIo-yBAtSS7X9 zr4EVgO{1IBO{Tv#?lfh;q$Fk9cD@1(^;QA4KFkG@Mr&z74EEq3F7$wa^p+ZDhxF(5 zv*217927@hW4+R6TBsW!jr?y8xL|!h!5APmJPEP%`X}q*)Tc@oW>RN%C2$9XLWJx_ z!pwgqd%jbnZ@@PDZt<8poAt_5cTO<6n}ek8G$eAw50<9(kbd^F8Lh6zk}$~tZk;sd z0=SURX=$LNK#LwqCQ*)^)vb1jH_x6?9dA;BBsC!Uo@ov6MR*;qhkF3HT{Ss#u zNE`Aaj;6bRkn%mTnCzz#k64yId1bIafJA~Uw&#qi7N=Ucq}v$`o7y~`V`kh#M?Zj_{n>H~ZByE-5WJ;j3oIMdSwB5E z<#hI@029Bp9%Ecb+bC{}2kgrV^`N}S5;^)fmTEX!ZosB#`4T-oQV0p}iL6ylPmoA{ z_mxWsKF3}S*vGY^LPkOCT+SOPb(wkWWUH~YvCZlRF)INM=T(me1Y_p(Przk(8h#az zgKYY^S%8VtUZ~LIW1zgqZ|Z}E<0_gvT?*N)WT432M{itH`%kPg#qn3>C{P0s(E7SU zK)otjSBjb$;@?M*onx!ShNrF3>@^_x=~BMV6399rTmdpfb=N`vQp*G6J1nLbFI_F^ zE-$(*BLAal>29giU!xTX&}?SdCJ6X)oj9MbPCY%%y>LciOT7Xtbp)e2>FfYDu-a=E zBUJPov}C!Qy-y=iy#bX?*Q*nBU9|;GW9J&<4-B^y14!0YPxS|oBufa$Z>f)Q*w_HK$+otgJ} zi@K&Z$LM5xue!jvt2h?a_OFh|i*AlfduK;Ot1;!FY3CFmZ6|)iVbj!$1ZCBoiV20MRRduefv?jN}A(SXrI!3ihY?sH7XHA;lfsRdV6lEZyF)ZsTV z&F((aqdB35CwBFFb4nZ7vv-{)cLtr$@I6Hqp77|gUG z3avlK?v>t-&7K3gA0!5#H%Z*d1$*MnX|7WBL9L4%QeJncIs9+`^BU05M!*!3EKY-C zL@wK4#X-OYgHTnO$~U=fvU;`1*MF~Q2j$GKGv@7F6^>wb-(wlcZ&$k!sM4n+1q$c7 z(L@*T8~T3YRp#8Q7_-Xzbfiv~W=wkAJ(BiL<^?nL)TiKc7;sk3#p;FB*&C}b!-j5P zl2a?s|2aVkL4dz#H{Lf+ArQ=Q43qAp{#y_vE$h;^)y5*SmNn#(J8-73Wj(pzdT*To_qj8J%0MfjS$taMR3vt%ZnqITj2rw z^j5FNEf8|dW@9v%GXVl`X9ZTX=N;}JE*>ii9)21T7GvZhH?Nhjy$E7(Z}IwlFy#WA zwL^4gsx2@PLl}{xc-C|-2LT-4b*ooV%ne~xF-te~Gq7fVDGvkcpCfTLV3!>>#i{h} zJutl>wEVVY$!wqjB*&NfHQqh}?5F7Lhq*pV$C0~NDX@w0dtX2T#{t_=plcj@4P(H?PD?x|5dI7#0-|cCZ;qLbN8%UQbls`Dml#p_&J0VPx-R3MYn{d0x zJ#hZZH!#G{_fa-EsS2C3Q}g_G&i2>O4Lz|kP=~DnDT`(d$Q{{M&BiS!2!4{LxERZY zZLb*ZiukPup@V{^TSKgolSiX|`PKI-&{Z8DmgYMN%F?u}iJt=(Ysno9`n$>FK}bM1 z?Yi-q`kWI83O?I{6EIX)jE@tXuvdsd2$f~d04Sd7me$>9%AyARq+;B$U&X8Vyegg5 zD{u%>fSPnIO=5B+)zY`;+ArgnPzWiq3GfM9?5PBd`#nL}6A2RY7MmG7Y&b8$gOAUb znt0MY+cr!Y4C)TFE_3nb_q^@w$S-cLf93JsIL$ug8t^dlcKo-cqY6O@SrhP~Q=_bf z#WN}-&Q;)~idDAdV-@lxS|UXg+S*T@rQq=79VS1C9MZ@RR9n+}SAoq^C) z7F0~SuYpr^9QoRf$B`Aty5a)u7831vKJKHm@(dWjG z&6L#t0u092uhATrR_`AU2f2;juFe&1>w0|NOG|rONHJ{tsBaHGA4-MCp6+(xEa*|X z$r9;dO=8*`oE~r_fT?l$K*1*XEWut7EG1 zt}h^!=7bF*v8POM{N5Fm;AGCEf%6?KVFA^M#TV{I&91zgV>sgI7JFdmOEhst zY~{;EJ;yt<)5m6sPz5K{WY|6Eh)*)nBc!E3Thv@C&6C@k+HX!Fmis?6ePuvY+xNDF zgd!4(0@5X|Al=d}ARvu8l$5jsQex0Bq(e(8-AJPsb!e%f8HGV&7?7d+-N)bm{eCZ= z*=L`<*LrI8ra$T+bvHRox(yMNf9W5AbJ&U`1Wp|(BCC48X|z+K+HHSgtKQej&IU(E zQ{#F0Q`vmWn)dneHb|Ikf3B=%>mztII@z3hTHWKDh5wYpoC-M$EP&h&q$Kum+x8%HOSBris`D%mcRiLb z_hs$B{5>E$z{84DUgm@ll5@F7{>-6{+?ED(q1)a5{Y8M`#qYkI;`GHNhw{G`Y1f3y z+M|hl6R@`iIr54#u34r2f>!)*rE=A|Xs_~55h#O9|FG%!$)iy4i)TSCw9NYA7ZIQ5 z;S12$Sk&MccrO6B)4u>p#!=WT!#X=B>8YQU`z${9s(4FzxbAb$W1 z{0rig?j{_fe*sr5TU*6uhTVK4e~vD6GLi>3uu4l4bhLupr}|y`!me9yAc;%VL(_RN zF7e0FWU9P+?|=Q%r-1z2&59Xkth@XwQZ_2S`d>rz(*(Fz4D-KI0-0o9a$baO%Eemq z44&_K#NTny@%{#~!3Q$po*(9(djY9Wd$N<6Rwj_wBsAI<3Dq&yGBht+ z5|by{riGqu=UVhUo7x9O;`khRtrg@sogkdo^&-{Fv$Pi=Es!3cVt#%kM0fLKNi=jd zWWN>Dz#4c2nvOGqQ>?SQv6f!Bz6oa zlMT0@VX~$0oZ2Dq}t3gYP&LAleqyPXRbr()T1MRnP&bxGb? znVaQd^=heQD9B%$0|~{e;I_?IyG{srorS4-xgBTiYQ9;9U&%C>SlX@wOP+R}| zKlxvH7XYP*BQh59DI!;t)|l{j@iEU#Uva#<<$b=k4${U)yE#_?*?Qi z>W^7Coqq(T)+0^!;A7pKrf-wsIXm^{%4%os2Y|hp53GF6QeT`3@fV-Lxx&}py0`}} z90a&G#UB;g(Y-{3*Pfhp^C=G3%@qyMI98kr(4P@9F8njm}AiSVn$SxivVFJwV0jHqeFm^_En#1Kd!}`)$h# z9x=j=&I;)oj>)LbEy6ld0{!SI_df7}OVtiuf=YMg7r@(tFaGd7D~?R9-s*#F!IJ5& z)me3-OCO;y0B+oVa(8=vzoV&i?NVx)xY{L$%G=3gE&>8MPoN7ko*0tXEli5_2_;V& zcxpXg1jnQPx~*$P1Ko_jYR>A`Rzm13P@aa?wTe$%m0Q2l^o5N1v&WK)24%S~^K9QX zBlqW8pa_F2{gpVqW3cKIcnCE|pzp~<3kw9g0d~TRFYEcMzKk~?T<)Ky{d(Ot@Xg6T zRv!D1`10z7G%B=%WR#FaaOx=(QH^V))#p3rX0HYjoPEz`I77)%4Gk;#G5$O0oi}7J zs*YUL`K6h&%qeb*-R-dG6Rc_ztF>YUr_>hZTW1Tgr)nKLhl2qfqiaDWXZuIDT;M1F za2^USWo7Qzr)KG(85Ss)h3K)?P-Ztl^3k`yW___Xb~RnuGti+8tZ| zsS^%beOpw9@Q`Qs^8ad-QNOed^@0^_u1z)^Fqmg6uj^)VJ zvef0B?t?daRUyxB{VP|Xz2hV>_C7gG3*;XwFg;IH`(SW?;*!GEH8yVj#)_U6g=Q@P zvyh0eU#+Q+p?bgM9|oX~Mq4965;^#P(ehF%tiJr1pEC{(<7Rc4TsWZbGQ@p&@2?Sl zE5(WbhAtTc{ZE!p7tjrA~wsl zAXeA|2Z#B0T1$E3^w3X^(Kv?d$lqR|gXN7zxNd{EhNrkMrF449Q#wj#TM5~1M&CsU zzou>N4p!LyNanVYW&G>Q^O%TE&pnPXh0mVh5{y;jWPnLzhdg+;qMnG^#RaH@^r}YP z1xy&=&?nbMEH&qAf@8RvX-l^4Ef~?imbhy`5;{-^A`;iuP@atML;i^8x`55ynmThE z`#Y6@rtc9P+D^0*8tn_{Jf7RBY;=eJEPC0O+knb5eI+4Q@NM6;7DSYupJLAE0;?U( zf?EvRu+4O)B1(rnhSSmMR#I5}?LA3TB%Z6Pqe1s*yxzOqBy=AW8X5p0!Rzk01tCwz z8r>tzJ<9&VbYIpW>uI4nckC1^bPHOAf8N>Jsj&67o+m5RZ82=I3xT3l4Jy@JQLe8% zo0k}xKqE9YXHe;Ym01rLy!;Qb=Fy>^z5i3i-3*^zy(^ zkURIe)RE*L=3eInrh%?pR6*eeGjmV+jF zSYi{GD<;(|+jn+9{BU4MYIo2fb`%rj!wY@M*m>gVUfV=eH0q$xq?TjI*|dmSFM=}7 zO-tlQHHdlWc4}e(6kAsq5p}e$nbY>ayv!AmL{>vu8szHrz*t68WtgLsg`fb<5i$QX zH33=x{YzNnDWW7;%)X)TbRUE9w~C_@TWY4Vh?!mR0!4=t%d_@W-tDj{{!p48J!0ft z_Nx;3dQ_c)*`vpA&^VWK@X`C~pr>=s_JBfTXy~QG8n`S+&3>tp{ajUdWPVt40-yz; zLk3)N57k?(`&)<^Wh_P!$)7Ayz{jFB1)_fEXfp{!{Z)izlFQiGUTkyMX_wr@NNg1o`ZF)8~XH#y;&9Dp6-%4e>-=$c(H7|~LqmjBy@ zu5-d(Zxz$!XBkWMwfJgg-nn5I)D|1WTW%!YkzQs_@u2naMr#tg=pcRkp$+8=ofvz&%?cIImWf zx+4jy>5rI|GV3q?s&D$Dt45|^z63F=M} zL~|aGla$rawLn7MfHzRG9w$?Ad*1D+hdFzauw;=g)jv70>fiVD3bzd!uTyI`_^G3E z|6nm8-A3qNcb-Fhms|~t$$<#3-EUu|I=15at==fKTDf`?b2fOJ+ab?BWK!!qv=f(r zzP>FAlskvTP|LwDa;3>jA*lW<$SdqaMTu7A*a{v(Uf)hq$3@;#d78sC1%<`?eTiR+ zW|>Ca5mOXEPk~G0_HqLVhUc937I=cDv%gwO4Yg zM*unX8|`6x3<*s&8y(c=9~gg23Rx=8DySVQUfC(Nap@!t#KtK|hVeFO_qfL4lc+Bg zc^0)S#>;6W1ze7l2l^+j)%9rkNpoUxf9;|9_n=NfJ%&H>3`B%L{d?40t*}a)tqL`z z2TCt*XZ^j}<4;#ydEd!ibO|~5k4<(L;nWDrHauKV^N>rHI!5W;hDXp}{)cYrg}*1^ zB=k$RKHQahRiTPjjSFA~{j%Kn)fxXrRe-(LVj6=v`gZvRHE!hYB?Aj1nQE6}m`MYyfY|m!Wq!8<%Fi^m(AqH=&+(Zul1-J31 z>sg6TE-+z&JsL_djk4l`M7QOI6~T1fHD74-KLdtw4cb$R+E*v(DKB{xI_vyc)v>4*7 zf#Z)r-R>%7g#($!tyZe+?#t{7D#@M!A7Cxxj#O+$JSPPg)@3NU5`~di<-y@ySMPB&s>VryQ0Ldmp9-j$t0{qAqS0!m#C;UQf%_}0JA=(kfV_mt zjNv<7p+K0qVqpy=te^m}!6Lc*5uO^bz1Q;Q_j9&6t}3Uh*asr49$_<&fc-;qTQVa? zq~)WZJj+fZdsADWIa!6+g;b{`U#8mt+&HS}iX?R&X|hA%X$TMWEwCJgtLm@r5$w%Q zBLsu3kJ3XTbOX_9vW5tEWb-yaygmryHbc*k(dDd^53>MIRw1>va^r$~)BrWok7LM2 zTa@6)1poZ3!~qnp9ziUX*%y>ws1B?P8}EE%tsgTMaRCOA8<)qUR~XWN=Tcns*RV)tD$xV8V2I z^)a(d-~U0>cTAMeg9o=gG5yx#RSp+d%Bm#Xt_+i^`TiRG)jUfXzoZH_o3-aZsr9uR zT1s-9EbAx%IH=PM*3&#k@SInL1_Bi-^q)@f{A90_+6mA0*2^*EXnIz71Nr8T*Fra1 znB>Uw1C;s5;zH{rW;}6i-}n>+wf1zw)L$`JxTl;=zWb;X*eto8)S)SdJJD7uvt$6E zirO&2@fMscs@^6CQwO5to7zX3W>nw0KVG#@RQN6n4zfG35XD<-D_-SZaU}nS$y0-# z#;AISO}s@Z4IE&ga7T5x96xchLWLMJfdBrdu5w3slP)?_e%;H$@M`x+)0`F)JmUxe z>U&%U;U6QicT4|oj3}yB~j6J#GG`IiNEfbEW4w8 zG_apm8>#5XOocGO4ucB{cv+L-OB%~$sp z`#9g*rUHNt84_Douj~Tnv0?IR8cJ!K)mPTQzcj98F*Wor5He?@0NPA5pq zo*bOb0KZd7imPP^5=>&<+-n}b_pgC|tIhsBEHYMZ3I1@kXe}%C=Yxot6$cBg`>DK1Sn}6O&urA@<5$ zx_byGBr0KAo2e%N5P6eWE*u63?=7o#S*8${2u4xk<#aGRVhn-ITy>g8?IIxtJDyCoaU6@(|rQv3?zXIj+q`{T0c(HlG;1qw8haYe}Gn z1rsHX0n>_n(fUrFw0_h_RyMWhn5TyN;0m(}7_;iWWN z;Uw9X2j^hmsW8bl+xfwdmZ?1}){YF@dl&MLM^(6{L;?p;?)IxzU+P&8h0phNl(9f` zp~d-;;|C0#bb^xL2uTbwzzYy8qjR>=<>LkZ9 zaI~W8G#^3ai^{c?-E-9$C;c)zf7iT6KLRD)O>o()vo_{n6hkLelu)M8=P0gzkiA)Z ztC=P}Wckb-{;a(%`tGUPexl!XVO{;0KUY%?he}dz=w{yEjq?Tz{rn>K+~x;c=m|>v zR&9sAXX(C}s~Ry^?&-Rd70Dxb8VWKsL0TlVVCWKt=d?*X=L@^IG?wr5`C#!hTd6H0 zkbknuWoqUWaSRNcZ9|IXn$B{~jM#s@BL0nWb3@QE|1*L31IWVm)YT(G z7*rvL0^=>NFAv5`^`)@sD;MhVt@R3GNPANjK^Fd84;uwR)7&j4L~Dn57*o@b{LfcY za|vY+0nr{8+izIF5Gxt{q1PZuTt)5*)5L=a?ne)O8MIb-acmWqm86tw)X^xm0&l_S z*%3kk+v)9Xq}X(aLFdwxFaiBuzKK!$f|^GO!3N8yK$ll@`Rc<4rgG@(-n_KZDJ+YQ z`QorP_6a%gQII{!0v?|I@787&h&JoWcVakZDZ_DHbcL)%6Nl*>buXmVd~zf5E_+hQKpPW!*TMMX7B8^A`H&E-3f_JYNu&{~ zA$7UG^G57{(x(;P#52cr2(IO3&v#F;iuDKZ7@(O1Gm^)!U39Z`& zmcFo#s=<60;24T*$>1*kzi1X^bz;I_zgAQ%RhnD)PMzs`gm;}!HI(Xc(;ytkj0xp@ zaQo)|+tz@ZqgTO|Gjf8kW4JrtZ6Np=4%2eCRDFmbOSx~uuD z=^2!*zmnO41~MOXSz*x^?^eBRjL_Z{x+_pV`8+MyNdyq+*dp9CTf6Hx?t4egpc{-g z`2}SNGyz<>8H^wh@$!I1KQRD40h(2DKTZlHBEr*{Zd;M)1ct9{l4Xb)7d5)T$OWMq zAnY>Kq*X{IWk3H)NiuKJlFpFm@@>^h$v_TY?<>}bp-r@M{3p8rSJP)maIrT*??VTj zKq0h{018Asx4JjYq4hKFed6l_PXFJanV(i`*%7a&BPa1#nG^%)Rz{eXa={0YXhu%e z;GiSWklqA)E}J z6MUwFS;7v}krzx&?1X-c&|kwtjQ@qZ(2Br6fdq)X zE|AvJ{Osfzld6*-pG>rG2e^@IMjq3pDe*KJj_RH1#tjB-z-mAu#($XXA$#fv#=hWRJZU)|q0SNQsCh%~CF860hlyRN+ z@a5iviW1F`6~-?hpMC@TQ!ZcN6!5|55^IS8K2r7zL=*262BW|PJ79Nh;$==Od4 zFRv~}J?W>>{&f@iy1Xup|GPyM2(;)~2fNGh?Boz!?T2yA0PJ3Dm_5>bjt)$WxB#9# z{5MzL`HeAJnr1+)(09S6O4gZ^{CmEaL8%`jtl!{Z5K*cOU~gL$uWp4;ghgIaV{y)M z%G5bKmbFbfv3$RGw^zN_n2eZ)A|`B|I|NtJltIq=qD!dbV8Jody>a!ZLgi7$@$esT zw(8wmFQx}C;1`DU3`pg^f1$ZZ05-JllB#l0!DoE?-&)4r4woh26m9!^|J528)tRd0 z`sDZ5H-A95g#;nsag*-e4TM4+Y;`5iS_sMi(}d2KLgINrpi9`NFnH<>qilhy_m?;> zgj-U|?~a*ljh#-k{gL>iunA9)0b>mN6_3}S0;cSWoC`1BsT`Nkj(Qrc^QJu_nC z3hig0c9FAOfMrl~!=P<)^AWMbiMX)UJG&*cjQeyw^>eOJ+XsgJ*L1I(RHD1 zkf$Jxs1*F5*NbKYLzl`q^i$Hq*-dgi!H`k$fQ?z+jQ%+?aW!wQf(xW?le)kROV8dD zMMwiR0Li914hKB_|04KL!J+krma)=+xOV4D2T(D<_?q7-3xwAzsP?w~_bD!sl86F4 z6^V8ph;-OEmry$Ray9mZ(&||1Z>2$>YlcXAcvh( z)B^5(Er7fC63wA@@}SXYCs;nEyO8ndaBeeX0xfq_&qm-he-H9oWdj0qzZS%NX{3v4U4`XN}UiEFtx0l1yuB%o`56e~WsavWrr7TCA$ zC_({hUy2pXqqUjlRRLje4uLMmr&s`-9xgsWeb^YmVY%}DY5ZM+o~X?CCJ{Req%+7x z2R7^Eyg4G-J3eMcWzynm&%Ws{(NLs?2_+x?$9DGMQKyN4%(twUgq}}Jo%`*Z$b^?z zu1`a#b?#r_3H4o?p(o7x)qOEMgcN(*5&2~w+_5|K_!l{58flMuqiE>X)26h5P3MkF zCcpnf4Kh;p2a(E*m>RJ8pY%h$lJsW*-OUyow&TekzzNBcLheiD9Y>*VID1zB z4T|H}*OW(}(}u+4-QTp4sky0-vCI9kS6WtN4u5dq66!e0#807|M@2N%rX$%U5U350 zS}(CV-&Q(*3e|u0npi&$z)@YkS=&l?fU`T#IMOTT@KN~%n3H4~5Mi_+aX9BJ1WeF> z^gRPjZv9O33|ji=E2q@~_WgHthqT=OJI%l`DCd}l+Btv@3@dt|qb9yc@o*_~oP`Dv zybSA!$>QWvp`Fq)b$o#$tN>BTt|PeXqW5{$-q?flGirY_1)~(<3u^;sFEx!<&UiSw z&9*vNv2NESJ9ure5f36&R5UY@Wuh+Nr`#f%(lX@SQ?G(*3Rn0Pjc(fS=Sy7duE+Qo zMo!5q8>1A3!9k+lfRBP@NW;0*egOfd#!DQxQI*D~fScelZ51mnw7%sMjiBUe)scTqKn^)ig>scg7QkQxe_ z-_DU&ztksC@b&shtNk4!MD5!LXQsqQBL{zs#xLx;ljBv{VZ;RT$`cOjoj{;MS>zva zSz1bP+_yfwCqs~>{M`BOo6GN4%A&+j=!G%cyGE~|(j*vK1I+EI#4%AJR`Eb(>O2%@ z?2wGVB00&Pb4fHv&99aFJ?AkwYD5?8YIZ&1HLKNh%;RSO903Kxuz~-1BGr|$irlqZ zN%2QX2k3!-V~$cJ4QhKh{BdOFWm{^in7CAjL_;N5Hl@eY#gRkqc&{4gfxnNE{k7b{G0=9Elyy3^>@0?UqS_L2jsR zxNM4gGIH0!woPnj-5<;b9n-Y{`$^lrH-+C32yGL@&w>7h8)&7Xo_a&VTlv(1<3Ih# zr{FSoEndKknqFE!;hM>v+5>WE{0F+MFxx^`tHIH7N@~WDQ?T z<(bY8L4IZlVMJQkZ(xM5r&c=ooAb0=y%}fnu8)%F=HiKd3LyGr@J08ebs$L_e`IywTT9P{-BAOt(_Oaiz@7#+(t*J8j zxgR9N(U%k`JO7b=YkKDr@ed)a=ZJuai8_xuE;4_<<}N|AQT8VX8RJ-o3Pq%vN#xVw z;hn<+@z-G}&=QwX9KOCl`iz?AXFk_Jlny?SYBqy=7qFH!rw8 zSH_@uENFMAqc=dts@#Vag$gia0 zl+>{1{3)X-ZxJae;V(_)=JoYO*YTK<79hy79PW>@4u8NX1%JiSm;*h4iD`Ksx@ApY zB46!KcEJ2pwBs>$&GYb-E1VzPCS7ddrNo@WLRAN+347;TPI~OZSAji}Ykt_b7X*>4 z=rJHUo}AyMM;$o#G+iLPd$Sp)RWJGZG*=SLzt$iRq>>AV5QNkDPZ7Um+ZQHR>cn!P z5}xT>Gw9v!i&N(7JId{(Sct1T@!YNh#Y2qgH9B#`M|L6-3gB4$E0iBb@W09V_+W ziO^GXxw4ra0nTdB7a&mvVp;2DX z3215p-1M&%>OSHLn%HT{7fo46_P1t~fv+in%5<^a4;G6%yWePiPrTQladT;tobjqB zyw7SOPHN_Ac_vuZ0m$lSX0Til%Q$uUdpi~^RQ!9k(-lqLj89L_z@MK$&GYA%xYa(h z+oOEsH!0vgZd&E8IlfOgmiLZdxbkc%;CYqqp0TAP(jhSFFBLJ3^ZT%4DT|yxQ>xro zE3#@8|5Il|VnDKlrECHFk?PkH9fg@R9&y=SQ^!bD%Hq$;m4q_sz>hdwfzrLUkB}qn zTcFy1HGTl{eK8!a0lh*b_nG&hS*K4!>=zeDusR&C2v#Q}+E!ola7YI;8HjOuvtp|#)`2bk4=sy}OZ8Y`vlzj8`;UHA=(E+i@<~se=B76Bu;T-OR`%E}wKJZu?vW2Ra!wLL+<^5SQqbNP9<0DYexbeBR zLKVln(Y~f4ftFt8n9@ewP{w$q!%M7LXM$?XW^bqs>-?#qF9#ROOr;u%`d(rlXlOoUu*0|M=eZa-CA{p7m zU%|fLpXR|FXog31bIC;g=*S-!mXlWNmwbs%}R<1;+$Ay>LKnUF$qTLQh?}jy?{W&XnqmnO31O-A?tI))+^awOaVo#X;pYD;j{% zuuBuKMXWsw`*rOW?2Q0r*|l;tsoH5^9xTlw z#Ko0U+xM`;{lzp(F$M>-Vz+7$<_j}oftwxl8&S#ve49qs9=P;i6h}0N z5tUnt4wkmx%X@oEHh2~{uFwhB@sF{911fsV)RTkjG6U2n%FW+6Y}K9TLM}P&$ISML zAMdMuapWf>qPWIx4-%_i{Xn`tcZx*K8xs`!T*M1POchkSas1-LN5h|=E-4w>JlO&` z1L@2U!Vm480ZqXpjS?Q_p*Js9$GkB_h?UI2y-<_|OB@Y6pPrN~std#qOoeP>mGr&T zMn$}GW|iJ6{SCib7{yC~Md&tV38jAA_I6L&>mwa>SS@gkSf9;V_D&~7(J>s?9_rY$ zPTOYijfsU_@Xj)3)*O=iYq|7@4O-u0H2Idk{2MicXP%TW5<+Vvhs zp?&XL&DMmb(j1M)eUWq4;Bww3jV4p;4owP>&iHaeFc_Gox>x1j2Kp9I$F+h59efjo8(0 z#BR=wrWp*gZhMME*eD+P|1`*0VGi;6cHNwtK;Txp5m0Ah(Yt6{+pnGW)c5$5gHT&P z$RY>I*g)&l)~wzNCEXTkP`J0O4LmV16*Iac5Tdkx1e+QMlLu&=dN8HKHCY;xMvbD3 za>G=Daxp;wZ40CLXjsxQf8mpDW@&Gs8wmge#@J<>t<&FrH9R&cr&KTThVfqs|4aF~ z#NSu00R}jb%{#mqy9~KvBU`^PjJ1tQ(~l&!9=)O`{0PXN0LcS%<1yka!6X?d!OWG$ zAx+Enku%6@(_H`rl6;8xw43##bFavU%TbN7vuYU9Z$W(AVim8mG&oDVKATsas#;rU zYcjhPJN$qTOP&1z?#+7(lLncR!W?`h+biP(y!>n|ryjG4J~Q?EG{drZ!^d1f29Uk! zu6KTXY+b!>PEY!^+Ju<(3cwwlD=pmI0Lttd>$ENtO9czUY%o(kBfU*b)PK)%KXbhr zrX8S!2N)(mC}`C3uuy@t^mTXAER>-xs^`wvQ@^Spn}wPj8ko}tkn*#Xm`n{7*^L;` zCnft~y-9fBKR%8by{qs;3;Id!71ZI?z0pN+FHs_K|SYD!*tn|%n7}2)Bz^uP1O2* zdhY=@W}M8DJ3LSCbw`aArAK?5q_60p`k;B%LQa5>DAT#Qn0F@ktNgTGes%|FTEs5) zenT+K(-k7_-W0BKpLmQxmA?b%YA}mIJ_>X~c6Wvq1$d{U6mwWOgm5jhVX&qtIk96N zSswNeZ~M!e!5};OEHE$NOE=Z~mHGlTB)y^hUFzqkyRBkmg|@3tSLF^77vA*WO574A zk_g~Ue#o1NZZWSf=iS{8>-SI6mxaEw?{zQMy+`IDgl6w%O>PhGCwP^t+jW+;QmLw7 z2kd6&`C0q|-7O!{sabTk1I+Vay*K&o1=2OX#2-q=7Q1mEeq=&vH>6=g(-Uux|Estl zKd7vXbfl>;?cNxD;9ELwFYr$J0%^X)Ii>J=5%V4iT&X}`Y{sN~7x^**GK+kt#sb!y zHguE?&-}!rWtI23OK5b2Af|sJII7?nfKIZ*u`lab-36QtfH(gkxje@@cenO+O(Vzd z6&_4=pHz9{Xyp*ryG{Y`M~0 ze63oU?FP3HvTxq?p*(aqc&fSk*nxdlcHb!Ltk=9FsoL&)B&qwly-~fLo||ii{zByb zr9^dRg+_cLBdPaMTFK#3TcLj+^ z?tJMMj>rza=K41k%#`sEYGD>16(L9nQhs$0@Nqc^4A;WzKB2n-d+!)h7!}0CQCBYY z_yj~D(^hva-c^j)0+~u}(VCO4P>q^q>WGvqy{_ZOp2?U!#+Afmj4-$vy6i#P*2t7+ zW9QW2y`VwPDfP1Shng0#MiKX_j?fwf8V7M2N>6!6M<_lYoBCdk)UxpM&ySNIExlXk zI1rsMoT98^c}MM1)OL>K3}?evIwaJw$i^S1uE7-AmcomwXPEQj&@E%CCXt!PwB%y$7O$j5T~w$jA4chW z2F=XKW<7QPk=@Q=;M1vl({Cn-XZpcJp>XSSryA75;O1GRRubQrPqyjO=slkBEA53C z?8;mM;F&ezU#GT9028B3aMnS}g@}Fzgaz-~h0-umrr9NFI_WymfKN%M=e+9!!D2b0fzH9( z!)OHD%Xy^Mm`Q!|(mmzUB@hOb*iCG1d46*^T|+6;Y{nyTUSmxT}d8 zB2&i4@ka-{|6zTQS4cj+;)1=}^tLPkhKzWbobBI}^4#JXXWbQ8)HlFN(aVw4ZWZ^h zYaO3*VUmjRx*Xv&UhA;6bD-~}lRFgKX2&O;K7^sQrg^?l9n+C3;-mdzJ>`;IhUVnG z{=lMQ-7KHkJy`|^v6BnwQltv~UX>F*J>to9P$#jvDXam}sy2UPo zDhn~ra%RP6DnSVplp&}jj`ohT-Krnr^etZWGOJFHf7=dwoOzgWx)14S%YS6&kZn#U zrRDCrO{6B3$G(}=PgX->2rBZ=Q3e#+54ldc(#iC9K44p!`EhmfxpY)MQdR>dw)5As zSqefUCYN|VW&~hzTihzjY-i?MHhDg;bVQ64x2f(0!EhX%N=xkvuK;03r*+&ZS*FoX z!^oAo*EBaddJ6Uu0< zFjdFcomOnZbA!27r1%;*z}%F0nfG zj!)?hx8NdP_6{y#{RcE2b0Yg=OfFXY;E2-d(s9f|d8dY?3pFH$b%Rudn=@R;@{{R% z?WAvJPf6F2%5=97Y|Y$VoJ6@GTcEeHG*~p8#iBSrT?mQLb(GHnp`TmrV~9O>-;R~2=1dne4d1+R(rDi+iYJwS!j2O+<^q^6vn@sV=>OXFA{opx^?XcK=Vz%?c zcD7^3^IpY74E4QB;YM6EGRF9QgZXyg8Wg(m%9&ABaQeTzqE4m4cvQ2Cdc2F^bV|*2 zcUVSQhwSSD&oQfEx`fx+`A%uM&SskTi*9QTM-y{Z+V6wp4YDs zzZ6}cKgFkjt7FqqFiI@Co=q_p?PxreuyB|4;=JWBtDNxRF^BAjlIy}f(dTRlX+P`p zM-ye;V)jII#%M2c(~E&PbKeDcnwm``+SHRFvotL#UN0UK%tF4g3_;~B0GclKNIL>L zkt|Z|`QuxS&st>KT3&>HfI4oJ;E`|yxpBV>k|gtk zEdtY4_x?gSprB>OXtd*_`h3PKbKTGS(l2P6p7MoPhfu#XQ|sfOAnjNuyN(VWj0DT76@4uACxY*p8Ye~;^Jcwz4f~{@!3h$ z+W2JUm@7{uMN7WBej&}aZ6-s+|t6G+xV_fbjw@) z%C#_xl72NV*Y}mqUF44q98BK7swU%MzjKom=H8r@&mMFgwd0JmwAo4ev`L^};|Wq( zF{ZCEmmEzov2Yy9VL4wR%PZ{^a*$s8Sa1({#D9GGa}ZVGB}Y`sfYQ>B3Dc#W7mS)N znvD!K6*ETl#_3tcbs!_5d-T{LO0O!|XuZ)P>h1bdj?A&?U7Rs}D!yqb3IJp&JQh&h zzL+cKN9nVVWt}QfTlY=ZLlGEd95h4n*xu1(-Qo0n zhb)^fepElbdFi+tp2i-w9>+mMD-&&QK$G;;ZmIjG%vZAzn9rlBt7>~Ph}?Sm)LAYw z2c@LPpSfvn6J4FM;=w2V$_tt{$jHL>%&Wj(g|#HaqMbJH7Ql}_{kY=Vf^z!zxzIB6 zlR$XSWujang!A5qsK$oKeFvQX6B`ER(zKe>0Wm*%8h%yLu3EQxdi#<*I#TwZ(nMb6 z4vRX6znSET3GZ^HPYEX2BZvXKE0{lc#WZH+QE|U!ew_OR0Q3(cpU8aT-Q+2`+@n$I z{Z%h#N^RIy3SZ1kGetTIU1;-?>>iN%Oq#S;`fmr|CUT7b0HLn)Op^0qNmSaa#?k+t zFM@i3>Vj&Wiqq2+`0vu{9uGUe(tO)Bpp%3C*B!3J##s%67RF-Tc(F6U^Igw1;|k4# z0)A}qiZ-;||!yXmAJ3n&qt>A`C| zy{#{GxYX3&*bu=n!Zn=pNFz+RuiOJ$(IgRE{nF^IYYFoLDS8+#9_Xh>CU4^I(zRE+XD+j+F?~m z#Z~_nq4@A8D=GV(HqxiVDNkF02Qq`lK$@A5UR8YQUW!F(CnJz$ad;GEAc$__ zXW6P8(ms`%mL~`oJ<$2hYnPPrrw7yk&~m)s`{i*>AjskML8$+DUjC?K?8%tdDL_W` z9^!(&>;HT0#H^H{h3BSB@%kmfJ_4ddEsXh(^#+jQe8YB0X*}t}!|PEeD*8eTCSHE? z4$oIE49n*R`nk%<9*NAa=u2+Q0PcDTwcwFIPuS%#$~l^>3so94A$JciUQu)6BM+#*eTJ|O@WQOdWJ&r=rurnGcG9#p9?_^~iD?1|%mAxG^ zk(HUujOceg>OGwI=llDs{&1e>x$pbh_v?CH%N2~QOux<#L-FvEn7Q*Wrf9A%h{oYt zb;$8n)4M}%b*+}f=b~#=*MV?5v668R#3t$}!-xR@F61tA&Ss5s)*U!}JQksUSH@>c zxdix&9(6P0VYhba-332pz`Il2Rx)-)CFIX62@K zK1>!(JgJRrkb&l#IueRPx#$Fh5>Hv{Ympdc81ASrq8%n44Fx(hh6;&yeuGr}(GA^C zr!s^Do_iB0%U8+6VRO05cW{-|$TK21HdI!?8z?l;F%I8@F2?sNP2T{wuqrIfR?6Vq zt-JV{)wUJrqOHtXd*GgUIo@KiF*$nh?4|K_KIZwaIP5q3SY4;9x9C9x=JElGJ zTlU_vGO1gdr|F7wtU*yr=o@AH+u|(KV5_!gLmh9_P9~be=%MlTN6B!r+<MkNaAz6#Uhh2I#OB`s!vvR=D`^=P8IP>RRy z!mXcpyQi^t-J6KW5%U3pFbVVs02{=RLG}G`t3ugk#24{K4j&#bJ5S(#DlU8I1tn?U zj^#t!cf^5_r&Ak>%5IyXPa0&up2@77wu-Vh&u%(jW#ps)D9l~G_BoCJZQy(1Nshuu z<>jG$4<`|@4s7`l@%UnhLC@;E@X)Jc&&Jtd^g;D|{Fq*Lp&D#=OkX)e3Hk2d!HXU7 zdGbp1s#WTNfu-T|{SN(pNk1v0&8rC5)LS;4pu2qO*e)gN*w`I2=wdxLe<-EsGCm9Q zoBC8=DI({~mR!A&k7rJVJ7DznNvsL~`Z9L{gf4p-K@`+K6arv}aqm08kO##H+vxRv zJ^TCfXNcD?3JN)XK3fd7CJsOppO7mpWi3)jh9h6&RK=on(^{*)i)KRNyC!9>JL-pO}`$BrJF_(j;C z>mJKj06IgE?W^b1>q8w*&&(HB828})p-aebkvB}S2>^xcZD3D}2ar#%`^bJq0dSbP z8=t;PTk!#T9@DS2=@M@` zB6msuSKy@4+DDR$BK;Gd0fo=}ji;*ZA}F(ifEP$j+k^`}?=QcNU*Lxh8T` z*P&9a6fVLR6>lRz7XQZAhdG$Slj&~A6Q*Zn?H6Kmb|$A7uH0~tc`hw?;;&QL-M-5! z0C|(#yeP=(IRvb3?cb^a0Mzpn&^`{Cgxepl%f9!|BrB1!p8NXAiWPN81{6l(XkrnC z;-Nos^G7VbW$Q-TYdjL0*=`NkyF-6plV$rK4Q8Y-^&J{g5HRQdak={^I*oAjiRP} zcdb}~w+J_<@jXg@Pdv<6aeww_j2iGb{XBXX^l;ZPwxY?|!V%}1%rcmO_|;eHtx$W2(39KHZ0}0_DKJmDb$N? zra_q*6V$9SpMC8oCOCphi738f%&+EQW1w7NTbZqGHZN+yh=i~I{m_A+cjr@zsaYAZDjMKgPU-6wQXrpRuJ+diAFRn`q)#sT$Q-Z zovZij2|$7z)p>GS9Znfm{QJb7jhp>#4u}|`u9rU z`)0t?ffeA5%MNjd_+nFoE-zjI?6k3zcPY-F1|7Kp%2;`~CDQ5lUJd1QMfY%~MjPhG zJIr)B^kTEQ=>K*Ken^6+26y&)Zl97JfVrwUzV+lNxSUP-P3k z)xTH%`*+si2-Wty}0vjv|XoW!tW|QW?PK36!&9|B@hh-p?GZ>aVAYv#b8-#hzUl>WQ zl$lzlL$>Fry}kLt%zil^x270`*<^|Ju4i&u0<2#Pf@?1ZW`}sB(1>1@OJ7Cq9(eN3 zs#w$vwxw{V;_Myw$>f?igQrNT{8U?bTw_(nr7N)AWy*NNycb`#H$RgatcGA5%Ha9L zdR;ov&3!jkl**Q02K+wRzjwz+3*SNZz2=>0rc6#BkUZb^hS5HC9YucP$9^t0-{o94 zJKOGT)y!pF!ZAdAK7u=8(P*=_yacD4_^_oPuxsZ#8T=#!wr#rli}I4ruBN9A<>%;U z_CRMF&L$wa+cqcUZen~$Amp1~JET?6HvT1FJd#~#AgLCbtZ_vCk6#`%i#{$q^jd_i zh!X5?IQq))tu>}@pBa;rRUX*3a)+A1^0*s+g(rWOdRd1!%Q3y?bMk<0`e$?@{7(Ml zd7-ly8Y+v@j-5z1aAvuhFqUG^vFrGG&oi#_6co*tgYbty$yyv|FeY@h#{x9(!NEvv$ja}tWk$A+xJYzrmRlg3^zs@LS{U_x3%lsyb zA<=&M?XFkq%fjTyVLIJ<<}k~b!>@Vgq2)7cFVQZVJe;E-dL*kmtl^QJI#HXd6C#Q( zB!(dZ{m=K%b0@jC9_*#0ex2h0_;b0Kixok9{W$CHNoR*$~i<%SX2JPS| zo+?4_*`D9)JVuTpN6rEZnapxl|0z8T;-|6wW#2$6Q_v}KiRwk(T`~=w$uFk~8aPe`2WHT`kucO&>L8@fHQT z&R;j1)ed7gRcxL?NXLNldMW|nEu5Ks@mmEE!VdAt*lW$Z7g#;Lr#@Wo;k>#CqcyX) zxk?|T>Wf7GXOrDQaI(zQqCSMos9=6BFHYo8CDL$cKeLlwj%#}_lVVKYsGi1uGwW&l z@08@Pg?q~eB3SO>R?V%R4Fp|`aQdCqz1;a+tp>}51zMtWUk(?%*y>#G)G;$QoA2V= zyVX8AM3IXjCAph!&I7_$t#k-+N`@#LsHOV`S8K>1vuCp4D z{S)~hQgV485t8i~dQ@dJS3$GxfaWF7jfwet#P03@KC{c5Pk?^WR;2I8rAKy%@Ca%V zJG~!kLLF(pnLeEnzLt3w%)CQoIF8MMXu8ZRvYh^uNx2z90#J&Mun;*kzdy>Ed9PyMWgR&v`^o82P#(~AKEwctxmP?C zu$${SFt75Hc2+VcVGP&;JLQ}Zs73G%H9|3L!`n4QrJ#O|^m7x&x~p+Hi&zKC0~7$( z{rk}2zwM~qD5A0^F=JM*e@J5$2GfZl>~g6{)w0zc+B?pTtBpdt(?UJ1-(y5F_gzOh z;aZRv4my;3*OSEi1k_2Sd@c zp3iC-d_dX-3bYEU(&F=F!0f{$H)m^+;Ka3meMg*HwDb0dFN&sfzsS8K+9i5?7zK$z3wb|_j1;OTx(8^)%0Tq82vYWI;4A~ z%3Bt-+rQAL*_$#B%`^r1*!&mo4oyPClK>3~#am6$el})pSvDV5uhfTHtp(VTsP>qT z<&y(RHLL%L?`wARYSTmzb zf@%D^zbIE#`{uv)w8j*dJW?rUb@;lT6_sZ%Jp;7-MpG4m+u z(A!^IMT)K3L}6UR)&3bq}i%+5u^h`U@Oj&VrV!Q1H+<%0~e*5!n)7UA=wJ#t1_sWgWx)GghoZOX6T&gpQ%;B1e;cQ8)x zLNKp+UO_kF@|078^5WL+t45=BDEAt&H#bnow%Bpj4Bc3ftvt4y+YEe}=Jt=s*$h%n znX#Llig?;`5( zG&<>w8>qNIf(+L}kisxH1wPzV!fqq5W{EZ2$19KKgnr4NUW_g8b?)DSA*Y*0g)~Ms~LjgY9fJt-?%YTAM`HTjpAcIRkBNiEms*xbtf)GoriWsC? z{z&hL$ySVd5QIdS$1tJtI6{0dP*zt)6^YXciNfxn>+bcA3?Cwq&(wZ)w*Tqf(T9uL zoS!aA$r}l>7F)l>*+1DgRI7K2U+5U&vkcjwKYYyT#VA_`+!2d8X+-SzE>gN>;95QI zA@3&fF4weQ=ETE?Q%Jmu&nKNo@7Ot-10g!iyKWqTXJSN-=)9iAV%laH_ph_CW3KvH zazuVb=E`PQ-45(Mmp55Q5SitL>VQfxWCPoi!KQ~dJEzpgA9)Ye%#5p_7{Iam0;&Yv zN$+CLtr=r4A_?Q|4_O#e^{cdrk%FuCg=?9PQr3)n-{I|u@4C$zMgHOSa!Wz}8dNd6 zEk?4iP2q96vp47VK3@K0GTxJ1H_TbQu-x}S4ItDhwU3;WP0pE7O-xHWa@&^|NQhuX zamzMT=J+$(U)0loTt#05-Fcp%=3t7CCVgBzU%9l2U$yZZN30&x_*6sHCSY`!^tuv~4k$6^*~P+a+<>{S#*6AwC0yi9n8v^DP$Y*`7s)lB)th0apCpPv?` zHQQUGS{eMvPIy55SgH8N7kQy8(4Td4R558?Z?G&5xu=5!s3lvAR76u~&y)5M+-JGG zr6R{AdbyFa4&$y-Af(t$B*MK?a~Epy%@7D!W@si@A{=QB7Q zD)4%~G$TYV!z^rc|P6`?6u1y><=L--kgJs#?)y}1yon%*@0@Zq|wVsLxw+PM<#G0b(w z(+eRA9k)@N-S@0g_6AJ?lmttgD%=ZAuYnHpYcotqvxh0Z&D}d2oiR1LMdEHN-$?4_ zaOz=tX;nz7&ztc-&WTvd91lp1$YK5a?&ObQ(S%xO8t;5|HMtowW<(6;8r%cE0mWZ! zo@>RE2%)%LBT}s`8}__+8|u*(r%!QGTLz9#k=+xR+1H|5O`&U2r%W@~shZ_pnWc^! zV+;_DuZBQzd!?*KfB16_x-=(Y1tM^3Qi4p|YXP>7u2(ur z+L)z#zH!WY6ft21%7n_FPvqO)SX0lDKo1>U#Wior_%m$}`O|kIC$Sa_V1oyRs91`X zMf=T<2szJmUwDAbKR}q30B-9n0eR($3zIZlrW6v}e6_V2Y?H4jch>h%=+ImxvN$oS zv%z^%8b{ZKm3HN*jNCXgAHXoN8!#RqY~X!!oKOqoKMv6Od{oBl#bqCaM7$ZsWZ2iN zl@_o!f=@UF93i^LPCzOrA}?9RcOlKdkyB&3OP9vE@6etoV}&KOS4Piub-@jL=|eFx zULza()N^y%5*nA(&L+TU;Qkx6q+@5LJd_W7Uda%fwm+4GzD;b|pn>(r$_+C-#yHZ? zAI;NWJu)=6mAV?mWwoq(sj*uHTA24}sqZ=PhDCkJCMe-T{1LcX5(7d!g#hECn;2S> zn-5@IiH~9rT+u#N4R{8DJ*7mJT)Xyua#yo53A@8`0TOemS~yR1KBD7w&ZAml5TTlv zz{JSb12IE)n4=YJPaV>^>k^F&_&qY+egwjql-irC#e`{OI_E!H8Lu9e6AQLJM2>Ab zNsAj*jy#ezWz?KV_?|x5nmFh)0q_nLm1l{k;fC;b-<5!VBTJa<%48}E1^emBBPNo z?@j&DG3@+Tdl)Zh38B3xjH@z#HwrMS+Io&`B=u{iROh_{s9omUfcjv6(qDRb4ykwC zV){lLNTMiap|2@tTQ#?0iWGGO@$ zhUD8*4rO%X?U6~@WZqpLCq{vy7(}Mm30?2WeP@Yub_M6B>I|2^c=wBM3T|$ct*zh= z?%Pt?vo(*QdCNQ*wr6X+$FMv(^#2J(IKYvstY*gdDJY6N!kmCfP?unn(3w2OcG6nRoLP3~9Gq8gIBsm=BC~a?Z~=)8-=6wGSw?zs=k(A;tMvPVheO z>#ldUA@lx%>1wxtBc{~LoD~cAmmSD(lu1&@y9#qB9ACUWNZf@`jwx1t^AqG6)Y>fs zcV99+I1aNfuJHj4(K0fFhYm3F&SG&@p7cjIm&Wsx-{CV)>7vsVpGbS9{9_Cz0@`te<6^K2D(7x=Vo75^dsOgJ5(&!@XoYjA~QCI(mT@DxO3} zlxHfzNVvCBIqb32EE2Na*^WmhIfU>mPV&xgH6JtJ%-ZHcWB864YD1s%o-aP=4@G1P zUE_%lF?wF-mWv|!S(Xt}`@St-glBnDMP^es6$7L@LSfL!5`1Jj1G&$>2Q}(O8u^XF zHwr`i=Z^Q#bTqR(BEbpju5g6*s_1V3*B>%36q(;ESvm5(mRt;(@G$xl$|Lo3Fd2$C zE?9JcGmN2x01BtGg$$cBg;mT1tSbk;rSOH|CKXJO!#iotGYbMe2DG8iKDWNw46|}+ z_|y7fd?BTfYKB&#t!Uq6-)=$Q@M#gIs|tFAm6&XPm!Y&<1&B^o{ZE~gB?jkjP?Jr~ z()k%dYO=~bfrd8YsBMKgb;}T^i1GZUN*Cs$}lR1Uz~vD7ERcYU2K2mHyGQ z;oYET*g#fsy?(I*tY%_Z9Mg6_+?WplyQTE@`}*)4h23n^rW zJY3yb+C?0topF-xefZ3%`q{K>8TNaD>wP@80NWx6ok?l^Q2mko-q0M%TdXQ^$S^jg z#<8E9@W-2xqUab-{{4D{)(>sD`7ypM3r&UWCt^{1d#c(k3|FJ7yo7#ZU#nh@* z>c_>~2p1P(=n*CiOBh9L2^1u*E#!uu0Z{SnLRGCoI{Rj$vLLz4&lg6{Sab_ih+8OSe zk_-XE(1VmwCQ5JT2sY%SGAqD}DYiDN#MIO{XtHC?WYi}Fj$ zMFS==Pg~N1V8E||-04sWc{g3BGu|G7u;-z|#q$0GnO!92iX3IHDO($k%F!$gqlf%<}ml8*@CfA}X`wB4KXGwJYy?aDIicVY^Z5#;AF{7SJ{TmDd>Uwvl(=VXyR!OlMXf?fA{qM4Ee$diUSY0S*9wdi7! zdSJ+F)p&MRoXkHLRQxUVZfJ9ejOq~15{N>*59+*+F@AaDO754JH^=b1yF<?3q6vyDaW!+Vs#l;JP zLOsKxF@>K-@4TI3*84f}HDC7XSCG7j?J^(-2YC>%sYogo33}#^QiL^}1Nh=)v99ySQY=jgK>F$$B&u8wfCGW#Ox0~=HN+EK8K!`U9 zK2KD!s&?KT0VQ!UrBbxa4Y1;14)@{9L!l$O`FRK-yeG-BZIuS4%PLG2e#xG#i^%VN z*EfAEkERvEH}>yUkx1@(jhx0u{2wwfMuEp7K&~E}gr*CBPllM3{r?UuVwqFN!;>0y zV<^~@Cbf|o+zjT*O!r{h%Qj`%ey%3F^qH{E6EB|)WFOnBy>CW#y>|8b-Im&F+!W8<@ItA!n8w+q{1D1BNU8Nf9FJ*luaJ(!R4ECI#EmCgcM+ zu$7SnS<_$j&zMs!MB2(vF_#q7I2v67Y%^C*b-_}lf7AJ`p#dyJ?iykh980c73CS>! z_upaY8>ZjU$%pW`mCxvZOe10{9862YQx_1h5<(?c40J@cJO#;Rp?As7B^i1ra^swm z!8thF#l@AwuB71x1MS{u%iG!VtDs|OTEhc$Cni7i7vWfrb8|J*BnazzwAy?O^+Lr}Ih zSHiCQoU_s#4oFt$677Qig@5aTk1&#@k$U!JHb15n(q<>L`H zU@BR2Ee{PNUNnRM1y!2cG115O_Z;^=f`hTUMUxLPofbl6AtG-$xpiN0Gj{zI1XNQ( zIQtMvBOCvBX+(JaN*k)<>~NW1d(CEzg);&n>Ivm&9Bf}Eo8o|q4J4V?_r>l1wlBZW zLX}nLqgVF`Azm1p(W;Prnc72lF1WdM$FK~Az{=^_w#R4PA!8~0deJi)IjsyT@!Vv}(_!68>p+~dRFoF*2H@) zm@Hf){Nj9MNb~c>57ZTX>}i>5Zblf~7~OG(Nl=1wsm9JZ_QZWM=RT!G91&SU+;=vZ zzB%Ub7#9~;IHWmSf97mF!VYCZYpVEHmB!Plc1BsC0$DcPK zXgi4=d^2j8iAVpS{g&TI6B3cr4f^h*+?t~*|PTl+yddC8o zzz$LCTS*B@P3Jj!VqFaS~bSw%x>D;Q=l2{cRmX1bapoRSts`a*FS?1?iG0`fSU>iaLN8wfaD| z_cg*@T&@}FGp(5toqTxoN14Bq$Pvbn;}+kgk?J_O^Zt|J)GXJeGU%{B=#HtonJ^`MP_$xSK9l0M(G#$SqcOtJTfwyYXsP5nq2dL>E;X%w7wLwW zF(h|8O3k#o|M?Cny1S+|{rTF<1TTsnipiE;uYoLRi>;;Jn^`a)^F- zvfQ#vYgYgC==|e4H7RstRJVaGWI#w!m z%pA4MEA0`awR_M6yLw3YS}+tzTS^9AAH(LnW~0QlD1gb8fuBvCShm9$ zZ@;|kuS%A&=1q2Lipes|GiB;{5QBInmF}K}@57IxtTaF}VKDD3 zaSwKVuu)?-R~ksXbD7c*&imw9bUu<7?cX&BSd51TI3s$ds|FxI%sJn*R-irG2b!59>Z$+uUbG%WJw=?4G#v3$AUZ4fJg^MBrT^#` zw2pp9>Bt0nt2%pNc%$t5<6%o1xrfbNXRbFl}tvQ@Mut0q-lxI{lx(zx-HB-kqOvHKc1P9;1|<*!c^619v>->kW& z9W(^^fb4-6SI@Y`#GiF7>hKYVE5pz47@o}}>qIjcs0wGpemx9e+Y_we5s{qs5%dh* zOf0Bg3PMy>6|;s&#mDADzra}Bv;@B+0$jgkMz-1M&2We0t8Z*}hMukF7>N@3FdYuO zf$9>z(odFMacwP3`|Ui&mLHzy$wRCg*0%GtXva;s8%1bo9XAi(3nvQBT+5l6AU@oJ zV`8Z|@j*(#lU&M_p1hz{A&K0Z>*fz+-BLYRE_{}-e4(9g&pA->A=3`1184N1Lu>yA+>1t4&;rY@H*EyV@7&^$9X!71hcCe zGxvott24>%a-#_foNnurvbfDP6r5v8{dg42VxHB#LwxtA&#s=1vN!s$dxIx}$vvHB zDlPnMWi~5Bb#AZs2|_V3>G>J&2ofkR2^}s|+^zCnxPfZ`lCL5nBE}>L%wj>Mw9PXs z>OTbS?3<9np%O;4Iscx)H)kErCf!0>NgRXZcP=^5OIeofl~@$W$FyGmZTJy_Jg6E_ z&7kD8ek-SQ$SS0rf7}^oRREaZRC0AOYEzgW& zB=@T(KW2O`q(~IVCRt;uukW06}Gj#d!s~Tjp(DA+sw7!714=POsNO6S#&E&_9>L7)8y6bprd-D5DAWpi!WxFdPdYXA3uIow}6pV`K z;=%_nQzD*u%|X^2@cz5o;$04%VGT+LRg?#nwRjCOFunE8L_S^mdA!bs&Jr)BT^i{l@_@@;B;v3_E4d zReZ*0$qz!)PWG4ydwjZC7YCz`TcF=jb29Z2pCI6c?azyN?YJ*Pm|fmg2)hFmC*F%mUidi;X>J|xnQBrr@T{>*pvHL`_1_pAn=b>#&*lhjX3CgHM08dQ9 zaJqnsjmd--Uvg~=9rG-S@R zmm8XMj$-Q!_>}qvEQkX8gmbrCmnY+palVGFIpvh7o55a^I6#3I~B!lxS=6LQjE_% zbr}?dnck6vv58n@+eK(EXTq(qf&B%~qu1E^UjMwsN}G{!3FiMKi^I&MF?K=0!;0qY zZLjiQ@3`IG+4x%3Uz&kfTVvWLoeD4@7M%Bqa4G;{0NJBhO^;<>nhDJss9PxuP_1YE z2~2?M=mn?;jE9e2#ATyB-5-6$*o_Qp?h&?sWMcBF9iyK{jr_WaggP1S$d zL1`2Ja5~Xtpy8tX;)nT? zPxZVJCJq(sVdt&n2@m)LN^t4pmMrE7g4V4-3O9~li8JlWsUT3KlR!qNuvh{2+LMsa z+dDsAgDi6pC69wtrdcCS{i_^@k8o2&ZQBhrjYA28k33R7*t=a-`QE80@!myis)&PSMjr_nDi`f ztO#Z`7XutZP}jZS)tca=Q)L%e3e2t|5fWfO)B~kJVjenO0wpw*x-dJc{0he<$+Li9 zU>~vz6@=?B>U)??=&y8{$Ue_{2lYi5T_2m}32-dc=fxbKfufK@G0AIZL$UJj?=3gQ z@;Hi*_ejN$cn! zuzyod=RQ1d>dMY6fx)8~_K2S%zzx?A`gyPG!Bt%&fuiaB84Er1CnltY`{p!52v6Od zR-9mZs4-m0-bnqFMW{$Apo$yR;^A8)x+Jo*WFcR4*&l#c1M7Eusc7v>bgvwIurKs$l#}}v2?jD_wL1=O;&$ZLWp%BVvo73TEzK|C+iAFAO0D zOR)!#2DbfMKnoyB&|QW6=$5k zaOHvD6#D!ni)XHraf+q|?_qF5*W&t{{p54?IYJ6>bs;g>s1Ji{Gf9eYG{WCUR`-`3 zD3JZKxXTap(Z(XE@0!RelIr*lNX<`k%(KOUov`SfynmQa02wkNzZR-Mb{a@4>{trE zDOzhNOb=F8=*#T2z<4q6wGN&=!a|A?YFrUTcBPj1EWbDkfjX22fTIK=3?3^TE)w&T zNTX3dw8Tgfd-N! z98i>>O+;XsbT{7M(c5|eNt_sfS#GJ+nAaD@0}$-RSijPVOXi<{PKw6;^zLc()Iv*^gK0mN6M z_#@4ACj%iQUII44SFC122&FmVWb5#bw9u}$wQss~Z|&dOtA3M2S#2gIO3}?u;rfil zye4_#K%IaIU1-CeHyir-^Q(*)VwWw^<9^ge#zGd2kr#<`d8Rj0xi{GA% z1tjt7SA^%<@I_?c6};CS@*P(p-@%sut0w>Zzy5x^mBtAX^(G3SXw203c&l*;I{pv8 zw&}`%r@U)e_XrP%vKep$p8W@qv;F>M&a>u5J! z^1VXuBL{=N%yTol>kNkP%&8SZWl-v+CbVFaaAy?RKA zapkYb;?LLWi1`#j8Nhk0?%wUeS>?zoPYb>1JW)EpUZXj?+uD7Z-)SPZgTuTE9kpVF zIQM(XF|xX^Rhv#LnA^=wDHqRdJ(1#a`HCb6)+ci|;|px0i;w8I@O90J81Nv+fbG$; z$I-BN>^eQ3{IlJ#4DxoFPmChTyAbZ-TQRXdsc*JJPcJ{=6iv7!pkF>g=% z`&YHc&Rj;nn!?RQf&OK;tUZu7Q5Q@bQ$E$+$fItd-=pIQv(tj~Ya({5c8QcNlSuYZ zoYzHzBf)P^0-aN#bS}ILmVeTSltw5_632ESOuk2SlFD$f~VhbC(Fa z*_5z(W8}PX2MklapC4f3HM$kfB(A31K#9IZ8ObS5q)r+7wAfrRbc97Nm4L!9l-H&~ zx2?_o<#0`Y!MzNt3`~{PWRLjp`3-E2304YSVD<_VEbDhaOeZ2k=t5<&Xd!Df`bR3C zyFsw;zzIMH5E0=qGZFZCqfq$r$zBxJcg)^z4*~9Z@b!qR)XC`gADbM+jpJmWTWFzE zxbUAIeafcEqsS*KU4MRjQqlC^zutXRyAaV0Mt0J$LIoPP5TiI*Ki?*@;Q3Vfc@~}X z8V%>Gw)Ld`e%)X1mMv#S*)p>E)c1ug#IXupt9q`(NwEF~t5k1rLR$U{kHp@ugrE7O z5;;}MT=O7S{K(QO#ZgF|&}FJh!!rLR!}tJ-NXqE2a5l?-e`L)3#zQwp<7OAXPyL1Q zK=$3AWeNHOwmD2a*;v_PZdH@x&G~lw(s~vRVJZKgP5JvPe=)z=HmZ`c+I=h4W_|OS zYicpHqa(ImbYwp&uQV32n|*QPKKI}EepVg<22L%ByAbJB9ML-SR*uSBDteXTBcmV3 zn&Z=(A8)=)^p{>*Y8-GQiQw?)$!vSY;33c`;E`ZiTmK8P@F74;hfRAyxbqDfR&IfZ zEL2#Bo*B)?6tbO>&p^KPeqkkvfF+UTLfYohvwg%D2-oV*)8C~a|L?~LqDG{%hPL}d znH4Cn?3?=e2!%)qSlxgs`-YlkEGp;ljKfR=*b=_9!zXvTz z1ZmXAUCi)Rs2@>1!3bW59v>qI@Y~l4@^5+aba_a3^En$3U^pEsa2AiPh(AoG^u=*}@s1=naLue&85H_tq~HWvI2<)#6| zaSl`MDf_)1-?`tL{6tJGOM|J?kKdx)M>dc!KiKkiPNaC)?KmBOL=pZv^WR#5Pw-ME zCwW>9(WQ|zZ6kV&=l(uWDy5nCyh!&F`#7WL`WL)f8N0Tx z9UqI|(W^8`j4i~o3Hbe%-BqyKOTO<=op|J zCc&u?gXLHr5o_d^3~?8+jj^EFK=(Snr(J&e`D%5XnbJnC=yr+a&6OM@YMIwv644B` zE2rj_ZGxAAI;~6T21{y4BN)1FIN@CxZacT!B=DrD%suTzlGWIA6-HO14Nr-tWJk|S zlZ9GUH6L0szkKb#TYSS%x~s??)-=*knR}+o)MFKGV(^8AS%=m~HCSNU*l_)gb7d^i zfqkv6&`9s4UhgBebRwu`>R-m@&mPY_+4tl@J@$xCBLT~*Us_c~>YEfk{4j(QLJ9!i z4QOpM&yDmkMqakB50;m7o<&Ddui3oFU!}G*Qhh;Pwi0T&y!0+tq4IH>ry-S1GW&@W zc+IXqzZeT_RvSLPIb3eBytLG5B*s6SA!IizKq_79x*?j#S;R0(G}a%`c9(LUqb;LU zPc%fi!|C|dz zfnosFtPyQ*BZqy^J&Ut? z&X+0V&$eGHyK)Y4%D6CmKDoZ1@z!5@Fd(-tfjU>c;!4mlzMMp=?Gb9-XzG;!RF_|Ezwon!UlOvA;H8%kG*9-Ns!bQyDe zF4Sw&Q%2z8(VYu2JW@Qt3q&Sj{HOR|X;fO29mBjHp(Zl|1p1r_ zKm_J=TJsE+=Rbb;w{@dMfhAN7 z{pV@2KbVkhNcizRLh=9epitT80Uy!$VGJ}H&j0l)Oa!UOX1-rI=BWH7<8#~ji~nOM zcaJFr`E2>Oga?>YB)Z$2{A^@GYBu?Pi^N}YyZeWE$Y(u2#(^%Q!lw*8TC0}+Kacsb z{xO1q>Hrnf|MBg>hz7)?=H*c3dzqd2Yyikh%)kBGS@`t{99P6Tk=3>bLgmu=MWL8s zcH7bm##Q`^kG4@xQwj3I_amHjwhd?32pMuBn4PH zo17YcY#Qc%ugLcci*#e3OzQ+Mqs>nRdcWbjW29nT8bTEa^*B+&shDgy2Ek0sUjf@6 z0fYrj=@B3U?SH^{_m{xZA-*x&=@4kotRsQ1Ewk-ZBFb|lPzR%wtyjXZMsej4*G&s! z#<$7&hxQz!&j7PP9o_Ahx@pQ4K$O;c>eWi;H|XQI4K+O1MwR&OE#R+-IP()mG`=pK z)gPj|uZ&mX@LderH_6tC6!sI=Xtr@T39Hxot2IS0*Ve3&FU1{X$R>S%gO$1|tTuSn87%~UB)j^__?O0jfZQXfs`TxAhcPxbo^<4ezdElU zuX}VmT#e&qgqDQO=%9cBM*t~p^kx^8%a2b-e*8;3zEiyMM>+T$IDP|4#o0+i!cR6C zp{$ZD;~+e*+)2K5GuJBYqYSMI)z+bWl`mUYWS)?ErfI%v$-L}6`_=MhWE!g0KTes^ zHJQCvlIScHC?CqQS%!ekeLrXLx-(yoNf1UZo|Jz8Z7gS9DZV^VC9J8Z?W9`s;Mq_V zud$}^zxH$`H?p&)d6Dw#qo1?!LqEK#7eA`s@YAvU^5BFEEYRzRvpn4%bt7T&Uge_e zah*S7_cMTuKK^H^zL~Qh1knQK^`U+i-)OIWH-o@Cag;+$`Rit|WmK%ZG3gH|oUYN% z>24m*J6c)C==D@?Lq7Q^0q5nCnJ1!;-D%6O9OAvUv5)Dvy?S}?fZT9py7tJC>-&H4 z^CwP7G%P&l>peZr+T^JCTGuqQL^98e-7&Gu{+^7WsrW=1s=SpUK^ z@9+=T{E8_>728}$UL@aEVR*9jm`=QN2&vABV(neQhi`J zmHd+8$Pp*OjhGAT{dhZ*V?PEC8U4KbxH{DS`(2+)z5*n#bv@InK{Z=cOx{$Vpc7F{ zo@PEt;0LVGoT#@oywN!tOz*T-8xQmK>?@ToCyPb8bRWJ*Mb{Kg6Z~55h}fd>K`tyP z0wWh6=R!Lj*}T!0!(a8ILxdAr?>=i=RH|WI8!LM}DKA2FL$3LO*r@9j8S-V3Nk-H| zwWJcP*49`bmrGXCDqn-TRlnAfWcH)AYds-qRn!YZpF}-PIH}fZTSm*CnH_h1Ln^(q z{y`>=VDkhiUUkBvWcO5vMQ+<2iJ8+R925ZxG#>sGbpcY=G78JojHB}6hZOIUFdePy9hbe&21$g*LucosS=>9mZMPCWSuNXMV@Gh=0&Rs%xcpAw>4aF5N}{C&&4dttTlvbiVDQp~GF@t!5a$t-x?38{W2!&8e%`IJ=;IhJ zc-+M=nK=Kp{K|KIB+k{&(WM`s*bdV&^_+7e;^;eAyAswau-#{49UxCJE0id`MAf=v zPxH>`X2zEER=pEQv&``1X*&^xK1H#<8& zp2`yNi=G*5Fr_Nt-qIn|*hnp)3^28!Jxujd@EOI$Uu$rA`4V{3n~=8GY*7>Yq-9j~ z)|HQ73O@Eb@{PoAWcB81bo@MP>{%Hsc8HbS>+hhck{&OYFrK)JIke2;=gSs_zMVol zLz<;^LTOZ)^gB_q+_;0oA0z6;HXx`xcK%|R^}`X-*ibTg`j=E@6$(pS#`4L0MBg_K z$n<=?s=CA7h5Ewr)O;W=X+Mm0(NnNp*R@j?9kFX0&-i9iUI7_Prv zfO@n3CFC6yt#W+u*9Wvw*Tve(uwMg^fP`|HX7zP}A=o%U+K{})xl#mVqL6Ghglp1o75(xzaNu{Mz zX#qi`1cs87MgbLv5Z-&vdH(*+`^^tt!^~ZK@3q!mn>WRQDdX^m`IU|UfdO&FWPinx zlxE9VkBut%Aw8!@2OjhxwDEL&Ym=_VSAv7Xi_@O5%H}fJHuMVrDW4cq2)0F^t`ENw zSdhrI>KAAb((7%IxDcXd2aQA}B{5=Fuo zo-8rJZ0qd0`H@;JhwB7g9k3pQ+eSSblw=8Pt->(dg@ZWt*DwC&VPosSVL|%9Z^mc!EVu zwQySNziABUzRy?U`bpvH`zq#sY?D=75$0F!RIKLX|(dX$u{xw$E|ms%((63;1hQ-Wg7Uf z=!=!oeBwrz38EG|vG=UbE~q|@J3s$sY{)iFxHel)iZvzbb0`6-vyE+R5|Sz^^(cU0 z3>{(|&&PAE%4y)6o8s7J3U_VNtP`P{UE#I4zwuB>id`vcs~}0**Mom{&*eN}uhY`1l)G+E?Rzhkw|C6FE#^B7Tg6~g_(o!vM--v3d zBE!7~Clt)T|K`4NhSnf?WQ4d{WY=@}N{XZO~5crHwZ;emr++JBT?dNk5@_z|F<^ZIpCJ6YLL_~xw1PJ=FmNb(oHBw0Q&6%QLC}& zCz5^U&>I#pAIx=RNHcyVE`RN5lKE=!>+ja!vXAYwZ2AY#YA;dxy8{UY|=18n=n08Ut6;M_!1l^|0T~j2#4R95P+vn`26mkiXvsBz$5} zus-#;W)Iwer_W43B`VR#pN?$z(d{mTK{~e(C?}zcEdiw>8sgg7{3}1bn;bLveLrG|o$PaN6XainyTQ@dOt@ zh42mVH*o}4)77CnAkz2NgA|kurrLGQy%X!>=LyKn2Q3y3=$d<;6E}GRutxE+z^el5 zNjGraWWz|6OA^m?ZisaU?eVzd?@Pr7ZNo(Z#|bX!9MxD7LOmmBjFJ$CeymclBqK)v zb61Mu1=<66AOtuFy{D6^4_qxKQcJ8EkzvWY0eXrTK@y&`BOh&rslP{kjQKw=fNvo4 z^#)zn)<;sl>sk-M^oADf1S(oS39Em8{O5Zn_J2=|(g?*eBZ>QJuK_HHOxp;OntdCm z)=V2(%x*Ydqmy4~bYWUh7}G-*mJ0m^CP^~3-D>@gV~j{Lk8TOJ zZ54i7e6SRNwIXGb@h{D}7t27ILm7TDY%^qE7?ZgZ;gSpG0MkTY_U?;zM2)S1$r^sQ z&=P}i1z9#tdz|GpIx4KpWA z){i8qZr*wOJ3gsmix{Z~M>URC>Zle|2@h4Cgu7>loYpJD!y?P1h|Cy9RLU4o~of-H`c(X(4!~f8M=@3n0_A% zGcE^arF+!SIM%QhCcyNjRRkC@+`SJ>XLXY21i+*{yc=Ugk;3i(>t@wTVCHp`Q~fhG z(69QxAcxDYPXGxFWp&L}ld%$eHck$f;vpf@T+^|bePUH0`1`xxh7u}Py%`*6<>E?G zf7*nQ)`6!B4vn+C`@l=-L5s#SvSXWLP+2vDDa*2FoC6_CHo*M@I}qW?Eg-RJH#$U9 zU?Z#ffB&d3J(8Z0gBnHRsmlkBJhd|0v_d@qhB9(~b3|-r)aJOSBPEdw=1@D`&$WWo z#MYout}L>KbfVl6{L9EhBZ)EN@j&tcL;Pa$ALR9l5p*bJ=zn8XLd2gS$C;b^6RPrG zM0-|;VO^t+ptp^hN*g~LyLYjTf=jhQcjEiXZ>@VFWk>8%QWB${Ga^u#tN zKw1Mu;-Yp>`7B-4{;jPnXdD%aW3UP2Wde63v~IYd zphyF?|6NMaU;MJ;p9lrIQ*XN3Eve|Mni~sRHsoPe#GRxnY>M zVzfg4oRjG*_MT5ZoQtG*StabEpzfZl@%%Tsa?K}57$h8}Twv`=%2q*@>&1Up{OEM| z4H&!#CJvSb?P<|4ayX1UL!)}P8Sr?{NmYW>CdHtgp$NJy;8?@n;WwvyA8 zg@$7X0or7wF=;)iF{SBPw-@peIE;C$V4tv(oe?lX5_~i)hN?DP+GYJ(V7Qvif9~sI zM8p)Fu#u3HS-YL72A+bgE&5Im?gC`2a^&tHZi6PVR zvM#%Hl3Z_iVeA+M+gbpoC}osbCmntkSq=~aUpbf?#mI4ylF2G0qX8z71eR|Z8|i-a zw$#n4t~_jD&6!BmVq@_V>%>a->_7M*NFVe|(JjWitr>)BEr&a= zo~cwSJBOlo%%axlwSCPR2H;laah&27{oWo7isxYKTS4HI&Uia>_tA}GkqinE71C6c zE?04t5?7#Q;eU8PkWbqx1{ppX=NnznG@r|zFb-uO+lA>nMucI{0KirIyMIVcr|F2^ ztZM5fG&=5QW~&(~mr&~fqz5@;8bIw__3so#uO-WR>SHGw$?^&Ys#vVK;B&Z(1up&n zb|^r-i%5`na4X#m{IFJ|hvh=xMMZ)f(R*!5S14w@NOR`l7n69t0g-7nL89~fX}Xf7 zTfnDVj#p4<1=tYzfcBaD{EuFS9{Z@qk?EFXP!z<%YLlj3(V+TVRsH{)hzi2coMHvj zP1Gp!d)oh3>3D}H9G{THY^XDG2(s(dl$517`p5Jb3LXg>@)?2*W)}4D+)d~{b`Z}UOdK> zdng3R%gV$RBd@rh?KeNTILu}aM0c;e%H~JZay8WkJyGJKtt-6tH!AvbLQI|i`Lne% z0gI^SAXic9 zG_B?lbBUe(s()CNEHi}7PogGL0oBRc0wh?Y)OBNW9kX8Otw!Shn|ITXuJe=O?MeVJh1s%82lEu~9JS0&|{1i2h zv=vkfP5|;w*~VG8X=TB*B~Zx+FB?u}IFRm!Qne6YZ|O=x+z?IpYCH!-gKJnnNvUCh z`r?U-!~=XKcl$#-e2y2YwR4pJLmQM}pP8|R@h_9?oErd0P&i$EqX$Ag;md-t^CX(^ z*jEeDEV6Wu>Sv-!9j>TEtkJs_2KHbD!F!qJc7zXr$z&UF?)kjJ_+_Oxt{o@A3s}xW<(?a*+3iaLQ zMW+K6Bi~@CA?g$ditxCHEFNbdf}DTynH=yRF79~`X9HEId6jSpn%n01Ug@z^hG>5p zBL^kw=Wi>c?|)xZWG&1b36qfR-)N#+)zHJrP+s4`hCS82(8PC+9#D?G;`3vQ<>&(M z!q8h)Gxu{sW}EetB|?D4h00PWCrpWa^6~0A+`z=l@W|U%${T%H2-iy0L?6HS#j3#zNJ|w@2D9AFtmGoW7L-%hH zrhG)RUeNEOUj4U>#m8;GPSn_Rl{rjpk5-`XH2`98!^4L+dAV+b#Zw7XY3R`CdMWI% z+Vf0S>d>KSvBJYg&i;5*QIx7)a2(W3f6GNFHu%~g$;_aY*aJMWRRfrDQbZ@~svq@J z>}doC<;@a1^5NGEvxT8+Z}za$>Hron+Dacq@pP%q#Rp-pwLkPt+j?#d!Pz8&d8))k zP+rLQ_rdcQ!={8S)oDt_IZm-bLQJ>{QVZno(QY7W^D%wYn+JL2?n0?S(Spt8so4YY zXWD@3xzOlo_pT*76%XlTq|^q*{YNhv?lu*AuyxgPHhDa8zM~FFeFZQ3TMe?0Il=m< zQGHf3zh;HY+Cw9-d+rMM9AxW()s}aOSVInwQEEg{2VWk2zi~9$T^4maue>XL;v2nT zyNs}cyZa5`FF*$CkyUPwKDzm3x1Z;v?rr9KPMdE)Z{c8}dmJMB&zFO8$SWp& zR{r~u`RY(RnA(u}Oe0#Ch7QuG*%j^yFoZPrqU8eaJ(3#b4m;2MD~vxN?rCY$!=??S z&>{NUELpYFpXY5&O#XYq#jtC4z-c!cQIZTpqCjfh$8EPKQNpg`pMzOAkm8$eo&}}b z^Wr~5{I!WUewP9$33Eo{^~>__O2HvYUDXLKEW6YU6Y;IpyXl&sdI?aCfKVt%$yc%W z=+#XIFp(Tx>DU?K#QwY(&iO=`e;8;494x;n8>Oqf zhRiw2{Qow9SBpzA?8SC224DiHyhYxl7}FY88|t-T`F^f_Vw`t+cF8_@v#pynyVB*H zV$7<%|2gO>*t7y%Wrxb$hwk*Bnc0P%6{*+O9)VMm-sGGX~{(13s z<|!xb&OkH69jryOVKgJh$k4U_=JM~7BdqCS?xbNO`MczTQy!=a+wymZ6GTkDE+e>u z=vL2QK68Z+vdZa~QvvErrw{qT$NoWiSwvoC$y&ku;+)>8$6o3DB{&$DNW6fe;9S5) z7KJ}QC&P9-POm|;IoL@5J?Xpvt88zjOR3dle4U&Re-8?hCp=x2~t zFn&{kw(zX7#2h}cPL0)478ijbU@#Wm5MJ|ln+X&-^l1&7-NRe9B->6JVtSzD(KpEc z=X;04g8-Rx_PTzj49<&E(m}#zKH^&#h_H{%z`RFCHo5y*%0Ss9l_GHEm1iiM%?n|N zBTbhFi})N}TcAD`pvrEV<0sYxdw)J>XPWWvq0Fy<*W|ndjB95_5M8}WJ_&SBjM7zC zL&l_Xa=1&!1BRrBd53Gyg(TCHub6Se?`^hyEg2IGiG5DJKURb3d$Q2jn`U#x@7u2# zn+Jg9d{c1-5$^B(wuWpGR|8-DjsSG$)DtyjGfo3pjOfC)r;)lp)@0Y`2gj{PUi&r4 zlVk;zfXBrJ%0J=v~Qq*KW4Y*X&=MZqc1tVbt+haHL5s#Fg4X~ZukaMk%o3V!|y=_)~T`9BPEO2+0<&@68Fz_*xcMwR07eIOVbOH4PDEk9$iMoa+ z-1{g}9E<8ORb$qpxFkY?{E@SJMe*_^rFRL`^Ly7jFsr!k+*$W2rGQSq4LCn8lCmnu z5dxy|9NQ1*M~x<>HU%*QnGDYtMyUA@>uBni`=)TPA+=J-D%++4uqwcJj)^J#;OMt` zh}W2JD(L_o8Nh$PdS42g9MWP}E8!1^sTs@cXdPy)(40vRxODPE6)?+qzn7!Pxx*F6 z!LXCZfQ$aM0LDSByvp&EQMB{{Pu?B1<#)foFqMjc#F#vq2HcrtHvqej>wwUz6u}zb zO70}b^&kUaCBqx1eYArOZD!DwfG}3ybO(nw%9Pc=%*2ryV<0Kc14NynM=TuxHJE>8Qp^F3!O9Y3!Czkl<*{k;;~^Nb z8mODK+RA6dfb&AK?ALUo1<Vc zq)d_@twBS?2cIm^ZzOdi(4fZE<^4IK(Vbo!iO-}Eieb)LeySqVIeu+6P}`eos5{6i zFAs92_yYld_M`K|0V?|r^pks3(Q9H=EuH6Ig*<`Tv$tDyt0PBES8DdyKPn1Yo>DkMPyjK^PDz*es3_z7{1j5XM4Db@!S$HF{N zXrGV#Z^YTh2q7TpPV8k}1apuj+2A*}i_;L|hgfOiZ!mP3}lKSkKpJ0At&xq!cPj6If-O0qn?7_vM2x!IY?CxIr=a zx*P7L0i4^;RVGuV<%^zbAo|gh&prbg3%e})9UIOrLc{k7(@hrG%^r9sP(UdGaH;LD zv&Ksu?=Y(gcpu*Zu-h}!!tv$>0c9uA^577*kkSJOR{2;x-=PkS+LD2pV*8^kJ=jPJ zSidCY!W!6%u|OVy^EBT9a0hQEqm7O4E3yI zpQxyRy&mTCw9q0PX(W10ejLGifzCaYP_t|1IaP^(Ez9HT!ewA;bqhc`yZ~BuSApaK z7dZX?zOh707|oF&)Dh%MJup3Bc7W{`qlk{^#~QKMGsRYg;!{_yHFk|9W&t1^Az9y& zdqwln`YK1wl&NJa*5n)PSRk_$l$~iqFxtXMMIzQ3WX zteu(2GKf;?QtcvAe=DF4QgB{ETpOI#0o59Ve~W-rjR>kfBA$t>tMA=&E+9O2E2<2d zX?87Rr8vN66Rt#mR3=^rx7Aw{;nSC22tWvaXokS+_g|l8WWG;t5v|hEQTgMY{=sh%ge+AT)Mt2laOrT4|-d8MYWW90H(sye2a(JPliL?wx)EE!*7z zC)h2u%W+LsX8*Sp85?y_et`aTL@mDfz@1Y`V<`CS7e3kZd6OG2=?;*bzTlVFpMeXO zb*_9y_|;opI8S1^9?Yk@(#&j3L*Uxn0XwiapmEziE#&wVt<(xjujK&*yW#b8S3yZg zAk~w`uK2~UH{(LYszhqiVLu;{-v0GE40hlqd*esaHBtG|CGZw5fV(SokiqQW{2q;HO zyd$=m7JBt#7dKHP)M|#fdl0Zf+xfbY!qw30xj{8*w>lw(ioeC~Pi&Uv1JLn&y$- ziRHW-^o0!?4EBE!+sQ2%9i(>;VG2oYVmE#dfgONa4UJ!3iUKJZl49A5_II{O-T-*` zkhPSpk13Y9@R?%ktQ-%^kkUL?%$D87Fl%^D2n?4yG z0dY2!{$5P?EyPvjV1&;J*oxEzYP;Pgrr1{>es}zSr)JqDg_*lP!tu)%^`l`@Pdl2! z34ma}lVs64!Oi>k$Di(HW7q~$ys+S8`>3ef2_pY$B&Op%+QNC?{e z2BuGV4lqCPr?d7?Z9hL6W`?@L_10a40zT)L?JbE?O~%6VCcz6dt^9~Dx>wiO@1scE zJUCv9V+E?JPpC=RpCsT&sQD5%JU95==#;Q%;KQy@1iA2A8&DMn3k;)nQ;JIK^*kNE zKistI{W+Al-4TW%)(uaaF4csGEKffNd@7F#$+2=sTp$~LH!EU8E}C$^ z&BWB=>Wxgi?p|z`_6qQQfj1ki$$N@Gx5U&tKu8e^Ni(Jg$njPnu^rFjFIvGE#RcXE z3#letH&~+2S^sQlW}UoCN3|Lov$+p2MpD4iW#5I-9m|6GAZP1u1gt_=>_2jfjkUR& z(L( zaeeJ`$UC2=;3ZadMWGMfaxJIekSfk)FD||pZW`O*WIa$kn;l&M!1{CkQ*2L%AQ;M6 z)=&v+;JqVHi)UXc{ZI^C8`*Mwr}0nPs!MZa&(z6tW&yP5r}P{d)(P-A3-v1#`kNqS z(n+3KCsZaN3a9Gwi4DZ(5RN5eBA1Sh7EaDq3WBmzN?wAIz^zMGEeiLY}5?=*u^4Q;IF%U9x$X*S!#bBs;H&l^CijSK^^bJSMz2TjdGQHo``jl8SI z^Vu^6PpEUll)Az%&VyhiKOTl${8?XV$(FYj*xOuMU$!iGdIi#-pm#Nm*FMMyYKZ0O-5`{)EzWeR? zbXLN(f{|;))7=r6>fl&t53m}!wHRF=X?RW7bzJLEMP=my8kTsZvTh$5s3$lxhv*8Xr zPm=oLZRydN(M!R<%f_a}c#%I6VHs1`fC|}N^5fXIB+N%Ey>C70n&>b^mQH^!X4-q zd-vKy!C~d+1W9zC-n5t8tP8@`bv* zD?hiN+b7?=usy@?L3xFr&9@eZk{ndh_yD1~yWQ_%3k#Tr>D^$5-@qzBFw5+DeU3+y zg(2w=q5<1XK)m#o3gk&3)a&FLf4r4gy9orn%+Q?NKq`2SWdBsq*%^(C# zctjaI2)l7Z+G1#9rpZzh3BxdfW)Qn*QgT}3rQJQu!N5i;BS+x5T{GXQ3JwTI6veZ; z6g`s3=K^u0%V7HH%M6Jp1ZxG-l96jyz0n7%yiYd|s<%F$Xg*XD+P$?@xNoZ(0!aMg z$Er^bcWo}`uzg{=Vk6>Z_EcTp9(0~rm%mL~-aXHI^exNXDs<$EWwjOt=$@`~k<>P@ zy@)V{OCa6vZV5*lz`3ad##q|cpK31v!~SRGtHfIQwQI5nn>j#v8Jg|%c=L_XTh8V&5WRazk3))ZF+=@6IQ=Nlovca@2UWjbvnQ|`eqw$P^1-t#jsSu)#-okW?9 zgu&?sA~#C7I*rMxZi`CagPa4o z>G_Igsl?+z#PHn5=Kcchj^|k7>w{T?DQ#d?6IEe|gC%Vtq6tWhh53>0hscXiU$SkqCic= z-SKXyW!#7CGg4zfX16kpve5igy;s;h;mx0pUPnZZv-6$hL9%4dlTUZE_BLZ4ufa6) zgrSUr*tC^H;P-s*A1sE$%(pQ+H-hyhwfSVGocY#V0b0Eml~-?Mc0 zbRu}aab^M7$K(j?c)u^O4mu4j9t#$`$p5KIxUlvfb6Q6*#)^TjeV`l@}^KD%Adf_Rg0 zrYEVRm41*%CTI*WCm69rQ|GiC>Kz@QM zY!}CjP59zH+~_xV7PR`EE0|p7d<3-H3z&TPhx{xcK;uHqOp!sH)CCH<$R~aMf#>y%1gtcn;I2U8c2n@Cl5>?Hk>;*9oGd8_*t6zqLz2NRQIguS| zyU;iS+}#7v<jj6Z0(kRI)3xsu$!e9J|k?@w(% zy$w*)M3v8Tid{(0_MpYFUJ1=NZ(;yub4%Q<)B>gU(a9Nl;tA}Yj?)O_+9p$Qi`7{>`KFbZuhWr%XR}2z;qV-^ zO%Yx(x{;nw(Suu>69|K|dA$($nlIW!Gk=}u8`>)7j(R?*@9`fY6i=&;Dvnh0O`tV| zM^g6au@wyl_dai(bj10<8fD5zsd3&|oN9vK*3<3NLPp}rzv5_G^6$2@ zGCXdY_4?W};L!O;dQ$cfP+c`*i*>m5Ay0qnMSO2;qh%Fzlwq~LX8icZ66R|1nr7z@ z7E~nMIXU7DJI>nUmRD2*E^JC&6vT-At|Vd1X(Fj?G~rmt0jY+aL8NZ>_j94o@H%n7 z4yC>nsfw2wh_*=eTfYSWLXP}&vk>FZ_ZjEH~O*R zQk%cE@x4?rF)9Bg`Y{a(GtjFAcgWvQ`^v_JJiznA&lZv9_&VzJx%XGQdYF;>OM{e7 zZ5RM~_$ijfSpAqy9WGA`_A4JyiC?I;3Lq2SR56rSg_ZPfz8U;}vz;FBNG{J-%6Dd! zm%EJLYn!&O$A?TfJv7ZVcL@|@tw_52+A>z3(8IQ}a!zbh_sv?x#S|WHxZj<==c_tm zBk71AT^#r0m;>l$Oetlmu)x`-|S0gBb&BD1evV7cD$}z(8k&U0P*wsoGDCfPveEd z@>+#rGFvx}^%qA(zv8{-LhHCDcIwC5`S;e_vF#4GUHQIUHYp$2Gxx&=j01tdQ4NEC zFFvK-%X=!EpprRRBf<62`OpzgL1uW!z{^g6v)^!XVI#ro3zP5EDU!yxaYfVz{Fx63 zVd4=;SQ1!Rj#}cQhh|biYAHZ7fU&HVy-BSk`Ba=|QJPbwjIpAMYfkiP+052Kv(&~9 zBkQ`W~>^s&gTr@S??`Y01qa1IZq99c}ERH}A72p)>XJlpPez2{1J@ zF`BLWkmM2-5uRHu1n2W@no<0A1m3{f+4K}$Dfip9Ybc?^<5QbUhW>GwoYOpj@YzQ2 zDc?U67*l?EUPL7QFmnEWh}4gdVy~a>#mZzH>Ou)h0BLclHc0^CVpW`0X7S|jewC@Y z`+Re0I?A2`ZSLfOn>RnFk|NC*(hkWxG=ba8UthRMD(FOj)~0y z7w113i*401s>fB@Q*SkSy`7#TVfQ$F-hN@Fjh=PV7l@GAwQV`VDjCJ(++iTfDAB)6p}(!S<@!1bW1 z5L|X!LHLg;$4B7JiFV;I^3u~G9`!8Ux>mLBjlgFVkZxMFaeFc4`P#)oQqFdl^EPLp z)+4|jn0ku*TqII8hle(k-I1al29RyPBaeM|w8q=j9ZY46!Zal8VO|7s9|KYRXub4A z$kREpiUB6${ermZCYw%CE*y3%`Nqa`E&%5pGfA`bOER8x*oxoIU3~c4(8QALRWEF;h9#oIC9bR2;qkHCw`P6J38-gh6Yph;YW*h!10+r3Uqi!zi$IhSx#&_OmgpIXJ=07dCzs( zcD1RX#1_r1HPuTg*t+W@;{iJtuE)ZyRj)1sSP%SMSsD^mb>T=)zjk_Off{df{vL9l zPKI6}hTzr|8G&W(cx*o@v$#>_9oMoxnDcZ%x4^T_O+sE_4ZzpCIW__VDsc)%b9R^~ zS3cY5OE=RWy>!NC0wwbPpaKeq%|$RDO=i%M*wdsYxyk)G0rDk{p@Yi=+KGmd7l?u6 zDjw{e-4V4IC5(RFp-YP(e@X=rp@6me5WGD1Sj)b~{k?GCH{7pY95&xg?DnLR$t?sq zXrRfQfv`|#GG|&o7NRfAycUb|l1@SKy=$ZCj%5|i_oazIxU_J9N|c<1!tnZNlV!hS z$HxwnaoQLfk@*ejn{1R+4&ww6;ioXcXzZ~2){ICdZJQuuorPHn$Kr#Qc!Gf&AxW@H z?rZ@hs+u>)oWk`zg8fa)k1IHu}hd zyD)z5hBdHKw>%{njfFY86_aB)#-2nd&%I5e!k__sV)%J4Nu?uVh-Jq0_$tMmw6G5G znK-EK@7f8)Nx^3c_TQ?th1X-PFz+SEUc!h)3l^z{Pez&Mj>7Q@0_-GggcSkN({C;i zUk%rSFSpNB`^X|={aYNrM`!LxYIeDL%uQT;FCKBb`husS>ATOlXHOU%m_Z_}wohE1 zB@`|Ux%jaz65lI$@V@|-K~6PS!9V3pv$POvIsek`G__oqEV0NAxht@H*D}6 z_jOl=q!yyetYl%f-4Q~q*D7lw=dn}rqEgWMYucfi{1xH&ua~&@_?6&D($w%Gf@pY4 zYQK#8#rYalu#{U(o(;>kFeTv`evKt0D!D@tpFXJ3rp-kyLp+qV2hpx|^kZBRUF_WC z{Ue15(?bMPtjz4Y$&kkRzmld5H!Mig+#}K@FpO47~B)guoaz70pIoJ+L9z@yvI^Qm-HL{t2 zDT#4t%R-txFjAiR8^BF7V*wqh%Ri-8s~mYpG1(PebY(m9MLPi2CH5)&PUWaE+?*MK-V|X4qDLIw&b^AqQMxkXqUT9=rt=pw z;WqZr9LptTZ3u)PX5{^Y$n#sl6PVPK<Qwo$Gg1g?8-0pimhu9F+EgW&j+b1hY_DmH3IhLg%s~sY@w8?Y$&<^JAweQ`JlrVs9Iq9PFy1X@kRJm&lRKSOZbw$oh zk<2!5+=<~S|zTH)syV3&7=S4SLG92qiB>jTTUOkQd<+Dea%}8&vP)If0 zZ7vACBMhB;93@DDs91^o1=?nH68junGh-H6eYv@gV&Oz1#mlLb%Fnc^^%D}$N34fU9qiWXAIQm^%vHy@M?Q6)q zu7$(n;;$|ZcaX5KYnsW3tdm-9ZS5wl_CKbvkksdYs)4ez-blpR?~rJY{<$c@>x{4` ztR3##V()0~?58_CDy^Ukm4&_9)i+PoBW1E@!gO!S)vnPuNf_*!L9u9ksr7(F^%%m! z_rq1cd1Mm-iTdj9!^|ff6&zd2+Qq3+u{d*|+N`0!4z6B+GSydFFKSH%ZF^>ezPCVn zhvX77p!hWD`p~I{5Yu6VoHNAsUQ7|o@ioQWdsM_pWHzYoa}I@_NHRo&QeCosmp-DJ zAa#`Mr69 zNCMj{`z-XBs3uJ@BqLP(dFS&@>5iDg8c&d^Xc4+TzoN_@?dhD>$^J1a#qSVvL})1? zU5=M%drmWFf)x%3W)rDyecq}Ngr>k3Rv3mT6<(qEIG_;rE+^rC^FpG&SA*+pPk7*m z`?Oz;y|VXM-R|StEJrp%75tTWZ%6GOxLD=-|q zE01|d9qrA4NP-04cKRwRuk{j=0?GkGMa+xIK*WcH5UebqCKaauhs5eslX9*gA&H`+ zP!)!>XGFKq_?U-?pPuHaD@pP{Mk@y?1^|CElU(h39(ng7HDa-3f%apJ^E z6KmPGa&NM0n&7jQKU|kt?fWs7aeh)+keSPu)HAa_{2#qhR z6BFOX<=oN2%|H*t6FxQ(!A96aYE_U}q%xLXGbb|E6Tucm<}{i^IAlKk+sB>f`xcqx z$%V$!rA+Rx_Ni~W*J|+a9`Pk1yd&RHT{<&0JoNs09RcN+KE}Jtj_9LrUK1X*6}vK; zA#Zo3ijThJ5AfeLZ(lRt|7jG^MvP0Ors6XqK|5nru=Q9^5@Hb-YIw^xooz5j%k^0BWjmfJFat*`cYEW~9Pw#($Zp8wS1EQj6E5mY)^*fFoH_ z$sR9!ON!RNCKTEEHioHzq^2c$@tMGtB${&?pPJ@XCO1LDH)*0{EnQx?7i^fqD2lhh zSoL~qC_y$=0qtn9&F9~n!I?_Myj6iiE4;++ANIi5!f&8$>JwM06ESZ^%~aTjp#f=} z2hJVK&Dl-rV3f5k475UBk_66i&kR}BKe#32ANux9S=p5;8TuTuBxUD3rtx7)bL!f& z&!|@;!-eGcM50-ftiE`;7RiLMkW6<10^!4V$7^$s6>>dMKd_HF)_5ltw?@tv{-hsS zd-8{+3FBR4Ev4f<-vgY#zu7J_NaEkQacLcBQ~Hjg-nt2pi8|@(^BfVRyd+wuQblOd z020+dQ)b92&X?-JJrKG`lJkHa?>z{U86x#Sg7EQJzg3rqu&{WJ15OhxY0QD`XL;1} z@Dd86VXFFDHi3oqTT2#<(L(x_eWO&}kW-0r#g3EWnMJiOmE{#TLKw>-4-d3l180|0 zy1jotnrEVHUIC(c?9D~mlwTS3gznw~Mnz5!C$Qi1x84Zdfv zGc`dnkj&a0rQ)9}qk2VD-+|VirBZ*+)4`~Frq0r%}4#y*a;Q|P+ zqM8ZgWx&)xT;S#UqL7ipi+_^sfu`Z4z^r{KH)4!ZE}f9X!6|@lB`OSEI%xQP+*QT; zeCiZg3g+SRK+5%y6*ojZT9;6uvPQohC~^E`!7PVrM z<|_Q|zm}zkf#vOT*xCFNcaz*v_Im?9G;#NcOpTNwow6BA=Pf!2VC_{ty!BO|Y&C6i zds`S_pV9m4SIZZfVPzC@zT^N%TIO3re=jzdaBiZ}Yuq%C_Qt%f3|MqWc?}E-IjyyP zqa}FbLq#jDy{$w%aHF6E`QV8V92xN%B`64cISP8c`dVc7QY@N*B#8SbJ7$m)wM@{g zhwy|Kv3of?_bf|G+{zyzB)vWFbbeh)o2vQw%GN!KIP$(4yv)}p57tvPCEuF7(PCmB z#+$)(5e}&h7JarIQI?>@;z4Cdx4rj=?Up*YQ-FkgOpc z?&5E9k`j-A!UoubX5(NCV-yj=UC#tUtfR(gYyjfF__fTglm-o55yUneU~N z_bLHB`GGa;_Mr&_D}(n(QsO{_4=B)Vo*2=F1AaMLgY9QF>SxOa(TG>Y&zks8RI!)_ zV50l6Px?mm7x9C=XSF^dL45{l?ZM3_B%G`S?LIp7gC;+OD@l@i1aL*1ySF4R*zO;u zFsRTkeO!4!%;*s;)zu{}djSMg(1Dy8Eh?-xo5pJuoVCe_41X=Ad)}l}PbXwLtxOKf4;$9DZZDJzZ*K$+cY&FR?xy8=ii>il ztJZ)`?}jb+KhbE8C8;i;X2oSy4DqN+=>1MWyNyYs!kVv0RRt!^-?X_I=T@!f9B_xO z*^n8y$Oxt)W9i+)lppo5FdxP%`Qb@b&;+zFy8-N6FBvIL`M3)sktjgtQ*-vU-BFXh zAUIOD^9{}Fq%tCs5N%#pO%ZxPh7?8AbSGI`e45Gbd&CN%rWf(*qZ1d)aXDxZy$=7f zoWjW0!pO{;R`sq*;KIK6WRw`l%RRQ|79*WkOM<%b>9{6jO6^Y!2O{qm(yZNI;VKyY z=}a1~`h1^#C@FAH@=;a?l;fRT+fL3ug4`RBjvCxKdS4*!PmhbRo{x3T_&IbZM`|^T zk+q2=(Q~a=h@|2RXdaFC;LS-978hrOfAGV0uD=q79`kuquK{sj09I{ehi1xV@gFmH z_FtgPrU`EL?`ENG5IgwPSDO`xkUXo2$++s@r#oOwt_9R|#O@$qE zv^b|`vX19@-F6uLPj90;dA)*nlcJ5s2#{60$K-c%Ylt0B+)W}f2ed1;FMjlFmYT>U z4gDkJwev4Gh?D&gWeVdeOs9XKQZcgwm=b%jC9<$g%}U`Nl0c1HpC8V=rhJ| z|6lX?aC?sYePTXFG;YxbQM0h8}o zR3{HM!qBuIbP2zKG;AE8h=nOjf)( zU)k1?heraMdHAp4);S(NW&}EoZX&0YFk%Oz86ac>!Vdm^- zPRf*y%;z5dI!lXWP6tY0*?>m5y_WhbShwY2?XX_fzWd%V%xDNyRSXJ=U_mWf@W8>hw8zthbD@>SV3XA z>{zt3zsEHayYFqg)nzi+(x0CUH(Kca$)E4x-#0+{YU=UF|k`UymBZL+05mB3Z`0{3y-yq}k}XN>4}n+DR8i$Ih!k*05>Qlfpa+#=to6Y2hkzn3nF<$_CA@cLUFDCWxdwkQ548%F*;6<_c^Qt9ifW z9$txUl8dt!q>jxe=>eV@XG%V>z>wd-Ab;w%FFbk=*cwMoctD~pJNn}#E8M5{KGJE}VuIfAS|Hsx_M^)9e{ldhir1nO-ySoLXQ|azTK|%!S z?rxBhl9Jw}3J3_&At;g}E!~}-x$o!P@AIDVePir@WFUL3HRrnK6~B;|#xST7ZV2L= zwN1lqFbbh~zG*4-wG{y|Q^e>NH4dZD_xGda3|13$v8^8?nHmv4XczXQy#*W$95F@y zzOm1(NocEtZ9O`~XSO@ZX`U*HsUV^Co)}xH=GM-G!u}+f^B3WKImaV4kn=P-H2dr>=1c=YVdVM=Ha# zz_}=uxSE*_aC&zo^2+tVnH*27B`jCZ+x-E1h=#>*q8}^4C_i;9zcf(jW z8S?LPRSi9XWY0gEFu=^?rB0txCrB+vOrBK#>2qovRpgIJvSGTt3rE(Xyy#M-5cy(= zkr0RAEi-=tJPH2Nnw0}P%f&E9L)~!CA+Y9B1BGn6@4C(hK?ulw2Si*q%2)2amtI)T zi`eHc2@`ixM=!OIEgo(ITcZrpgyw{$KhzvqGWkI|Ni@&z5Uh3s(&hO;AHeM};yNml zS)rMh=>t{S+jybFD_~EMhiO_El3I@3IQOEebn^|_QUBupBe^3!bynG$?^YY&9Mm)S zE%N{AJ@@L=g45&6^s#dLc6aVKB{I?mT&nD&34``USeG?AKpRAzwBKK?2lgx7@z5Z1 zMk7jzaOo?|nKK)fnrQ`E>!eoOGmPo&6?~<0Z~^UWAI7ImyI>DMPhiT}u@pi8Omd{s zaJ`5F$`BlDJS9ik@!%9)u_iPwm?;_#V!$Q9aRk`rhb0oiTTuN=V+vLX9a17*MiWX0JDOahRSI%; zGYHsKACdE+h*M7?fT>|kB+2**uwRF%OgOWNIf-Mh!4W<*O3srn})WmZil*REB~A@To1W ztgsW?5V|E}4`}+RyiB7whHH8z48bg9%FqT^k>VO^{L5{a%cY|9&6AAzpucxI5-Y~$ zltK-7BplEB+68WjC^9iuM{E_6wAumUN0q@f+_EZnX2hL#!`r-PFt)O5RFpN1k z2$Pk{+|&g^l~166LjK6eS~XSQYV}X+Pv83ChYU?R2GlV!WVc2knDxs^LRZN^mjQ_D)l6teoV&u1DB8X3yU@P+@G1AFlAb*_BXX)j)EgzyT9nn2^B&Y=X&Qs0xt195 zp>7xD;!%n1B1u6JSBAs`Vq^QFuXvb0=xpov0Xs+4DQZ?wsvXptOm`Q1o`t^vXgRHz zts1ok7t^}B2S$Ew^)(t+(a}J;ja&@QnIFrf+OQ8`$ny!I9l;h`^(qQzI~d{muaf@; zlr-@P|B&R(8Ji>z(a_gmV$W#v98H1IT1YcXzwF!N2lH#~mJ;C>*|`6x#0Nm(X^rut z{^E~6ZyAO+O3;#KKq6AHR7L?{g77{VH>o;+&{{n;kUeEyQ#fdvzY3{e9pI^9TY6ach?`{5|$;FWvTMTy`ILKA3l= zp)LsaFSlYjrlHi8-l{`?{WeL!)W_o_E#PcI6o%R&OGssrnmks?Wov z&7g<4xmO!CJKg1%?@3TDx1Ujk1ERU1JRvd4LWJ~Yvrxo?5)&eF9~9Uz>X(Qh#z-H9 z0Dt+eqYQnjsUn~dtoE!Ki{&kR)r;L;?qkY&FgAqazJ|}p?&Lc&=(T+K{)A)QjzyNv zzAD@^%jB&^-uOB0zHC})c5-RjdF<((4@(~)s5e!gAJEYyM|1(Sbk37=k~bBq3W#MTN8||) z%h;k>Z4#!VHU5eu_zP7Qx8F-62z5_AUIRRma&J|%E6-D@$rJk?8s52-*9nVMH?Hhw1ux(&5`BS|{S@Hov>q9o1kk&oJOd zL8uw);L6ICyfEma2I?nhN>_llq&`;j`S1PBKBAToo)V4HBQfZ+b(l1`ar+t94AbsC z9_oW{U+Lgv{N_fJGXUeJ1U~>dqpU3sV@~sc3U&`n0TxW!FB|-w9nqZ_G~&eI<#_H% zUMo`$jfKhYI-)ele+S|r^jC+2Ba5UqFb{8mxJzy>3`G?&4&?Yg9fmb*Nw~tUq zHRX1BPVIKt8@Xf7W^74p0*Jx1fv~~IR~y|9kk})8Ah8|m%y`Fxv|FS;r9c~MLp3GC zVF@(qgjJR$6_9Ssc3hd{CR+)aptv+nk;9aq+^Y_~-&D2(t08B)@>B%c8T~3HqtOt0X;!FU> zjH)^T=#$OWs&|_VKR4^?h{PnR_gFXZ*GobYhIjNR8rF8gyb-V;ibsHS83w_`Y&Y?M z`v1M2?o@XPKHQsZdh&FAUXB(F1Ui{a-JAQ>Hk!-weYXiyK#`_|l0r!Tne5AN6m+wr z37kIb#eYwCWqqs~OE$;1i`D628zUp+i zFL`f^(&0*AJBB46=$rZDnFn62uXWr=SVdbSI)_00xg0}4PI@--~rv+2+q~9G5fB;9ny2|^&T+A6Emt4lw7wvWt z|8#M*r>D_rI@u-Ggjq%q!SX}f%Xiiu# zLhJ1XF-;EdLL4+}nmCVbuQ+Rt>c|`R^zYvBG%nVIS&m#H*&p?vCoIH?9_83VdbgrN zZb=fy%1YQT{LOA>zXiVe)o=J1u5 zp=X3SL2m2xUCmnW^*?>$M7*5OO&J^)Ksst&{NREX1<+0E8qZ}CMOQGrz<((7y#^H7 zvX#AG1nKlp))lF!U!4&8f@C1u8ES6~u9;2w-K6z@c)(&tXK@|6g$zRa!jW%TJSiDG z)NFB45WwVjqgJWtXs;~VJpd~!BkbqP8Qg53YDdL*4SE%!VW(0%_uf;z219^?`xB84 zs-_l%1vlR<6*P}kyjzo0q8*Nm4tEHUWb4?Lbb5S+XZ*b8MMB|NiW2o!nsz%QM7Abj zn;v?UBx4Ft*)E^YrZ}+Fgmi>a5K;zzjQ>GLggv`7cC1-Hr0@iRzs%4CAStwuDjE1L z*g*@U5?yaEN}=oC5WE2R^J%`C927lSJlVdq#!}n}Gd(XrHy5#U>bUyj4wyu5!^#H0 zATQ_F%Wn!97FU44=!Vep6aSNCWBSIo*(v5S+OEIeyAU*T21(O-AZ{?wyOp+Nn_k4c zMVMhA!x3sU2qyHEYdC{mmI&0o6P!SJbBvd(YBs!OVnBqQF$j)Uy6VTq`ZOlooZ{$1 z+pbVP{?v%Tx3^D1sTvR#>@I*=pi#>4odUDVa}@MYSAm>yC;m26F#6REvx`b&e*{a8 z{kyN4e+wTFF@^`?HMSyj@Z-nqJU%W6wk@+9?0t{bN&E>C#= z-E8H#eaw(LLkL0*F+ybz2;->@%T_J(1YN~}CH=u4K}UNCK+Pp=R79>7jz8X)JXP) z@Q3PI_BktZ*i1hT+nV2s z_i~-04F$==xh^S_(R**isz4%AhiWPu>7=2kH=Jz*wvtD3WFgEA1g=&f*#A|H_X{NE zF+mXWSY#mZwsSmE<262Hf;u2qOLDzPGBm=xb-xzYSpKclMb2XTGSm148~VBzGK%TJ z{9|brCmq^uAp@2su^I$}tX%mmXw{9qP%7gm+VX|C2JC~{{*S&zz*$*9h^I}SRAGcK zH5W9)A^JZ-X#=}~;*vW~DNcjR{M7DDTwmNXan^PBd{?yZ#nabj^jUCpbwnEWo`bj- z{Y64mZR?9#=4cHJlq_(Z&1&dNU?7vWmHtQ}qn~I^sw>p&>0LyeF7Ive$@ss15nTh? z?)$e@G$t>LsD&hfVUVgKu-3UAdChc=fg(z7LzO7CO`4%l;@4d?rHIfX`@RLFZ$`%> z7NQYqn0h!$A1n)0Yeite$@jqW(ZevPJ2LMvy^Wv$W>FL~&8(AghSgsmtTTx@NdO#0 zl!-&0aEoq4Zli^gGWOhTmQ??7keE_+8|zyTmt#UC-qIBSV)!p`)aTBiv(3eUUSb`b z7h<==xusOVx42W1XPYFYDz;Uk5T&r*A&X=|YSsBC$FcSm?_}ol%?kj4&GWn7Dt8px z-@{u1Y6YoA9zeHXO!c5lo-^x64`zcHHHw;kLx`h_n{GS?9S-Kc%Q7J0=d!Ks(Eb~E z8CSFbVT4{?)0Arl5bRjhcS`6`#Oa~2w_rv}x=Aglwhksjq|==s-Yo#BzEu|hy3>uS zH|Kn(kXos$9sbQG{>&N_1*vfCsrZFMcj-{r0kSM{L4(N2W2D7G?(`74ew>qR_Z){X zQf*mRNF*-BpF@mHmeOyIgrVv5|489J<*b)ac~_eG3Aov$;tK5MBhF8i8@PQ3xLIZk zZsWVu2XuXa<(EOqLMiYpz6X~Ei20cvH^+&RY^@IkivW*zDyXYD zP*F|*KiXH4x=y)4)PCuTn&-$jH@kuBs0lc}Z7-fQrFG z3I0Fu2W>rpC_}>-m;$7E>fpwj&&?Axh3#en8gWyPuH1~-P2;Y z&>~X$>v`DT?5n1$2mK0NFBl7%xO0w;&48QWNrUzl!mhDr5h2n^`qTu3d7Udg(DOrz zoKFgBsEa_%^Kw(S5#a;rGdz!H!%A?57vlNPdrOia*XGD+x|n1l^Uf?;tFi zW@0TO$Q`0hLH;uMhsJwx4wnV#ORJv$|zhJV!ZzNjwK2ocY4*C#*EcwMB9@377Ngt?L)83-s~N zR<1I3EFg#GFBH5jn6!UYT{^yjN&qQc+SnrVNVMFYR%>D!^_kio`2ND+#yQ#H^Zl568<#?a>uMEe?c?IJ_y1gTsD$*e|RV&ZJycldyKMd{+*{i`1D zu4~rMa5rk0zp@nwJGy8=|F;#@G#c>pwf6~@Eq>JilT!3px*emD>2z{6NPGQ7)*A%2xi5?$qw!CzP00uM=aVf7^ELm8)NX{dBk>JRKLr##lhSFXll0^%~3FR=}-Z{O0RszGLvKMljZ(Z#_ zJ>*9c%0I?akhro=*VSzJz8Qu_e?KvD>|63CT>63z(Uk|0=k=oNWUh^RswU5#zMtd# zI=*t1pmk>d3KsS?8nI!QT}5M`ODEB_osHqytpi%FIp@wilQQSo**2NMAemdAb7ED))M2ic8F~*j`rj`>kDW&6R9$R~5yJ#JbtSBqoaBYX z>)7Pbc7MI2)8t?6M6pEmW}EKgwy+nFF=uKQSw$!@_-KD6F_+OC-z4jMW)F^-(Mg;y zXEWoZAxJT&Tr~Pwc0XW!enfeXmN8KiEjSx&oaDyz3m? zQB2C=kn>QFdqZ0+c~{$BFF>=+E=VWqA7v*c5p&Y6k;a*$dksg@$h31u3!v@nItF?K zHb?0Pj&6tEIM@gP<9=r6t(}}WCnxqi_iNG6j1Af*ly63J4}CnglkxT#obCgnaJdsn zjG{iF?HYgnehqA;RUsB{i2v*t+@F@Ou@W`;)6yb}gIvly_g!vjD#ov4r6ro%wiw!CK zJ-fOeoVwsev~`B6eWdaHNB9fG^~h$4&YQwwrTq!0o3;TJ`{{uL*S9b{l|~p>Vm4L3 z;(7)NF>)In`a0BtL@Mu zgpU7$kA3)tG#bMhC)erd>nlSEVQ#WJI)Q?)bdQB$2iVW5{p%QA=c72GCu_d;iZ|v zjZp(i-0R;L9UI(0ls%m}&bdw9ij*KCGU9;t@{2nPYR63bZ=hilv$PCEP*mb=t}kVm zGA`_BPp(sKTHave-fU*LRk04IHhxyZ135Kw~oy>;iE697Q=o2y>s|yoiQ7GbS_jJmwC{4n;Q2X4xe!Wui0<#%$`}XAlf4@E1^itHka@E~lEy+e;Az8O=1NugI@3CtUrVfStRF3*VPWitZem zd*BeJPjLLc1~SS~?e34vCP-!c=-f7eC~BVrIZh$-RPNH-<8h%+N2@7ryO5R89_Q$jR+*(aZtnIXkap%!SyL>6(} zF7?kz5U!WItnZ3$D=c1ZrYBf^c$}2SV9N909-Sihf)sDMNA@zL?)(?#GPcxi@*0)n zRyaN;iRF*4Vl$6Jasu+@s5fl%$pWn!eJ+o4m_EYoNF${w=AgS${`PR!A7JABG1xBP z^76rc8@;R>Wk{hj+SJRIiK9IMICa2MZwD{#_T`M>gq2PL|K$1Ma3iHZ2aXJ74#t!W z(yF+r0`?aY;b1rhDqZwJGI}niC_1DF+Qi)eh~;NP@pA8P2V33$V~T09W- znW&Q+2U|`Pu{%mr?ue&CrT|I;?^5<*Je`wYmp3m}K0apMrfd920miuE@ek=LDw5*5 zB?jUJ&uh|Ryzu0YXv5|a4{+hn4q`~#qyHEtJO`LV2uWK?&&e}1Aa<_@w7>8%U7bm7 z@XcpavRx=e+XvvHiG0w7=&1GCe>yWC{0VVSir9@icHQi3lqGgW-z;ndy5Q%aPW75H zsy$jy58K6&{wy9j{9V3ldMg?9Q#y!M7_NEMw_jfG)<|~n4o&|aEKXo}lt3xS;lBq> z-#Cg;^q^S33JB@K>c$E}Jp)RkILSu|)p)l4L|voAGwBn{BKXr6->pve<~^*ao3O?; zNPmsAc@p2Q{Gq_qa((@k1U*%ssDp^UD1g#yz+JS@Rw&`tZCv<^Y2%pGz4b|K_^Y0H zqGT(VYhe%C({A*+2Xg*%-iyy2T)rsA2E84sGZCb2t#c9~Hv3)|cHoGtRa}SHenx>0 zg0=C5Nr>_r%g>94dO;D=Fvv%x?W% zdgNDB_j2`^=h<#CT<9n%r2V!HZox?m#8 zTJvmrm${gMCayR5iED-!zhAj$)H#1obQM;#_~OXfN*pB?9w7HsV1!qWYG(T>^<~Gh zZ-owYR}qh#9)U+AR0{rP!?9ELZW2KyM%1AS;?-`ipzu|o&+m|GPqMLK|v+Bg_NjRce+jZJk+PIm*6S?t7PTGqdCwNpDPP=-*8ZaT$hlf4fH+ zwq3UF{NDKMZ=)BrWh0^9b)6FT1xhAoDC89uy^H}=28&+tYcv*e29YWzv+n&ftk&Jw z+QQ(?2PgzzgFilC=TIaj)Ro4eO=VKi{mvx0*tyu}wV$1{pS_=5xxbzLA%EkhwX$`4 zvckec!z-^g*lJ%vhZ-)#Hnqem7S(Q;Sy{5z#zBE<^t)wrwB82?7&5HTVA`raUr^d~PaiB3NJBU^l`VHV*uF%GRg zzEf3v#Y5f)pLgweir@c6aQyeLwn9SC<^^ufzW7u#h1~@Hy}NjR{2S&oZgg_M^!GzW zrd2eHqM4Q{=O?*J{&TiNrM4@bLq#meOVyf2JRQpQIl3;ddQB=`N9Y7*iszhesb?lq zfn(9W($jZBVbOZB_k&o6^bS>Wj>GGyw#UY!wc%xC4eg{-{EhRmKOOfw1#j{KmAgr- zohst!Ruvotb$LeDO(d)raCf#7o8Ugfx0hyzKfDdcli|zbP&AJk)j$2Py*-N` z(fyO;@xI;GfcG6bAw(*KvZ_5{PdN~Egf>Q#x5V=0rfhmVueW_(E z4OhbtSQnxN^*!OszvcaI`hI{8fW4t`UYv69-m}G5ENqDv61IeQ`s(y1J_?p|Sci|C z4~jKC-pDW#4w7Zs&=C6vm&AvQ=v9gd7tze)*wst)l@8e%pUGkjw9u*=~LqeS_ka zmFeL;Iu4&N8uk$&Z?{}|tZA@)8K8`}4Cy%~SdVBezt6g%GTEWQbAb{-;T>U)n9l)#YtVSuKpwtkKHr&X$Gyi@l}DCHco zNr8=5-8(jQPK=#VOTy%r@B0EaC`7WA`Y$1uF187yY3VP--N@p6(F!VL!FJr0Yv1n2}fo&A|suM>lg(+busLappChYUJWDP$~z3n#j!y=PWZ0_R$- zR6lfO9@9tp+JvLy2ySu~Q(rd3(yeal-t=71p-!`k`E-PFlKR0PlEy1-dfOzR|8h_$&|wvL0QXMafI1#1{)2e12M;HRKr zn@u-(RHou$82*U%t!ZNC6j}=v^*XOwINtO-A>SD-2rC8y4F1tEI|Z9T(US8RSJ7~C zG7;slv#?v*N}O<#WfFnfAcsc4i0s-1Ep^d6?HhKSmhzkMrbTe_1Ka3E4LYy&a$j>w z;mBu&>bE3#5EXsg*o>VbI9A%{b)MzZH?tY!s_OrEq%h4Ihr>j;LV9oSl)LF=CkNIgO} zXDkYAVws4>Pgb!V@iiY#cBV+f8dhGujxqX1Xh))!c{q=pXLS?tyADI_?y@1;1Iheu z+n{Yj^5T;Z3t1$()63Yp0n)&vvC0GZd4v>huZDmF=?~;_2MRk0>Bqbe(A$y=brKvT z5Y|%nKNo6EkbMwZQ_jus2u?A0)|=o8P6a%4$|{zQ!|VBaei|GYsmw}J-;xitRkRpT z=2-wEw`{NcfQ3C}1Rqj?UitZh{%i z8<;f=#QG{7IX`8v>oLHl)Vm4U7r!K5o`2|kY%xw=jKs%|-}!2$chLM|1j9yock$Vq zQX4+xj<|^J;6YmL5uPbHNni$;Zctfv0V4r z+dZ*a3XNP3dq0WB%wMeQ&rNLp*xAZ;Y&0aA?SB)?(;7-0UxqZmus@&oJx)(JCLKuj z=(zq)yHFA|s~r1jm*b~2xVi?bWp|@;Z*GD8$*IRXs$+J=4%fQFo_yIojf^*9=Ir=a z4Rz=4*~{;E4&X?EB3$!M#^2y;k9wSUWw{t>gAqAGgz~`2z}R zdUOsd0R|;*>}f~g?V?PUd5*YQu` z+4m085)k{%qo+6xcpNB6=8Ds_xFMHZfqz@IBWbV3Ajx&`_VP#DS}>pRH)ugV<1v|+ ztmm)AsoATc@V`$f}q_%1r>5v?=bI-yaj-kq~!R5(P3e%GUori`)drU9V443K6*(!_HaVW+AdLsL zBKxFJZU-;BL+J^xc~>++HxZdVElJ*M@|CKV@&Y_XDKS!~+mqQ=3JIz+Oa{3YiV4&6 zj37MpRi}iEPfoa*pPbS)YIfL6Y(2-wvtMyN+o{Hx#6BCD`m`nBGV@?%9bXZbl=n(p zMtH!We`2QZcGkWkWEUYuXpXl(ia=(gZdA#CMZ8ve+qxpS`xduj*CT@+o6(MYbwPKI z3-8h;36R@qit-wot3uO}dL-r_&tGvwt75@I99X2ol!eKKI)f?$p_M&$=AJe~YRDng z6DP)(a(lUzIb0%sRt}Xj#_NVuj&@FTuO8w&I-oi+obL!UmVT?7GE)z6w6KH?TD5() z%)yAXb{7?%;}(c)-9?+TqLXrh#d*-fpCM1Z?1JIR{jUGW>S_E1CGQ^Pty)hPA#L?Z zh?J?H0Z>PmJcInufSV(iHCa|ZFq`5Vo<^;gyYWd>s?H9{6PFeJMPX+$9+@ol1U)hg zuWIN0?Y++-_WErE7aPg6p>P9p0eaXL|IEtA%AUzuKcMuP)C3tyER0SL02WVXS$Iw#PR@s$If$TzC@MSu1wy3CG(EpZHQ3h9+#a^koi~ zmC2JS8G_)9SjeI7Oj#luRimb=RSYJWZ|Yi-&{RJPO{AG4+NmR~*+^A8Abszo)fyrn zqhv{EL3)o6;R)G`D-xT^?!pt)qiUI|*}fGDw+v?YtN;2Ey8x!QezRr$Q|OJU#a~S; z(+ghTap}m2CadZ`+3yPCu@USBZpJV!QV+`ZtPbQh$VA0)|Ctk19uW))1p$SD{=sxocM zBb(b^*QtTvCH-CZt zcG{6e7%9DYpW8p`S>p4>SB0=zri;I%zDn`@iAs%3xZR9rHKTltyfj3<5E ztJBjpf0-#K>}}b4!;&nQIIfmL110d#2vGbwCA2GF#`vZ_=vU|Vqdtk!V({x&h(#r*vtG9kSH^m+5C?v6 z>M^kxZv~wor)L_!v!7osKl-APrk!=&QshWsSN2sh{6F6(|5POs`~e)4Z{B?Zztlt) z(mQW0*TPI6g&>T3m(~Chc{l%R(tW}nv)+TDto^`Km28-I>Z}i=v25h^ z?n-hiJud?UsFO64RczV*`~`p^*l7P)ivjWfQKhg;2CzTa;3oV<6CGyuoSqhiemnq0 zBk(wF0jTL|8^ab!L$~;Wp~Mph-#jQX%sU)uu~*ik`|>y1Rt|r0l`5nX1`kkm)UAHg zW?VUiL*L=+?6nyXa0_?F+1@VCUWbD_8Q?t0YT8#bwLwI-fs&fi+kd`?I|QH#zU2Yk zrHm=`SpXB5)6*}FlUBk!3=r0385si2l0;L8wg4N-ZdwtgpqrN?1&jRyod@{Z^*av~=tIRowzi7FNke6}EM zqshVTM-?NlPEL^dT>{tL6P$KNXowP^6GSX1;9h>nSOUM|*BI2^h2kK-P{q@9h@VaB zAWT@7lsGt1=ryH-52lLBMhY=I%7MJu9U2q+6R;mZo1jmnGam8S=pRHebuAtLoU{z2JMyH}#${t{jK{hx_OE%!sFEiRA?9@O?0Z_5vP>B|Y4a#dqOh;s7)B z3k;+j(gt}zJPGdtlIL#=)^ENimt<&6d^`pdI^D72aPS9`pqM^9#$|{gub#iBY|^WJ zV)D4c?}b=PMbf&B_=)rO zMf@wh9CTO$luXflL*9}PdA{Fv(e7xLS26)wF)$&F9z@IW%$bqQqrq2L`%ntIeqB40 zdAOjZA;&UWxJQl}G*RXgfAr~|!N98h=Mz#1xqQVQDI@O~t?P_d`Y@yyVhc4Ic}G;R z$J%f*6H6aM{Nm~mIrV*Io@NzWCAfrFCxkH17oM${ui)!a{n*1ws@C z{3aUyIZ9SOP$_s)M=`#(zwYY!p zH(DAzG44LEQmF5XaAnNcRK6;Ry(W&?U$Cdlc7nM~Kf*tFeLRbDNv<7gq;imyZZ*y% zJpFSAGa->&%Vp>B3}CeQ??uW&mu@&0Gh_#BqtqGXfviWo5d#!xtnGpv%#dm6dDMt! z^_`$|WTr)+&&8Nm$B)NAz{v{%vhDHoWc$=|HS1Z-&33ev!|jhLcaSA<)zi)IB<`7z zeG1Njf0tMjTyU9?yeuq(682uB>s)Q-Wd)C&ccM>^#ttV|As@ob_#!3ApRu5muu8_z zd^+*mdL6aI(L-6qf}GWQ=j+bLieu|(rDMd-{LhCLg52@#*o-Y)H35nxqs9L$CV0;# zvYmvh2Rw$|;Mpg}z1pY6Fl!<2N{6DO5xlcs_^v%j)<7(qO`wQ_*=G;7gj(_#x7ET; z2M1s*D=3L^>8Id717a&f{_lPSyE0xW_&||PDzZV2C2YXk`JHK_61ZvE-*InjL8Q4K zm;qO}R;Sdpp;8-P$5Ipc;!V_BwG1PR;r%uH*rJ!vEj&60_Wyff;PQ?z4_DI_qrcj4 z4$||+g8Nj@ld;uZ_vkQz?Dh^}D1Ug7{HEoNmFg-cE`3XTld>|!zfT?+W}lC=*l7!O z$p8F=5SakP?L_#>C(ZsT#pv+L;I$H&F7s;8=Pa7F=0f0v5v1uh1kV%U{v4;lxal|~ zEfa=0Khk0oYFLu`-#ZkP1uq?cv|y-7M6@&=^1MilkgR-MCGk50TQih@7OyrwYYT`# z;9R6;wwD-u1>%bvz=tAF!XJd?Pate|SvjujQsNTUpBedXm7dV&{QvCCwiFzCI{PP) z;DL6X^VyKe3(DHIw`kSUK`xQVQcEUu5WBK~4-yfyW3q^lg-$}aWm3hXAmr0P26FZb zNObQfitmeFfCi{YWwC{!jv`Y~cywJ<_y4RsJ#QT3Qj+33Z8x6T{6Ti(On#-i2$rA~ z)WJDiStOUytmdlNnJ0(2xp`gqXLg-&=SBO*V^kG{&}Ya;06YJ_d;kEzu0Wp8|NaA@ z^x~H|(ueNFG*nByp6!tN+G4ZWz^L)>wv~pof+yEVhf8mYD$_RddYwHv-}xat;Nfqn zB99l&;h5q+EvZEuBE!c68Ex5}mNDtcaLx9)?+eYW5CwEVR(Ak)@r z6jrh!CmIc>ll2c9NG=g$`tPO@kOSXyvhfXH|GDR;EfgzKH?-_+S21<-jOiDceoc(9thGR`{08g2#b>aj5_~PaQe(Y_|_lq6k9X} zjhT&21qtkb-kWhfl-=BL!DzT#Ih%mlXZ8}W9}p*+vjjNpTMIyi;p{X1ZQem|7rnr{=Qpdk$MF-$&$1&slqw1 zq7S*8fvNGGDn$$Gxi|%;f)nrG7dH*MB(4?DCcq!9`kjAB{3-Z;fj2TG%|h~*3^p8( zo+ePV*0LQry?EWj8p)FPTH7jBqC3ExfbVm{(vy$cz#Bm|Fw*Y!3P#}O3|)E@d4j<$Vs+1P}B(EEhu0e zho>=9#^s;u=6^N>l>kLA;B6Sb?*_iRrld&&z+6p=hN}ULug*%Z5I|OvL@N7Oupg>5 zbk-XrsQy=kQ$X=&NtI#jok~$}ltt|S1gOI(Z8r^>$so7jE@&t#c>&pD=4My>2fv)e z%Ph};jBnfm0*^yYPAZxRU@87>3&1Y6io{~gr~6_45&(2|cIW_QMqdljXGj<5_nbcr z*Bx+E3~iYV{=G9xa?Al*e%zY(Vy`oT_wd#7WJ=-^j$5Zca~B97%}sX*i^P_gVwg(^ zO7u=;tQWm48=}-NuM6073@D%Q%sM}=H27S})4_R&I4Wkf=t)dnL)rjWdMFhmp6BLkl1ww zD;r=0V%u$pYc^=;QuqHRj%Wi1;xNG zl0>2_V{*=AE|BrFYWFiI)WjTFdQ`~fOb`y94_2eON8 zfP3lj5fF+ipXISq4L1`{#6a){t6}tVOPlHceo>by0VvM0ojmo#WWb6DdLVM;nUM3I zie`*6RW+XNAHq@v_-93dYE&Hm6059+-9#bJCqp2D&AQUDY)d-9JCa4(F zAyZQ08K)&ONci9M;1y!a;R&t7LJlF-GK_2XTvZi{(T3zicV?S{4FU`4wVX+qx$K$i zN0&efA>;RL^qJhDNMhSjAH_`r@S1)RuxyCjz@QUplfFSAZ=Ft;{Xfxxew+!V9+#eP zaj*5OQEO}$D++&fJblT@Ak6;VL#gWT9Wra6t$z()E8QUGGEP5Ww|$7v00P%r2cw@D ze**vaDS$(r(Gr)QuJcKwJ2=z_m^D#g16c5vG14HHTX6=FI|vhzXjWVmv3Nx6Pf7_r zp8p=@WGE?qh9lz94)F5VvA(-DH1j022(3n*E5HEWK=gXfy7?a4ZU`i3F_Y-6 zpN-hWH~c@LM!Jh!Ubfa`{A-&T$>kFz*!w5Jit;4|Vqwa9m=z_BQkHC<a`9j3Jw_tJ^i(0dqS3E6!N3Q68-0>ZH1MW5&8l8Qz>w%0rUz&kI9>`nXOFL0i( zBV@5V+yT?s00fm_2Kh@=Xc`kPJ*U7kBfe~vmj6D#aQZ{Q#5$Kh3aBRG3yEqt?GQqi zx65NgX@9QiKqhbO;yb@Qoit-d zD$S*|g{Wun?g0NYA7eR2X0t)!f4A-?eF#Fr&_MjO&CuS*%$6fhp%4$*Q67AiG8L4q zq*=;Rjf_nD{l!PIre6C5p(#?$s}nF0kT%u&3QP}I5G87d4p0}Up9h|XH!Ic{IfBt6 z1yYM#^u6a)?1f>mo)Y6E~hD#;Ywu2ks zRU-iO{=;AwAoc%@0ST@SQ7MG1%%?ohxxpk)nwy#ax1V7c1oF|e;sp-j@7z*mWTO5* zVI{6-#xurp3F(}mF3ya$w_qHwz+B=eQ%Gf`xPih z1aZiRM;HL++tYvE|2(C9;4f0jQyYV%gy?XdpFP8+SDVzZ`=8(yEjJHpuAb& zwrlyW2HA#!a$G{^IkW$T!++BT z0mWc+Sm4_SdUxZ;vRh1#=#$xz{l0zWj~;p2d|a^=F}4LHgSJ7f6ZG+?fyL(|2f~V@ z!?Ppt182|bddbX^<^J>)PODGzG(*7E#nKW7uA?)s3Uk0A! zPE;qOqWQuEnXgno+kOe1@}7LwhE|mfiTO8>M|VLWAlC_z@Y)15$m*>6Z^#SwDooYK z$bNMLw5ihWG#-;$`+(FwTCT+jIICE|1v5L@|2 z;A5Djbp=CD22iGtJY(pVI9GsJcYyHWp;&ldfd90`YPZ`C8lz_T-@OGG98;Ju#8;bV zepEJq+2;g*Huy6dC-7!fOU)p5y}sv{Zh6wWy4$b7o4o~K5}$S;Mo=ZgJW)5=?xaKw zj>9cT*iUeJnO#hlOExz=Fcet(hXl3qu)y$N%e>3yAEb{^Vh&{AEuO51~6vYx?iYueT|11RjeXzw>RKHe?i>Y~Cv%jC* zVst*S-N;kgPJ1w!*>PHm7ldF1g(Xf35ZzT%`x^?E7K4evaKulgz<+}>=%zBEnCkxY z?30}=IPfl7j~rLym#0-gnPPq9fu2Gb^0eF)bj+gR&&<4$j;vI|l%3v8(RMcsJ_TLf ze*;yP5DGw2IC9lqmp$n^UjSFt6scTUBUNOs=22F`JZuBzSE=uVN|3+aBTZ&%KaLWL z23=AQmnUrhzA?Oc5K-B$fq8gZIvq43XNe@T&j+C)!dNqYuG^sCKE0ya5)P^QKXkoi zR8ZUZH4KP^lG5GXsS?s%(k)%m-6ayzAR?eNQX<{bUD6#Q0@4lA@b2Ti_jkGfXS`$R z2OWAi`|Q0}%r)0s+Xu*JJ3YTi41R9^T4^7(AdCq6|KAB4IyP6_Q6>0UYQv~daPJRV z%kM3y(G9aTZ!J}ymo0eYJkwvB4Y@IuN-T4Y6`|%e>c#tKLFKIo{ zIm``fE%4DRNP5gc{e>Rf_INjo8Q-V3B&d`q-GyJEZz@q#em4?R0j0g322Z2YbnxVp z#P@y$n5A7}1mY^zBpYK=0jchihluG$B%iS$L+Ofqq5|;y{$e_jf`5;#p%>CHxpA{% zXgC}YDIf$=7>nEX$t~NoM5VlP z!u-tq-yqfjRFQ&^gal?vz86%)`cV$k!p@O}yDnPJE%53-F`Q9w&+1oOlaTZ68)azv z2ov>4p)5fpr-8*q@*n4E_!}(h(M1IJ;=#7&-c0sq!U#~m+Ey($P!qH`NdOhBtG;v4 zfb_f%8|e&>VUFwmLVfPNnR9uQiFQKqvsFqB?Rcht4h=b60oIQM0`OJs88#m8z*~w( z&al1wM$Wkt_1OC4h)u<9(;`YN>>@sOi2L<6B>_lR-LF&}0oe|ZMj2Gj{xOgPD8Tb2 zC+BKNV9}8WNgJcOfN{QGK`ONfv8Z=Jx~3ri)olV;=k4*D&+3om==eF!o|xM{k@^i} z$KGg1n%+Ur4&k{)sn2QSUvY^BAjBn2pnk1$lUKls1xkzlvk>k9X(qUVM)`bQ^JKF} z!<(2m(^lqmDN~BYMU{Nla3r+9XX{E*hb$AKQkc=A+TuQZ$AigcfCd5%LqG)k#O`& zDc>(`kXV!KoH=U0da{;oq4$nTYsSS7e+~b=cZ0vco`mKDjoz&q+jn3D7~@9Y=sw@_ z$;%k^tDYDa2`gBHQ<7aw(xqnLk(|uclhj`6#!4f^!O@#3L)wIUUG-4j1Q6=sOJ#15le^-Vl0sp#y#0_~V3z;(o9NKe_xO;z^P@1)4fWo}S+qS8bK7P zf=Q`m!IHxdl*s45em`NY0l{(or$n7XduquUq=#M0zOjC!n3}jl+-BXeC!6)m4bILh zuSgnOFi{0&ohOIluw0|*E4I;{Jc=V(E)g3LV$p2%kGrkmKhU+rP!&5~jo8N=x3gQ` zYMCc1zU>HF13>nn(7gNpa@eyzP`z2<1-fQ39q8ZYQbbJiHQuCk1k2zZy)q=w=hNXG z*7fPK7X&6gn$6Kgkw9FM>(0B+kb`&6M5VO%8v8(eGwZg_T`{)h9@*bkeKf!=a8cg% zri8LxiHq50XvDw`9(@x`fXmT)wu@+86fgoOC;h`7^m4NbvNm5GM*AljDX&P~RDVRF zhTI~=(Klp2 ztvXkq-dIxF_zNUQOKH76w_w@NTqf5AHK#uSX}vJap7Po9)OY0@E00P?vm4&g z^07WN#y0ln=;%wy~;O2`qO`5!zu;tQQ`|ZV4 z&uXI=M>ZAJ7!uyr)9sO9b&Fwu%esk$-C3qDMRE|W|2xfo`5Kc*!fetk)z}MMcAIx- zn=6atwVA73RKjkiefu7xg{wN1mda`SAZ~Lw`=EiC>mmKa^4iCNJ$k8}ij-PFs##D+ zXq3VR$dWsHz|5Q}-(($2I5MYSTo`S#Gk^beGNvwQ(SLA+9h`SUsMh!svy?6V_>wIt zvc#+C?PBhit?1g4(2>-0H0TA9AT6 z3pWW8PJ5RjpI=-H<{xYm{exQz2S0KlMLcaDIFr)pId=_7wC`H(jB(sQLU&z}GaB$Z zFIQ$gj@v?j^+q4wBU8o_8bn8zQ+2UZ`zUCC%{%R9f+kbk*?@0z( zH{~sqPXGbFh7FiP9bh;U9)b0!6e_pTZTGVpv^~U@Z09y-jVYkNsjdUQ-;YEfa&5HtzxN zVLP*BDKy)cF`_2|Yy=;B7%QGSoi_Sx-6G6asUcjt~x>%@I!{_K1d zEHS@{uO-%OiLa!sV=J+$Vn{Ts=Y7pn#_G5ZiUxlAgjG*SPl0*L8JfkG3p4>&D#8ff957qPoz((V1 zu7)i-3Z+I7t)321+1!%Pg(Nr0>P=Lx2T<+gbQf|yN9z~m@A)gpuaqJOwM=g$H76uE z52G@>k9bla{1dNDrE*b*II$*t1>m-Ge$eRub9w)+ab%5ML_sYJ3K!qZ`JG*E z(~+01uz*p*l5VB{RQ3)E`ZwV5Trqn7nx_^Y%JLKZI6e$6jF44c?gV?%n z?wFd?gv38){-2u4EbVZLNX|}pjB3+PMq&G+(r|ez!NJCwDxEmE#113`vUeQ&-#?K3 zV*msoMnNphjAkCM2Q%VNwPm&Y{B!<{&wQNhJR7Z~V{V!5>XA z3LH_1oq8z#XdeKqINtj>n=G|bjLhUWzWiVE z@ekJj4FedppPEXk!s*gA?KVSpQ&}WCy=Y$vTRG;Vc^TFJpZ$0%%!v@U(ZSDOHEJ2+ zV7pU7@kp_p`rpL+p8zu!oMP(CWpQ|)pfE|E$e6bv#$J|FQ~ba0ivgSd*9%|eA`zKe zqppXcJ=7pbLa z!T(v~|9runM@dt52aLWtNN3pu;7&E0K~IIOH6(XY11ax-SgDT%-~icN@<~5IUBTrF znk(wmEZ7Ek2B)Xj5KiuElqRKf847Io4W#+h`r~yonD&|jqF7`9%xdx z=sPV(HdsOf^*_Rd2q}{MW@BP}w@ntnROTl;6KdE0`5YN<;X!H0B|a@-7n6Pq>GV;L zYBEd|5->1kusn~PycD%@6Tg{g^L3tL3C{)2fLZlp{-PBB-H1R_==2&PMx-{GhM_30 zcrRY2UH(86TAu`x+lt>qaExupXiPJ5KvvLqsi zbMxncvfep2P6%eGP|m6)IgzlZTT4^Q3v7a%GvyDgAA1x(&j*T}kH;DMWO26mO4v!S4v2AX#>vF-#0f29u(R;iqx2A(l6)N=7C)HFEE`_C6osblKN;6 z!};7GvvT+;YcN77?2tbD?{gMyi(g^Kt~ac`j5LYWx(G(Qb&T;%k_S{t{a{`soO$Qc zhxCIl12(p+ElFy=-*tt`cZO^wA+_Qlb8N{S!=FC8XD>a1B!tv~zFP>l^V}-Dt&aSf ziMnMi9fV<=P#Bg*J=hmajQ{P+yv&tG;>qPINdLx03{EZkNfW2iD*S$t;Xq4kx4$jg zmicM>;;et=Vng(`aw0@{ynt-!h~%H7qZ7lgt`q(&d#@RvRguC#9nLf?l!Lfk{b)+2 z`@PRf*$p3Of?F(%WK>J$TY61-m?<8~`{(||EGykH577H9qA-s&pJrVb=2TdPA9gf^ zMr<%vKZM_(U#W2Zn4O;YGBvqG*2{ZGN9b_2^aPzh)J0y`g*p6lR^Disa5%g}$;?Q8 zdn9xAC{F6>GKe^c=mv*O+Rgl$ix@Y;N7bS#R@J|?y9nU+maeHZtyM+K7MMbT&e!AKNb$j{F1oH@=0i?8Xems^Bjz+ zSt|6)A^L5x=={7Gl`B0eJ6$R#b5Th21@lyj2)q!pgHK$TdTXrHm*R1uoGaG;a+~Mq z2L#PL2f`sx_EaX2w^Ld%e>l#k`c8O^N_;Os)xS)RB-RgS7Z=%2mf}pSH}PjFmY1#Uzq(6ctBb3QaOGh4Fb;k^g`- zb@li9UE@cIHkj*l0c#J^S&omw5{{47yU&esj+P~ZyL-80b3r)or~O;c(m$>t>&a); z5znqpaHv?rvx_CxZ1k}F5@0P{y{OI0YdMn>^j(-oGwD;KOwt_Fr(BP4K9xMuq(R+W z&8t{)yng%3t&K+#nNQ!K815hkY7DcHkgh`-EOiP4T?{zCJqxWUqdsqIip5fqmjtoWd4OOnLa zB0(shqc52UEOL~449w{K#sYHE^LyxK%ji>Y1$F%2IlGREy_g{@4lH*zJ8(VP=L-_x zyYS4Bzio(0=eAw&u2H-wv;v9ec#PlebjQDXzmmXQ=RpT&w_Q_VPXNQP|55vH~iilnSJ#7tW9 zsW?a*V97%%J1O@Iu0|x!|N6ab_-#dnvS(LS<`^5sAI?&5SSKkG2Tj`t`D4oZKA6{m zWPoFfF-V%YzS8iqEykOZj4&}7m4D$z5UFN-3`iv;-4fJ9h;zERXJLc*E_E_cJiG@v zYzQdw^N;-YA~KlW|H%BXpvckGgEf^z0w7$pyZ89bB&ozDt0D*gg~0-lT!H~bPyj<; z9h?{PYLanUK0Y2bB57Iu4`7lgDt%ViEc~=NaLcwLIGkEkVLZ`lJ%!X_NDh7SfDLp2 z212nl^8F{uZyCoZRc$v>F9dCR{15!{VwSXU&}iOYgvL*~KK1+hX6TGX7DRRjG>K~f zbo}^@+QJgUs;x&nNE&OZpIScQPMsWlxlN2(pc`_u!m;QK$j9Tbak8^XsBy9+-4pE= z?TXj6hvrYopro9=jKs~F_>J-;b8n8wYg>YfT_7!{#M<;`i^g1#`hQr~pE#cxmQdVA z&$1a{E@881QdlIZgoQNk)d1r2sC@aPYya#~zq1asR4{HVnGqoR1@Ug9+~8nN_2(QW z++odu_fsJUy&(gqdf@9-R@#OorH&Ia(!5Qu=`cH_r4Zvx)ibVxx5g;DIV9#&4Q>JR zK~m{VbTxXp><9_OyJ+%!?uLB~SQ#SJxzWJ&OgnuH-jkV_tkP=L~@K* zy!Aji`G?gqK)5jT9CM)tsB|RXtTM$US$2_jd@rC>^e4Ga5mIt&$lj^Bh6(G!*Y?W~ z_>DAFGj>Od4)g^V&q3@KFuaemrW04U7LvvKRM}|;!f&^nXPI+`xGQmxkyzNt#~=`I z!^{<-{@CFpLr9@AC~0x`K-bHGVz2S<2oS!wlxe$78zDj>lWQTd=SOSqWe=h@1{8fw z?kg&bn06DTFX5|KB4BRCK)VFybkKND`0lqNrlOuI!XFNk$HxcqKA)ie9|jHf$}j{O zRKB#9*+3x_&EF{qe%g~vAUmlZ-V(;@RRIZ(@wKa~#)Ti;B+MZITP1kH9Q@q~#4qNA z??#G&c376ieUXzG8^3wtBY$rRgz9@`0 zGMeRg@7pXTyDCClHMx>?T~i>X8$g5lt;$u0IP+Ge-L8HPIYeG)2Zfz<(_1H+&Rzym zJu#k0KlE(uV3fL3+k$Sb4BSUftRun4$(WQ1uhg|EV>ttp5HOm*yQzhpTy9_&D+Nem z5;AYfh(bpd#=8c|4)ce!b(3cIa1(T`d^wo#?j@PxV(rhx?dX2gDQicHO&xO4 zN^=|nE1DE47FZ>sZn1Sl9D4mGvy~=yRUk4qp7LKj9-O)f%q8zj&V~?j>CvD`pJ6l*psfL#pkK`rwYx|_PTiX?>$d&8TWuivL7WIR zvla9>qfcy!lIOFq)*M*p|9PBJfU3E%--pZw_ux*ft&uC75>8(Zl6gM3o+^sFq# zcc5{V#oiH=TAkBQfS73^6Q!3IxD#++L^>`^5_qgRXZ(ma@jl-qumwpox81EtP!1!& zMq|W@fKDb0chIc= z71H2b08|q}h2h)+mTtE5yt@-B+Jd-xB+gWYt47B&k!};Yv&wP zf+uW&maQG=P<9&f-F2pVsix1(GDF;|ENIb~kWj+FbyCYvb@yA&!jpm)a=q51Yf1v~ z(I$kh$!I)O{wb4(gj^=NtBD1WMm1j%$>@D4H-*Yf5}f!HkrhCM3-#XfUgRG(We+>s z1v5q+&iH}E+&usH1GLiuC11ux!5*b4(^`fgq&1Df4|(ZFv~!wfcR_#-n&H=gGRVN? zI3nXU25gOe{XFC90;qNOmJk_lEfI-t*A4qY)_iz1l4{R6{Zh{!FN5#g2zv%h$(ZMv z(o?0=-LF)D2&&W`V=@_c*97(;`O?e&`Z}8?5X3h#{5E18tRAoApXH_DA`fr zr-h6RxFl1#HyRMsi>v}9TgmvWIrs~C#{1qLQp!l=sU4RRgQ;2zFG`Cl7UYJrLbs=L zIn-q)$PXbJYUwP5sCkcl_&)A#2in{Q@l<_#UxA;STI0fv)>USsNmcv=CZJRiw zO6PEnS0cfY;JcPX<%f-nOr!EkgZ7VpFd?0c&-;=^)Df*eqyi3us2}L|8L|;t;o1 z2jhESA>T-7{l^L9zJqWhGJ1aBZTO2dRV3YoeESDpI2*!$qAi?_c;k=u7}}uT7_a=+ zb`oBdU$o<5?sXtZV3GHrbJ|<`#!H|pJQ2mI*Z7O4#1Zf~8^7&B4B#dQRX_zwq5R@> z*MR2631eMnnu2}+YhgE?uet6vzP-WS)2mTTiw4;4OKri+;SbVEWZa+&j98+E8VHe> z4K`~UR#|qb$j-wc7WL)NemKCuHC!y%^WF3IUCM=1z!xt)vW7lNl!hrbhfbyN4I0PM|E|u63dP>^&cu ze}`~5h^mXppgsCP|201nT8!@qSgxEw?Mp)-GPEIAZ#k0Hapk1?9wmW}2hclDtB&ccJQ>BjL zP`6RE=QC5#Dwlz*O+HpkHCAsesCC7EpNuhmY^`bzi0{Dt^J(aQ1fy97SyR4(@jOqj zcy=r?S|@AEJ~k#o5t-b7E+Z6?`H^u+&VYd1SdBGT-c83OafD{3K?~dx>@7pQtQ^1X z2G03kW#EeRMV{0s``gr6FVtr9L9GN#UC_7!=t6LpTlLAScBn_+G50Opn1*Cr=BWSx z6RCNFP>?w|oHJfkFv!zSRH#Nt7r~dm-H@9>Iah?7>y`o@-P)S2V=N`4Hn9fS+3r## zu4)E{7J^&ae?1>Z-#*i&v`Y~5xJan~=Zc#L1IU_3XV&fcjMbMo-`x1z0qAIxSUz-n zfv8xCA>y|EgOgYcho3RWHmJu0F#nnuFQ_Ch6cgdWY?9I@u6~x9kI;nqi=QbE_ESLT zRBqU3;=6&T6=lnf`@qffzixqI83nilk0k`>hTquKx2yt=1gS8$|IJlkZ8c?zsbv)T zDW>8PHlKK<7< z03U(z7UnXEqaT!r9#zC=n$AVzTq$rrr(ZDXKhWl$DJapc0eQ7Ic|^&r@GAtWo?_%w zaNMGCtxD?~vmoK}G_o|V!6)rFRq6(mSZDj8LN=l?9;dU`C4sfMl$MYR)Bjl?EM~qG zs8tX)d_T^8T!+d#-O_m0$>s*kMe1@aAax{g!~W^_M5xpP6A%%9&V~b%VQ@tdof*UP zN^4mVioI~E~-pv74a++i-;4itCP03-LilZPNyBu8eJ@lI2ixZx}k z_W{(FNvQb^^mVR66I$ybr;KKem9y<}(6KFld18_5=ksz2h+U7^fe>M*;H2wu(+I?L zZgiMSqn#Td6e(&(MHuoNbVdjdR$c~!MexIFFl*@+_d^~}=g;4!oi+s~1ggNZCpBijxabQdo)G@rt8Zm zP@30@*BCqqu|Rykgx?2%2asTW4j%w|^%mp=k)(S*Yy6jylz?d(aXB3NY~{sa{Ckuf zXa@PN8^FK<&vK~%5f3by4-nY?-$fIO8*g&4MEP z9<;j)RH1IUF&ns123}ty{GEImY<&*a(|Bv5dR4@Pgy)_)jr9#+Mkx6YdUj^Z~ z6-%8L(r6j$SYfLP=1T2u_%?R93ARe8s9a|0o*lf|r4u@r`4Sva){c@p3DHP1CkS*& zrwGHJ9(E}vTmAGRAPV9951=Eg0O!+ecAIbhTekPDw%F@;rk|LsO2lO_OKrP<%geS# z(~G$!S};%8S_t9L}OjtmNH zVcg?MjQ`2@8Idb_&CD6C3RNgEVqs7zxntq67qHBLQF$^7;y?=!?YT9B>p6eWO|}Px zT`Sp=8v3s4h*UkmZ0|pZCE}2bO#kL4Jt5A7cM1kLOj#b}gPprRa)^9LwtW-_Yi*tFa>^D=azGy}JphqPq>9AQTL zM|Jv~AsK1hVI3=sK|R!(35R#Tb&h<-8%#>zk5uf!SU0-{x%sE`i`t7e2Y9WsP;D(1 zE-c>p_IQnvKLQ3cci^=RNAKN=E}(Y;;<8SJR1q8hKK-lIi?B|GFP7yM0w(H{Zj)vh z$a2Ym>81w~RGSYnDR;b;FdvYBFutV^F$X_dOGblF(u{_S6}Czrze(Zy!|DpVYonK( zb1Ie~J?YBG#H8fNmj#{o|5Q!Ggdf0jo6!bq#G+9Sv%|V&SQ#2W2_XbwchDvkrxnH< zE6Y)&IFB8=-n$=b=MgZ9`ZtV?w6JbJ;#kb-nsRkgPH zvu?GZxnK!=0Ws9q0XCe4r=rxfnt)4XWxD}7*#DPAVWa@JpZNwMpDg8FXb$3$l~&HyP@b2#Ayq`Rx5MrH;|LUk{oh^riQ$!RgT zU0gXt_9-&HtrtZ*Nmct*`3o<-S2uA=S3c40_|=J0aU=CUA~ChGLdh7?-+}@Eyw8}d zFqQZtyBUCfVoca&aqVpNa$b7`Z(;tZnt)g0cM?H}^V;ENLOkLiuHWf|^P{9Yd#Vf(TKT7{TWX9hL1K z%NM#Uz56|pCe)<>k|YyLDEEL*VGoR!CxOr9Z{MWCTwRi#Ee3a{}4saM+`}WQk!y*vmkU#7Di67;m5w-PiG@ zjA8{sS}k+wUuC97Q!!vd3nL!-E%3I4pYvy(X3Rgrwf+J6fz!M%Z9-cn^ds>o=_f+@ z2!7kv>+ZnU;s4$bC<#D&^aHUg=}(GTWZIYd@-xxN&JR}{h1KGNGAc^*DlRJmE&s|0 z(-8P>-Y|#yfk$%8ajI3Pizr4K>UT4(+=Lc+0`dCckm~lh6p`2UpK>+(c3z^wFo5(W zM4_^Pq|VSYXWmYK`4oa$j*P$Do+ zOdm){PI`wz^YsndOM3me*fE}G+yVjWQ!eNeP0*7vSBA=o|M0lAn;xmT-`UYXM~D=D zX}jD*kVpq5G|5igm!K7CPynflQ9OQn9d=tVwe`-HWo;3p78`@15` zK8=JaVIpJ;`Sis*8u*N*vSQ*-|KXklVk3i=C?QQIbAM?nP;9xP{b2JGbe$2Of|ju1 zFLl3C8b>I3#N}O8+-~YsWUOJ5CI)lN46hlgfZ=|z{`1ie;H`}~l zbw$Jxyrlk-kM-g!^XCvGn!oLIAT{zwhc@J01|`q%Ig51Pc_5bcJ||iQ=ys4l3#3IL zr{)r+em2-ECmd=5xFByIhLZ=TQs@j_IlQI}4bLGJ!>aO8Q8m}I-!kHsC`5<@qlyMo z!JyFH4lJbsgJ|9bm$95`nPMk`ZuYR}xhRzCI>J_e#fW}*4{ z6fTYP&u*`G*{DKzPr@woi5p>8_$mCc-%aVLfa}-{Ov-|) z=wr;lUr5s<#$^|bP{nOyx!>10DX)UsiFzv6dPbwpb+ea3Yh4=9a7JCrB^BnV`7hgz zua%j!RBIe_etK6NOU2p&&;qSe*Mu2RJjd{p*k3KUhx?|3h>GH8Lb7Zsv zwfF53{(VsUgvwcZ$z$whv|Z>=P$?S}x9^$gP4@khJ2GIj3_7_e6j8L4hezXoIRu@5 zbCA@wUApgY$A`Gg^s>26fPX-OKTCT!;&Y0s9|?skvM(%<3>9agu@)bjxC5ri0<>gx zOoC7-ml!HgP4LC(-3=bneuxcPuix>5p+2EGlMLX;YC$c9@yVQHrgZEYRG+Bdr&2lDiat(!Q=YUuG$%@9SQI(f>9f`;)4|GJ0D z&&*ff`Mq2%N#i=zBKwPk79 zK*3`wQCaS1a9gy=cpG&CCY;S!Qt+RuCUZrx4gVJlDEIl$IB`r|h3WhniD|myq^;Zq zJ5ZZ?A6Vbgl3DOlkLST4uTi}2mW{mh-EBf7F#Rvm`}FJLw=uA3*l-{=3Y`P-%YyFr z!^AhM*<;NU8oH}r_rEk8_U$4HowaswetxjeF9?k~o+wfto$)5P4_RL2zPRV$J1oKP zHs9VXA$JLUfVi=qjpH#h@yMpCbHP^dw=WEzL>~JwK)ZTfFpKIsL*C|1*)_!jZa;TZ z<(v+qK78(5nA~n?tm3_QL2>Ojk{qo-7C^*)4`h>mWe03Kjcu#R$Z*`$#Qs+YyFfqx zWBwr7)eppl62FtrdI%_RY+fS~^B_>-iyJ6RzE)J4vkp7+nj>+za()0RbsbbKi?zcv zmdX<;;v6VYw^47+OLfE?S^=68@c;x2Hy0)|LasRA>u!^NW|>xax$dN8H*vps4d0e6J}o5};x%JtiYJ-^3`sTh%l^qs!GKqcZ=1dB-{ zmiz$#IBe%50Is2^aP(!ASC$IS7Ndoq^+Kwtj5~o9ka5V7<<8~6$@9r44jDAIb0`!$ zn|W%yRxioZt=Pv&kzhmI531(HK&LkF!go_O-RX>Ib=ckm-1=vX$q-YRV9A2EOe7i2V>3V5Ha{-MM#i% zX0;pCv?!23K+;TyfDk7$59BD8d^*}8fTiig!S8oPdL2;m?SfR2(#5Za8^AiYF&UmC zP&Go6f4>4QdZ#{Uman@7;{$d-Uq1izP|;Ye4v;2WR^UN%_5}mi_HkL;+w6yIuxy;; zT#3Pq4U@nwcEVd)3QG~Pku_9DsMg?F`(y2wTeR<3sedsgWbj#*sG4tARi^dae_Z)z5uI!m% zO-$Roh$lNgat2v!fD`#bSC8cuz~He;$?f@$HdVHB8HwaHpsk;trsWi95y)clxW9@D zd^BTaTYP*h_b8p+w!=C?fB=okw+x$5_$;e)|0lN9*|Xd8p*1#RBHPM1U8_&;MxPgE z;ePzNOvJ`$PL?u2<~6isW=We0gf8+GU<^7rT>O$QA>m5UI*wikfz{VwelV*-U!3PB zUb#CH`V8M_BxzqWzXH$*9&$u7``c%U_4>;9HTi(|vQ~V7* z&wiK1xLOyLh3JCG7Kg!fp=$0w#(}qbTe86G&?at3-3k;-((N5>Jc&J5;(!^wY(7c= zWvYmFG{$T{E{;cMptJ1LB`}0>Y&o|nQw_8R(qq_ooe>rslBR|!yt#QTrGZ{X3w%Y$ zhxWI>?LSGP-4b{L;Sh1MZ_4mXph3RRJ-^kCIR?Zq^A+jWHldx8pZWYh%sGPCb`MYQ zy`N|Hu}?eE_a9(@(P2@J0m@#W34AMtiRWe;(*X$R5 z)4A`OiQuP0$|^s>Qo91V^lHN@w@W9Y2pP*dQ=)cq(PYSM>Ld1I1!~jn0~6^(4NCsC z%F0l4?hme zko_vnet@Fq0^yp^ep3FbK+1jrA;hOaWLE*5zof<=I-UXKcKJ|W5p>Y!Vl#E3Puli>&01-+q5 zmt7cr%a|c?fAlkZFuT+ReAq6=~`BHlPTiuI54-%A*4bVoqL!$0!~EKrU%ZxANv;y1!Ylv3LYe2s;Vx zeB>%Sy|-kvwP18$u>ILUD=`A*Vr$C*XwKym zLccdn9}N^_HVG-0zq0`ufS3g!`0&w(8&%TZXUE#Wxn|Mt>m`Uux0=9>`KsCQ6USE_ zh_rpWXnO$6dIwF*Gmv&UK9##bdDQX{)2;|Vbpqpw=TSFj?0#@;s#}QIIx9g82DvUF zo2b))*`XIQlsmT+VFhsfU@|zDB?|%JSdG0VL0SXQoY-IyjNU|=jl?$!lwkSH=R&!7 zF>jz;v)Lsew^G+GjfQU+PJGu1;IpsMQvR7Bsk zJA!C{^nw-s9%iJu8JZ-{I(pDFeW)^P(hV2$4%Dbj*Hxmq*z1o#TW8>K-A?}slg|K1 z^dH5^xm`fv@pHADpTJrZQnt9yXIY3N;lvYAv?-D?5obANnh8IK81v5SPF-;pCxmqV zVFKf@68k~tfTjZxP0YXe^60S+y(XQrssjG^Nn&)^}v6O>7Z7WPYsnLyRK{JFw4dy>*{^KtYLPjL&j#oS zs))-txRoc#XR2x6K@1$Nms4q}HVoB`5JNIaas`YSW#WP|5xc^((4L~GHR;wm7#So8 zQ6h(s7I_CM(@v5AO^Jp=Zx7JQ8rpY~V-HX)^+ z%g*;vr99E(1KtRJPeEh_=3_)}m89bpt^o2tCL^{g8qqzo{G%t*vxfu%A2d*2;0pnU zKphC7AKzXC3$Cd;HdOb#T^ha^A#V#7B0$q0WbUWZoohwv4C3@meI3vA6lB^F$dF1< z#J$^NqYijtNiq~?c2>ho^{qmDad!244|y-p9tIf)Dn;hB5aMg$W2=h!ti2B$p>U~Q zz5rf5e58?zFP}S~7zF!hB&(F6CF?F{DSJc;pgYKA)2N|EH1mh&&G{O?fOCY~?;rHo zzd|{>-L<%@ECn$mzqjy_tyhvcw7{Zm3ALOz$4%GSBn27W55Zl;QFwbeHjbKY%P!IG z&K}Ux6C_m0KiW!eLV}{xbcBjHN&bp~1z0Hk4SH-nXNl~O+g`L%#E!2Ga)RRSOQv#$ zTH}OBEIvWXhTF}|awmQflL~H;tUn0I&Mn2_fDNUtGH+S5Lzx3LoL0?wEA=^U&;%)6`e6L0tn)ShpyKF5=4FL{zx!Dgn(5 z($R}H@>u%|FF$|zC3VuKwG6*{3zg7r&(m*bB2ERT84$aIIYUVV)t&)UiA zy9Iu6$fuEUR3X%xJiv6bFGI7AAV;4EifGnKt5i6pE!*s&Br=BFtrzG&-lWa2wAq_aBf}3!irM7#Us3e(`NKz-P>m)Mh&Np>?EO;JDH2#j_nb8R zl`eG#6tfA!ZUATTDMoh~9w@?{t0lZ|SfM%3YQd*5C6O$|CTQGFNH*FE?PxN{|9tDS zdh~wH6$CwDasfk4TBqN$e-;|(^YQQA#3;HVAE@d8=F^qRSJhXmZ|f&5g{moze?=v} z`aaH7&B`56x{K1y>YljrcBd3=xK_X?S-<|a>r2Jq8{xO``)1$W{X)y=Q4ce?BJ}A& z(cjfTd9Vc-J<5N>~RM1 zh_kv3T_!Yf2C=!8H8blz+Mpy%tL0U_+{yn_fhgpwNgX7F zQ5jE7-O#e_yQm-znX`Lu#h4Blv<(8v=HXMj+*O&IoFCxr02R|m@PV$k3pu1dM)Xce zpFzj;ICam*A`MM!=Q=tswaqv<6AK6tAaL|}I&Kdq zS%s}om07eVxaWCKmF`NJ7+=9KPxFjF&?rS*TVD$;re`ZgV<{ij9}(Ms6V5Dw4_MX3sD(rQ@JZk>Dm; zh<)2I<-sPh7*%#pXL#EdLws`Zq##-r? zn5Pk8jDTGf?nE1iv|fahPR@Mh%7SZ-nWpLBEB^M=UEut~%dzM~dw{z6$|ycrdfmhh zH_emRgCEG^-_78h5%4N3tAPJ4VL&N4Fm+Yh)nL}$i$@KY4d>tNcY~K&MT;j#o^(F> zO5-?!O_xAMkuLr9KU|kgpo)GC-l1yzN1!o!Df$t3}f4)|02O zP14{fA*_eD$F%nu*&%JyY4 zE|u=K7varh37C^NxHmtSDwCAE9v02cw_=gzX})NMA9qG%p)}3nX?w-q+}wn$wG^X^ zm`yr8I8FBH)x`yWHVRYTZXIPdrsQNoR9HSGnQCJnu_UXO3DTG#$Y%2%3mHF%tL@(~d`uxn+i(?WP1t7LTxS>kn~_VSbv0FE3y}3y_DWy4>`z3&cLhGyy?h z7T#r9@C{cs8`mdmkI{ODW>CoeQ3Vu51Ipm)v3IUi)TLaCt_Rt0C98@pO<_Xj$Ce`a zB$W;4^j$~~PDrRk_jzV-X9oqcquC!|yT-<$hmhdQoe_mRVSZ%20Libt*f(SI)*eDf zmuxjmKux#srK^D9tJT_rUp+IpD8oXY?56iRAmQC?VGCNy>XhR6hD=3Ps)~U^+r$Cy zS!U}bUjslhRQzG~)CYW-sojod2oLaU!YAuR2-v63r|8`3GLPI@%58rTF(x5O;j5=z zZ(FSK6V7;SPcIX-0EIHr52h`(YeeKJO$I&)?CaS2bNDpo3;8T*wmQ&p>qFir(Ns0> zBj9mEKv!xa*77%bke`tORZVsXD+7>&4}P(Ett-;=4Gmso?-mIyivqRsDWaDoL4YQT zX%8W7$k+<@?V6Vm24(WF+x{Gc*=dd~w<0!zV4pw~afR>MymrO10mv!|;n-KL-qOKJo_BL7&8*Q;BaM$0<|svy1)&_=l67y!R@T!G@t0 zef@4J(_7aY9YHtlN25Hx&Dc?BUsvMgiu!i>7tf>lH}kVEeqMP2x&qW-!wGxiha;HH z^pGH)J()*5WIbtsL4dgIgfIklx4fSfYIc{Gtii@MLVR{#zi{2qpxPr_y%&!k6_vcq zvs^67ilnI7?9tb>3UVP@&1$lzT}>~vqN6F44UPOWNY{<{o;t+tO4$8--2( zPq%G&P3(?!W^korh&Ame{0;asOG-z9#Osz*T&;x2^I15m4@&Qa@0HRpp-^eL;97sq z2#Wa<~cnHVcJa)h*{i5*3J(0|gdMO|a_GBzF82tSgR;}uRXb9j3_H-PQv zA{3vN2Ekf^866ICEL8~*E&HhRIYhbItp{Ki75Dn`@ut zkbk$44sIFj0wm_IWDJ8GTDM^tAD3mc>ku8&6vRggH$8(uf-$!J--QDgV_Z8EEtu5K z((~9H{RU*QtO_ZzE~8JFS84nTf?B)>ry(4tBGYcDjxzke~caTP3A2xx4&DuAuZT zXkf1MjavkWQuQ75Vkae`h#+Dt8dz4Lu#9cK*jR2PZ6l`TIr^2tdLW=fvGDDJv&As zr$o!rd5S^Im!n=e9e1K6!V&2*P9MT(oB~3irGy7AxMsL`(Iq0?!g~`j;&WV~R0PKI?z*Rx$}` zLgP_s?4t^bmw6npa~+3S!^93cNd93og^M4){X_9+hAS(QSiDKlbh3WT&>O&p?*t9N z)aK~Kf#)#fSB6^9V}s#xo}~l_+bDrI>~ zZ1ED5NekvlBxgi_>W!UZ@Xn5m69?P4KU$R*(Z%-&>xXyOZ$FhTPlY>7t4i(-xL%gGuu~e)^)$Bl_Gti; z^Q84p+@|yw_zzg>Ft0&bo_z{dp$iT6W${hwFPTNY$gK|CCpmnnJP)5N=r8y_U}-=z zWT}|eT$kso&~4wLp|NV8J=QP~CzR}fJQMkOOXPARcO4f9D`Yl$b*yZ~620^JK3?{16dZAnNf63--o)K|oNkj27zUT4 zrhNnxPW$9n9bWg+8-q5vQo8)P&n8{TXYyiPl_Ry-veTs!^HsUXtWLXyP04jRaF)=S@B@fNBs!Q{ZAgQfZr`VjbOrcrlcH(@pOk*(np_x#jSTh5+!qUt| zpW}gj;WyK0^3vA8-Nwj?2W%hmJ-}kv0+9r}8e^ZiOg;KQ<=6gmdf59z8;4!-N;L7w;$87$&p)LY+7fLtB-c&LtwuS;a=Cw^!;#d6Cwa#&yR zE2>xGqN17`wqXVDj$b#h!p;j<R(2 zZOPDOh+d}AOerB(z9c24jx$wqn~Yb}ytX8^aIt_<)IFC$-%*5z2}7Ym2Px(w_}TP&0aR`RJ%giB2VA8WuW%CRP1K9N9R zg)v%yT;P%)6exfp?G27^wgcyDza*aTW|4v#MrRi5kWB2$P(E+ipCEDex z;7Ljsal>;hjii?fFX{WTnkPq{@#){{Sqv+8tAz?tcI9hh&iG9k7jHb5k4-ULC1M># z0TbMI6*$yNb}A=K@+o(_OpEk?gR2*O2wrb4Qb5o;tHS+#Wv}UIFb4Pen-Bc2)_Kgj zF~o4%yX>SgkqXu)_t!3YPAf)zz-V;oxhaDtE3%|UHzj=zD}SP3A&yE267>TR+SS<3 zj9Pi`3OLOqM7%O*l=nC=D3n%a+Psz)aTDxdvm^Rmrau0f$WwPMKR3EmE@@XxUbZg@ zu#N!&Sc|lQS`W|LAK$IDB@txGcT(Q-;J;nVS)BTl?37ZF?K7>vXor3tRfd0??7t(j zTMwRKY^r%f`Cqipo4;)5yRNWlqOs9SrzsP}KXoQ_q8A`7JJ54?D3bfRda6T9OM5^e zf)@)RXLD&LFE^D2G}p7^W76=P1fo>(&FZ8Gb?z8iQVehEC>14F^2)#f#R)|ZaSvB* zNyF3`1v;A=kTKr?wd!gF7nzvH=8rgk@~47L7a`RD=xsNbC+i=4b|%Jp>OVAASug+) zu;9>G+O42dI_zy}B&h^|QocIWSzh(r$uYZZ1IV9}0Zc`kseyQX*FSk*N%k@M0mto^$cUmw-NqSG^B#6l-W!eYhszxw< zw&D;`6a@%AnR)j#t0EkjPC8QP^1ogi*x7#4>B^q?_upoe4pS~=<#!DywF}EuQCRMw zOptr=j`&520}K~6+HKatBiGchl&`<9_C`oBwyN>|6OFL&GNpKLh0$||$5Gx9<2rxg zwFd$LkU)_0Z)#5Wm3u!njT@8yLIyM`Vfq327`T?ALJ$@#0cn@?&T=u!HC2it3=1Da zV!Yb6&!IP(x__16!$;BJ_(!ZO6ra^>HT&uBefe^LoCcN=shpMa&BMPmq&AZ?=pfe@ z)oqu5tE1k|5=Jg|s&Ii^#~)_6Z-l0R2ZP^jlXpoK1JBe&@z`a7u=gMIJa!YMNq-}MIlDz!l;9uK1YIERN~K`odQAxYGYG8>kby|IWg3Pc z=@K^Xrg;#JRtHUWX9<1xQOWI0lYO`3%)`61r1&s?$kAXV=3YAyKN!+;c-p`cCPYa& zRa}GY9xS;MAK|EQoTW4~k*nOyU~AUHpL2Q)M-fqQ#Y~ zE#|G&g`t|N1&cxearHVhl(o<60bONUkiF;A*&ZER$!G{{OVdc5*;roj1UxzJE*kc(v;^+p^ z`8Hn||GX&jixRXj&^oQCENqX9zm ze6hR^l}Pwvn2WzY>1ADVcG%&l`wcZjn1KnBd8bw1*xZG9LxJ@)@&K@BIqvi?( zid;VYjt+JCUR%o5$=A=ZJP(S7Z=K{O-UBW=Mw>y2dBhAs2Re>m4ZlHYGC3Lm71oQr zR|ft_=Vf5B#8oPDX8X$x~?3UQ#ZY=lweB*$F@x)Fqv`wSqn05<)X(8_B^Bf=;yqX0DcblI8gIh_?VEc-*4El=*k!E<*>fh8yuhhrl zW-wu3&@_N{Jmn;?v3H5dpF3-`OLme~ zf*VYFQmVq-U&uuhkrXM!^mkkhqfH<6 zCu9N7Q8dI4g2na`s8Ch$hs@Z+^bE{#8=ic4RQu?1W`whTc8LI_Qte!m8Q91BC{p1* z`mr6(FLGAxa&beQVjMtz>9aC zM()i5PL{xra#`@$U7rFWn_fXLXlFQ&wTS4bV18kD!2#~}aU0rGS$5k)(Fj)Ja=K$1h$cogy0^FrhCY_w7R52N%*OxkYY z9c#VKfv$RF%dZ?#Fz(Zi(YM|9*Vz#X>N07eIQx39Fw#-H4n5XB0tm-OV&47*)IA9q z6O_o7lC?No=~equwI%cbS(x*{l0s^@a)zNCF zTlfI?YT0+an+|TJ?pzh5F$*XqqYVl2R3}%TVgC(WmFC!cd(zL8!c|lGEG@RmBTkhT zA=c23BGK+47X0!Yq%1JiZx*CA0++M0XBoerLFJ3es+Jisi;BM>@u6w3@SHw zv)988{Rh5aZV0p0ex#Zv_=5(tt1PPoM9Zidq8;a168`~C^{-deGejQ?8OP_!A?1pJ zF0IB_I$)O3kk}>h55!G_m;D>7aAVNznCc-)gKa-$4VP9$nNYW@0dX@OS_wa5%%g9$ zR)&iQ7i9(E3dR|uLcH^$@_K%Jwq`!tMLGjRdlOo8i6S4-DqzdVnADmjhdk4N8n?@n z#Pv{Dz@hLB_xI!XK#6^`u=8PEZi3J5OI?Gcb^Y~j*EC>aG`IBUnX=c+ahgRl{mjX(`iDY$3%08f1caqQrNcsDv)|L}gpFX>YtQ+_19p z67X~-cdEX0GPHu$J9rIM_EA4>s59ww4X@Z$j4s$SvP6H_X+GU{w?D6ql!Xp-P4jM!W$Dp)bQ4p;5j zp)|se$JjjjvElr>n2-M73MdB}p(UBq9BUx%`l1R3j)(4TGsB<4$Y?r03Z>MYKzVLH z`$2SW;5l-*3nb~^JfkQ%i+S4=^ldtZC?FF|Js<^~UGYH#dwEJ3;~~`XVWP$Sx^BLu zT3Gx-IcOx<>m}Yj99}FvDrInHA>yJZdCmC+0KNmlo!+99@l5jaDt$2wxkbWeVw4Q| zbsWY)t=d($MgR2Otb=fya%g*m@@RJjK`*69J)||fZoTd%qG?8lRS|8J$-na$67LV_ z8G?h{?D@g6(U+knN#F(NV8Z4fATog5Qr4s~VMI7^RWGAs#SuM!l_Kd10If!l;oLR{ zI_nJ-JbXCopsBe&d(=*rDkO`>eXhMZ7PRjTOot#2e;o{y7QVKC^ecivx>A|o5$?a6 zd3xnnC)NSxeQ1z=+q!THuv*#Vb-*J}0~kihz)K-qIuUK>dx;kC_6mlTa0ql&MB$ir z7mv!c@yQ*;uwok8WXb+XaI;S9oOpKf%a~j%D3OQji%_R)hrcbzwfu6^9`^6SUB1>3QbY4pK@!Phq_InZqhA=Y-8qzM!57pkDp7{F zXI=sc2ezPuv>3Ze1iW1~grz#!1+$?JKQ8&qDXEvUc_pY=Mem#C$N3&#r1I*f@3SafL(Z(;W%ZxwTSIpV-T4h7L=U!6%3g~rHT^t64=CuL6Vs+WMD z=6jv)#>bID9QrGYg1<^CO1f1tYFhpG#d;>4SZxnOWYq2qP0#hWmMbw#W`cIV>ux_% zz6bHQV1xNFAL9og_1zL&%NfpeB4?|1;`XZ#60zuSHH3eoVTQFs0B>!BMC=XCn@Re# ziNG5O<_7*|neC2bQ0!(DacpHLX$rUZZ`{-meO>0yq=~Ju(iPwYn0VAyS)>Kr}Y)zL7R;C?D* zP1HU!SvYKdlxUM$7Nqx`2&)*rEs3PXzsCTLDGZDEPw~q@E9(AS#D?ht?Mx$S?`Hvq zI0j_D8=MWBg+6mMW#fIi=A+RE1YKC^%rF^kO9bi!$RyP3p^*LF49Wp(T-U=CgQ!rUXP*%G7R{!tdv!8H9)<}%xm;hDE;hX*br zH!eaEoUcBw{Du&3qeMVVgAuIfN`KclI!q9w1wj`f}eb z++F#FCYc3>)n*Z&@+d0v!O5`|YW{NXZDb|p`g;?TZ(zAz=B&ddk} z(mjO*<&NwLuAj!Z7JXk&$8WkdAAv6@N#*W+;s>;z``uP zO2>$ovkN~srl5@3K1434g0mq_5FMc6p;J|K;Y2m5SH+PjQl)dpVVg5@!#`q@oetnm z5bbna0d?uC{#QzeH5mfL&V7@M{cTGVMa#g#CQ;4WLchtgdtHd4g0MXud9VrydESLh zk-4{$qElrRmXS}y$@;gi0)=bElbKWAoy^v^C_J&7$7J~JaOrlKkMkdK(Tw975mzOo;@=6yFK;k0af^I z#bF~hogV&`b{|!f zlmqN7;fKgq>A(wvzE{wYOpG9UKJ-Ps+X(`_i%s}`2C9BzTnGQKG&5#Vn!aDw9qJjspEMefePuOyJBMlh4gj-Y#1kO)1O;@#peLY2~oMHcpo_w zS@EB91e-mD5HFV!5zo;m{L7k8f$d2}RyKdr(dTM9EClUPyR_S+l z=9hW(oGzE|+BbwdmHK`63Pm9(GcD^ zyo6CZ`1y_$eqv^%o#9kcnH1h>e}^fu(Vnu07DihTa$z?%sU@0#ygm*_UWsbD zy<-`h6}9S*sfKQvj`Re63Qfn;<|bw_mzzgD%|;FTg@4JPZniVVftnju5sELV7D=qO z@eRm3bp_F5Y%6wGuHUr|CsCQv?FO)_q+p;jlkPb`osfHJBzWq}br zB0E^2p)#d_j;9Y<=M8+ryrc_^Z&sL}?7`ea<^{8y5XERhvQDA81VOsPH?dBZ@mDqlp!6u~;@D_-=>^;SnBG*NumUHlYbrUXh*U zM{c{iF@8RmxO8WIUyc|wll*TSqItb)1jE7!12HnoDl!$j8Hgf~9OsYymp^0V!fdiY zXP{}Ko`86M*({OFz?b7Y)7!6D=T3)5&CmVm56;<17E?k|+tlCrBje|Ct+flzqg-JS zM{zGuv^6eL&%?~!(9*XuFUJc#*k;zg=Xbwj!2P(SCKfu?0uFvoFDHpTe`lFi=*S$N zGiDfBqjjgu0~;sX$Yb!2_c<{zk?47U(oM@ z_E6XlJz>yfd+m;J5B^dMUm;9?01G$)x=sdzbJEmGIESM9gDf8x2Y5|n%NicpAZN8N z`j}bXeX^yK_E_KJT>Eh>#TY~YZh@X1#u=IvJU~mT1HO^0b;hfRnDkc&zb~J~hT<`D zPZYd!u#GCAnUr>hBC$IR;}ifu?c{U_?a@>RSX!Zb_%Nt3BgC~wi;K9taYK$-t2p%u z^GT5J?b9#_aC!K~ags4~rrD^*!x1@o3elcxtktUtglU+Wy(jnzpSpc0>~P$0PCr_I z!jpia-{Q`ByXyDWR6A(|BV@`{;B{_-DU3O!b9> z_Axydlh~&qcE(fTOvSl&F|u8oVUw4lj6UdktMU?|f>y=_sn* ztN^y#>CgJ)^r$B)>q3^n?}0_@KUX^rR6>BvNLZuBNk z8@}Hhiedr0j8Q7sYNKD{_hcrYxl%=bR;HT`EwUX+r^hE>1Gb(LJ`|l|CwqxWm!8LO zJm1rWNe{fSSdgaR?ZJ^$mp+iQa#=>}vKie1c5))((s~|EN%|AsMg@BH#1dhtk_-0w#k^j>iy3DBZ| z8IA7F2WWBnF8uDH@oQ8xKt^Zac|7p;D{0Tfd$jGJ=b7JQS}XF0%wPv;kiyy;S{;)|IzPF zg@lng7)9*eVoh#R#%Uh+GbyMXt#SjaGllBYM1t)nQ%L5b$JZZuHNTaa$y6H4hO!+I&>(}Ik$vB}FNKX0N3fbUt@}(Fnd?=`f#% zJV>J(OZax*i!ni8$v8p;rjCm|+mEM_sd;m-alc&BW5~5c zk;d#wU|sgmznA?iV1W705Y~-2zN>4vK;B(NZpX$$e3`sg9%via{`?Pr-kM+WyC0Xt zAJfyQX})igW3{&od(SGuFdT_QR#Zdp^a>l#sZbQjvNA=WQRqjd+wj z6%PZt4xcCmLeOE}mzMDEGpFYTQJB`f^LtTur;q;}6()>qI*3eIS0H!Mk242Z*JkW6eUTGbb%#V25` zIT6kri)w6=88f}ZHLmbUb7DCuXBTBDP>12oLH635ON$AuIeB_l4~CGOp6t*dltZ#@ ze$gSQ-jphtMr)P{OR-7$(JM>vhy%VVqGSuw+4mO}9V@RL)*4;Wh6B z^4)w%niQYJB+QJ@#86}`kbx5qH93O_%9?SyGj3sr(rPT+*_?7u_dxo=6Q;wJWfh51 zOhqCDJtk^0GOlZm1{9&zVCR?>yF@rhth(NsfKRnJ(CS5fZdik_S5aHC=y!rf04wcm z)6)MM%JXy;5aqU7*bYGoGVyeSRIaiNQQ?cqB;R6i2g?9yI%$E-h~x!4jBR(xBB$Eo z;i&2CfJyN>MW>!+cP4Dz!1JD;qPuDJW}#vX@-8GAywVv+DV#|%Q8J_aCpv&x@p{tT zJEi=WAOUj!(_TE)tQ&ZvD2u1m~=6pX_Wt*7TC&#qX7l!fZ zqvfhzYauxcoR7I&f4MlGx;QEr*JeZoRo zlvypzj@u!LF@=>LW6&=YJYJ4Cz#ZDH5yVSf?Nmn=2{==#zDA_&H2H@x^ZrE3#phYV zHE9Y6!o+nlv%uUoJ2FF4oNpv`4n|b858pvk2L40d!w1}upt%3*YDN%$I|gju^Ec>j zQ}I7M8oYAhS>sW_a4YibGS8%msNqgdmv(|;sYem^Fr z_aDX9jm)a;+w(!On#VM*mTpI5U1l4}U_j}Y0mk7=?w|7xsnij-p(}Hk7-hcT4J*U0 zt>Am@VGQJ8dqA-BR!`*H4!&?f%npkup*3%R;?6$M;oFbxgKa8ah!o^wD4hSG_i#${ zi)8wvnculbmwlX&Zbb|trfLRYQzmqUqRAIIDlpG3R~)9K0V5-{(Is?2ka zStLXzAQSc$rBHE(lvAh$0}tRG?ZBeu{jMAawI(=8zM@v}uc1Ei_n{n{v_`Nz62_Sa zM1plLdxbHE@NYug_kl9=>-M`j#pk7EEzJ>9CBds9lYxv=r|-iA1LObrJtx1|cc&D4 zAFyqFo=KrNH%xf>GPnH~VQxW%vV#=z*ffmC0pr&@{G!M|DYffIr#Hk3%0b&(Aga1w z#aNi@QG46dXK}+5B)eKke&}o^;TR-Y8KhMaN0}3HPj}_W*pmQ$O?FxjywJ`&ecf^%DlQkOJx!@;YWgJ@6w&B}?fDOccgk zed?k-xmfi(@nI*+PH&XK9 z;l%!hH<9|Tx1z|na;(+m5-@uV>|N@=BaO;eVz~lRhQ_i!4&;=yPjv7}P8$ronK-SE z*bGBT?b>h>7(S?gRj2!I{Sb#{@ztK2=d+8z4SDXgL7_m!U8Q`O+l@+ps(8Z0>v1en zfg+p|yHIno{;+fxC@(T7uwf$pLVKQ}Ok>lW&66&mv%cCqQom`ow;V017>Vc!sEmov zRtOut%0+i}>YQc!F+EeRol!t#Mb{IM}y|ea9Ygt-4VH(l0 z)wpC@T$LQo{dLUJ&Va`vJWuOB@UDE}U4)KdQ;1Cfx48A}QTB)C$5&$kU}Zq*C@)s}=mZL}{Hr!L^LO z*|4v`Tc4>j1(v7Wj@#lSWNZaDrTDJJN+Ob@hX1sH{0EDeM)g6il|N*0beS%7XUxa= z-m7bQLP~fvE*-=UKKBlJ9EjXQmI>{CPG0NfG7Gjlx|bYMQzP^kac);oV~q>H4NVc%$zd#JqKTkXKX;nO=4VFu6%O5!*{lUdG4XNRaSdp? z3{9clU$DVvsP1bz6~+3ImmH5h4EBYv=D#qL5%hrz%hD#9{RwV+7{tV~?C^ zj-sW;T9vy|ALH?AFgh8pmF{4{_trw%4OUiyv-MH3N=P#X`8jHLH33v0XMA4ZR>;$w?zyl5unBV zT(a;Zj~_E*=DM82@BP*p&*tnuFSt^!O!fj^pg`(gi(?s+=P z|7MqTzMed>@lGwWo5S3B!N&qx}|f)YBvJeJDS6}tLS(deMp@M_8s$Ww%NcoyNX>0;8&+9Ux6*p0uG>R9VD`qI~dQg?PwM#mP35mRl@ti|)X-bM|}AnI6ErDU+$} zgDJ65q9T|WB4xT~zr{0Pq6q8Z5x}Et7gT;Ks`CYw@i5;j<|7yHOrj6ERpdP9fa4(#zQ&o`60<*IE<<>7i-H-iVntI0BDu^Pxv` zhrz;OCw&!g&Vr?MFl(P;==Nk()Fh*RKzGrjL{0Xt^I40d`iLkjbdjEdArAQ1imP2Q z<;C<0%ga7zOX8S;6;sfSM|)j#SEjhV?>`jF_#$=-aEV^Bj<&5!y@#b6Y^UzqG*#X+ zFD8w79r@_pX$4<`uI|6@e0!JjC3FFZ3gZ33H_~^2O7xj5(NZ>v%K8eDb*rU>v3Ww! z7%h07;^-{!urd50=?n=@lh+ID5T)Nqj#jNI`j#^P8i?gA{q#1#kj)d&J=J^%?zF0r zZ76lubDSlrx_FUWxo!+R1JF|uq~HLWm8zj9^uBs0F`md3YUWAAvVzkDnL-_G7g#A$ z5gN1w_a^HRA5Ic}D$n@y!xQwAW2PD3lMUfg^7~)s&KKKVg31%3o%44C@>> z#H=ie$WA|R5F0msT0=5yoZQ5zGJB6$$k`vkN;1t$@*#V*(1+NDEdVz3%}E8F44+qW zwlF98r#Z}xQR#F2sR1^eS6o=WqJpmA$Kp{$E^P9}@RzB23ZV#bv+-0zdNBb5KcmFq zL-YMORfuG?K|%K3?8NxCSRVcB)*IzKx)F@(#khJRhO}o88Yk1jyFhspE*-T#2X=w> z;U?bbG}9tKZW_)^W7};>C+-5%QL%?YUV6J bjA)(MtV&G$b7L@IZ-8gkDK5XC-^ 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