From 1170394af88c65cd3c1902fb1cd67dd67deff8a9 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 29 Aug 2023 17:38:18 -0700 Subject: [PATCH 1/3] refactor: rename smtinsert advice injector into smtset --- assembly/src/ast/nodes/advice.rs | 12 ++++++------ assembly/src/ast/parsers/adv_ops.rs | 4 ++-- core/src/operations/decorators/advice.rs | 4 ++-- docs/src/user_docs/assembly/io_operations.md | 2 +- processor/src/decorators/adv_stack_injectors.rs | 2 +- processor/src/decorators/mod.rs | 2 +- processor/src/decorators/tests.rs | 10 ++++------ stdlib/asm/collections/smt.masm | 6 +++--- 8 files changed, 20 insertions(+), 22 deletions(-) diff --git a/assembly/src/ast/nodes/advice.rs b/assembly/src/ast/nodes/advice.rs index 9ea83d4aad..510c8eda03 100644 --- a/assembly/src/ast/nodes/advice.rs +++ b/assembly/src/ast/nodes/advice.rs @@ -18,7 +18,7 @@ pub enum AdviceInjectorNode { PushU64div, PushExt2intt, PushSmtGet, - PushSmtInsert, + PushSmtSet, PushMapVal, PushMapValImm { offset: u8 }, PushMapValN, @@ -37,7 +37,7 @@ impl From<&AdviceInjectorNode> for AdviceInjector { PushU64div => Self::DivU64, PushExt2intt => Self::Ext2Intt, PushSmtGet => Self::SmtGet, - PushSmtInsert => Self::SmtInsert, + PushSmtSet => Self::SmtSet, PushMapVal => Self::MapValueToStack { include_len: false, key_offset: 0, @@ -72,7 +72,7 @@ impl fmt::Display for AdviceInjectorNode { PushU64div => write!(f, "push_u64div"), PushExt2intt => write!(f, "push_ext2intt"), PushSmtGet => write!(f, "push_smtget"), - PushSmtInsert => write!(f, "push_smtinsert"), + PushSmtSet => write!(f, "push_smtset"), PushMapVal => write!(f, "push_mapval"), PushMapValImm { offset } => write!(f, "push_mapval.{offset}"), PushMapValN => write!(f, "push_mapvaln"), @@ -92,7 +92,7 @@ impl fmt::Display for AdviceInjectorNode { const PUSH_U64DIV: u8 = 0; const PUSH_EXT2INTT: u8 = 1; const PUSH_SMTGET: u8 = 2; -const PUSH_SMTINSERT: u8 = 3; +const PUSH_SMTSET: u8 = 3; const PUSH_MAPVAL: u8 = 4; const PUSH_MAPVAL_IMM: u8 = 5; const PUSH_MAPVALN: u8 = 6; @@ -110,7 +110,7 @@ impl Serializable for AdviceInjectorNode { PushU64div => target.write_u8(PUSH_U64DIV), PushExt2intt => target.write_u8(PUSH_EXT2INTT), PushSmtGet => target.write_u8(PUSH_SMTGET), - PushSmtInsert => target.write_u8(PUSH_SMTINSERT), + PushSmtSet => target.write_u8(PUSH_SMTSET), PushMapVal => target.write_u8(PUSH_MAPVAL), PushMapValImm { offset } => { target.write_u8(PUSH_MAPVAL_IMM); @@ -139,7 +139,7 @@ impl Deserializable for AdviceInjectorNode { PUSH_U64DIV => Ok(AdviceInjectorNode::PushU64div), PUSH_EXT2INTT => Ok(AdviceInjectorNode::PushExt2intt), PUSH_SMTGET => Ok(AdviceInjectorNode::PushSmtGet), - PUSH_SMTINSERT => Ok(AdviceInjectorNode::PushSmtInsert), + PUSH_SMTSET => Ok(AdviceInjectorNode::PushSmtSet), PUSH_MAPVAL => Ok(AdviceInjectorNode::PushMapVal), PUSH_MAPVAL_IMM => { let offset = source.read_u8()?; diff --git a/assembly/src/ast/parsers/adv_ops.rs b/assembly/src/ast/parsers/adv_ops.rs index c4688a9ac6..30d58e6e47 100644 --- a/assembly/src/ast/parsers/adv_ops.rs +++ b/assembly/src/ast/parsers/adv_ops.rs @@ -33,8 +33,8 @@ pub fn parse_adv_inject(op: &Token) -> Result { 2 => AdvInject(PushSmtGet), _ => return Err(ParsingError::extra_param(op)), }, - "push_smtinsert" => match op.num_parts() { - 2 => AdvInject(PushSmtInsert), + "push_smtset" => match op.num_parts() { + 2 => AdvInject(PushSmtSet), _ => return Err(ParsingError::extra_param(op)), }, "push_mapval" => match op.num_parts() { diff --git a/core/src/operations/decorators/advice.rs b/core/src/operations/decorators/advice.rs index ca2b12d6c5..3cbb429aaa 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/operations/decorators/advice.rs @@ -185,7 +185,7 @@ pub enum AdviceInjector { /// - (1, 0): depth 32 -> 48 /// - (1, 1): depth 16, 32, or 48 -> 64 /// - E_KEY and E_VALUE are the key-value pair for a leaf which is to be replaced by a subtree. - SmtInsert, + SmtSet, // ADVICE MAP INJECTORS // -------------------------------------------------------------------------------------------- @@ -253,7 +253,7 @@ impl fmt::Display for AdviceInjector { Self::Ext2Inv => write!(f, "ext2_inv"), Self::Ext2Intt => write!(f, "ext2_intt"), Self::SmtGet => write!(f, "smt_get"), - Self::SmtInsert => write!(f, "smt_insert"), + Self::SmtSet => write!(f, "smt_set"), Self::MemToMap => write!(f, "mem_to_map"), Self::HdwordToMap { domain } => write!(f, "hdword_to_map.{domain}"), Self::HpermToMap => write!(f, "hperm_to_map"), diff --git a/docs/src/user_docs/assembly/io_operations.md b/docs/src/user_docs/assembly/io_operations.md index 053c34a509..1fde53a1ff 100644 --- a/docs/src/user_docs/assembly/io_operations.md +++ b/docs/src/user_docs/assembly/io_operations.md @@ -53,7 +53,7 @@ Advice injectors fall into two categories: (1) injectors which push new data ont | 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. | | adv.smt_get | [K, R, ... ] | [K, R, ... ] | Pushes values onto the advice stack which are required for successful retrieval of a value under the key $K$ from a Sparse Merkle Tree with root $R$. | -| adv.smt_insert | [V, K, R, ...] | [V, K, R, ...] | Pushes values onto the advice stack which are required for successful insertion of a key-value pair $(K, V)$ into a Sparse Merkle Tree with root $R$. | +| adv.smt_set | [V, K, R, ...] | [V, K, R, ...] | Pushes values onto the advice stack which are required for successful insertion of a key-value pair $(K, V)$ into 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]$. | diff --git a/processor/src/decorators/adv_stack_injectors.rs b/processor/src/decorators/adv_stack_injectors.rs index f11195d00e..b3c146cc65 100644 --- a/processor/src/decorators/adv_stack_injectors.rs +++ b/processor/src/decorators/adv_stack_injectors.rs @@ -370,7 +370,7 @@ where /// /// # Panics /// Will panic as unimplemented if the target depth is `64`. - pub(super) fn push_smtinsert_inputs(&mut self) -> Result<(), ExecutionError> { + pub(super) fn push_smtset_inputs(&mut self) -> Result<(), ExecutionError> { // get the key, value, and tree root from the stack let value = self.stack.get_word(0); let key = self.stack.get_word(1); diff --git a/processor/src/decorators/mod.rs b/processor/src/decorators/mod.rs index a4e601e438..060f47173c 100644 --- a/processor/src/decorators/mod.rs +++ b/processor/src/decorators/mod.rs @@ -45,7 +45,7 @@ where AdviceInjector::Ext2Inv => self.push_ext2_inv_result(), AdviceInjector::Ext2Intt => self.push_ext2_intt_result(), AdviceInjector::SmtGet => self.push_smtget_inputs(), - AdviceInjector::SmtInsert => self.push_smtinsert_inputs(), + AdviceInjector::SmtSet => self.push_smtset_inputs(), AdviceInjector::MemToMap => self.insert_mem_values_into_adv_map(), AdviceInjector::HdwordToMap { domain } => self.insert_hdword_into_adv_map(*domain), AdviceInjector::HpermToMap => self.insert_hperm_into_adv_map(), diff --git a/processor/src/decorators/tests.rs b/processor/src/decorators/tests.rs index 6cccdd8eb5..2958e0679f 100644 --- a/processor/src/decorators/tests.rs +++ b/processor/src/decorators/tests.rs @@ -160,7 +160,7 @@ fn inject_smtinsert() { let is_16_or_32 = ONE; let is_16_or_48 = ONE; let expected_stack = [is_update, is_simple_insert, is_16_or_32, is_16_or_48]; - let process = prepare_smt_insert(key_a, val_a, &smt, expected_stack.len(), Vec::new()); + let process = prepare_smt_set(key_a, val_a, &smt, expected_stack.len(), Vec::new()); assert_eq!(build_expected(&expected_stack), process.stack.trace_state()); // --- update same key with different value ------------------------------- @@ -186,11 +186,11 @@ fn inject_smtinsert() { ZERO, ]; let adv_map = vec![build_adv_map_entry(key_a, val_a, 16)]; - let process = prepare_smt_insert(key_a, val_b, &smt, expected_stack.len(), adv_map); + let process = prepare_smt_set(key_a, val_b, &smt, expected_stack.len(), adv_map); assert_eq!(build_expected(&expected_stack), process.stack.trace_state()); } -fn prepare_smt_insert( +fn prepare_smt_set( key: Word, value: Word, smt: &TieredSmt, @@ -205,9 +205,7 @@ fn prepare_smt_insert( let mut process = build_process(stack_inputs, advice_inputs); process.execute_op(Operation::Noop).unwrap(); - process - .execute_decorator(&Decorator::Advice(AdviceInjector::SmtInsert)) - .unwrap(); + process.execute_decorator(&Decorator::Advice(AdviceInjector::SmtSet)).unwrap(); move_adv_to_stack(&mut process, adv_stack_depth); diff --git a/stdlib/asm/collections/smt.masm b/stdlib/asm/collections/smt.masm index 72b95b156b..9bb402e426 100644 --- a/stdlib/asm/collections/smt.masm +++ b/stdlib/asm/collections/smt.masm @@ -1151,7 +1151,7 @@ export.insert # arrange the data needed for the insert procedure on the advice stack and move the # first 4 flags onto the operand stack; meaning of the flags f0, f1, and f2 depends # on what type of insert is being executed (4 cycles) - adv.push_smtinsert adv_push.4 + adv.push_smtset adv_push.4 # => [is_update, f0, f1, f2, V, K, R, ...] # execute the actual insert procedure @@ -1362,7 +1362,7 @@ proc.delete # => [V_old, R_new, R_new, K, R, ...] # prepare the stack for TSMT insert operation (9 cycles) - swapw.3 dupw.3 adv.push_smtinsert adv_push.4 + swapw.3 dupw.3 adv.push_smtset adv_push.4 # => [f0, f1, f2, f3, V_old, K, R_new, R_new, V_old, R, ...] # insert key-pair (K, V_old) into TSMT with root R_new - i.e., insert the key-value pair @@ -1416,7 +1416,7 @@ export.set # arrange the data needed for the update procedure on the advice stack and move the first # 4 flags onto the operand stack; meaning of the flags f0, f1, f2, and f3 depends on what # type of update is being executed (4 cycles) - adv.push_smtinsert adv_push.4 + adv.push_smtset adv_push.4 # => [f0, f1, f2, f3, V, K, R, ...] # determine if the value is an empty word (15 cycles) From ed012cced86581a4bc7dead54176a2928e9fd7ea Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 29 Aug 2023 21:24:18 -0700 Subject: [PATCH 2/3] feat: implement adv.push_smtpeek decorator --- CHANGELOG.md | 1 + assembly/src/ast/nodes/advice.rs | 24 ++-- assembly/src/ast/parsers/adv_ops.rs | 4 + core/src/operations/decorators/advice.rs | 16 +++ docs/src/user_docs/assembly/io_operations.md | 3 +- docs/src/user_docs/stdlib/collections.md | 40 +++++-- .../src/decorators/adv_stack_injectors.rs | 113 +++++++++++++----- processor/src/decorators/mod.rs | 1 + processor/src/decorators/tests.rs | 71 +++++++++-- 9 files changed, 214 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df133e26f1..187d3f4442 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Added support for the arithmetic expressions in constant values (#1026). - Added support for module aliases (#1037). - Added `adv.insert_hperm` decorator (#1042). +- Added `adv.push_smtpeek` decorator (#1056). #### VM Internals - Simplified range checker and removed 1 main and 1 auxiliary trace column (#949). diff --git a/assembly/src/ast/nodes/advice.rs b/assembly/src/ast/nodes/advice.rs index 510c8eda03..a95507ffb4 100644 --- a/assembly/src/ast/nodes/advice.rs +++ b/assembly/src/ast/nodes/advice.rs @@ -19,6 +19,7 @@ pub enum AdviceInjectorNode { PushExt2intt, PushSmtGet, PushSmtSet, + PushSmtPeek, PushMapVal, PushMapValImm { offset: u8 }, PushMapValN, @@ -38,6 +39,7 @@ impl From<&AdviceInjectorNode> for AdviceInjector { PushExt2intt => Self::Ext2Intt, PushSmtGet => Self::SmtGet, PushSmtSet => Self::SmtSet, + PushSmtPeek => Self::SmtPeek, PushMapVal => Self::MapValueToStack { include_len: false, key_offset: 0, @@ -73,6 +75,7 @@ impl fmt::Display for AdviceInjectorNode { PushExt2intt => write!(f, "push_ext2intt"), PushSmtGet => write!(f, "push_smtget"), PushSmtSet => write!(f, "push_smtset"), + PushSmtPeek => write!(f, "push_smtpeek"), PushMapVal => write!(f, "push_mapval"), PushMapValImm { offset } => write!(f, "push_mapval.{offset}"), PushMapValN => write!(f, "push_mapvaln"), @@ -93,15 +96,16 @@ const PUSH_U64DIV: u8 = 0; const PUSH_EXT2INTT: u8 = 1; const PUSH_SMTGET: u8 = 2; const PUSH_SMTSET: u8 = 3; -const PUSH_MAPVAL: u8 = 4; -const PUSH_MAPVAL_IMM: u8 = 5; -const PUSH_MAPVALN: u8 = 6; -const PUSH_MAPVALN_IMM: u8 = 7; -const PUSH_MTNODE: u8 = 8; -const INSERT_MEM: u8 = 9; -const INSERT_HDWORD: u8 = 10; -const INSERT_HDWORD_IMM: u8 = 11; -const INSERT_HPERM: u8 = 12; +const PUSH_SMTPEEK: u8 = 4; +const PUSH_MAPVAL: u8 = 5; +const PUSH_MAPVAL_IMM: u8 = 6; +const PUSH_MAPVALN: u8 = 7; +const PUSH_MAPVALN_IMM: u8 = 8; +const PUSH_MTNODE: u8 = 9; +const INSERT_MEM: u8 = 10; +const INSERT_HDWORD: u8 = 11; +const INSERT_HDWORD_IMM: u8 = 12; +const INSERT_HPERM: u8 = 13; impl Serializable for AdviceInjectorNode { fn write_into(&self, target: &mut W) { @@ -111,6 +115,7 @@ impl Serializable for AdviceInjectorNode { PushExt2intt => target.write_u8(PUSH_EXT2INTT), PushSmtGet => target.write_u8(PUSH_SMTGET), PushSmtSet => target.write_u8(PUSH_SMTSET), + PushSmtPeek => target.write_u8(PUSH_SMTPEEK), PushMapVal => target.write_u8(PUSH_MAPVAL), PushMapValImm { offset } => { target.write_u8(PUSH_MAPVAL_IMM); @@ -140,6 +145,7 @@ impl Deserializable for AdviceInjectorNode { PUSH_EXT2INTT => Ok(AdviceInjectorNode::PushExt2intt), PUSH_SMTGET => Ok(AdviceInjectorNode::PushSmtGet), PUSH_SMTSET => Ok(AdviceInjectorNode::PushSmtSet), + PUSH_SMTPEEK => Ok(AdviceInjectorNode::PushSmtPeek), PUSH_MAPVAL => Ok(AdviceInjectorNode::PushMapVal), PUSH_MAPVAL_IMM => { let offset = source.read_u8()?; diff --git a/assembly/src/ast/parsers/adv_ops.rs b/assembly/src/ast/parsers/adv_ops.rs index 30d58e6e47..3813d929ed 100644 --- a/assembly/src/ast/parsers/adv_ops.rs +++ b/assembly/src/ast/parsers/adv_ops.rs @@ -37,6 +37,10 @@ pub fn parse_adv_inject(op: &Token) -> Result { 2 => AdvInject(PushSmtSet), _ => return Err(ParsingError::extra_param(op)), }, + "push_smtpeek" => match op.num_parts() { + 2 => AdvInject(PushSmtPeek), + _ => return Err(ParsingError::extra_param(op)), + }, "push_mapval" => match op.num_parts() { 2 => AdvInject(PushMapVal), 3 => { diff --git a/core/src/operations/decorators/advice.rs b/core/src/operations/decorators/advice.rs index 3cbb429aaa..af90f1d4c1 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/operations/decorators/advice.rs @@ -187,6 +187,21 @@ pub enum AdviceInjector { /// - E_KEY and E_VALUE are the key-value pair for a leaf which is to be replaced by a subtree. SmtSet, + /// 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, ...] + SmtPeek, + // ADVICE MAP INJECTORS // -------------------------------------------------------------------------------------------- /// Reads words from memory at the specified range and inserts them into the advice map under @@ -254,6 +269,7 @@ impl fmt::Display for AdviceInjector { 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::MemToMap => write!(f, "mem_to_map"), Self::HdwordToMap { domain } => write!(f, "hdword_to_map.{domain}"), Self::HpermToMap => write!(f, "hperm_to_map"), diff --git a/docs/src/user_docs/assembly/io_operations.md b/docs/src/user_docs/assembly/io_operations.md index 1fde53a1ff..e7b01c6623 100644 --- a/docs/src/user_docs/assembly/io_operations.md +++ b/docs/src/user_docs/assembly/io_operations.md @@ -52,8 +52,9 @@ Advice injectors fall into two categories: (1) injectors which push new data ont | 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. | -| adv.smt_get | [K, R, ... ] | [K, R, ... ] | Pushes values onto the advice stack which are required for successful retrieval of a value under the key $K$ from a Sparse Merkle Tree with root $R$. | +| adv.smt_get | [K, R, ... ] | [K, R, ... ] | Pushes values onto the advice stack which are required for successful retrieval of a value under the key $K$ from a Sparse Merkle Tree with root $R$. | | adv.smt_set | [V, K, R, ...] | [V, K, R, ...] | Pushes values onto the advice stack which are required for successful insertion of a key-value pair $(K, V)$ into a Sparse Merkle Tree with root $R$. | +| adv.smt_peek | [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]$. | diff --git a/docs/src/user_docs/stdlib/collections.md b/docs/src/user_docs/stdlib/collections.md index fa21e90343..3dfcabc4c3 100644 --- a/docs/src/user_docs/stdlib/collections.md +++ b/docs/src/user_docs/stdlib/collections.md @@ -1,22 +1,42 @@ # Collections -Namespace `std::collections` contains modules for commonly authenticated data structures. +Namespace `std::collections` contains modules for commonly-used authenticated data structures. This includes: + +- A Merkle Mountain range. +- A Sparse Merkle Tree with 64-bit keys. +- A Sparse Merkle Tree with 256-bit keys. ## Merkle Mountain Range Module `std::collections::mmr` contains procedures for manipulating [Merkle Mountain Range](https://github.com/opentimestamps/opentimestamps-server/blob/master/doc/merkle-mountain-range.md) data structure which can be used as an append-only log. -| Procedure | Description | +The following procedures are available to read data from and make updates to a Merkle Mountain Range. + +| Procedure | Description | | ----------- | ------------- | -| get | Loads the leaf at the absolute `pos` in the MMR onto the stack.

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

Inputs:
- Operand stack: [pos, mmr_ptr, ...]

Output:
- Operand stack: [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:
- Operand stack: [N, mmr_ptr, ...]

Outputs:
- Operand stack: [...]

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

Inputs:
- Operand stack: [mmr_ptr, ...]

Outputs:
- Operand stack: [HASH, ...]

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

Inputs:
- Operand stack: [HASH, mmr_ptr, ...]

Outputs:
- Operand stack: [...]

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. | +| 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. | ## Sparse Merkle Tree (64) Module `std::collections::smt64` contains procedures for manipulating key-value maps with single-element keys and 4-element values. The current implementation is a thin wrapper over a simple Sparse Merkle Tree of depth 64. In the future, this will be replaced with a compact Sparse Merkle Tree implementation. -| Procedure | Description | +The following procedures are available to read data from and make updates to a Sparse Merkle Tree. + +| Procedure | Description | +| ----------- | ------------- | +| get | Returns the value located under the specified key in the Sparse Merkle Tree defined by the specified root.

If no values had been previously inserted under the specified key, an empty word is returned.

Inputs: `[key, ROOT, ...]`
Outputs: `[VALUE, ROOT, ...]`

Fails if the tree with the specified root does not exist in the VM's advice provider. | +| set | Inserts the specified value under the specified key in a Sparse Merkle Tree defined by the specified root. If the insert is successful, the old value located under the specified key is returned via the stack.

If `VALUE` is an empty word, the new state of the tree is guaranteed to be equivalent to the state as if the updated value was never inserted.

Inputs: `[VALUE, key, ROOT, ...]`
Outputs: `[OLD_VALUE, NEW_ROOT, ...]`

Fails if the tree with the specified root does not exits in the VM's advice provider. | +| insert | Inserts the specified value under the specified key in a Sparse Merkle Tree defined by the specified root. If the insert is successful, the old value located under the specified key is returned via the stack.

This procedure requires that `VALUE` be a non-empty word.

Inputs: `[VALUE, key, ROOT, ...]`
Outputs: `[OLD_VALUE, NEW_ROOT, ...]`

Fails if:
- The tree with the specified root does not exits in the VM's advice provider.
- The provided value is an empty word. | + +## Sparse Merkle Tree (256) + +Module `std::collections::smt` contains procedures for manipulating key-value maps with 4-element keys and 4-element values. The underlying implementation is a Tiered (compacted) Sparse Merkle where leaves can exist only at specific depths called "tiers". These depths are: 16, 32, 48, and 64. Initially, when a tree is empty, it is equivalent to an empty Sparse Merkle Tree of depth 64 (i.e., leaves at depth 64 are set to [ZERO; 4]). As non-empty values are inserted into the tree, they are added to the first available tier. + +The following procedures are available to read data from and make updates to a Sparse Merkle Tree. + +| Procedure | Description | | ----------- | ------------- | -| get | Returns the value located under the specified key in the Sparse Merkle Tree defined by the specified root.

If no values had been previously inserted under the specified key, an empty word (i.e., [ZERO; 4]) is returned.

Inputs:
- Operand stack: [key, ROOT, ...]

Outputs:
-Operand stack: [VALUE, ROOT, ...]

Fails if the tree with the specified root does not exist in the VM's advice provider. | -| set | Inserts the specified value under the specified key in a Sparse Merkle Tree defined by the specified root. If the insert is successful, the old value located under the specified key is returned via the stack.

If `VALUE` is an empty word (i.e., [ZERO; 4]), the new state of the tree is guaranteed to be equivalent to the state as if the updated value was never inserted.

Inputs:
- Operand stack: [VALUE, key, ROOT, ...]

Outputs:
- Operand stack: [OLD_VALUE, NEW_ROOT, ...]

Fails if the tree with the specified root does not exits in the VM's advice provider. | -| insert | Inserts the specified value under the specified key in a Sparse Merkle Tree defined by the specified root. If the insert is successful, the old value located under the specified key is returned via the stack.

This procedure requires that `VALUE` be a non-empty word (i.e., not [ZERO; 4]).

Inputs:
- Operand stack: [VALUE, key, ROOT, ...]

Outputs:
-Operand stack: [OLD_VALUE, NEW_ROOT, ...]

Fails if:
- The tree with the specified root does not exits in the VM's advice provider.
- The provided value is an empty word. | +| get | Returns the value located under the specified key in the Sparse Merkle Tree defined by the specified root.

If no values had been previously inserted under the specified key, an empty word is returned.

Inputs: `[KEY, ROOT, ...]`
Outputs: `[VALUE, ROOT, ...]`

Fails if the tree with the specified root does not exist in the VM's advice provider. | +| set | Inserts the specified value under the specified key in a Sparse Merkle Tree defined by the specified root. If the insert is successful, the old value located under the specified key is returned via the stack.

If `VALUE` is an empty word, the new state of the tree is guaranteed to be equivalent to the state as if the updated value was never inserted.

Inputs: `[VALUE, KEY, ROOT, ...]`
Outputs: `[OLD_VALUE, NEW_ROOT, ...]`

Fails if the tree with the specified root does not exits in the VM's advice provider. | +| insert | Inserts the specified value under the specified key in a Sparse Merkle Tree defined by the specified root. If the insert is successful, the old value located under the specified key is returned via the stack.

This procedure requires that `VALUE` be a non-empty word.

Inputs: `[VALUE, KEY, ROOT, ...]`
Outputs: `[OLD_VALUE, NEW_ROOT, ...]`

Fails if:
- The tree with the specified root does not exits in the VM's advice provider.
- The provided value is an empty word. | diff --git a/processor/src/decorators/adv_stack_injectors.rs b/processor/src/decorators/adv_stack_injectors.rs index b3c146cc65..91254a1713 100644 --- a/processor/src/decorators/adv_stack_injectors.rs +++ b/processor/src/decorators/adv_stack_injectors.rs @@ -291,7 +291,7 @@ where /// - f2 is a boolean flag set to `1` if the key is not zero. /// /// # Errors - /// Will return an error if the provided Merkle root doesn't exist on the advice provider. + /// 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`. @@ -300,21 +300,9 @@ where let key = self.stack.get_word(0); let root = self.stack.get_word(1); - let index = &key[3]; - let depth = self.advice_provider.get_leaf_depth(root, &SMT_MAX_TREE_DEPTH, index)?; - debug_assert!(depth < 65); - - // normalize the depth into one of the tiers. this is not a simple `next_power_of_two` - // because of `48`. using a lookup table is far more efficient than if/else if/else. - let depth = SMT_NORMALIZED_DEPTHS[depth as usize]; - if depth == 64 { - unimplemented!("handling of bottom tier is not yet implemented"); - } - - // fetch the node value - let index = index.as_int() >> (64 - depth); - let index = Felt::new(index); - let node = self.advice_provider.get_tree_node(root, &Felt::new(depth as u64), &index)?; + // 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, depth, _) = self.get_smt_node(root, key)?; // set the node value; zeroed if empty sub-tree let empty = EmptySubtreeRoots::empty_hashes(64); @@ -343,6 +331,55 @@ where 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(super) fn push_smtpeek_result(&mut self) -> Result<(), ExecutionError> { + // fetch the arguments from the operand stack + let key = self.stack.get_word(0); + let root = self.stack.get_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, depth, _) = self.get_smt_node(root, key)?; + + let empty = EmptySubtreeRoots::empty_hashes(64)[depth as usize]; + if node == Word::from(empty) { + // if the node is a root of an empty subtree, then there is no value associated with + // the specified key + self.advice_provider.push_stack(AdviceSource::Word(TieredSmt::EMPTY_VALUE))?; + } else { + // get the key and value stored in the current leaf + let (leaf_key, leaf_value) = self.get_smt_upper_leaf_preimage(node)?; + + // if the leaf is for a different key, then there is no value associated with the + // specified key + if leaf_key == key { + self.advice_provider.push_stack(AdviceSource::Word(leaf_value))?; + } else { + self.advice_provider.push_stack(AdviceSource::Word(TieredSmt::EMPTY_VALUE))?; + } + } + + Ok(()) + } + /// Pushes values onto the advice stack which are required for successful insertion of a /// key-value pair into a Sparse Merkle Tree data structure. /// @@ -362,7 +399,7 @@ where /// - OLD_VALUE is the value previously associated with the specified KEY. /// /// # Errors - /// Will return an error if: + /// Returns an error if: /// - The Merkle store does not contain a node with the specified root. /// - The Merkle store does not contain all nodes needed to validate the path between the root /// and the relevant TSMT nodes. @@ -376,21 +413,9 @@ where let key = self.stack.get_word(1); let root = self.stack.get_word(2); - // determine the depth of the first leaf or an empty tree node - let index = &key[3]; - let depth = self.advice_provider.get_leaf_depth(root, &SMT_MAX_TREE_DEPTH, index)?; - debug_assert!(depth < 65); - - // map the depth value to its tier; this rounds up depth to 16, 32, 48, or 64 - let depth = SMT_NORMALIZED_DEPTHS[depth as usize]; - if depth == 64 { - unimplemented!("handling of depth=64 tier hasn't been implemented yet"); - } - - // get the value of the node at this index/depth - let index = index.as_int() >> (64 - depth); - let index = Felt::new(index); - let node = self.advice_provider.get_tree_node(root, &Felt::from(depth), &index)?; + // 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, depth, index) = self.get_smt_node(root, key)?; // if the value to be inserted is an empty word, we need to process it as a delete if value == TieredSmt::EMPTY_VALUE { @@ -423,6 +448,30 @@ where // TSMT UPDATE HELPER METHODS // -------------------------------------------------------------------------------------------- + /// Returns first leaf or an empty tree node for the provided key in the Sparse Merkle tree + /// with the specified root. + /// + /// Also returns the depth and index of the returned node at this depth. + fn get_smt_node(&self, root: Word, key: Word) -> Result<(Word, u8, Felt), ExecutionError> { + // determine the depth of the first leaf or an empty tree node + let index = &key[3]; + let depth = self.advice_provider.get_leaf_depth(root, &SMT_MAX_TREE_DEPTH, index)?; + debug_assert!(depth < 65); + + // map the depth value to its tier; this rounds up depth to 16, 32, 48, or 64 + let depth = SMT_NORMALIZED_DEPTHS[depth as usize]; + if depth == 64 { + unimplemented!("handling of depth=64 tier hasn't been implemented yet"); + } + + // get the value of the node at this index/depth + let index = index.as_int() >> (64 - depth); + let index = Felt::new(index); + let node = self.advice_provider.get_tree_node(root, &Felt::from(depth), &index)?; + + Ok((node, depth, index)) + } + /// Retrieves a key-value pair for the specified leaf node from the advice map. /// /// # Errors diff --git a/processor/src/decorators/mod.rs b/processor/src/decorators/mod.rs index 060f47173c..98278864fc 100644 --- a/processor/src/decorators/mod.rs +++ b/processor/src/decorators/mod.rs @@ -46,6 +46,7 @@ where AdviceInjector::Ext2Intt => self.push_ext2_intt_result(), AdviceInjector::SmtGet => self.push_smtget_inputs(), AdviceInjector::SmtSet => self.push_smtset_inputs(), + AdviceInjector::SmtPeek => self.push_smtpeek_result(), AdviceInjector::MemToMap => self.insert_mem_values_into_adv_map(), AdviceInjector::HdwordToMap { domain } => self.insert_hdword_into_adv_map(*domain), AdviceInjector::HpermToMap => self.insert_hperm_into_adv_map(), diff --git a/processor/src/decorators/tests.rs b/processor/src/decorators/tests.rs index 2958e0679f..ff6e40e50e 100644 --- a/processor/src/decorators/tests.rs +++ b/processor/src/decorators/tests.rs @@ -9,6 +9,7 @@ use vm_core::{ hash::{Rpo256, RpoDigest}, merkle::{EmptySubtreeRoots, MerkleStore, MerkleTree, NodeIndex, TieredSmt}, }, + utils::collections::Vec, utils::IntoBytes, AdviceInjector, Decorator, ONE, ZERO, }; @@ -73,7 +74,7 @@ fn push_smtget() { let value = seeded_word(&mut seed); // check leaves on empty trees - for depth in [16, 32, 48] { + for depth in [16_u8, 32, 48] { // compute node value let depth_element = Felt::from(depth); let store = MerkleStore::new(); @@ -81,7 +82,7 @@ fn push_smtget() { // expect absent value with constant depth 16 let expected = [ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ONE, ONE]; - assert_case_smtget(depth, key, value, node, initial_root, store, &expected); + assert_case_smtget(key, value, node, initial_root, store, &expected); } // check leaves inserted on all tiers @@ -112,7 +113,7 @@ fn push_smtget() { is_16_or_32, is_16_or_48, ]; - assert_case_smtget(depth, key, value, node, root, store, &expected); + assert_case_smtget(key, value, node, root, store, &expected); } // check absent siblings of non-empty trees @@ -137,15 +138,72 @@ fn push_smtget() { let root = store.set_node(initial_root, sibling, sibling_node).unwrap().root; let expected = [ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, is_16_or_32, is_16_or_48]; - assert_case_smtget(depth, key, value, sibling_node, root, store, &expected); + assert_case_smtget(key, value, sibling_node, root, store, &expected); } } -// SMTINSERT TESTS +// SMTPEEK TESTS +// ================================================================================================ + +#[test] +fn inject_smtpeek() { + let mut smt = TieredSmt::default(); + + // insert a single value into the tree (the node will be at depth 16) + let raw_a = 0b_00000000_11111111_00011111_11111111_10010110_10010011_11100000_00000000_u64; + let key_a = build_key(raw_a); + let val_a = [Felt::new(3), Felt::new(5), Felt::new(7), Felt::new(9)]; + smt.insert(key_a.into(), val_a); + + // peeking key_a should return val_a (in stack order) + let process = prepare_smt_peek(key_a, &smt); + let mut expected = val_a; + expected.reverse(); + assert_eq!(build_expected(&expected), process.stack.trace_state()); + + // peeking another key should return empty word + let raw_b = 0b_11111111_11111111_00011111_11111111_10010110_10010011_11100000_00000000_u64; + let key_b = build_key(raw_b); + let process = prepare_smt_peek(key_b, &smt); + assert_eq!(build_expected(&[ZERO; 4]), process.stack.trace_state()); + + // peeking another key with the same 16-bit prefix as key_a should return empty word + let raw_c = 0b_00000000_11111111_10011111_11111111_10010110_10010011_11100000_00000000_u64; + let key_c = build_key(raw_c); + let process = prepare_smt_peek(key_c, &smt); + assert_eq!(build_expected(&[ZERO; 4]), process.stack.trace_state()); +} + +fn prepare_smt_peek(key: Word, smt: &TieredSmt) -> Process { + let root: Word = smt.root().into(); + let store = MerkleStore::from(smt); + + let adv_map = smt + .upper_leaves() + .map(|(node, key, value)| { + let mut elements = key.as_elements().to_vec(); + elements.extend(&value); + (node.as_bytes(), elements) + }) + .collect::>(); + let advice_inputs = AdviceInputs::default().with_merkle_store(store).with_map(adv_map); + + let stack_inputs = build_stack_inputs(key, root, [ZERO; 4]); + let mut process = build_process(stack_inputs, advice_inputs); + + process.execute_op(Operation::Noop).unwrap(); + process.execute_decorator(&Decorator::Advice(AdviceInjector::SmtPeek)).unwrap(); + + move_adv_to_stack(&mut process, 4); + + process +} + +// SMTSET TESTS // ================================================================================================ #[test] -fn inject_smtinsert() { +fn inject_smtset() { let mut smt = TieredSmt::default(); // --- insert into empty tree --------------------------------------------- @@ -228,7 +286,6 @@ fn build_expected(values: &[Felt]) -> [Felt; 16] { } fn assert_case_smtget( - _depth: u8, key: Word, value: Word, node: RpoDigest, From 0aa1ffcdd11da13172efa08069b4239962538c8c Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 29 Aug 2023 21:41:52 -0700 Subject: [PATCH 3/3] refactor: move smt decorator handlers into stand-alone module --- .../src/decorators/adv_stack_injectors/mod.rs | 265 ++++++++++++++++++ .../smt.rs} | 261 +---------------- 2 files changed, 273 insertions(+), 253 deletions(-) create mode 100644 processor/src/decorators/adv_stack_injectors/mod.rs rename processor/src/decorators/{adv_stack_injectors.rs => adv_stack_injectors/smt.rs} (71%) diff --git a/processor/src/decorators/adv_stack_injectors/mod.rs b/processor/src/decorators/adv_stack_injectors/mod.rs new file mode 100644 index 0000000000..25689a3f52 --- /dev/null +++ b/processor/src/decorators/adv_stack_injectors/mod.rs @@ -0,0 +1,265 @@ +use super::{super::Ext2InttError, AdviceProvider, AdviceSource, ExecutionError, Process}; +use vm_core::{ + utils::collections::Vec, Felt, FieldElement, QuadExtension, StarkField, Word, ONE, WORD_SIZE, + ZERO, +}; +use winter_prover::math::fft; + +mod smt; + +// TYPE ALIASES +// ================================================================================================ +type QuadFelt = QuadExtension; + +// ADVICE INJECTORS +// ================================================================================================ + +impl Process +where + A: AdviceProvider, +{ + /// 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(super) fn copy_merkle_node_to_adv_stack(&mut self) -> Result<(), ExecutionError> { + // read node depth, node index, and tree root from the stack + let depth = self.stack.get(0); + let index = self.stack.get(1); + let root = [self.stack.get(5), self.stack.get(4), self.stack.get(3), self.stack.get(2)]; + + // look up the node in the advice provider + let node = self.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) + self.advice_provider.push_stack(AdviceSource::Value(node[3]))?; + self.advice_provider.push_stack(AdviceSource::Value(node[2]))?; + self.advice_provider.push_stack(AdviceSource::Value(node[1]))?; + self.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(super) fn copy_map_value_to_adv_stack( + &mut self, + include_len: bool, + key_offset: usize, + ) -> Result<(), ExecutionError> { + if key_offset > 12 { + return Err(ExecutionError::InvalidStackWordOffset(key_offset)); + } + + let key = [ + self.stack.get(key_offset + 3), + self.stack.get(key_offset + 2), + self.stack.get(key_offset + 1), + self.stack.get(key_offset), + ]; + self.advice_provider.push_stack(AdviceSource::Map { key, include_len }) + } + + /// 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(super) fn push_u64_div_result(&mut self) -> Result<(), ExecutionError> { + let divisor_hi = self.stack.get(0).as_int(); + let divisor_lo = self.stack.get(1).as_int(); + let divisor = (divisor_hi << 32) + divisor_lo; + + if divisor == 0 { + return Err(ExecutionError::DivideByZero(self.system.clk())); + } + + let dividend_hi = self.stack.get(2).as_int(); + let dividend_lo = self.stack.get(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.advice_provider.push_stack(AdviceSource::Value(r_hi))?; + self.advice_provider.push_stack(AdviceSource::Value(r_lo))?; + self.advice_provider.push_stack(AdviceSource::Value(q_hi))?; + self.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(super) fn push_ext2_inv_result(&mut self) -> Result<(), ExecutionError> { + let coef0 = self.stack.get(1); + let coef1 = self.stack.get(0); + + let element = QuadFelt::new(coef0, coef1); + if element == QuadFelt::ZERO { + return Err(ExecutionError::DivideByZero(self.system.clk())); + } + let result = element.inv().to_base_elements(); + + self.advice_provider.push_stack(AdviceSource::Value(result[1]))?; + self.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(super) fn push_ext2_intt_result(&mut self) -> Result<(), ExecutionError> { + let output_size = self.stack.get(0).as_int() as usize; + let input_size = self.stack.get(1).as_int() as usize; + let input_start_ptr = self.stack.get(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 = self + .get_memory_value(self.system.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.advice_provider.push_stack(AdviceSource::Value(*element))?; + } + + Ok(()) + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +fn u64_to_u32_elements(value: u64) -> (Felt, Felt) { + let hi = Felt::new(value >> 32); + let lo = Felt::new((value as u32) as u64); + (hi, lo) +} diff --git a/processor/src/decorators/adv_stack_injectors.rs b/processor/src/decorators/adv_stack_injectors/smt.rs similarity index 71% rename from processor/src/decorators/adv_stack_injectors.rs rename to processor/src/decorators/adv_stack_injectors/smt.rs index 91254a1713..9f39d081b7 100644 --- a/processor/src/decorators/adv_stack_injectors.rs +++ b/processor/src/decorators/adv_stack_injectors/smt.rs @@ -1,17 +1,14 @@ -use super::{super::Ext2InttError, AdviceProvider, AdviceSource, ExecutionError, Process}; +use super::{ + AdviceProvider, AdviceSource, ExecutionError, Felt, Process, StarkField, Word, ONE, WORD_SIZE, + ZERO, +}; use vm_core::{ crypto::{ hash::{Rpo256, RpoDigest}, merkle::{EmptySubtreeRoots, NodeIndex, TieredSmt}, }, utils::collections::{btree_map::Entry, BTreeMap, Vec}, - Felt, FieldElement, QuadExtension, StarkField, Word, ONE, WORD_SIZE, ZERO, }; -use winter_prover::math::fft; - -// TYPE ALIASES -// ================================================================================================ -type QuadFelt = QuadExtension; // CONSTANTS // ================================================================================================ @@ -26,249 +23,13 @@ const SMT_NORMALIZED_DEPTHS: [u8; 65] = [ 48, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, ]; -// ADVICE INJECTORS +// SMT ADVICE INJECTORS // ================================================================================================ impl Process where A: AdviceProvider, { - /// 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(super) fn copy_merkle_node_to_adv_stack(&mut self) -> Result<(), ExecutionError> { - // read node depth, node index, and tree root from the stack - let depth = self.stack.get(0); - let index = self.stack.get(1); - let root = [self.stack.get(5), self.stack.get(4), self.stack.get(3), self.stack.get(2)]; - - // look up the node in the advice provider - let node = self.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) - self.advice_provider.push_stack(AdviceSource::Value(node[3]))?; - self.advice_provider.push_stack(AdviceSource::Value(node[2]))?; - self.advice_provider.push_stack(AdviceSource::Value(node[1]))?; - self.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(super) fn copy_map_value_to_adv_stack( - &mut self, - include_len: bool, - key_offset: usize, - ) -> Result<(), ExecutionError> { - if key_offset > 12 { - return Err(ExecutionError::InvalidStackWordOffset(key_offset)); - } - - let key = [ - self.stack.get(key_offset + 3), - self.stack.get(key_offset + 2), - self.stack.get(key_offset + 1), - self.stack.get(key_offset), - ]; - self.advice_provider.push_stack(AdviceSource::Map { key, include_len }) - } - - /// 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(super) fn push_u64_div_result(&mut self) -> Result<(), ExecutionError> { - let divisor_hi = self.stack.get(0).as_int(); - let divisor_lo = self.stack.get(1).as_int(); - let divisor = (divisor_hi << 32) + divisor_lo; - - if divisor == 0 { - return Err(ExecutionError::DivideByZero(self.system.clk())); - } - - let dividend_hi = self.stack.get(2).as_int(); - let dividend_lo = self.stack.get(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.advice_provider.push_stack(AdviceSource::Value(r_hi))?; - self.advice_provider.push_stack(AdviceSource::Value(r_lo))?; - self.advice_provider.push_stack(AdviceSource::Value(q_hi))?; - self.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(super) fn push_ext2_inv_result(&mut self) -> Result<(), ExecutionError> { - let coef0 = self.stack.get(1); - let coef1 = self.stack.get(0); - - let element = QuadFelt::new(coef0, coef1); - if element == QuadFelt::ZERO { - return Err(ExecutionError::DivideByZero(self.system.clk())); - } - let result = element.inv().to_base_elements(); - - self.advice_provider.push_stack(AdviceSource::Value(result[1]))?; - self.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(super) fn push_ext2_intt_result(&mut self) -> Result<(), ExecutionError> { - let output_size = self.stack.get(0).as_int() as usize; - let input_size = self.stack.get(1).as_int() as usize; - let input_start_ptr = self.stack.get(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 = self - .get_memory_value(self.system.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.advice_provider.push_stack(AdviceSource::Value(*element))?; - } - - Ok(()) - } - /// Pushes values onto the advice stack which are required for successful retrieval of a /// value from a Sparse Merkle Tree data structure. /// @@ -295,7 +56,7 @@ where /// /// # Panics /// Will panic as unimplemented if the target depth is `64`. - pub(super) fn push_smtget_inputs(&mut self) -> Result<(), ExecutionError> { + pub(in crate::decorators) fn push_smtget_inputs(&mut self) -> Result<(), ExecutionError> { // fetch the arguments from the operand stack let key = self.stack.get_word(0); let root = self.stack.get_word(1); @@ -350,7 +111,7 @@ where /// /// # Panics /// Will panic as unimplemented if the target depth is `64`. - pub(super) fn push_smtpeek_result(&mut self) -> Result<(), ExecutionError> { + pub(in crate::decorators) fn push_smtpeek_result(&mut self) -> Result<(), ExecutionError> { // fetch the arguments from the operand stack let key = self.stack.get_word(0); let root = self.stack.get_word(1); @@ -407,7 +168,7 @@ where /// /// # Panics /// Will panic as unimplemented if the target depth is `64`. - pub(super) fn push_smtset_inputs(&mut self) -> Result<(), ExecutionError> { + pub(in crate::decorators) fn push_smtset_inputs(&mut self) -> Result<(), ExecutionError> { // get the key, value, and tree root from the stack let value = self.stack.get_word(0); let key = self.stack.get_word(1); @@ -815,12 +576,6 @@ where // HELPER FUNCTIONS // ================================================================================================ -fn u64_to_u32_elements(value: u64) -> (Felt, Felt) { - let hi = Felt::new(value >> 32); - let lo = Felt::new((value as u32) as u64); - (hi, lo) -} - fn get_common_prefix(key1: &Word, key2: &Word) -> u8 { let k1 = key1[3].as_int(); let k2 = key2[3].as_int();