From 44a031e033f417032d5c45f430fb8b02e2fe3a93 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 22 Mar 2024 19:41:15 +0100 Subject: [PATCH 01/32] chore: fix no-std errors --- air/src/constraints/chiplets/bitwise/mod.rs | 3 ++- air/src/constraints/chiplets/hasher/mod.rs | 3 ++- air/src/constraints/chiplets/hasher/tests.rs | 2 +- air/src/constraints/chiplets/memory/mod.rs | 3 ++- air/src/constraints/chiplets/memory/tests.rs | 3 ++- air/src/constraints/chiplets/mod.rs | 3 ++- air/src/constraints/range.rs | 4 +++- air/src/constraints/stack/field_ops/mod.rs | 3 ++- air/src/constraints/stack/io_ops/mod.rs | 6 ++---- air/src/constraints/stack/mod.rs | 3 ++- air/src/constraints/stack/overflow/mod.rs | 3 ++- .../constraints/stack/stack_manipulation/mod.rs | 3 ++- air/src/constraints/stack/system_ops/mod.rs | 6 ++---- air/src/constraints/stack/u32_ops/mod.rs | 3 ++- air/src/errors.rs | 3 ++- air/src/lib.rs | 14 +++++++++----- air/src/proof.rs | 5 ++--- air/src/trace/main_trace.rs | 2 +- air/src/utils.rs | 4 ++-- assembly/src/assembler/context.rs | 8 ++++---- .../src/assembler/instruction/procedures.rs | 2 +- assembly/src/assembler/mod.rs | 10 +++++----- assembly/src/assembler/module_provider.rs | 3 ++- assembly/src/assembler/procedure_cache.rs | 5 ++--- assembly/src/assembler/span_builder.rs | 3 ++- assembly/src/assembler/tests.rs | 2 ++ assembly/src/ast/code_body.rs | 2 +- assembly/src/ast/format.rs | 2 +- assembly/src/ast/imports.rs | 4 +++- assembly/src/ast/mod.rs | 4 +++- assembly/src/ast/module.rs | 3 ++- assembly/src/ast/nodes/advice.rs | 2 +- assembly/src/ast/nodes/mod.rs | 2 +- assembly/src/ast/nodes/serde/debug.rs | 2 +- assembly/src/ast/nodes/serde/deserialization.rs | 2 +- assembly/src/ast/nodes/serde/mod.rs | 2 +- assembly/src/ast/nodes/serde/signatures.rs | 2 +- assembly/src/ast/parsers/constants.rs | 4 +++- assembly/src/ast/parsers/context.rs | 3 ++- assembly/src/ast/parsers/io_ops.rs | 3 ++- assembly/src/ast/parsers/labels.rs | 3 ++- assembly/src/ast/parsers/mod.rs | 7 +++---- assembly/src/ast/procedure.rs | 6 +++++- assembly/src/ast/program.rs | 3 ++- assembly/src/ast/tests.rs | 6 +++++- assembly/src/errors.rs | 5 ++++- assembly/src/lib.rs | 7 ++++--- assembly/src/library/masl.rs | 4 +++- assembly/src/library/mod.rs | 2 +- assembly/src/library/path.rs | 2 +- assembly/src/library/tests.rs | 1 + assembly/src/procedures/mod.rs | 6 +++++- assembly/src/tests.rs | 1 + assembly/src/tokens/lines.rs | 2 +- assembly/src/tokens/mod.rs | 6 +++++- assembly/src/tokens/stream.rs | 2 +- core/src/errors.rs | 3 ++- core/src/lib.rs | 6 ++++-- core/src/operations/decorators/assembly_op.rs | 2 +- core/src/operations/decorators/mod.rs | 2 +- core/src/program/blocks/join_block.rs | 2 +- core/src/program/blocks/loop_block.rs | 2 +- core/src/program/blocks/mod.rs | 2 +- core/src/program/blocks/span_block.rs | 3 ++- core/src/program/blocks/split_block.rs | 2 +- core/src/program/info.rs | 2 +- core/src/program/mod.rs | 5 ++--- core/src/program/tests.rs | 1 + core/src/stack/inputs.rs | 6 ++++-- core/src/stack/outputs.rs | 3 ++- core/src/utils/mod.rs | 6 +++--- processor/src/debug.rs | 9 ++++----- processor/src/errors.rs | 2 +- processor/src/host/advice/injectors/smt.rs | 3 ++- processor/src/host/advice/inputs.rs | 5 ++--- processor/src/host/advice/map.rs | 6 ++++-- processor/src/host/advice/mod.rs | 2 +- processor/src/host/advice/providers.rs | 7 ++++++- processor/src/host/debug.rs | 5 ++++- processor/src/host/mod.rs | 2 ++ processor/src/lib.rs | 8 +++++--- processor/src/operations/comb_ops.rs | 4 ++-- processor/src/range/aux_trace.rs | 3 ++- processor/src/range/mod.rs | 4 +++- processor/src/stack/aux_trace.rs | 3 ++- processor/src/stack/mod.rs | 2 +- processor/src/stack/overflow.rs | 3 ++- processor/src/stack/trace.rs | 3 ++- processor/src/system/mod.rs | 2 +- processor/src/trace/mod.rs | 4 ++-- processor/src/trace/utils.rs | 6 ++---- processor/src/utils.rs | 2 +- test-utils/src/lib.rs | 12 ++++++------ test-utils/src/test_builders.rs | 2 +- verifier/src/lib.rs | 17 ++++++++++------- 95 files changed, 224 insertions(+), 148 deletions(-) diff --git a/air/src/constraints/chiplets/bitwise/mod.rs b/air/src/constraints/chiplets/bitwise/mod.rs index c01e10f4d9..b5450e2066 100644 --- a/air/src/constraints/chiplets/bitwise/mod.rs +++ b/air/src/constraints/chiplets/bitwise/mod.rs @@ -5,9 +5,10 @@ use crate::{ BITWISE_A_COL_IDX, BITWISE_A_COL_RANGE, BITWISE_B_COL_IDX, BITWISE_B_COL_RANGE, BITWISE_OUTPUT_COL_IDX, BITWISE_PREV_OUTPUT_COL_IDX, BITWISE_SELECTOR_COL_IDX, }, - utils::{are_equal, binary_not, collections::*, is_binary, is_zero, EvaluationResult}, + utils::{are_equal, binary_not, is_binary, is_zero, EvaluationResult}, ONE, ZERO, }; +use alloc::vec::Vec; use winter_air::TransitionConstraintDegree; #[cfg(test)] diff --git a/air/src/constraints/chiplets/hasher/mod.rs b/air/src/constraints/chiplets/hasher/mod.rs index 17e4ecc211..e58828d80b 100644 --- a/air/src/constraints/chiplets/hasher/mod.rs +++ b/air/src/constraints/chiplets/hasher/mod.rs @@ -7,9 +7,10 @@ use crate::{ }, HASHER_NODE_INDEX_COL_IDX, HASHER_SELECTOR_COL_RANGE, HASHER_STATE_COL_RANGE, }, - utils::{are_equal, binary_not, collections::*, is_binary, EvaluationResult}, + utils::{are_equal, binary_not, is_binary, EvaluationResult}, ONE, ZERO, }; +use alloc::vec::Vec; #[cfg(test)] mod tests; diff --git a/air/src/constraints/chiplets/hasher/tests.rs b/air/src/constraints/chiplets/hasher/tests.rs index 5c278efc3b..c36219a655 100644 --- a/air/src/constraints/chiplets/hasher/tests.rs +++ b/air/src/constraints/chiplets/hasher/tests.rs @@ -4,9 +4,9 @@ use super::{ }; use crate::{ trace::chiplets::hasher::{Selectors, LINEAR_HASH, STATE_WIDTH}, - utils::collections::*, Felt, TRACE_WIDTH, }; +use alloc::vec::Vec; use rand_utils::rand_array; use vm_core::chiplets::hasher::apply_round; use winter_air::EvaluationFrame; diff --git a/air/src/constraints/chiplets/memory/mod.rs b/air/src/constraints/chiplets/memory/mod.rs index 159719428e..b4325c4a02 100644 --- a/air/src/constraints/chiplets/memory/mod.rs +++ b/air/src/constraints/chiplets/memory/mod.rs @@ -5,8 +5,9 @@ use crate::{ MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX, MEMORY_D_INV_COL_IDX, MEMORY_TRACE_OFFSET, MEMORY_V_COL_RANGE, }, - utils::{binary_not, collections::*, is_binary, EvaluationResult}, + utils::{binary_not, is_binary, EvaluationResult}, }; +use alloc::vec::Vec; use winter_air::TransitionConstraintDegree; #[cfg(test)] diff --git a/air/src/constraints/chiplets/memory/tests.rs b/air/src/constraints/chiplets/memory/tests.rs index 910aabf36d..868dcbdb47 100644 --- a/air/src/constraints/chiplets/memory/tests.rs +++ b/air/src/constraints/chiplets/memory/tests.rs @@ -9,7 +9,8 @@ use crate::trace::{ }, TRACE_WIDTH, }; -use crate::{chiplets::memory, utils::collections::*, Felt, FieldElement, ONE, ZERO}; +use crate::{chiplets::memory, Felt, FieldElement, ONE, ZERO}; +use alloc::vec::Vec; use rand_utils::rand_value; // UNIT TESTS diff --git a/air/src/constraints/chiplets/mod.rs b/air/src/constraints/chiplets/mod.rs index 7dc36f9d26..ec2772940f 100644 --- a/air/src/constraints/chiplets/mod.rs +++ b/air/src/constraints/chiplets/mod.rs @@ -1,7 +1,8 @@ use super::super::{ EvaluationFrame, Felt, FieldElement, TransitionConstraintDegree, CHIPLETS_OFFSET, }; -use crate::utils::{are_equal, binary_not, collections::*, is_binary}; +use crate::utils::{are_equal, binary_not, is_binary}; +use alloc::vec::Vec; mod bitwise; mod hasher; diff --git a/air/src/constraints/range.rs b/air/src/constraints/range.rs index 9b3a756a51..5459705ff5 100644 --- a/air/src/constraints/range.rs +++ b/air/src/constraints/range.rs @@ -2,9 +2,11 @@ use crate::{ chiplets::ChipletsFrameExt, constraints::MainFrameExt, trace::range::{B_RANGE_COL_IDX, M_COL_IDX, V_COL_IDX}, - utils::{are_equal, collections::*}, + utils::are_equal, Assertion, EvaluationFrame, Felt, FieldElement, TransitionConstraintDegree, }; +use alloc::vec::Vec; + use vm_core::{ExtensionOf, ZERO}; use winter_air::AuxTraceRandElements; diff --git a/air/src/constraints/stack/field_ops/mod.rs b/air/src/constraints/stack/field_ops/mod.rs index b2ec9981f3..bc4894e6ca 100644 --- a/air/src/constraints/stack/field_ops/mod.rs +++ b/air/src/constraints/stack/field_ops/mod.rs @@ -1,8 +1,9 @@ use super::{op_flags::OpFlags, EvaluationFrame, FieldElement, TransitionConstraintDegree}; use crate::{ stack::EvaluationFrameExt, - utils::{are_equal, collections::*, is_binary}, + utils::{are_equal, is_binary}, }; +use alloc::vec::Vec; #[cfg(test)] pub mod tests; diff --git a/air/src/constraints/stack/io_ops/mod.rs b/air/src/constraints/stack/io_ops/mod.rs index 3b6179289d..7f1c64f9c9 100644 --- a/air/src/constraints/stack/io_ops/mod.rs +++ b/air/src/constraints/stack/io_ops/mod.rs @@ -1,8 +1,6 @@ use super::{op_flags::OpFlags, EvaluationFrame, FieldElement, TransitionConstraintDegree}; -use crate::{ - stack::EvaluationFrameExt, - utils::{are_equal, collections::*}, -}; +use crate::{stack::EvaluationFrameExt, utils::are_equal}; +use alloc::vec::Vec; #[cfg(test)] pub mod tests; diff --git a/air/src/constraints/stack/mod.rs b/air/src/constraints/stack/mod.rs index e3fc571a2f..519ee89ac2 100644 --- a/air/src/constraints/stack/mod.rs +++ b/air/src/constraints/stack/mod.rs @@ -4,7 +4,8 @@ use super::super::{ STACK_AUX_TRACE_OFFSET, STACK_TRACE_OFFSET, ZERO, }; use crate::decoder::{IS_CALL_FLAG_COL_IDX, IS_SYSCALL_FLAG_COL_IDX, USER_OP_HELPERS_OFFSET}; -use crate::utils::{are_equal, collections::*, is_binary}; +use crate::utils::{are_equal, is_binary}; +use alloc::vec::Vec; use vm_core::{stack::STACK_TOP_SIZE, StackOutputs}; pub mod field_ops; diff --git a/air/src/constraints/stack/overflow/mod.rs b/air/src/constraints/stack/overflow/mod.rs index 32bd93093a..346bba5906 100644 --- a/air/src/constraints/stack/overflow/mod.rs +++ b/air/src/constraints/stack/overflow/mod.rs @@ -1,5 +1,6 @@ use super::{op_flags::OpFlags, EvaluationFrame, FieldElement, TransitionConstraintDegree}; -use crate::{stack::EvaluationFrameExt, utils::collections::*}; +use crate::stack::EvaluationFrameExt; +use alloc::vec::Vec; #[cfg(test)] pub mod tests; diff --git a/air/src/constraints/stack/stack_manipulation/mod.rs b/air/src/constraints/stack/stack_manipulation/mod.rs index 3fdee5cae0..4163633e56 100644 --- a/air/src/constraints/stack/stack_manipulation/mod.rs +++ b/air/src/constraints/stack/stack_manipulation/mod.rs @@ -1,8 +1,9 @@ use super::{op_flags::OpFlags, EvaluationFrame, FieldElement, TransitionConstraintDegree}; use crate::{ stack::EvaluationFrameExt, - utils::{are_equal, binary_not, collections::*}, + utils::{are_equal, binary_not}, }; +use alloc::vec::Vec; #[cfg(test)] pub mod tests; diff --git a/air/src/constraints/stack/system_ops/mod.rs b/air/src/constraints/stack/system_ops/mod.rs index 2775f73e22..5a1d8a6a0f 100644 --- a/air/src/constraints/stack/system_ops/mod.rs +++ b/air/src/constraints/stack/system_ops/mod.rs @@ -1,8 +1,6 @@ use super::{op_flags::OpFlags, EvaluationFrame, FieldElement, TransitionConstraintDegree}; -use crate::{ - stack::EvaluationFrameExt, - utils::{are_equal, collections::*}, -}; +use crate::{stack::EvaluationFrameExt, utils::are_equal}; +use alloc::vec::Vec; #[cfg(test)] pub mod tests; diff --git a/air/src/constraints/stack/u32_ops/mod.rs b/air/src/constraints/stack/u32_ops/mod.rs index 401166f72c..22b693d514 100644 --- a/air/src/constraints/stack/u32_ops/mod.rs +++ b/air/src/constraints/stack/u32_ops/mod.rs @@ -1,8 +1,9 @@ use super::{op_flags::OpFlags, EvaluationFrame, Felt, FieldElement, TransitionConstraintDegree}; use crate::{ stack::EvaluationFrameExt, - utils::{are_equal, collections::*, is_binary}, + utils::{are_equal, is_binary}, }; +use alloc::vec::Vec; #[cfg(test)] pub mod tests; diff --git a/air/src/errors.rs b/air/src/errors.rs index feda629ab7..cd9ef5bdb7 100644 --- a/air/src/errors.rs +++ b/air/src/errors.rs @@ -1,4 +1,5 @@ -use crate::{trace::MIN_TRACE_LEN, utils::string::*}; +use crate::trace::MIN_TRACE_LEN; +use alloc::string::String; use core::fmt::{Display, Formatter}; // EXECUTION ERROR diff --git a/air/src/lib.rs b/air/src/lib.rs index a597604ce4..426ea836f6 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -1,11 +1,15 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] -#[cfg(not(feature = "std"))] #[macro_use] extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + +use alloc::vec::Vec; + use vm_core::{ - utils::{collections::*, ByteReader, ByteWriter, Deserializable, Serializable}, + utils::{ByteReader, ByteWriter, Deserializable, Serializable}, ExtensionOf, ProgramInfo, StackInputs, StackOutputs, ONE, ZERO, }; use winter_air::{ @@ -173,7 +177,7 @@ impl Air for ProcessorAir { ); // Add initial assertions for the range checker's auxiliary columns. - range::get_aux_assertions_first_step(&mut result); + range::get_aux_assertions_first_step::(&mut result); // --- set assertions for the last step --------------------------------------------------- let last_step = self.last_step(); @@ -187,7 +191,7 @@ impl Air for ProcessorAir { ); // Add the range checker's auxiliary column assertions for the last step. - range::get_aux_assertions_last_step(&mut result, last_step); + range::get_aux_assertions_last_step::(&mut result, last_step); result } diff --git a/air/src/proof.rs b/air/src/proof.rs index 43d8dec278..c092e68ad3 100644 --- a/air/src/proof.rs +++ b/air/src/proof.rs @@ -1,8 +1,7 @@ +use alloc::vec::Vec; use vm_core::{ crypto::hash::{Blake3_192, Blake3_256, Hasher, Rpo256}, - utils::{ - collections::*, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, - }, + utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, }; use winter_air::proof::StarkProof; diff --git a/air/src/trace/main_trace.rs b/air/src/trace/main_trace.rs index 405f5471db..454074a714 100644 --- a/air/src/trace/main_trace.rs +++ b/air/src/trace/main_trace.rs @@ -16,7 +16,7 @@ use super::{ CHIPLETS_OFFSET, CLK_COL_IDX, CTX_COL_IDX, DECODER_TRACE_OFFSET, FMP_COL_IDX, FN_HASH_OFFSET, STACK_TRACE_OFFSET, }; -use crate::utils::collections::*; +use alloc::vec::Vec; use core::ops::{Deref, Range}; use vm_core::{utils::range, Felt, ONE, ZERO}; diff --git a/air/src/utils.rs b/air/src/utils.rs index 687b8432fc..9a9e8487c0 100644 --- a/air/src/utils.rs +++ b/air/src/utils.rs @@ -1,10 +1,10 @@ use super::FieldElement; +use alloc::vec::Vec; use core::ops::Range; -use vm_core::utils::{collections::*, range as create_range}; +use vm_core::utils::range as create_range; // RE-EXPORTS // ================================================================================================ -pub use vm_core::utils::{collections, string}; // BASIC CONSTRAINT OPERATORS // ================================================================================================ diff --git a/assembly/src/assembler/context.rs b/assembly/src/assembler/context.rs index 6d30ef70c2..295b96c927 100644 --- a/assembly/src/assembler/context.rs +++ b/assembly/src/assembler/context.rs @@ -2,10 +2,10 @@ use super::{ AssemblyError, CallSet, CodeBlock, CodeBlockTable, Kernel, LibraryPath, NamedProcedure, Procedure, ProcedureCache, ProcedureId, ProcedureName, RpoDigest, }; -use crate::{ - ast::{ModuleAst, ProgramAst}, - utils::{collections::*, string::*}, -}; +use crate::ast::{ModuleAst, ProgramAst}; +use alloc::collections::BTreeMap; +use alloc::string::ToString; +use alloc::vec::Vec; // ASSEMBLY CONTEXT // ================================================================================================ diff --git a/assembly/src/assembler/instruction/procedures.rs b/assembly/src/assembler/instruction/procedures.rs index 1801caac39..b95b7bf775 100644 --- a/assembly/src/assembler/instruction/procedures.rs +++ b/assembly/src/assembler/instruction/procedures.rs @@ -2,7 +2,7 @@ use super::{ Assembler, AssemblyContext, AssemblyError, CodeBlock, Operation, ProcedureId, RpoDigest, SpanBuilder, }; -use crate::utils::collections::*; +use alloc::vec::Vec; // PROCEDURE INVOCATIONS // ================================================================================================ diff --git a/assembly/src/assembler/mod.rs b/assembly/src/assembler/mod.rs index 67686db4b4..1d18d4dcda 100644 --- a/assembly/src/assembler/mod.rs +++ b/assembly/src/assembler/mod.rs @@ -1,12 +1,12 @@ use super::{ ast::{instrument, Instruction, ModuleAst, Node, ProcedureAst, ProgramAst}, - btree_map, crypto::hash::RpoDigest, - AssemblyError, BTreeMap, CallSet, CodeBlock, CodeBlockTable, Felt, Kernel, Library, - LibraryError, LibraryPath, Module, NamedProcedure, Operation, Procedure, ProcedureId, - ProcedureName, Program, ONE, ZERO, + AssemblyError, CallSet, CodeBlock, CodeBlockTable, Felt, Kernel, Library, LibraryError, + LibraryPath, Module, NamedProcedure, Operation, Procedure, ProcedureId, ProcedureName, Program, + ONE, ZERO, }; -use crate::utils::collections::*; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; use core::{borrow::Borrow, cell::RefCell}; use vm_core::{utils::group_vector_elements, Decorator, DecoratorList}; diff --git a/assembly/src/assembler/module_provider.rs b/assembly/src/assembler/module_provider.rs index f25d0b33d6..f5e2eab677 100644 --- a/assembly/src/assembler/module_provider.rs +++ b/assembly/src/assembler/module_provider.rs @@ -1,5 +1,6 @@ use super::{Library, LibraryError, Module, ProcedureId}; -use crate::utils::collections::*; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; // MODULE PROVIDER // ================================================================================================ diff --git a/assembly/src/assembler/procedure_cache.rs b/assembly/src/assembler/procedure_cache.rs index 3caa49a1c5..31761f922d 100644 --- a/assembly/src/assembler/procedure_cache.rs +++ b/assembly/src/assembler/procedure_cache.rs @@ -1,6 +1,5 @@ -use super::{ - btree_map::Entry, AssemblyError, BTreeMap, NamedProcedure, Procedure, ProcedureId, RpoDigest, -}; +use super::{AssemblyError, BTreeMap, NamedProcedure, Procedure, ProcedureId, RpoDigest}; +use alloc::collections::btree_map::Entry; // PROCEDURE CACHE // ================================================================================================ diff --git a/assembly/src/assembler/span_builder.rs b/assembly/src/assembler/span_builder.rs index b767fb2dbb..43e7901781 100644 --- a/assembly/src/assembler/span_builder.rs +++ b/assembly/src/assembler/span_builder.rs @@ -2,7 +2,8 @@ use super::{ AssemblyContext, AssemblyError, BodyWrapper, Borrow, CodeBlock, Decorator, DecoratorList, Instruction, Operation, }; -use crate::utils::{collections::*, string::*}; +use alloc::string::ToString; +use alloc::vec::Vec; use vm_core::{AdviceInjector, AssemblyOp}; // SPAN BUILDER diff --git a/assembly/src/assembler/tests.rs b/assembly/src/assembler/tests.rs index 8edd17cb65..fad93cd59b 100644 --- a/assembly/src/assembler/tests.rs +++ b/assembly/src/assembler/tests.rs @@ -1,5 +1,7 @@ use super::{combine_blocks, Assembler, CodeBlock, Library, Module, Operation}; use crate::{ast::ModuleAst, LibraryNamespace, LibraryPath, Version}; +use alloc::string::ToString; +use alloc::vec::Vec; use core::slice::Iter; // TESTS diff --git a/assembly/src/ast/code_body.rs b/assembly/src/ast/code_body.rs index 230035f83c..d484f4fa84 100644 --- a/assembly/src/ast/code_body.rs +++ b/assembly/src/ast/code_body.rs @@ -2,7 +2,7 @@ use super::{ ByteReader, ByteWriter, Deserializable, DeserializationError, Node, Serializable, SourceLocation, MAX_BODY_LEN, }; -use crate::utils::collections::*; +use alloc::vec::Vec; use core::{iter, slice}; // CODE BODY diff --git a/assembly/src/ast/format.rs b/assembly/src/ast/format.rs index 2ba9c6fec3..cf96708fdf 100644 --- a/assembly/src/ast/format.rs +++ b/assembly/src/ast/format.rs @@ -2,7 +2,7 @@ use super::{ CodeBody, FormattableNode, InvokedProcsMap, LibraryPath, ProcedureAst, ProcedureId, ProcedureName, }; -use crate::utils::collections::*; +use alloc::vec::Vec; use core::fmt; const INDENT_STRING: &str = " "; diff --git a/assembly/src/ast/imports.rs b/assembly/src/ast/imports.rs index 4a275e45cd..efdef0f379 100644 --- a/assembly/src/ast/imports.rs +++ b/assembly/src/ast/imports.rs @@ -3,7 +3,9 @@ use super::{ ParsingError, ProcedureId, ProcedureName, Serializable, Token, TokenStream, MAX_IMPORTS, MAX_INVOKED_IMPORTED_PROCS, }; -use crate::utils::{collections::*, string::*}; +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; // TYPE ALIASES // ================================================================================================ diff --git a/assembly/src/ast/mod.rs b/assembly/src/ast/mod.rs index 9f0652ad44..c96274b72b 100644 --- a/assembly/src/ast/mod.rs +++ b/assembly/src/ast/mod.rs @@ -7,7 +7,9 @@ use super::{ LabelError, LibraryPath, ParsingError, ProcedureId, ProcedureName, Serializable, SliceReader, StarkField, Token, TokenStream, MAX_LABEL_LEN, }; -use crate::utils::{collections::*, string::*}; +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::vec::Vec; use vm_core::utils::bound_into_included_u64; pub use tracing::{event, info_span, instrument, Level}; diff --git a/assembly/src/ast/module.rs b/assembly/src/ast/module.rs index e15099a401..266aa3fe40 100644 --- a/assembly/src/ast/module.rs +++ b/assembly/src/ast/module.rs @@ -11,8 +11,9 @@ use super::{ Token, TokenStream, }, }; -use crate::utils::{collections::*, string::*}; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; use core::{fmt, str::from_utf8}; use vm_core::utils::Serializable; diff --git a/assembly/src/ast/nodes/advice.rs b/assembly/src/ast/nodes/advice.rs index 2b0fdc8b86..a819ccee76 100644 --- a/assembly/src/ast/nodes/advice.rs +++ b/assembly/src/ast/nodes/advice.rs @@ -5,7 +5,7 @@ use super::{ }, serde::signatures, }; -use crate::utils::string::*; +use alloc::string::ToString; use core::fmt; use vm_core::{AdviceInjector, Felt, SignatureKind, ZERO}; diff --git a/assembly/src/ast/nodes/mod.rs b/assembly/src/ast/nodes/mod.rs index f10feb087c..1d97c981f4 100644 --- a/assembly/src/ast/nodes/mod.rs +++ b/assembly/src/ast/nodes/mod.rs @@ -1,5 +1,5 @@ use super::{AstFormatterContext, CodeBody, Felt, FormattableCodeBody, ProcedureId, RpoDigest}; -use crate::utils::collections::*; +use alloc::vec::Vec; use core::fmt; use vm_core::DebugOptions; diff --git a/assembly/src/ast/nodes/serde/debug.rs b/assembly/src/ast/nodes/serde/debug.rs index 3b8b30b0b5..d1652680ad 100644 --- a/assembly/src/ast/nodes/serde/debug.rs +++ b/assembly/src/ast/nodes/serde/debug.rs @@ -1,5 +1,5 @@ use super::{super::DebugOptions, ByteReader, ByteWriter, DeserializationError}; -use crate::utils::string::*; +use alloc::string::ToString; const STACK_ALL: u8 = 0; const STACK_TOP: u8 = 1; diff --git a/assembly/src/ast/nodes/serde/deserialization.rs b/assembly/src/ast/nodes/serde/deserialization.rs index 7a86a5caf6..babdc98247 100644 --- a/assembly/src/ast/nodes/serde/deserialization.rs +++ b/assembly/src/ast/nodes/serde/deserialization.rs @@ -2,7 +2,7 @@ use super::{ super::AdviceInjectorNode, debug, ByteReader, CodeBody, Deserializable, DeserializationError, Felt, Instruction, Node, OpCode, ProcedureId, RpoDigest, MAX_PUSH_INPUTS, }; -use crate::utils::string::*; +use alloc::string::ToString; // NODE DESERIALIZATION // ================================================================================================ diff --git a/assembly/src/ast/nodes/serde/mod.rs b/assembly/src/ast/nodes/serde/mod.rs index a81ddaaa7a..cc4f9fff8a 100644 --- a/assembly/src/ast/nodes/serde/mod.rs +++ b/assembly/src/ast/nodes/serde/mod.rs @@ -1,6 +1,6 @@ use super::{CodeBody, Felt, Instruction, Node, ProcedureId, RpoDigest}; -use crate::utils::string::*; use crate::MAX_PUSH_INPUTS; +use alloc::string::ToString; use num_enum::TryFromPrimitive; use vm_core::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; diff --git a/assembly/src/ast/nodes/serde/signatures.rs b/assembly/src/ast/nodes/serde/signatures.rs index ebaf358562..484f814f83 100644 --- a/assembly/src/ast/nodes/serde/signatures.rs +++ b/assembly/src/ast/nodes/serde/signatures.rs @@ -1,5 +1,5 @@ use super::{ByteReader, ByteWriter, DeserializationError}; -use crate::utils::string::*; +use alloc::string::ToString; use vm_core::SignatureKind; const RPOFALCON512: u8 = 0; diff --git a/assembly/src/ast/parsers/constants.rs b/assembly/src/ast/parsers/constants.rs index 3303eec94b..84477200d3 100644 --- a/assembly/src/ast/parsers/constants.rs +++ b/assembly/src/ast/parsers/constants.rs @@ -1,5 +1,6 @@ use super::{Felt, LocalConstMap, ParsingError, Token}; -use crate::utils::{collections::*, string::*}; +use alloc::string::String; +use alloc::vec::Vec; use core::fmt::Display; // CONSTANT VALUE EXPRESSIONS @@ -298,6 +299,7 @@ mod tests { }, ONE, }; + use alloc::string::ToString; use Operation::*; #[test] diff --git a/assembly/src/ast/parsers/context.rs b/assembly/src/ast/parsers/context.rs index 41c3dadb9f..32bf0d8a63 100644 --- a/assembly/src/ast/parsers/context.rs +++ b/assembly/src/ast/parsers/context.rs @@ -4,7 +4,8 @@ use super::{ ModuleImports, Node, ParsingError, ProcedureAst, ProcedureId, ProcedureName, ReExportedProcMap, Token, TokenStream, MAX_BODY_LEN, MAX_DOCS_LEN, }; -use crate::utils::{collections::*, string::*}; +use alloc::string::ToString; +use alloc::vec::Vec; // PARSER CONTEXT // ================================================================================================ diff --git a/assembly/src/ast/parsers/io_ops.rs b/assembly/src/ast/parsers/io_ops.rs index 2a23a9af68..11f2328f58 100644 --- a/assembly/src/ast/parsers/io_ops.rs +++ b/assembly/src/ast/parsers/io_ops.rs @@ -5,7 +5,8 @@ use super::{ Node::{self, Instruction}, ParsingError, Token, CONSTANT_LABEL_PARSER, HEX_CHUNK_SIZE, }; -use crate::{utils::collections::*, StarkField, ADVICE_READ_LIMIT, MAX_PUSH_INPUTS}; +use crate::{StarkField, ADVICE_READ_LIMIT, MAX_PUSH_INPUTS}; +use alloc::vec::Vec; use core::ops::RangeBounds; use vm_core::WORD_SIZE; diff --git a/assembly/src/ast/parsers/labels.rs b/assembly/src/ast/parsers/labels.rs index 61ea86445e..05a5f3ce03 100644 --- a/assembly/src/ast/parsers/labels.rs +++ b/assembly/src/ast/parsers/labels.rs @@ -1,5 +1,6 @@ use super::{Deserializable, LabelError, RpoDigest, SliceReader, MAX_LABEL_LEN}; -use crate::utils::{collections::*, string::*}; +use alloc::string::ToString; +use alloc::vec::Vec; // LABEL PARSERS // ================================================================================================ diff --git a/assembly/src/ast/parsers/mod.rs b/assembly/src/ast/parsers/mod.rs index 1b8c8f39cd..0046c906f3 100644 --- a/assembly/src/ast/parsers/mod.rs +++ b/assembly/src/ast/parsers/mod.rs @@ -5,10 +5,9 @@ use super::{ SliceReader, StarkField, Token, TokenStream, MAX_BODY_LEN, MAX_DOCS_LEN, MAX_LABEL_LEN, MAX_STACK_WORD_OFFSET, }; -use crate::{ - utils::{collections::*, string::*}, - HEX_CHUNK_SIZE, -}; +use crate::HEX_CHUNK_SIZE; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; use core::{fmt::Display, ops::RangeBounds}; mod adv_ops; diff --git a/assembly/src/ast/procedure.rs b/assembly/src/ast/procedure.rs index 4ad93afce2..bd1fe0d5eb 100644 --- a/assembly/src/ast/procedure.rs +++ b/assembly/src/ast/procedure.rs @@ -1,10 +1,14 @@ +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; + use crate::ast::{MAX_BODY_LEN, MAX_DOCS_LEN}; use super::{ super::tokens::SourceLocation, code_body::CodeBody, nodes::Node, ByteReader, ByteWriter, Deserializable, DeserializationError, LibraryPath, ProcedureId, ProcedureName, Serializable, }; -use crate::utils::{collections::*, string::*}; use core::{iter, str::from_utf8}; // PROCEDURE AST diff --git a/assembly/src/ast/program.rs b/assembly/src/ast/program.rs index 26139ab9ad..b180848d24 100644 --- a/assembly/src/ast/program.rs +++ b/assembly/src/ast/program.rs @@ -1,3 +1,5 @@ +use alloc::vec::Vec; + use crate::ast::MAX_BODY_LEN; use super::{ @@ -18,7 +20,6 @@ use super::{ SliceReader, Token, TokenStream, }, }; -use crate::utils::collections::*; use core::{fmt, iter}; #[cfg(feature = "std")] diff --git a/assembly/src/ast/tests.rs b/assembly/src/ast/tests.rs index f465ffa1e4..52035cc66d 100644 --- a/assembly/src/ast/tests.rs +++ b/assembly/src/ast/tests.rs @@ -2,7 +2,11 @@ use super::{ AstSerdeOptions, CodeBody, Felt, Instruction, LocalProcMap, ModuleAst, Node, ParsingError, ProcedureAst, ProcedureId, ProcedureName, ProgramAst, SourceLocation, Token, }; -use crate::utils::{collections::*, string::*}; +use alloc::{ + collections::BTreeMap, + string::{String, ToString}, + vec::Vec, +}; use vm_core::utils::SliceReader; // UNIT TESTS diff --git a/assembly/src/errors.rs b/assembly/src/errors.rs index 414bcceb72..4cc962a224 100644 --- a/assembly/src/errors.rs +++ b/assembly/src/errors.rs @@ -2,7 +2,10 @@ use super::{ ast::ProcReExport, crypto::hash::RpoDigest, tokens::SourceLocation, KernelError, LibraryNamespace, ProcedureId, ProcedureName, Token, }; -use crate::utils::{collections::*, string::*}; +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; use core::fmt; // ASSEMBLY ERROR diff --git a/assembly/src/lib.rs b/assembly/src/lib.rs index a7cc116704..af34c3f20c 100644 --- a/assembly/src/lib.rs +++ b/assembly/src/lib.rs @@ -1,15 +1,16 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] -#[cfg(not(feature = "std"))] #[macro_use] extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + use vm_core::{ code_blocks::CodeBlock, crypto, errors::KernelError, utils::{ - collections::{btree_map, BTreeMap}, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader, }, CodeBlockTable, Felt, Kernel, Operation, Program, StarkField, ONE, ZERO, diff --git a/assembly/src/library/masl.rs b/assembly/src/library/masl.rs index 51eca33fd5..10bdf3da52 100644 --- a/assembly/src/library/masl.rs +++ b/assembly/src/library/masl.rs @@ -3,7 +3,7 @@ use super::{ LibraryError, LibraryNamespace, LibraryPath, Module, ModuleAst, Serializable, Version, MAX_DEPENDENCIES, MAX_MODULES, }; -use crate::utils::collections::*; +use alloc::{collections::BTreeSet, vec::Vec}; use core::slice::Iter; // CONSTANT DEFINITIONS @@ -119,6 +119,8 @@ impl MaslLibrary { #[cfg(feature = "std")] mod use_std { + use alloc::{collections::BTreeMap, string::ToString}; + use super::{super::super::ast::instrument, *}; use std::{fs, io, path::Path}; diff --git a/assembly/src/library/mod.rs b/assembly/src/library/mod.rs index 56608a9bba..0ebdf6453f 100644 --- a/assembly/src/library/mod.rs +++ b/assembly/src/library/mod.rs @@ -3,10 +3,10 @@ use super::{ ByteReader, ByteWriter, Deserializable, DeserializationError, LibraryError, PathError, Serializable, MAX_LABEL_LEN, NAMESPACE_LABEL_PARSER, }; -use crate::utils::string::*; use core::{cmp::Ordering, fmt, ops::Deref, str::from_utf8}; mod masl; +use alloc::string::{String, ToString}; pub use masl::MaslLibrary; mod path; diff --git a/assembly/src/library/path.rs b/assembly/src/library/path.rs index 7bb7fb0192..5192315922 100644 --- a/assembly/src/library/path.rs +++ b/assembly/src/library/path.rs @@ -2,7 +2,7 @@ use super::{ ByteReader, ByteWriter, Deserializable, DeserializationError, PathError, Serializable, MAX_LABEL_LEN, }; -use crate::utils::string::*; +use alloc::string::{String, ToString}; use core::{fmt, ops::Deref, str::from_utf8}; // CONSTANTS diff --git a/assembly/src/library/tests.rs b/assembly/src/library/tests.rs index 92886923c4..0b3a5b1ff6 100644 --- a/assembly/src/library/tests.rs +++ b/assembly/src/library/tests.rs @@ -1,4 +1,5 @@ use super::{Library, LibraryNamespace, LibraryPath, MaslLibrary, Module, ModuleAst, Version}; +use alloc::vec::Vec; use vm_core::utils::{Deserializable, Serializable, SliceReader}; #[test] diff --git a/assembly/src/procedures/mod.rs b/assembly/src/procedures/mod.rs index 4519de64cd..8180a562ba 100644 --- a/assembly/src/procedures/mod.rs +++ b/assembly/src/procedures/mod.rs @@ -3,7 +3,10 @@ use super::{ ByteReader, ByteWriter, CodeBlock, Deserializable, DeserializationError, LabelError, LibraryPath, Serializable, PROCEDURE_LABEL_PARSER, }; -use crate::utils::{collections::*, string::*}; +use alloc::{ + collections::BTreeSet, + string::{String, ToString}, +}; use core::{ fmt, ops::{self, Deref}, @@ -366,6 +369,7 @@ impl ops::Deref for CallSet { #[cfg(test)] mod test { use super::{super::MAX_LABEL_LEN, LabelError, ProcedureName}; + use alloc::borrow::ToOwned; #[test] fn test_procedure_name_max_len() { diff --git a/assembly/src/tests.rs b/assembly/src/tests.rs index f76fbc4e32..b41d8f401c 100644 --- a/assembly/src/tests.rs +++ b/assembly/src/tests.rs @@ -3,6 +3,7 @@ use crate::{ Assembler, AssemblyContext, AssemblyError, Library, LibraryNamespace, LibraryPath, MaslLibrary, Module, ProcedureName, Version, }; +use alloc::{string::ToString, vec::Vec}; use core::slice::Iter; // SIMPLE PROGRAMS diff --git a/assembly/src/tokens/lines.rs b/assembly/src/tokens/lines.rs index 16d5b2083e..b066aea4af 100644 --- a/assembly/src/tokens/lines.rs +++ b/assembly/src/tokens/lines.rs @@ -1,5 +1,5 @@ use super::{SourceLocation, Token}; -use crate::utils::collections::*; +use alloc::vec::Vec; use core::{iter, str::Lines}; // LINES STREAM diff --git a/assembly/src/tokens/mod.rs b/assembly/src/tokens/mod.rs index 4534cb0bbf..19b5d99ac8 100644 --- a/assembly/src/tokens/mod.rs +++ b/assembly/src/tokens/mod.rs @@ -3,7 +3,11 @@ use super::{ ByteReader, ByteWriter, Deserializable, DeserializationError, LibraryPath, ParsingError, ProcedureName, Serializable, }; -use crate::utils::{collections::*, string::*}; +use alloc::{ + collections::BTreeMap, + string::{String, ToString}, + vec::Vec, +}; use core::fmt; mod lines; diff --git a/assembly/src/tokens/stream.rs b/assembly/src/tokens/stream.rs index 0d81227807..b5c4e116b3 100644 --- a/assembly/src/tokens/stream.rs +++ b/assembly/src/tokens/stream.rs @@ -1,5 +1,5 @@ use super::{LineTokenizer, LinesStream, ParsingError, SourceLocation, Token}; -use crate::utils::{collections::*, string::*}; +use alloc::{collections::BTreeMap, string::String, vec::Vec}; use core::fmt; // TOKEN STREAM diff --git a/core/src/errors.rs b/core/src/errors.rs index 2956794cc1..d7b0c80695 100644 --- a/core/src/errors.rs +++ b/core/src/errors.rs @@ -1,6 +1,7 @@ -use crate::utils::string::*; use core::fmt; +use alloc::string::String; + // INPUT ERROR // ================================================================================================ diff --git a/core/src/lib.rs b/core/src/lib.rs index 3653452e4d..bc50dd027d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,6 +1,8 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] + +#[cfg(feature = "std")] +extern crate std; -#[cfg(not(feature = "std"))] #[macro_use] extern crate alloc; diff --git a/core/src/operations/decorators/assembly_op.rs b/core/src/operations/decorators/assembly_op.rs index b18b893787..56689724cf 100644 --- a/core/src/operations/decorators/assembly_op.rs +++ b/core/src/operations/decorators/assembly_op.rs @@ -1,4 +1,4 @@ -use crate::utils::string::*; +use alloc::string::String; use core::fmt; // ASSEMBLY OP diff --git a/core/src/operations/decorators/mod.rs b/core/src/operations/decorators/mod.rs index 9fb653c32e..12cb478e70 100644 --- a/core/src/operations/decorators/mod.rs +++ b/core/src/operations/decorators/mod.rs @@ -1,4 +1,4 @@ -use crate::utils::collections::*; +use alloc::vec::Vec; use core::fmt; mod advice; diff --git a/core/src/program/blocks/join_block.rs b/core/src/program/blocks/join_block.rs index 711b10d481..02a9c358a9 100644 --- a/core/src/program/blocks/join_block.rs +++ b/core/src/program/blocks/join_block.rs @@ -1,5 +1,5 @@ use super::{fmt, hasher, CodeBlock, Digest, Felt, Operation}; -use crate::utils::boxed::*; +use alloc::boxed::Box; // JOIN BLOCKS // ================================================================================================ diff --git a/core/src/program/blocks/loop_block.rs b/core/src/program/blocks/loop_block.rs index eb68df6324..3f6c8fbdca 100644 --- a/core/src/program/blocks/loop_block.rs +++ b/core/src/program/blocks/loop_block.rs @@ -1,5 +1,5 @@ use super::{fmt, hasher, CodeBlock, Digest, Felt, Operation}; -use crate::utils::boxed::*; +use alloc::boxed::Box; // LOOP BLOCK // ================================================================================================ diff --git a/core/src/program/blocks/mod.rs b/core/src/program/blocks/mod.rs index 1fc7328600..f06bff6acb 100644 --- a/core/src/program/blocks/mod.rs +++ b/core/src/program/blocks/mod.rs @@ -1,6 +1,6 @@ use super::{hasher, Digest, Felt, Operation}; -use crate::utils::collections::*; use crate::DecoratorList; +use alloc::vec::Vec; use core::fmt; mod call_block; diff --git a/core/src/program/blocks/span_block.rs b/core/src/program/blocks/span_block.rs index 8ba4a8c74b..d0d7f7f15f 100644 --- a/core/src/program/blocks/span_block.rs +++ b/core/src/program/blocks/span_block.rs @@ -1,5 +1,6 @@ use super::{fmt, hasher, Digest, Felt, Operation}; -use crate::{utils::collections::*, DecoratorIterator, DecoratorList, ZERO}; +use crate::{DecoratorIterator, DecoratorList, ZERO}; +use alloc::vec::Vec; use winter_utils::flatten_slice_elements; // CONSTANTS diff --git a/core/src/program/blocks/split_block.rs b/core/src/program/blocks/split_block.rs index 91c4e36f54..3407afbcdd 100644 --- a/core/src/program/blocks/split_block.rs +++ b/core/src/program/blocks/split_block.rs @@ -1,5 +1,5 @@ use super::{fmt, hasher, CodeBlock, Digest, Felt, Operation}; -use crate::utils::boxed::*; +use winter_utils::Box; // SPLIT BLOCK // ================================================================================================ diff --git a/core/src/program/info.rs b/core/src/program/info.rs index b51f6405de..083fcb0c23 100644 --- a/core/src/program/info.rs +++ b/core/src/program/info.rs @@ -3,7 +3,7 @@ use super::{ ByteReader, ByteWriter, Deserializable, DeserializationError, Digest, Felt, Kernel, Program, Serializable, }; -use crate::utils::collections::*; +use alloc::vec::Vec; // PROGRAM INFO // ================================================================================================ diff --git a/core/src/program/mod.rs b/core/src/program/mod.rs index 5205fcfbdd..1de4bba969 100644 --- a/core/src/program/mod.rs +++ b/core/src/program/mod.rs @@ -2,9 +2,8 @@ use super::{ chiplets::hasher::{self, Digest}, errors, Felt, Operation, }; -use crate::utils::{ - collections::*, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, -}; +use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; +use alloc::{collections::BTreeMap, vec::Vec}; use core::fmt; pub mod blocks; diff --git a/core/src/program/tests.rs b/core/src/program/tests.rs index ab11bac3e4..3e5e0a5329 100644 --- a/core/src/program/tests.rs +++ b/core/src/program/tests.rs @@ -1,5 +1,6 @@ use super::{blocks::Dyn, Deserializable, Digest, Felt, Kernel, ProgramInfo, Serializable}; use crate::{chiplets::hasher, Word}; +use alloc::vec::Vec; use proptest::prelude::*; use rand_utils::prng_array; diff --git a/core/src/stack/inputs.rs b/core/src/stack/inputs.rs index b05e35bd84..32845306b7 100644 --- a/core/src/stack/inputs.rs +++ b/core/src/stack/inputs.rs @@ -1,4 +1,6 @@ -use crate::utils::{collections::*, ByteReader, Deserializable, DeserializationError}; +use alloc::vec::Vec; + +use crate::utils::{ByteReader, Deserializable, DeserializationError}; use super::{ByteWriter, Felt, InputError, Serializable, ToElements}; use core::slice; @@ -75,7 +77,7 @@ impl<'a> IntoIterator for &'a StackInputs { impl IntoIterator for StackInputs { type Item = Felt; - type IntoIter = vec::IntoIter; + type IntoIter = alloc::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.values.into_iter() diff --git a/core/src/stack/outputs.rs b/core/src/stack/outputs.rs index 3e1cc891da..2277ad0dce 100644 --- a/core/src/stack/outputs.rs +++ b/core/src/stack/outputs.rs @@ -1,4 +1,5 @@ -use crate::utils::{collections::*, range, ByteReader, Deserializable, DeserializationError}; +use crate::utils::{range, ByteReader, Deserializable, DeserializationError}; +use alloc::vec::Vec; use miden_crypto::{Word, ZERO}; use super::{ diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs index c90ceb8cd1..f39dd058ec 100644 --- a/core/src/utils/mod.rs +++ b/core/src/utils/mod.rs @@ -1,9 +1,9 @@ use crate::Felt; +use alloc::{string::String, vec::Vec}; use core::{ fmt::{self, Debug, Write}, ops::{Bound, Range}, }; -use {collections::*, string::*}; // RE-EXPORTS // ================================================================================================ @@ -11,8 +11,8 @@ use {collections::*, string::*}; pub use winter_utils::{group_slice_elements, group_vector_elements}; pub use miden_crypto::utils::{ - boxed, collections, string, uninit_vector, vec, Box, ByteReader, ByteWriter, Deserializable, - DeserializationError, Serializable, SliceReader, + collections, uninit_vector, ByteReader, ByteWriter, Deserializable, DeserializationError, + Serializable, SliceReader, }; pub mod math { diff --git a/processor/src/debug.rs b/processor/src/debug.rs index 48df4dfde8..d87275d5b1 100644 --- a/processor/src/debug.rs +++ b/processor/src/debug.rs @@ -1,10 +1,9 @@ use crate::{ - range::RangeChecker, - system::ContextId, - utils::{collections::*, string::*}, - Chiplets, ChipletsLengths, Decoder, ExecutionError, Felt, Host, Process, Stack, System, - TraceLenSummary, + range::RangeChecker, system::ContextId, Chiplets, ChipletsLengths, Decoder, ExecutionError, + Felt, Host, Process, Stack, System, TraceLenSummary, }; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; use core::fmt; use vm_core::{AssemblyOp, Operation, StackOutputs, Word}; diff --git a/processor/src/errors.rs b/processor/src/errors.rs index 46587b2424..2702a1a7ea 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -3,7 +3,7 @@ use super::{ system::{FMP_MAX, FMP_MIN}, CodeBlock, Digest, Felt, QuadFelt, Word, }; -use crate::utils::string::*; +use alloc::string::String; use core::fmt::{Display, Formatter}; use vm_core::{stack::STACK_TOP_SIZE, utils::to_hex}; use winter_prover::{math::FieldElement, ProverError}; diff --git a/processor/src/host/advice/injectors/smt.rs b/processor/src/host/advice/injectors/smt.rs index f7c96d7f62..4438f4abf5 100644 --- a/processor/src/host/advice/injectors/smt.rs +++ b/processor/src/host/advice/injectors/smt.rs @@ -1,5 +1,6 @@ use super::super::{AdviceSource, ExecutionError, Felt, HostResponse, Word}; -use crate::{utils::collections::*, AdviceProvider, ProcessState}; +use crate::{AdviceProvider, ProcessState}; +use alloc::vec::Vec; use vm_core::{ crypto::{ hash::RpoDigest, diff --git a/processor/src/host/advice/inputs.rs b/processor/src/host/advice/inputs.rs index c289222aae..c23dd59368 100644 --- a/processor/src/host/advice/inputs.rs +++ b/processor/src/host/advice/inputs.rs @@ -1,7 +1,6 @@ -use vm_core::crypto::hash::RpoDigest; - use super::{AdviceMap, Felt, InnerNodeInfo, InputError, MerkleStore}; -use crate::utils::collections::*; +use alloc::vec::Vec; +use vm_core::crypto::hash::RpoDigest; // ADVICE INPUTS // ================================================================================================ diff --git a/processor/src/host/advice/map.rs b/processor/src/host/advice/map.rs index 62a0b393ce..aeb3217334 100644 --- a/processor/src/host/advice/map.rs +++ b/processor/src/host/advice/map.rs @@ -1,6 +1,8 @@ use super::Felt; -use crate::utils::collections::*; -use vm_core::{crypto::hash::RpoDigest, utils::collections::btree_map::IntoIter}; +use alloc::collections::btree_map::IntoIter; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; +use vm_core::crypto::hash::RpoDigest; // ADVICE MAP // ================================================================================================ diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index a217462f0a..98723e5c0f 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -1,12 +1,12 @@ use super::HostResponse; use crate::{ExecutionError, Felt, InputError, ProcessState, Word}; +use alloc::vec::Vec; use core::borrow::Borrow; use vm_core::{ crypto::{ hash::RpoDigest, merkle::{InnerNodeInfo, MerklePath, MerkleStore, NodeIndex, StoreNode}, }, - utils::collections::*, AdviceInjector, SignatureKind, }; diff --git a/processor/src/host/advice/providers.rs b/processor/src/host/advice/providers.rs index 6453f7d1da..3b5b6e186e 100644 --- a/processor/src/host/advice/providers.rs +++ b/processor/src/host/advice/providers.rs @@ -1,8 +1,13 @@ +use crate::ProcessState; + use super::{ injectors, AdviceInputs, AdviceProvider, AdviceSource, ExecutionError, Felt, MerklePath, MerkleStore, NodeIndex, RpoDigest, StoreNode, Word, }; -use crate::{utils::collections::*, ProcessState}; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; +use vm_core::utils::collections::KvMap; +use vm_core::utils::collections::RecordingMap; use vm_core::SignatureKind; // TYPE ALIASES diff --git a/processor/src/host/debug.rs b/processor/src/host/debug.rs index 1cd9810e13..a72188768b 100644 --- a/processor/src/host/debug.rs +++ b/processor/src/host/debug.rs @@ -1,5 +1,8 @@ +use std::{print, println}; + use super::ProcessState; -use crate::{system::ContextId, utils::collections::*}; +use crate::system::ContextId; +use alloc::vec::Vec; use vm_core::{DebugOptions, Word}; // DEBUG HANDLER diff --git a/processor/src/host/mod.rs b/processor/src/host/mod.rs index c2817ff55b..b18c93af58 100644 --- a/processor/src/host/mod.rs +++ b/processor/src/host/mod.rs @@ -1,3 +1,5 @@ +use std::println; + use super::{ExecutionError, Felt, ProcessState}; use crate::MemAdviceProvider; use vm_core::{crypto::merkle::MerklePath, AdviceInjector, DebugOptions, Word}; diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 1db3b5d73d..d885436c61 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -1,9 +1,12 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] + +#[cfg(feature = "std")] +extern crate std; -#[cfg(not(feature = "std"))] #[macro_use] extern crate alloc; +use alloc::vec::Vec; use core::cell::RefCell; use miden_air::trace::{ @@ -20,7 +23,6 @@ use vm_core::{ code_blocks::{ Call, CodeBlock, Dyn, Join, Loop, OpBatch, Span, Split, OP_BATCH_SIZE, OP_GROUP_SIZE, }, - utils::collections::*, CodeBlockTable, Decorator, DecoratorIterator, FieldElement, StackTopState, }; diff --git a/processor/src/operations/comb_ops.rs b/processor/src/operations/comb_ops.rs index 4ef439bb61..19ce0ef593 100644 --- a/processor/src/operations/comb_ops.rs +++ b/processor/src/operations/comb_ops.rs @@ -172,11 +172,11 @@ where #[cfg(test)] mod tests { use crate::utils::collections::*; + use crate::{ContextId, Process, QuadFelt}; + use alloc::borrow::ToOwned; use test_utils::{build_test, rand::rand_array}; use vm_core::{Felt, FieldElement, Operation, StackInputs, ONE, ZERO}; - use crate::{ContextId, Process, QuadFelt}; - #[test] fn rcombine_main() { // --- build stack inputs ----------------------------------------------------------------- diff --git a/processor/src/range/aux_trace.rs b/processor/src/range/aux_trace.rs index cd93359f45..71b47f7bb6 100644 --- a/processor/src/range/aux_trace.rs +++ b/processor/src/range/aux_trace.rs @@ -1,5 +1,6 @@ use super::{uninit_vector, Felt, FieldElement, NUM_RAND_ROWS}; -use crate::utils::collections::*; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; use miden_air::trace::main_trace::MainTrace; use miden_air::trace::range::{M_COL_IDX, V_COL_IDX}; diff --git a/processor/src/range/mod.rs b/processor/src/range/mod.rs index 51f4a220cb..4512e96d52 100644 --- a/processor/src/range/mod.rs +++ b/processor/src/range/mod.rs @@ -1,5 +1,7 @@ use super::{trace::NUM_RAND_ROWS, Felt, FieldElement, RangeCheckTrace, ZERO}; -use crate::utils::{collections::*, uninit_vector}; +use crate::utils::uninit_vector; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; mod aux_trace; pub use aux_trace::AuxTraceBuilder; diff --git a/processor/src/stack/aux_trace.rs b/processor/src/stack/aux_trace.rs index 325a642227..2f42ca816a 100644 --- a/processor/src/stack/aux_trace.rs +++ b/processor/src/stack/aux_trace.rs @@ -1,5 +1,6 @@ use super::{Felt, FieldElement, OverflowTableRow}; -use crate::{trace::AuxColumnBuilder, utils::collections::*}; +use crate::trace::AuxColumnBuilder; +use alloc::vec::Vec; use miden_air::trace::main_trace::MainTrace; // AUXILIARY TRACE BUILDER diff --git a/processor/src/stack/mod.rs b/processor/src/stack/mod.rs index 492fd388ba..f5035c04cc 100644 --- a/processor/src/stack/mod.rs +++ b/processor/src/stack/mod.rs @@ -1,5 +1,5 @@ use super::{Felt, FieldElement, StackInputs, StackOutputs, ONE, STACK_TRACE_WIDTH, ZERO}; -use crate::utils::collections::*; +use alloc::vec::Vec; use core::cmp; use vm_core::{stack::STACK_TOP_SIZE, Word, WORD_SIZE}; diff --git a/processor/src/stack/overflow.rs b/processor/src/stack/overflow.rs index 96be4bf725..e4c2e3dc29 100644 --- a/processor/src/stack/overflow.rs +++ b/processor/src/stack/overflow.rs @@ -1,5 +1,6 @@ use super::{AuxTraceBuilder, Felt, FieldElement, ZERO}; -use crate::utils::collections::*; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; use vm_core::{utils::uninit_vector, StarkField}; // OVERFLOW TABLE diff --git a/processor/src/stack/trace.rs b/processor/src/stack/trace.rs index a4fbee690b..a3eec3abe2 100644 --- a/processor/src/stack/trace.rs +++ b/processor/src/stack/trace.rs @@ -1,7 +1,8 @@ use super::{ super::utils::get_trace_len, Felt, FieldElement, MAX_TOP_IDX, ONE, STACK_TRACE_WIDTH, ZERO, }; -use crate::utils::{collections::*, math::batch_inversion}; +use crate::utils::math::batch_inversion; +use alloc::vec::Vec; use miden_air::trace::stack::{H0_COL_IDX, NUM_STACK_HELPER_COLS, STACK_TOP_SIZE}; // STACK TRACE diff --git a/processor/src/system/mod.rs b/processor/src/system/mod.rs index 7c4cceb05c..3a4ec059c6 100644 --- a/processor/src/system/mod.rs +++ b/processor/src/system/mod.rs @@ -1,5 +1,5 @@ use super::{ExecutionError, Felt, FieldElement, SysTrace, Word, EMPTY_WORD, ONE, ZERO}; -use crate::utils::collections::*; +use alloc::vec::Vec; use core::fmt::{self, Display}; #[cfg(test)] diff --git a/processor/src/trace/mod.rs b/processor/src/trace/mod.rs index 0124e41d9e..38ee581400 100644 --- a/processor/src/trace/mod.rs +++ b/processor/src/trace/mod.rs @@ -5,7 +5,7 @@ use super::{ stack::AuxTraceBuilder as StackAuxTraceBuilder, ColMatrix, Digest, Felt, FieldElement, Host, Process, StackTopState, }; -use crate::utils::collections::*; +use alloc::vec::Vec; use miden_air::trace::{ decoder::{NUM_USER_OP_HELPERS, USER_OP_HELPERS_OFFSET}, main_trace::MainTrace, @@ -165,7 +165,7 @@ impl ExecutionTrace { let mut row = [ZERO; TRACE_WIDTH]; for i in 0..self.length() { self.main_trace.read_row_into(i, &mut row); - println!("{:?}", row.iter().map(|v| v.as_int()).collect::>()); + std::println!("{:?}", row.iter().map(|v| v.as_int()).collect::>()); } } diff --git a/processor/src/trace/utils.rs b/processor/src/trace/utils.rs index 848e63f8a5..e321aaa344 100644 --- a/processor/src/trace/utils.rs +++ b/processor/src/trace/utils.rs @@ -1,8 +1,6 @@ use super::{Felt, FieldElement, NUM_RAND_ROWS}; -use crate::{ - chiplets::Chiplets, - utils::{collections::*, uninit_vector}, -}; +use crate::{chiplets::Chiplets, utils::uninit_vector}; +use alloc::vec::Vec; use core::slice; use miden_air::trace::main_trace::MainTrace; diff --git a/processor/src/utils.rs b/processor/src/utils.rs index 7c316a1699..503b7e122b 100644 --- a/processor/src/utils.rs +++ b/processor/src/utils.rs @@ -1,5 +1,5 @@ use super::Felt; -use collections::*; +use alloc::vec::Vec; // RE-EXPORTS // ================================================================================================ diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index f7c7f28bc2..1ef1712fad 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -1,18 +1,18 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] + +#[cfg(feature = "std")] +extern crate std; -#[cfg(not(feature = "std"))] #[macro_use] extern crate alloc; +use alloc::{string::String, vec::Vec}; // IMPORTS // ================================================================================================ #[cfg(not(target_family = "wasm"))] use proptest::prelude::{Arbitrary, Strategy}; -use vm_core::{ - chiplets::hasher::apply_permutation, - utils::{collections::*, string::*}, -}; +use vm_core::chiplets::hasher::apply_permutation; // EXPORTS // ================================================================================================ diff --git a/test-utils/src/test_builders.rs b/test-utils/src/test_builders.rs index dbcec147ee..564594e5d3 100644 --- a/test-utils/src/test_builders.rs +++ b/test-utils/src/test_builders.rs @@ -107,7 +107,7 @@ macro_rules! build_test_by_mode { .with_merkle_store(store); $crate::Test { - source: String::from($source), + source: std::string::String::from($source), kernel: None, stack_inputs, advice_inputs, diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index f704758ec6..63a53a4b23 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -1,13 +1,16 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] + +#[cfg(feature = "std")] +extern crate std; + +#[macro_use] +extern crate alloc; use air::{HashFunction, ProcessorAir, ProvingOptions, PublicInputs}; use core::fmt; -use vm_core::{ - crypto::{ - hash::{Blake3_192, Blake3_256, Rpo256}, - random::{RpoRandomCoin, WinterRandomCoin}, - }, - utils::vec, +use vm_core::crypto::{ + hash::{Blake3_192, Blake3_256, Rpo256}, + random::{RpoRandomCoin, WinterRandomCoin}, }; use winter_verifier::verify as verify_proof; From 7078870cc1358408655fe1e8f59aa3ae11d7e3a9 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 10 May 2024 19:37:20 +0200 Subject: [PATCH 02/32] feat: migrate to new Winterfell release --- assembly/src/ast/instruction/mod.rs | 27 +++++++++++++++-------- assembly/src/ast/instruction/opcode.rs | 30 +++++++++++++++++--------- core/src/operations/mod.rs | 24 ++++++++++++++------- processor/src/host/mod.rs | 2 -- prover/src/lib.rs | 1 + 5 files changed, 55 insertions(+), 29 deletions(-) diff --git a/assembly/src/ast/instruction/mod.rs b/assembly/src/ast/instruction/mod.rs index e5bba8e65a..5d4055f575 100644 --- a/assembly/src/ast/instruction/mod.rs +++ b/assembly/src/ast/instruction/mod.rs @@ -60,7 +60,8 @@ pub enum Instruction { Gte, IsOdd, - // ----- ext2 operations ---------------------------------------------------------------------- + // ----- ext2 operations + // ---------------------------------------------------------------------- Ext2Add, Ext2Sub, Ext2Mul, @@ -68,7 +69,8 @@ pub enum Instruction { Ext2Neg, Ext2Inv, - // ----- u32 manipulation --------------------------------------------------------------------- + // ----- u32 manipulation + // --------------------------------------------------------------------- U32Test, U32TestW, U32Assert, @@ -125,7 +127,8 @@ pub enum Instruction { U32Min, U32Max, - // ----- stack manipulation ------------------------------------------------------------------- + // ----- stack manipulation + // ------------------------------------------------------------------- Drop, DropW, PadW, @@ -205,7 +208,8 @@ pub enum Instruction { CDrop, CDropW, - // ----- input / output operations ------------------------------------------------------------ + // ----- input / output operations + // ------------------------------------------------------------ Push(ImmFelt), PushU8(u8), PushU16(u16), @@ -243,7 +247,8 @@ pub enum Instruction { AdvInject(AdviceInjectorNode), - // ----- cryptographic operations ------------------------------------------------------------- + // ----- cryptographic operations + // ------------------------------------------------------------- Hash, HMerge, HPerm, @@ -252,11 +257,13 @@ pub enum Instruction { MTreeMerge, MTreeVerify, - // ----- STARK proof verification ------------------------------------------------------------- + // ----- STARK proof verification + // ------------------------------------------------------------- FriExt2Fold4, RCombBase, - // ----- exec / call -------------------------------------------------------------------------- + // ----- exec / call + // -------------------------------------------------------------------------- Exec(InvocationTarget), Call(InvocationTarget), SysCall(InvocationTarget), @@ -264,11 +271,13 @@ pub enum Instruction { DynCall, ProcRef(InvocationTarget), - // ----- debug decorators --------------------------------------------------------------------- + // ----- debug decorators + // --------------------------------------------------------------------- Breakpoint, Debug(DebugOptions), - // ----- event decorators --------------------------------------------------------------------- + // ----- event decorators + // --------------------------------------------------------------------- Emit(ImmU32), Trace(ImmU32), } diff --git a/assembly/src/ast/instruction/opcode.rs b/assembly/src/ast/instruction/opcode.rs index fb206ca72b..2c399eb7bb 100644 --- a/assembly/src/ast/instruction/opcode.rs +++ b/assembly/src/ast/instruction/opcode.rs @@ -51,7 +51,8 @@ pub enum OpCode { Gte, IsOdd, - // ----- ext2 operations ---------------------------------------------------------------------- + // ----- ext2 operations + // ---------------------------------------------------------------------- Ext2Add, Ext2Sub, Ext2Mul, @@ -59,7 +60,8 @@ pub enum OpCode { Ext2Neg, Ext2Inv, - // ----- u32 manipulation --------------------------------------------------------------------- + // ----- u32 manipulation + // --------------------------------------------------------------------- U32Test, U32TestW, U32Assert, @@ -116,7 +118,8 @@ pub enum OpCode { U32Min, U32Max, - // ----- stack manipulation ------------------------------------------------------------------- + // ----- stack manipulation + // ------------------------------------------------------------------- Drop, DropW, PadW, @@ -196,7 +199,8 @@ pub enum OpCode { CDrop, CDropW, - // ----- input / output operations ------------------------------------------------------------ + // ----- input / output operations + // ------------------------------------------------------------ PushU8, PushU16, PushU32, @@ -233,7 +237,8 @@ pub enum OpCode { AdvInject, - // ----- cryptographic operations ------------------------------------------------------------- + // ----- cryptographic operations + // ------------------------------------------------------------- Hash, HMerge, HPerm, @@ -242,11 +247,13 @@ pub enum OpCode { MTreeMerge, MTreeVerify, - // ----- STARK proof verification ------------------------------------------------------------- + // ----- STARK proof verification + // ------------------------------------------------------------- FriExt2Fold4, RCombBase, - // ----- exec / call -------------------------------------------------------------------------- + // ----- exec / call + // -------------------------------------------------------------------------- Exec, Call, SysCall, @@ -254,14 +261,17 @@ pub enum OpCode { DynCall, ProcRef, - // ----- debugging ---------------------------------------------------------------------------- + // ----- debugging + // ---------------------------------------------------------------------------- Debug, - // ----- event decorators --------------------------------------------------------------------- + // ----- event decorators + // --------------------------------------------------------------------- Emit, Trace, - // ----- control flow ------------------------------------------------------------------------- + // ----- control flow + // ------------------------------------------------------------------------- IfElse, Repeat, While, diff --git a/core/src/operations/mod.rs b/core/src/operations/mod.rs index 0bef6cbc01..080e2a976a 100644 --- a/core/src/operations/mod.rs +++ b/core/src/operations/mod.rs @@ -14,7 +14,8 @@ pub use decorators::{ /// These operations take exactly one cycle to execute. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Operation { - // ----- system operations -------------------------------------------------------------------- + // ----- system operations + // -------------------------------------------------------------------- /// Advances cycle counter, but does not change the state of user stack. Noop, @@ -43,7 +44,8 @@ pub enum Operation { /// instruction. Clk, - // ----- flow control operations -------------------------------------------------------------- + // ----- flow control operations + // -------------------------------------------------------------- /// Marks the beginning of a join block. Join, @@ -79,7 +81,8 @@ pub enum Operation { /// by the VM (HALT operation itself excepted). Halt, - // ----- field operations --------------------------------------------------------------------- + // ----- field operations + // --------------------------------------------------------------------- /// Pops two elements off the stack, adds them, and pushes the result back onto the stack. Add, @@ -139,13 +142,15 @@ pub enum Operation { /// shifted to the right by one bit. Expacc, - // ----- ext2 operations ---------------------------------------------------------------------- + // ----- ext2 operations + // ---------------------------------------------------------------------- /// Computes the product of two elements in the extension field of degree 2 and pushes the /// result back onto the stack as the third and fourth elements. Pushes 0 onto the stack as /// the first and second elements. Ext2Mul, - // ----- u32 operations ----------------------------------------------------------------------- + // ----- u32 operations + // ----------------------------------------------------------------------- /// Pops an element off the stack, splits it into upper and lower 32-bit values, and pushes /// these values back onto the stack. U32split, @@ -210,7 +215,8 @@ pub enum Operation { /// If either of the elements is greater than or equal to 2^32, execution fails. U32xor, - // ----- stack manipulation ------------------------------------------------------------------- + // ----- stack manipulation + // ------------------------------------------------------------------- /// Pushes 0 onto the stack. Pad, @@ -326,7 +332,8 @@ pub enum Operation { /// If the popped element is neither 0 nor 1, execution fails. CSwapW, - // ----- input / output ----------------------------------------------------------------------- + // ----- input / output + // ----------------------------------------------------------------------- /// Pushes the immediate value onto the stack. Push(Felt), @@ -380,7 +387,8 @@ pub enum Operation { /// - All other stack elements remain the same. Pipe, - // ----- cryptographic operations ------------------------------------------------------------- + // ----- cryptographic operations + // ------------------------------------------------------------- /// Performs a Rescue Prime Optimized permutation on the top 3 words of the operand stack, /// where the top 2 words are the rate (words C and B), the deepest word is the capacity (word /// A), and the digest output is the middle word E. diff --git a/processor/src/host/mod.rs b/processor/src/host/mod.rs index 13574f11e4..b54553dc55 100644 --- a/processor/src/host/mod.rs +++ b/processor/src/host/mod.rs @@ -1,5 +1,3 @@ -use std::println; - use super::{ExecutionError, Felt, ProcessState}; use crate::MemAdviceProvider; use vm_core::{crypto::merkle::MerklePath, AdviceInjector, DebugOptions, Word}; diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 38cc68f054..40fb3a2705 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -19,6 +19,7 @@ use processor::{ ExecutionTrace, }; use tracing::instrument; +use winter_air::AuxRandElements; use winter_prover::{ matrix::ColMatrix, ConstraintCompositionCoefficients, DefaultConstraintEvaluator, DefaultTraceLde, ProofOptions as WinterProofOptions, Prover, StarkDomain, TraceInfo, From df9b1108cdbfb92065fe7cf57a47dddd7ad6897b Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 22 Mar 2024 19:41:15 +0100 Subject: [PATCH 03/32] chore: fix no-std errors refactor: serialize usize with write_usize (#1266) --- processor/src/trace/mod.rs | 2 + processor/src/trace/virtual_bus/mod.rs | 3 + .../virtual_bus/multilinear/lagrange_ker.rs | 42 +++ .../src/trace/virtual_bus/multilinear/mod.rs | 320 ++++++++++++++++++ .../src/trace/virtual_bus/sum_check/domain.rs | 183 ++++++++++ .../src/trace/virtual_bus/sum_check/mod.rs | 143 ++++++++ .../trace/virtual_bus/sum_check/prover/mod.rs | 177 ++++++++++ .../virtual_bus/sum_check/verifier/mod.rs | 67 ++++ 8 files changed, 937 insertions(+) create mode 100644 processor/src/trace/virtual_bus/mod.rs create mode 100644 processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs create mode 100644 processor/src/trace/virtual_bus/multilinear/mod.rs create mode 100644 processor/src/trace/virtual_bus/sum_check/domain.rs create mode 100644 processor/src/trace/virtual_bus/sum_check/mod.rs create mode 100644 processor/src/trace/virtual_bus/sum_check/prover/mod.rs create mode 100644 processor/src/trace/virtual_bus/sum_check/verifier/mod.rs diff --git a/processor/src/trace/mod.rs b/processor/src/trace/mod.rs index 888d6064e1..545e45ce63 100644 --- a/processor/src/trace/mod.rs +++ b/processor/src/trace/mod.rs @@ -18,6 +18,8 @@ use winter_prover::{crypto::RandomCoin, EvaluationFrame, Trace, TraceInfo}; mod utils; pub use utils::{AuxColumnBuilder, ChipletsLengths, TraceFragment, TraceLenSummary}; +mod virtual_bus; + #[cfg(test)] mod tests; #[cfg(test)] diff --git a/processor/src/trace/virtual_bus/mod.rs b/processor/src/trace/virtual_bus/mod.rs new file mode 100644 index 0000000000..1d52fc46ef --- /dev/null +++ b/processor/src/trace/virtual_bus/mod.rs @@ -0,0 +1,3 @@ + +mod multilinear; +mod sum_check; \ No newline at end of file diff --git a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs new file mode 100644 index 0000000000..77ec33bc03 --- /dev/null +++ b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs @@ -0,0 +1,42 @@ + +use super::{FieldElement, MultiLinear}; + +pub struct LagrangeKernel { + r: Vec, +} + +impl LagrangeKernel { + pub fn new(r: Vec) -> Self { + let mut tmp = r.clone(); + tmp.reverse(); + LagrangeKernel { r: tmp } + } + + pub fn evaluate(&self, rho: &[E]) -> E { + assert_eq!(self.r.len(), rho.len()); + (0..rho.len()) + .map(|i| self.r[i] * rho[i] + (E::ONE - self.r[i]) * (E::ONE - rho[i])) + .fold(E::ONE, |acc, term| acc * term) + } + + pub fn evaluations(&self) -> Vec { + let nu = self.r.len(); + + let mut evals: Vec = vec![E::ONE; 1 << nu]; + let mut size = 1; + for j in 0..nu { + size *= 2; + for i in (0..size).rev().step_by(2) { + let scalar = evals[i / 2]; + evals[i] = scalar * self.r[j]; + evals[i - 1] = scalar - evals[i]; + } + } + evals + } + + pub fn new_ml(evaluation_point: Vec) -> MultiLinear { + let eq_evals = LagrangeKernel::new(evaluation_point.clone()).evaluations(); + MultiLinear::from_values(&eq_evals) + } +} \ No newline at end of file diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs new file mode 100644 index 0000000000..61ea3f9e32 --- /dev/null +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -0,0 +1,320 @@ +use core::ops::Index; +use miden_air::trace::TRACE_WIDTH; +use std::sync::Arc; // TODO: change to alloc +use vm_core::{Felt, FieldElement}; +use winter_prover::math::log2; + +mod lagrange_ker; +pub use lagrange_ker::LagrangeKernel; + +#[derive(Clone, Debug)] +pub struct MultiLinear { + num_variables: usize, + evaluations: Vec, +} + +impl MultiLinear { + pub fn new(values: Vec) -> Self { + assert!(values.len().is_power_of_two(), "A multi-linear polynomial should have a power of 2 number of evaluations over the Boolean hyper-cube"); + Self { + num_variables: log2(values.len()) as usize, + evaluations: values, + } + } + + pub fn from_values(values: &[E]) -> Self { + assert!(values.len().is_power_of_two(), "A multi-linear polynomial should have a power of 2 number of evaluations over the Boolean hyper-cube"); + Self { + num_variables: log2(values.len()) as usize, + evaluations: values.to_owned(), + } + } + + pub fn num_variables(&self) -> usize { + self.num_variables + } + + pub fn evaluations(&self) -> &[E] { + &self.evaluations + } + + pub fn num_evaluations(&self) -> usize { + self.evaluations.len() + } + + pub fn evaluate(&self, query: &[E]) -> E { + let tensored_query = tensorize(query); + inner_product(&self.evaluations, &tensored_query) + } + + pub fn bind(&self, round_challenge: E) -> Self { + let mut result = vec![E::ZERO; 1 << (self.num_variables() - 1)]; + for i in 0..(1 << (self.num_variables() - 1)) { + result[i] = self.evaluations[i << 1] + + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); + } + Self::from_values(&result) + } + + pub fn bind_assign(&mut self, round_challenge: E) { + let mut result = vec![E::ZERO; 1 << (self.num_variables() - 1)]; + for i in 0..(1 << (self.num_variables() - 1)) { + result[i] = self.evaluations[i << 1] + + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); + } + *self = Self::from_values(&result); + } + + pub fn extend(&mut self, other: &MultiLinear) { + let other_vec = other.evaluations.to_vec(); + assert_eq!(other_vec.len(), self.num_evaluations()); + self.evaluations.extend(other_vec); + self.num_variables += 1; + } +} + +impl Index for MultiLinear { + type Output = E; + + fn index(&self, index: usize) -> &E { + &(self.evaluations[index]) + } +} + +/// A multi-variate polynomial for composing individual multi-linear polynomials +pub trait CompositionPolynomial: Sync + Send { + /// The number of variables when interpreted as a multi-variate polynomial. + fn num_variables(&self) -> usize; + + /// Maximum degree in all variables. + fn max_degree(&self) -> usize; + + /// Given a query, of length equal the number of variables, evaluate [Self] at this query. + fn evaluate(&self, query: &[E]) -> E; +} + +#[derive(Clone, Copy, Debug)] +pub struct ProjectionComposition { + coordinate: usize, +} + +impl ProjectionComposition { + pub fn new(coordinate: usize) -> Self { + Self { coordinate } + } +} + +impl CompositionPolynomial for ProjectionComposition +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + 1 + } + + fn max_degree(&self) -> usize { + 1 + } + + fn evaluate(&self, query: &[E]) -> E { + query[self.coordinate] + } +} + +pub fn gkr_merge_composition_from_composition_polys + 'static>( + composition_polys: &Vec>>>, + sum_check_combining_randomness: E, + merge_randomness: Vec, + num_variables: usize, +) -> GkrCompositionMerge { + let eq_composer = Arc::new(ProjectionComposition::new(TRACE_WIDTH)); + let left_numerator = composition_polys[0].to_owned(); + let right_numerator = composition_polys[1].to_owned(); + let left_denominator = composition_polys[2].to_owned(); + let right_denominator = composition_polys[3].to_owned(); + GkrCompositionMerge::new( + num_variables, + sum_check_combining_randomness, + merge_randomness, + eq_composer, + right_numerator, + left_numerator, + right_denominator, + left_denominator, + ) +} + +#[derive(Clone)] +pub struct GkrComposition +where + E: FieldElement, +{ + pub num_variables_ml: usize, + pub combining_randomness: E, +} + +impl GkrComposition +where + E: FieldElement, +{ + pub fn new(num_variables_ml: usize, combining_randomness: E) -> Self { + Self { + num_variables_ml, + combining_randomness, + } + } +} + +impl CompositionPolynomial for GkrComposition +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + self.num_variables_ml // + TODO + } + + fn max_degree(&self) -> usize { + 3 + } + + fn evaluate(&self, query: &[E]) -> E { + let eval_left_numerator = query[0]; + let eval_right_numerator = query[1]; + let eval_left_denominator = query[2]; + let eval_right_denominator = query[3]; + let eq_eval = query[4]; + let res = eq_eval + * ((eval_left_numerator * eval_right_denominator + + eval_right_numerator * eval_left_denominator) + + eval_left_denominator * eval_right_denominator * self.combining_randomness); + res + } +} + +#[derive(Clone)] +pub struct GkrCompositionMerge +where + E: FieldElement, +{ + pub num_variables_ml: usize, + pub sum_check_combining_randomness: E, + pub tensored_merge_randomness: Vec, + pub degree: usize, + + pub eq_composer: Arc>, + pub right_numerator_composer: Vec>>, + pub left_numerator_composer: Vec>>, + pub right_denominator_composer: Vec>>, + pub left_denominator_composer: Vec>>, +} + +impl GkrCompositionMerge +where + E: FieldElement, +{ + pub fn new( + num_variables_ml: usize, + combining_randomness: E, + merge_randomness: Vec, + eq_composer: Arc>, + right_numerator_composer: Vec>>, + left_numerator_composer: Vec>>, + right_denominator_composer: Vec>>, + left_denominator_composer: Vec>>, + ) -> Self { + let tensored_merge_randomness = LagrangeKernel::new(merge_randomness.clone()).evaluations(); + + let max_left_num = left_numerator_composer.iter().map(|c| c.max_degree()).max().unwrap(); + let max_right_num = right_numerator_composer.iter().map(|c| c.max_degree()).max().unwrap(); + let max_left_denom = + left_denominator_composer.iter().map(|c| c.max_degree()).max().unwrap(); + let max_right_denom = + right_denominator_composer.iter().map(|c| c.max_degree()).max().unwrap(); + let degree = + 1 + core::cmp::max(max_left_num + max_right_denom, max_right_num + max_left_denom); + + Self { + num_variables_ml, + sum_check_combining_randomness: combining_randomness, + eq_composer, + degree, + right_numerator_composer, + left_numerator_composer, + right_denominator_composer, + left_denominator_composer, + tensored_merge_randomness, + } + } +} + +impl CompositionPolynomial for GkrCompositionMerge +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + self.num_variables_ml // + TODO + } + + fn max_degree(&self) -> usize { + self.degree + } + + fn evaluate(&self, query: &[E]) -> E { + let eval_right_numerator = + self.right_numerator_composer.iter().enumerate().fold(E::ZERO, |acc, (i, ml)| { + acc + ml.evaluate(query) * self.tensored_merge_randomness[i] + }); + let eval_left_numerator = + self.left_numerator_composer.iter().enumerate().fold(E::ZERO, |acc, (i, ml)| { + acc + ml.evaluate(query) * self.tensored_merge_randomness[i] + }); + let eval_right_denominator = self + .right_denominator_composer + .iter() + .enumerate() + .fold(E::ZERO, |acc, (i, ml)| { + acc + ml.evaluate(query) * self.tensored_merge_randomness[i] + }); + let eval_left_denominator = + self.left_denominator_composer.iter().enumerate().fold(E::ZERO, |acc, (i, ml)| { + acc + ml.evaluate(query) * self.tensored_merge_randomness[i] + }); + let eq_eval = self.eq_composer.evaluate(query); + let res = eq_eval + * ((eval_left_numerator * eval_right_denominator + + eval_right_numerator * eval_left_denominator) + + eval_left_denominator + * eval_right_denominator + * self.sum_check_combining_randomness); + res + } +} + +fn to_index>(index: &[E]) -> usize { + let res = index.iter().fold(E::ZERO, |acc, term| acc * E::ONE.double() + (*term)); + let res = res.base_element(0); + res.as_int() as usize +} + +fn inner_product(evaluations: &[E], tensored_query: &[E]) -> E { + assert_eq!(evaluations.len(), tensored_query.len()); + evaluations + .iter() + .zip(tensored_query.iter()) + .fold(E::ZERO, |acc, (x_i, y_i)| acc + *x_i * *y_i) +} + +pub fn tensorize(query: &[E]) -> Vec { + let nu = query.len(); + let n = 1 << nu; + + (0..n).map(|i| lagrange_basis_eval(query, i)).collect() +} + +fn lagrange_basis_eval(query: &[E], i: usize) -> E { + query + .iter() + .enumerate() + .map(|(j, x_j)| if i & (1 << j) == 0 { E::ONE - *x_j } else { *x_j }) + .fold(E::ONE, |acc, v| acc * v) +} diff --git a/processor/src/trace/virtual_bus/sum_check/domain.rs b/processor/src/trace/virtual_bus/sum_check/domain.rs new file mode 100644 index 0000000000..c3603b4e36 --- /dev/null +++ b/processor/src/trace/virtual_bus/sum_check/domain.rs @@ -0,0 +1,183 @@ +use vm_core::{FieldElement, StarkField}; +use winter_prover::math::batch_inversion; +pub struct EvaluationDomainCoeff +where + B: StarkField, + E: FieldElement, +{ + max_degree: usize, + vandermonde_matrix: Vec>, +} + +impl EvaluationDomainCoeff +where + B: StarkField, + E: FieldElement, +{ + pub fn new(max_degree: usize) -> Self { + let n = max_degree + 1; + let xs: Vec = (0..n).map(|x| E::from(x as u32)).collect(); + + let mut vandermonde_matrix: Vec> = Vec::with_capacity(n); + for i in 0..n { + let mut row = Vec::with_capacity(n); + let x = xs[i]; + row.push(E::ONE); + row.push(x); + for j in 2..n { + row.push(row[j - 1] * x); + } + row.push(E::ZERO); + vandermonde_matrix.push(row); + } + + Self { + max_degree, + vandermonde_matrix, + } + } + + fn interpolate_naive(evals: &[E]) -> Vec { + let n = evals.len(); + let xs: Vec = (0..n).map(|x| E::from(x as u32)).collect(); + + let mut vandermonde: Vec> = Vec::with_capacity(n); + for i in 0..n { + let mut row = Vec::with_capacity(n); + let x = xs[i]; + row.push(E::ONE); + row.push(x); + for j in 2..n { + row.push(row[j - 1] * x); + } + row.push(evals[i]); + vandermonde.push(row); + } + + gaussian_elimination(&mut vandermonde) + } + + fn interpolate(&mut self, evals: &[E]) -> Vec { + let mut vandermonde = self.vandermonde_matrix.clone(); + for row in 0..(self.max_degree + 1) { + vandermonde[row][self.max_degree] = evals[row]; + } + gaussian_elimination(&mut vandermonde) + } +} + +pub fn gaussian_elimination(matrix: &mut [Vec]) -> Vec { + let size = matrix.len(); + assert_eq!(size, matrix[0].len() - 1); + + for i in 0..size - 1 { + for j in i..size - 1 { + echelon(matrix, i, j); + } + } + + for i in (1..size).rev() { + eliminate(matrix, i); + } + + // Disable cargo clippy warnings about needless range loops. + // Checking the diagonal like this is simpler than any alternative. + //#[allow(clippy::needless_range_loop)] + for i in 0..size { + if matrix[i][i] == E::ZERO { + println!("Infinitely many solutions"); + } + } + + let mut result: Vec = vec![E::ZERO; size]; + for i in 0..size { + result[i] = matrix[i][size] / matrix[i][i]; + } + result +} + +fn echelon(matrix: &mut [Vec], i: usize, j: usize) { + let size = matrix.len(); + if matrix[i][i] == E::ZERO { + } else { + let factor = matrix[j + 1][i] / matrix[i][i]; + (i..size + 1).for_each(|k| { + let tmp = matrix[i][k]; + matrix[j + 1][k] -= factor * tmp; + }); + } +} + +fn eliminate(matrix: &mut [Vec], i: usize) { + let size = matrix.len(); + if matrix[i][i] == E::ZERO { + } else { + for j in (1..i + 1).rev() { + let factor = matrix[j - 1][i] / matrix[i][i]; + for k in (0..size + 1).rev() { + let tmp = matrix[i][k]; + matrix[j - 1][k] -= factor * tmp; + } + } + } +} + +pub struct EvaluationDomain +where + + E: FieldElement +{ + max_degree: usize, + evaluation_points: Vec, + barycentric_weights: Vec, +} + +impl EvaluationDomain +where + E: FieldElement +{ + pub fn new(max_degree: usize) -> Self { + let points: Vec = (0..=max_degree).map(|x| E::from(x as u32)).collect(); + let weights = barycentric_weights(&points); + + Self { + max_degree, + evaluation_points: points, + barycentric_weights: weights, + } + } + + pub fn evaluate(&self, evaluations: &[E], r: E) -> E { + evaluate_barycentric(&self.evaluation_points, evaluations, r, &self.barycentric_weights) + } +} + +pub fn evaluate_barycentric( + x_i: &[E], + y_i: &[E], + r: E, + barycentric_weights: &[E], +) -> E { + for (&x_i, &y_i) in x_i.iter().zip(y_i.iter()) { + if x_i == r { + return y_i; + } + } + + let l_x: E = x_i.iter().fold(E::ONE, |acc, &x_i| acc * (r - x_i)); + + let sum = (0..x_i.len()).fold(E::ZERO, |acc, i| { + let w_i = barycentric_weights[i]; + acc + (w_i / (r - x_i[i]) * y_i[i]) + }); + + l_x * sum +} + +pub fn barycentric_weights(points: &[E]) -> Vec { + let n = points.len(); + let tmp = (0..n) + .map(|i| (0..n).filter(|&j| j != i).fold(E::ONE, |acc, j| acc * (points[i] - points[j]))) + .collect::>(); + batch_inversion(&tmp) +} diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs new file mode 100644 index 0000000000..2d763a9d7e --- /dev/null +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -0,0 +1,143 @@ +use vm_core::FieldElement; + +use self::domain::EvaluationDomain; + +mod domain; +mod prover; +mod verifier; + +#[derive(Debug, Clone)] +pub struct RoundProof { + pub poly_evals: Vec, +} + +impl RoundProof { + pub fn to_evals(&self, claim: E) -> Vec { + let mut result = vec![]; + + // s(0) + s(1) = claim + let c0 = claim - self.poly_evals[0]; + + result.push(c0); + result.extend_from_slice(&self.poly_evals); + result + } + + // TODO: refactor once we move to coefficient form + pub(crate) fn evaluate(&self, domain: EvaluationDomain, claim: E, r: E) -> E { + let poly_evals = self.to_evals(claim); + domain.evaluate(&poly_evals, r) + } +} + +#[derive(Debug, Clone)] +pub struct Proof { + pub round_proofs: Vec>, +} + +#[derive(Debug)] +pub struct RoundClaim { + pub partial_eval_point: Vec, + pub current_claim: E, +} + +pub fn reduce_claim( + domain: &EvaluationDomain, + current_poly: &RoundProof, + current_round_claim: RoundClaim, + round_challenge: E, +) -> RoundClaim { + let poly_evals = current_poly.to_evals(current_round_claim.current_claim); + let new_claim = domain.evaluate(&poly_evals, round_challenge); + + let mut new_partial_eval_point = current_round_claim.partial_eval_point; + new_partial_eval_point.push(round_challenge); + + RoundClaim { + partial_eval_point: new_partial_eval_point, + current_claim: new_claim, + } +} + +#[derive(Clone)] +pub struct FinalEvaluationClaim { + pub evaluation_point: Vec, + pub claimed_evaluation: E, +} + +pub fn eval(p: &[E], x: E) -> E +where + E: FieldElement, +{ + // Horner evaluation + p.iter().rev().fold(E::ZERO, |acc, &coeff| acc * x + coeff) +} + +#[cfg(test)] +mod test { + use super::{ + domain::EvaluationDomain, eval, prover::SumCheckProver, verifier::SumCheckVerifier, + RoundClaim, + }; + use crate::trace::virtual_bus::multilinear::{MultiLinear, ProjectionComposition}; + use test_utils::{ + crypto::Rpo256, + rand::{rand_array, rand_value, rand_vector}, + }; + use vm_core::{crypto::random::RpoRandomCoin, Felt, Word, ZERO}; + + #[test] + fn test_evaluation_domain() { + let max_degree = 5; + let eval_domain = EvaluationDomain::::new(max_degree); + + let r = rand_value(); + let coefficients: [Felt; 6] = rand_array(); + + let evaluations: Vec = (0..=max_degree) + .into_iter() + .map(|x| eval(&coefficients, Felt::from(x as u8))) + .collect(); + + assert_eq!(coefficients.len(), evaluations.len()); + + let result = eval_domain.evaluate(&evaluations, r); + let expected = eval(&coefficients, r); + + assert_eq!(result, expected); + } + + #[test] + fn test_sum_check() { + let num_variables = 10; + let values = rand_vector(1 << num_variables); + let claim = values.iter().fold(ZERO, |acc, &x| x + acc); + let ml = MultiLinear::new(values.to_vec()); + let mut mls = vec![ml.clone()]; + let virtual_poly = ProjectionComposition::new(0); + let prover = + SumCheckProver::::new( + virtual_poly, + num_variables, + ); + + let mut coin = RpoRandomCoin::new(Word::default()); + let (final_claim, round_proofs) = prover.prove(claim, &mut mls, &mut coin); + + let verifier = + SumCheckVerifier::::new( + virtual_poly, + num_variables, + ); + + let mut coin = RpoRandomCoin::new(Word::default()); + verifier.verify(claim, round_proofs, &mut coin); + + let RoundClaim { + partial_eval_point, + current_claim, + } = final_claim; + + assert_eq!(ml.evaluate(&partial_eval_point), current_claim); + } +} diff --git a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs new file mode 100644 index 0000000000..74fb650076 --- /dev/null +++ b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs @@ -0,0 +1,177 @@ +use super::{domain::EvaluationDomain, reduce_claim, Proof, RoundClaim, RoundProof}; +use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinear}; +use core::marker::PhantomData; +use vm_core::{FieldElement, StarkField}; +use winter_prover::crypto::{ElementHasher, RandomCoin}; + +/// A struct that contains relevant information for the execution of the multivariate sum-check +/// protocol. +/// The sum-check protocol is an interactive protocol (IP) for proving the following relation: +/// +/// v = \sum_{(x_0,\cdots, x_{\nu - 1}) \in \{0 , 1\}^{2^{\nu}}} g(f_0((x_0,\cdots, x_{\nu - 1})), \cdots , f_c((x_0,\cdots, x_{\nu - 1}))) +/// +/// where: +/// +/// 1. v ∈ 𝔽 where 𝔽 is a finite field. +/// 2. f_i are multi-linear polynomials i.e., polynomials in 𝔽[X_i, \cdots ,X_{\nu - 1}] with degree at most one in each variable. +/// 3. g is a multivariate polynomial with degree at most d in each variable. +/// +/// The verifier is given commitments to each `f_i` in addition to the claimed sum `v`. The Prover then engages in an IP +/// to convince the Verifier that the above relation holds for the given `f_i` and `v`. +/// More precisely: +/// +/// 0. Denote by w(x_0,\cdots, x_{\nu - 1}) := g(f_0((x_0,\cdots, x_{\nu - 1})), \cdots , f_c((x_0,\cdots, x_{\nu - 1}))). +/// +/// 1. In the first round, the Prover sends the polynomial defined by: +/// s_0(X_0) := \sum_{(x_{1},\cdots, x_{\nu - 1}) w(X_0, x_{1}, \cdots, x_{\nu - 1}) +/// +/// 2. The Verifier then checks that s_0(0) + s_0(1) = v rejecting if not. +/// +/// 3. The Verifier samples a random challenge `r_0 ∈ 𝔽` and sends it to the Prover. +/// +/// 4. For each i in 1...(\nu - 1): +/// a. The Prover sends the univariate polynomial defined by: +/// +/// s_i(X_i) := \sum_{(x_{i + 1},\cdots, x_{\nu - 1}) w(r_0,\cdots, r_{i - 1}, X_i, x_{i + 1}, \cdots, x_{\nu - 1}) +/// b. The Verifier checks that s_{i - 1}(r_{i - 1}) = s_{i}(0) + s_{i}(1) rejecting if not. +/// +/// c. The Verifier samples a random challenge `r_i ∈ 𝔽` and sends it to the Prover. +/// +/// 5. The Verifier now queries each of the oracles behind the commitments i.e., `f_i` at `(r_0, \cdots , r_{\nu - 1})` to get +/// u_i = f_i(r_0, \cdots , r_{\nu - 1}). The Verifier then accepts if and only if: +/// +/// s_{\nu - 1}(r_{\nu - 1}) = g(u_0, \cdots , u_{\nu - 1}) +/// +/// A few remarks: +/// +/// 1. The degree bound on `g` implies that each of the `s_i` polynomials is a univariate polynomial of degree at most `d`. +/// Thus, the Prover in each round sends `d + 1` values, either the coefficients or the evaluations of `s_i`. +/// +/// 2. The Prover has each `f_i` in its evaluation form over the hyper-cube \{0 , 1\}^{2^{\nu}}. +/// +/// 3. An optimization is for the Prover to not send `s_i(0)` as it can be recoverd from the current reduced claim s_{i - 1}(r_{i - 1}) +/// using the relation s_{i}(0) = s_{i}(1) - s_{i - 1}(r_{i - 1}). This also means that the Verifier can skip point 4.b. +pub struct SumCheckProver +where + B: StarkField, + E: FieldElement, + C: RandomCoin, + H: ElementHasher, +{ + pub virtual_poly: P, + pub eval_domain: EvaluationDomain, + num_rounds: usize, + _challenger: PhantomData, + _hasher: PhantomData, +} + +impl SumCheckProver +where + B: StarkField, + E: FieldElement, + P: CompositionPolynomial, + C: RandomCoin, + H: ElementHasher, +{ + pub fn new(virtual_poly: P, num_rounds: usize) -> Self { + let max_degree = virtual_poly.max_degree(); + let eval_domain = EvaluationDomain::new(max_degree); + + Self { + virtual_poly, + eval_domain, + num_rounds, + _challenger: PhantomData, + _hasher: PhantomData, + } + } + + pub fn prove( + &self, + claim: E, + mls: &mut Vec>, + coin: &mut C, + ) -> (RoundClaim, Proof) { + // Setup first round + let mut prev_claim = RoundClaim { + partial_eval_point: vec![], + current_claim: claim, + }; + let mut round_proofs = vec![]; + + let mut output = sumcheck_round(&self.virtual_poly, mls); + round_proofs.push(output); + + for i in 1..self.num_rounds { + let round_challenge = coin.draw().unwrap(); + let new_claim = + reduce_claim(&self.eval_domain, &round_proofs[i - 1], prev_claim, round_challenge); + mls.into_iter().for_each(|ml| ml.bind_assign(round_challenge)); + + output = sumcheck_round(&self.virtual_poly, mls); + round_proofs.push(output); + + prev_claim = new_claim; + + let poly_evals = &round_proofs[i].poly_evals; + coin.reseed(H::hash_elements(poly_evals)); + } + let round_challenge = coin.draw().unwrap(); + mls.into_iter().for_each(|ml| ml.bind_assign(round_challenge)); + + let final_round_claim = reduce_claim( + &self.eval_domain, + &round_proofs[self.num_rounds - 1], + prev_claim, + round_challenge, + ); + let round_proofs = Proof { round_proofs }; + + (final_round_claim, round_proofs) + } +} + +fn sumcheck_round( + composer: &dyn CompositionPolynomial, + mls: &mut Vec>, +) -> RoundProof { + let polynomial = composer; + let num_ml = mls.len(); + let num_vars = mls[0].num_variables(); + let num_rounds = num_vars - 1; + + let mut evals_zero = vec![E::ZERO; num_ml]; + let mut evals_one = vec![E::ZERO; num_ml]; + let mut deltas = vec![E::ZERO; num_ml]; + let mut evals_x = vec![E::ZERO; num_ml]; + + let total_evals = (0..1 << num_rounds).into_iter().map(|i| { + for (j, ml) in mls.iter().enumerate() { + evals_zero[j] = ml.evaluations()[(i << 1) as usize]; + evals_one[j] = ml.evaluations()[(i << 1) + 1]; + } + let mut total_evals = vec![E::ZERO; polynomial.max_degree()]; + total_evals[0] = polynomial.evaluate(&evals_one); + evals_zero + .iter() + .zip(evals_one.iter().zip(deltas.iter_mut().zip(evals_x.iter_mut()))) + .for_each(|(a0, (a1, (delta, evx)))| { + *delta = *a1 - *a0; + *evx = *a1; + }); + total_evals.iter_mut().skip(1).for_each(|e| { + evals_x.iter_mut().zip(deltas.iter()).for_each(|(evx, delta)| { + *evx += *delta; + }); + *e = polynomial.evaluate(&evals_x); + }); + total_evals + }); + let evaluations = total_evals.fold(vec![E::ZERO; polynomial.max_degree()], |mut acc, evals| { + acc.iter_mut().zip(evals.iter()).for_each(|(a, ev)| *a += *ev); + acc + }); + RoundProof { + poly_evals: evaluations, + } +} diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs new file mode 100644 index 0000000000..b8c2f07418 --- /dev/null +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -0,0 +1,67 @@ +use crate::trace::virtual_bus::multilinear::CompositionPolynomial; + +use super::{domain::EvaluationDomain, FinalEvaluationClaim, Proof}; +use core::marker::PhantomData; +use vm_core::{FieldElement, StarkField}; +use winter_prover::crypto::{ElementHasher, RandomCoin}; + +pub struct SumCheckVerifier +where + B: StarkField, + E: FieldElement, + C: RandomCoin, + P: CompositionPolynomial, + H: ElementHasher, +{ + pub virtual_poly: P, + pub eval_domain: EvaluationDomain, + pub num_rounds: usize, + _channel: PhantomData, +} + +impl SumCheckVerifier +where + B: StarkField, + E: FieldElement, + C: RandomCoin, + P: CompositionPolynomial, + H: ElementHasher, +{ + pub fn new(virtual_poly: P, num_rounds: usize) -> Self { + let max_degree = virtual_poly.max_degree(); + let eval_domain = EvaluationDomain::new(max_degree); + + Self { + virtual_poly, + eval_domain, + num_rounds, + _channel: PhantomData, + } + } + + pub fn verify( + &self, + claim: E, + round_proofs: Proof, + coin: &mut C, + ) -> FinalEvaluationClaim { + let mut claimed_evaluation = claim; + let mut evaluation_point = vec![]; + for proof in round_proofs.round_proofs { + let partial_evals = proof.poly_evals.clone(); + coin.reseed(H::hash_elements(&partial_evals)); + let evals = proof.to_evals(claimed_evaluation); + + let r = coin.draw().unwrap(); + let reduced_evaluation = self.eval_domain.evaluate(&evals, r); + + claimed_evaluation = reduced_evaluation; + evaluation_point.push(r); + } + + FinalEvaluationClaim { + evaluation_point, + claimed_evaluation, + } + } +} From 699cb11bf2946620a928c80e3db479b46b29c76c Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Thu, 28 Mar 2024 15:59:54 +0100 Subject: [PATCH 04/32] feat: sum-check protocol --- processor/Cargo.toml | 3 +- processor/src/trace/mod.rs | 1 + processor/src/trace/virtual_bus/mod.rs | 4 +- .../trace/virtual_bus/multilinear/error.rs | 5 + .../virtual_bus/multilinear/lagrange_ker.rs | 77 +++-- .../src/trace/virtual_bus/multilinear/mod.rs | 306 ++++-------------- .../src/trace/virtual_bus/sum_check/domain.rs | 163 ++-------- .../src/trace/virtual_bus/sum_check/mod.rs | 240 ++++++++++---- .../virtual_bus/sum_check/prover/error.rs | 21 ++ .../trace/virtual_bus/sum_check/prover/mod.rs | 228 +++++++++---- .../virtual_bus/sum_check/verifier/error.rs | 9 + .../virtual_bus/sum_check/verifier/mod.rs | 124 +++++-- 12 files changed, 639 insertions(+), 542 deletions(-) create mode 100644 processor/src/trace/virtual_bus/multilinear/error.rs create mode 100644 processor/src/trace/virtual_bus/sum_check/prover/error.rs create mode 100644 processor/src/trace/virtual_bus/sum_check/verifier/error.rs diff --git a/processor/Cargo.toml b/processor/Cargo.toml index 6d022e1683..5ff16e7c07 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -26,8 +26,9 @@ std = ["vm-core/std", "winter-prover/std"] tracing = { version = "0.1", default-features = false, features = [ "attributes", ] } -vm-core = { package = "miden-core", path = "../core", version = "0.8", default-features = false } +thiserror = { version = "1.0", default-features = false } miden-air = { package = "miden-air", path = "../air", version = "0.8", default-features = false } +vm-core = { package = "miden-core", path = "../core", version = "0.8", default-features = false } winter-prover = { package = "winter-prover", version = "0.9", default-features = false } [dev-dependencies] diff --git a/processor/src/trace/mod.rs b/processor/src/trace/mod.rs index 545e45ce63..51c176f844 100644 --- a/processor/src/trace/mod.rs +++ b/processor/src/trace/mod.rs @@ -19,6 +19,7 @@ mod utils; pub use utils::{AuxColumnBuilder, ChipletsLengths, TraceFragment, TraceLenSummary}; mod virtual_bus; +pub use virtual_bus::{SumCheckProver, SumCheckVerifier}; #[cfg(test)] mod tests; diff --git a/processor/src/trace/virtual_bus/mod.rs b/processor/src/trace/virtual_bus/mod.rs index 1d52fc46ef..c95e0f4ba8 100644 --- a/processor/src/trace/virtual_bus/mod.rs +++ b/processor/src/trace/virtual_bus/mod.rs @@ -1,3 +1,3 @@ - mod multilinear; -mod sum_check; \ No newline at end of file +mod sum_check; +pub use sum_check::{SumCheckProver, SumCheckVerifier}; diff --git a/processor/src/trace/virtual_bus/multilinear/error.rs b/processor/src/trace/virtual_bus/multilinear/error.rs new file mode 100644 index 0000000000..cd4f3c5789 --- /dev/null +++ b/processor/src/trace/virtual_bus/multilinear/error.rs @@ -0,0 +1,5 @@ +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("A multi-linear polynomial should have a power of 2 number of evaluations over the Boolean hyper-cube")] + EvaluationsNotPowerOfTwo, +} diff --git a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs index 77ec33bc03..4fe7e048d8 100644 --- a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs +++ b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs @@ -1,42 +1,69 @@ +use super::{tensorize, FieldElement, MultiLinear}; -use super::{FieldElement, MultiLinear}; - -pub struct LagrangeKernel { +/// The EQ (equality) function is the binary function defined by +/// +/// EQ: {0 , 1}^ν ⛌ {0 , 1}^ν ⇾ {0 , 1} +/// ((x_0, ..., x_{ν - 1}), (y_0, ..., y_{ν - 1})) ↦ \prod_{i = 0}^{ν - 1} (x_i * y_i + (1 - x_i) * (1 - y_i)) +/// +/// Taking It's multi-linear extension EQ^{~}, we can define a basis for the set of multi-linear +/// polynomials in ν variables by +/// {EQ^{~}(., (y_0, ..., y_{ν - 1})): (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν} +/// where each basis function is a function of its first argument. This is called the Lagrange or +/// evaluation basis with evaluation set {0 , 1}^ν. +/// +/// Given a function (f: {0 , 1}^ν ⇾ 𝔽), its multi-linear extension (i.e., the unique +/// mult-linear polynomial extending f to (f^{~}: 𝔽^ν ⇾ 𝔽) and agrees with it on {0 , 1}^ν) is +/// defined as the summation of the evaluations of f against the Lagrange basis. +/// More specifically, given (r_0, ..., r_{ν - 1}) ∈ 𝔽^ν, then: +/// +/// f^{~}(r_0, ..., r_{ν - 1}) = \sum_{(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν} +/// f(y_0, ..., y_{ν - 1}) EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) +/// +/// We call the Lagrange kernel the evaluation of the EQ^{~} function at +/// ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) for all (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν for +/// a fixed (r_0, ..., r_{ν - 1}) ∈ 𝔽^ν. +/// +/// [EqFunction] represents EQ^{~} the mult-linear extension of +/// +/// ((y_0, ..., y_{ν - 1}) ↦ EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))) +/// +/// and contains a method to generate the Lagrange kernel for defining evaluations of multi-linear +/// extensions of arbitrary functions (f: {0 , 1}^ν ⇾ 𝔽) at a given point (r_0, ..., r_{ν - 1}) +/// as well as a method to evaluate EQ^{~}((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1}))) for +/// (t_0, ..., t_{ν - 1}) ∈ 𝔽^ν. +pub struct EqFunction { r: Vec, } -impl LagrangeKernel { +impl EqFunction { + /// Creates a new [EqFunction]. pub fn new(r: Vec) -> Self { let mut tmp = r.clone(); tmp.reverse(); - LagrangeKernel { r: tmp } + EqFunction { r: tmp } } - pub fn evaluate(&self, rho: &[E]) -> E { - assert_eq!(self.r.len(), rho.len()); - (0..rho.len()) - .map(|i| self.r[i] * rho[i] + (E::ONE - self.r[i]) * (E::ONE - rho[i])) + /// Computes EQ((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1}))). + pub fn evaluate(&self, t: &[E]) -> E { + assert_eq!(self.r.len(), t.len()); + + (0..self.r.len()) + .map(|i| self.r[i] * t[i] + (E::ONE - self.r[i]) * (E::ONE - t[i])) .fold(E::ONE, |acc, term| acc * term) } + /// Computes EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) for all + /// (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν i.e., the Lagrange kernel at r = (r_0, ..., r_{ν - 1}). pub fn evaluations(&self) -> Vec { - let nu = self.r.len(); - - let mut evals: Vec = vec![E::ONE; 1 << nu]; - let mut size = 1; - for j in 0..nu { - size *= 2; - for i in (0..size).rev().step_by(2) { - let scalar = evals[i / 2]; - evals[i] = scalar * self.r[j]; - evals[i - 1] = scalar - evals[i]; - } - } - evals + tensorize(&self.r) } - pub fn new_ml(evaluation_point: Vec) -> MultiLinear { - let eq_evals = LagrangeKernel::new(evaluation_point.clone()).evaluations(); + /// Returns the evaluations of + /// ((y_0, ..., y_{ν - 1}) ↦ EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))) + /// over {0 , 1}^ν. + pub fn ml_at(evaluation_point: Vec) -> MultiLinear { + let eq_evals = EqFunction::new(evaluation_point.clone()).evaluations(); MultiLinear::from_values(&eq_evals) + .expect("should not fail because evaluations is a power of two") } -} \ No newline at end of file +} diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index 61ea3f9e32..5ad5c10ab1 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -1,12 +1,17 @@ use core::ops::Index; -use miden_air::trace::TRACE_WIDTH; -use std::sync::Arc; // TODO: change to alloc -use vm_core::{Felt, FieldElement}; +use vm_core::FieldElement; use winter_prover::math::log2; +#[allow(dead_code)] mod lagrange_ker; -pub use lagrange_ker::LagrangeKernel; +mod error; +use self::error::Error; + +// MULTI-LINEAR POLYNOMIAL +// ================================================================================================ + +/// The evaluations of a mult-linear polynomial over the boolean hyper-cube {0 , 1}^ν. #[derive(Clone, Debug)] pub struct MultiLinear { num_variables: usize, @@ -14,62 +19,77 @@ pub struct MultiLinear { } impl MultiLinear { - pub fn new(values: Vec) -> Self { - assert!(values.len().is_power_of_two(), "A multi-linear polynomial should have a power of 2 number of evaluations over the Boolean hyper-cube"); - Self { + /// Constructs a [MultiLinear] from an array of values in the field. + pub fn new(values: Vec) -> Result { + if !values.len().is_power_of_two() { + return Err(Error::EvaluationsNotPowerOfTwo); + } + Ok(Self { num_variables: log2(values.len()) as usize, evaluations: values, - } + }) } - pub fn from_values(values: &[E]) -> Self { - assert!(values.len().is_power_of_two(), "A multi-linear polynomial should have a power of 2 number of evaluations over the Boolean hyper-cube"); - Self { + /// Constructs a [MultiLinear] from a slice of values in the field. + pub fn from_values(values: &[E]) -> Result { + if !values.len().is_power_of_two() { + return Err(Error::EvaluationsNotPowerOfTwo); + } + Ok(Self { num_variables: log2(values.len()) as usize, evaluations: values.to_owned(), - } + }) } + /// Returns the number of the multi-linear polynomial. pub fn num_variables(&self) -> usize { self.num_variables } + /// Returns the evaluations over the boolean hyper-cube. pub fn evaluations(&self) -> &[E] { &self.evaluations } + /// Returns the number of evaluations. This is equal to the size of the boolean hyper-cube. pub fn num_evaluations(&self) -> usize { self.evaluations.len() } + /// Evaluate the multi-linear at some query (r_0, ..., r_{ν - 1}) ∈ 𝔽^ν. + /// + /// It first computes the evaluations of the Lagrange basis polynomials over the interpolating + /// set {0 , 1}^ν at (r_0, ..., r_{ν - 1}) i.e., the Lagrange kernel at (r_0, ..., r_{ν - 1}). + /// The evaluation then is the inner product, indexed by {0 , 1}^ν, of the vector of + /// evaluations times the Lagrange kernel. pub fn evaluate(&self, query: &[E]) -> E { let tensored_query = tensorize(query); inner_product(&self.evaluations, &tensored_query) } + /// Computes f(r_0, y_1, ..., y_{ν - 1}) using the linear interpolation formula + /// (1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1}). The resulting + /// returned multi-linear is defined over a domain of half the size. pub fn bind(&self, round_challenge: E) -> Self { let mut result = vec![E::ZERO; 1 << (self.num_variables() - 1)]; - for i in 0..(1 << (self.num_variables() - 1)) { - result[i] = self.evaluations[i << 1] + for (i, res) in result.iter_mut().enumerate() { + *res = self.evaluations[i << 1] + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); } - Self::from_values(&result) + Self::from_values(&result).expect("should not fail given that it is a multi-linear") } + /// Computes f(r_0, y_1, ..., y_{ν - 1}) using the linear interpolation formula + /// (1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1}) and assigns + /// the resulting multi-linear, defined over a domain of half the size, to `self`. pub fn bind_assign(&mut self, round_challenge: E) { let mut result = vec![E::ZERO; 1 << (self.num_variables() - 1)]; - for i in 0..(1 << (self.num_variables() - 1)) { - result[i] = self.evaluations[i << 1] + for (i, res) in result.iter_mut().enumerate() { + *res = self.evaluations[i << 1] + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); } - *self = Self::from_values(&result); - } - - pub fn extend(&mut self, other: &MultiLinear) { - let other_vec = other.evaluations.to_vec(); - assert_eq!(other_vec.len(), self.num_evaluations()); - self.evaluations.extend(other_vec); - self.num_variables += 1; + *self = + Self::from_values(&result).expect("should not fail given that it is a multi-linear"); } } @@ -81,7 +101,10 @@ impl Index for MultiLinear { } } -/// A multi-variate polynomial for composing individual multi-linear polynomials +// COMPOSITION POLYNOMIAL +// ================================================================================================ + +/// A multi-variate polynomial for composing individual multi-linear polynomials. pub trait CompositionPolynomial: Sync + Send { /// The number of variables when interpreted as a multi-variate polynomial. fn num_variables(&self) -> usize; @@ -89,213 +112,16 @@ pub trait CompositionPolynomial: Sync + Send { /// Maximum degree in all variables. fn max_degree(&self) -> usize; - /// Given a query, of length equal the number of variables, evaluate [Self] at this query. + /// Given a query, of length equal the number of variables, evaluates [Self] at this query. fn evaluate(&self, query: &[E]) -> E; } -#[derive(Clone, Copy, Debug)] -pub struct ProjectionComposition { - coordinate: usize, -} - -impl ProjectionComposition { - pub fn new(coordinate: usize) -> Self { - Self { coordinate } - } -} - -impl CompositionPolynomial for ProjectionComposition -where - E: FieldElement, -{ - fn num_variables(&self) -> usize { - 1 - } - - fn max_degree(&self) -> usize { - 1 - } - - fn evaluate(&self, query: &[E]) -> E { - query[self.coordinate] - } -} - -pub fn gkr_merge_composition_from_composition_polys + 'static>( - composition_polys: &Vec>>>, - sum_check_combining_randomness: E, - merge_randomness: Vec, - num_variables: usize, -) -> GkrCompositionMerge { - let eq_composer = Arc::new(ProjectionComposition::new(TRACE_WIDTH)); - let left_numerator = composition_polys[0].to_owned(); - let right_numerator = composition_polys[1].to_owned(); - let left_denominator = composition_polys[2].to_owned(); - let right_denominator = composition_polys[3].to_owned(); - GkrCompositionMerge::new( - num_variables, - sum_check_combining_randomness, - merge_randomness, - eq_composer, - right_numerator, - left_numerator, - right_denominator, - left_denominator, - ) -} - -#[derive(Clone)] -pub struct GkrComposition -where - E: FieldElement, -{ - pub num_variables_ml: usize, - pub combining_randomness: E, -} - -impl GkrComposition -where - E: FieldElement, -{ - pub fn new(num_variables_ml: usize, combining_randomness: E) -> Self { - Self { - num_variables_ml, - combining_randomness, - } - } -} - -impl CompositionPolynomial for GkrComposition -where - E: FieldElement, -{ - fn num_variables(&self) -> usize { - self.num_variables_ml // + TODO - } - - fn max_degree(&self) -> usize { - 3 - } - - fn evaluate(&self, query: &[E]) -> E { - let eval_left_numerator = query[0]; - let eval_right_numerator = query[1]; - let eval_left_denominator = query[2]; - let eval_right_denominator = query[3]; - let eq_eval = query[4]; - let res = eq_eval - * ((eval_left_numerator * eval_right_denominator - + eval_right_numerator * eval_left_denominator) - + eval_left_denominator * eval_right_denominator * self.combining_randomness); - res - } -} - -#[derive(Clone)] -pub struct GkrCompositionMerge -where - E: FieldElement, -{ - pub num_variables_ml: usize, - pub sum_check_combining_randomness: E, - pub tensored_merge_randomness: Vec, - pub degree: usize, - - pub eq_composer: Arc>, - pub right_numerator_composer: Vec>>, - pub left_numerator_composer: Vec>>, - pub right_denominator_composer: Vec>>, - pub left_denominator_composer: Vec>>, -} - -impl GkrCompositionMerge -where - E: FieldElement, -{ - pub fn new( - num_variables_ml: usize, - combining_randomness: E, - merge_randomness: Vec, - eq_composer: Arc>, - right_numerator_composer: Vec>>, - left_numerator_composer: Vec>>, - right_denominator_composer: Vec>>, - left_denominator_composer: Vec>>, - ) -> Self { - let tensored_merge_randomness = LagrangeKernel::new(merge_randomness.clone()).evaluations(); - - let max_left_num = left_numerator_composer.iter().map(|c| c.max_degree()).max().unwrap(); - let max_right_num = right_numerator_composer.iter().map(|c| c.max_degree()).max().unwrap(); - let max_left_denom = - left_denominator_composer.iter().map(|c| c.max_degree()).max().unwrap(); - let max_right_denom = - right_denominator_composer.iter().map(|c| c.max_degree()).max().unwrap(); - let degree = - 1 + core::cmp::max(max_left_num + max_right_denom, max_right_num + max_left_denom); - - Self { - num_variables_ml, - sum_check_combining_randomness: combining_randomness, - eq_composer, - degree, - right_numerator_composer, - left_numerator_composer, - right_denominator_composer, - left_denominator_composer, - tensored_merge_randomness, - } - } -} - -impl CompositionPolynomial for GkrCompositionMerge -where - E: FieldElement, -{ - fn num_variables(&self) -> usize { - self.num_variables_ml // + TODO - } - - fn max_degree(&self) -> usize { - self.degree - } - - fn evaluate(&self, query: &[E]) -> E { - let eval_right_numerator = - self.right_numerator_composer.iter().enumerate().fold(E::ZERO, |acc, (i, ml)| { - acc + ml.evaluate(query) * self.tensored_merge_randomness[i] - }); - let eval_left_numerator = - self.left_numerator_composer.iter().enumerate().fold(E::ZERO, |acc, (i, ml)| { - acc + ml.evaluate(query) * self.tensored_merge_randomness[i] - }); - let eval_right_denominator = self - .right_denominator_composer - .iter() - .enumerate() - .fold(E::ZERO, |acc, (i, ml)| { - acc + ml.evaluate(query) * self.tensored_merge_randomness[i] - }); - let eval_left_denominator = - self.left_denominator_composer.iter().enumerate().fold(E::ZERO, |acc, (i, ml)| { - acc + ml.evaluate(query) * self.tensored_merge_randomness[i] - }); - let eq_eval = self.eq_composer.evaluate(query); - let res = eq_eval - * ((eval_left_numerator * eval_right_denominator - + eval_right_numerator * eval_left_denominator) - + eval_left_denominator - * eval_right_denominator - * self.sum_check_combining_randomness); - res - } -} - -fn to_index>(index: &[E]) -> usize { - let res = index.iter().fold(E::ZERO, |acc, term| acc * E::ONE.double() + (*term)); - let res = res.base_element(0); - res.as_int() as usize -} +// COMPOSITION POLYNOMIAL +// ================================================================================================ +/// Computes the inner product of two vectors of the same length. +/// +/// Panics if the vectors have different lengths. fn inner_product(evaluations: &[E], tensored_query: &[E]) -> E { assert_eq!(evaluations.len(), tensored_query.len()); evaluations @@ -304,17 +130,21 @@ fn inner_product(evaluations: &[E], tensored_query: &[E]) -> E .fold(E::ZERO, |acc, (x_i, y_i)| acc + *x_i * *y_i) } +/// Computes the evaluations of the Lagrange basis polynomials over the interpolating +/// set {0 , 1}^ν at (r_0, ..., r_{ν - 1}) i.e., the Lagrange kernel at (r_0, ..., r_{ν - 1}). pub fn tensorize(query: &[E]) -> Vec { let nu = query.len(); let n = 1 << nu; - (0..n).map(|i| lagrange_basis_eval(query, i)).collect() -} - -fn lagrange_basis_eval(query: &[E], i: usize) -> E { - query - .iter() - .enumerate() - .map(|(j, x_j)| if i & (1 << j) == 0 { E::ONE - *x_j } else { *x_j }) - .fold(E::ONE, |acc, v| acc * v) + let mut evals: Vec = vec![E::ONE; n]; + let mut size = 1; + for query in query.iter() { + size *= 2; + for i in (0..size).rev().step_by(2) { + let scalar = evals[i / 2]; + evals[i] = scalar * *query; + evals[i - 1] = scalar - evals[i]; + } + } + evals } diff --git a/processor/src/trace/virtual_bus/sum_check/domain.rs b/processor/src/trace/virtual_bus/sum_check/domain.rs index c3603b4e36..6d346b954d 100644 --- a/processor/src/trace/virtual_bus/sum_check/domain.rs +++ b/processor/src/trace/virtual_bus/sum_check/domain.rs @@ -1,157 +1,46 @@ -use vm_core::{FieldElement, StarkField}; +use vm_core::FieldElement; use winter_prover::math::batch_inversion; -pub struct EvaluationDomainCoeff -where - B: StarkField, - E: FieldElement, -{ - max_degree: usize, - vandermonde_matrix: Vec>, -} - -impl EvaluationDomainCoeff -where - B: StarkField, - E: FieldElement, -{ - pub fn new(max_degree: usize) -> Self { - let n = max_degree + 1; - let xs: Vec = (0..n).map(|x| E::from(x as u32)).collect(); - - let mut vandermonde_matrix: Vec> = Vec::with_capacity(n); - for i in 0..n { - let mut row = Vec::with_capacity(n); - let x = xs[i]; - row.push(E::ONE); - row.push(x); - for j in 2..n { - row.push(row[j - 1] * x); - } - row.push(E::ZERO); - vandermonde_matrix.push(row); - } - - Self { - max_degree, - vandermonde_matrix, - } - } - - fn interpolate_naive(evals: &[E]) -> Vec { - let n = evals.len(); - let xs: Vec = (0..n).map(|x| E::from(x as u32)).collect(); - - let mut vandermonde: Vec> = Vec::with_capacity(n); - for i in 0..n { - let mut row = Vec::with_capacity(n); - let x = xs[i]; - row.push(E::ONE); - row.push(x); - for j in 2..n { - row.push(row[j - 1] * x); - } - row.push(evals[i]); - vandermonde.push(row); - } - - gaussian_elimination(&mut vandermonde) - } - - fn interpolate(&mut self, evals: &[E]) -> Vec { - let mut vandermonde = self.vandermonde_matrix.clone(); - for row in 0..(self.max_degree + 1) { - vandermonde[row][self.max_degree] = evals[row]; - } - gaussian_elimination(&mut vandermonde) - } -} - -pub fn gaussian_elimination(matrix: &mut [Vec]) -> Vec { - let size = matrix.len(); - assert_eq!(size, matrix[0].len() - 1); - - for i in 0..size - 1 { - for j in i..size - 1 { - echelon(matrix, i, j); - } - } - - for i in (1..size).rev() { - eliminate(matrix, i); - } - - // Disable cargo clippy warnings about needless range loops. - // Checking the diagonal like this is simpler than any alternative. - //#[allow(clippy::needless_range_loop)] - for i in 0..size { - if matrix[i][i] == E::ZERO { - println!("Infinitely many solutions"); - } - } - - let mut result: Vec = vec![E::ZERO; size]; - for i in 0..size { - result[i] = matrix[i][size] / matrix[i][i]; - } - result -} - -fn echelon(matrix: &mut [Vec], i: usize, j: usize) { - let size = matrix.len(); - if matrix[i][i] == E::ZERO { - } else { - let factor = matrix[j + 1][i] / matrix[i][i]; - (i..size + 1).for_each(|k| { - let tmp = matrix[i][k]; - matrix[j + 1][k] -= factor * tmp; - }); - } -} - -fn eliminate(matrix: &mut [Vec], i: usize) { - let size = matrix.len(); - if matrix[i][i] == E::ZERO { - } else { - for j in (1..i + 1).rev() { - let factor = matrix[j - 1][i] / matrix[i][i]; - for k in (0..size + 1).rev() { - let tmp = matrix[i][k]; - matrix[j - 1][k] -= factor * tmp; - } - } - } -} +/// Implements barycentric evaluation where the interpolation domain is `0, 1, ..., max_degree` +/// where `max_degree` is maximal polynomial supported degree. pub struct EvaluationDomain where - - E: FieldElement + E: FieldElement, { - max_degree: usize, - evaluation_points: Vec, + interpolation_points: Vec, barycentric_weights: Vec, } impl EvaluationDomain where - E: FieldElement + E: FieldElement, { pub fn new(max_degree: usize) -> Self { - let points: Vec = (0..=max_degree).map(|x| E::from(x as u32)).collect(); - let weights = barycentric_weights(&points); + let interpolation_points: Vec = (0..=max_degree).map(|x| E::from(x as u32)).collect(); + let barycentric_weights = barycentric_weights(&interpolation_points); Self { - max_degree, - evaluation_points: points, - barycentric_weights: weights, + interpolation_points, + barycentric_weights, } } pub fn evaluate(&self, evaluations: &[E], r: E) -> E { - evaluate_barycentric(&self.evaluation_points, evaluations, r, &self.barycentric_weights) + evaluate_barycentric(&self.interpolation_points, evaluations, r, &self.barycentric_weights) } } +/// Computes the barycentric weights for a set of interpolation points. +pub fn barycentric_weights(points: &[E]) -> Vec { + let n = points.len(); + let tmp = (0..n) + .map(|i| (0..n).filter(|&j| j != i).fold(E::ONE, |acc, j| acc * (points[i] - points[j]))) + .collect::>(); + batch_inversion(&tmp) +} + +/// Computes the value of the polynomial with minimal degree interpolating the set `{(x_i, y_i)}` +/// at the point `r` given pre-computed barycentric weights. pub fn evaluate_barycentric( x_i: &[E], y_i: &[E], @@ -173,11 +62,3 @@ pub fn evaluate_barycentric( l_x * sum } - -pub fn barycentric_weights(points: &[E]) -> Vec { - let n = points.len(); - let tmp = (0..n) - .map(|i| (0..n).filter(|&j| j != i).fold(E::ONE, |acc, j| acc * (points[i] - points[j]))) - .collect::>(); - batch_inversion(&tmp) -} diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index 2d763a9d7e..b5c1ab15d1 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -1,17 +1,26 @@ -use vm_core::FieldElement; - use self::domain::EvaluationDomain; +use vm_core::FieldElement; mod domain; + mod prover; +pub use prover::SumCheckProver; mod verifier; +pub use verifier::SumCheckVerifier; +/// A sum-check round proof. +/// +/// This represents the polynomial sent by the Prover during one of the rounds of the sum-check +/// protocol. The polynomial is in evaluation form and excludes the zero-th coefficient as +/// the Verifier can recover it from the first coefficient and the current reduced claim. #[derive(Debug, Clone)] pub struct RoundProof { pub poly_evals: Vec, } impl RoundProof { + /// Completes the evaluations of the round polynomial by computing the zero-th coefficient + /// using the round claim. pub fn to_evals(&self, claim: E) -> Vec { let mut result = vec![]; @@ -22,69 +31,76 @@ impl RoundProof { result.extend_from_slice(&self.poly_evals); result } - - // TODO: refactor once we move to coefficient form - pub(crate) fn evaluate(&self, domain: EvaluationDomain, claim: E, r: E) -> E { - let poly_evals = self.to_evals(claim); - domain.evaluate(&poly_evals, r) - } } +/// A sum-check proof. +/// +/// Composed of the round proofs i.e., the polynomials sent by the Prover at each round as well as +/// the (claimed) openings of the multi-linear oracles at the evaluation point given by the round +/// challenges. +/// Openings is an [Option] as there are situations where we would like to run the sum-check +/// protocol for a certain number of rounds that is less than the number of variables of the +/// multi-linears. This is the case for example when we have a merged polynomial. #[derive(Debug, Clone)] pub struct Proof { + pub openings: Option>, pub round_proofs: Vec>, } +/// Contains the round challenges sent by the Verifier up to some round as well as the current +/// reduced claim. #[derive(Debug)] pub struct RoundClaim { - pub partial_eval_point: Vec, - pub current_claim: E, + pub eval_point: Vec, + pub claim: E, } +/// Reduces an old claim to a new claim using the round challenge. pub fn reduce_claim( domain: &EvaluationDomain, current_poly: &RoundProof, current_round_claim: RoundClaim, round_challenge: E, ) -> RoundClaim { - let poly_evals = current_poly.to_evals(current_round_claim.current_claim); + // construct the round polynomial using the current claim + let poly_evals = current_poly.to_evals(current_round_claim.claim); + // evaluate the round polynomial at the round challenge to obtain the new claim let new_claim = domain.evaluate(&poly_evals, round_challenge); - let mut new_partial_eval_point = current_round_claim.partial_eval_point; + // update the evaluation point using the round challenge + let mut new_partial_eval_point = current_round_claim.eval_point; new_partial_eval_point.push(round_challenge); RoundClaim { - partial_eval_point: new_partial_eval_point, - current_claim: new_claim, + eval_point: new_partial_eval_point, + claim: new_claim, } } -#[derive(Clone)] -pub struct FinalEvaluationClaim { +/// Represents an opening claim at an evaluation point against a batch of oracles. +/// +/// After verifying [Proof], the verifier is left with a final question being whether a number +/// of oracles open to some value at some given point. This question is answered either using +/// further interaction with the Prover or using a polynomial commitment opening proof in +/// the compiled protocol. +#[derive(Clone, Debug)] +pub struct FinalOpeningClaim { pub evaluation_point: Vec, - pub claimed_evaluation: E, -} - -pub fn eval(p: &[E], x: E) -> E -where - E: FieldElement, -{ - // Horner evaluation - p.iter().rev().fold(E::ZERO, |acc, &coeff| acc * x + coeff) + pub openings: Vec, + // TODO: add a Vec to give more information on which main trace columns we would like + // to open. } #[cfg(test)] mod test { use super::{ - domain::EvaluationDomain, eval, prover::SumCheckProver, verifier::SumCheckVerifier, - RoundClaim, - }; - use crate::trace::virtual_bus::multilinear::{MultiLinear, ProjectionComposition}; - use test_utils::{ - crypto::Rpo256, - rand::{rand_array, rand_value, rand_vector}, + domain::EvaluationDomain, + prover::SumCheckProver, + verifier::{FinalQueryBuilder, SumCheckVerifier}, }; - use vm_core::{crypto::random::RpoRandomCoin, Felt, Word, ZERO}; + use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinear}; + use test_utils::rand::{rand_array, rand_value, rand_vector}; + use vm_core::{crypto::random::RpoRandomCoin, Felt, FieldElement, Word, ONE, ZERO}; #[test] fn test_evaluation_domain() { @@ -108,36 +124,152 @@ mod test { } #[test] - fn test_sum_check() { - let num_variables = 10; + fn test_sum_check_sum() { + let num_variables = 14; let values = rand_vector(1 << num_variables); let claim = values.iter().fold(ZERO, |acc, &x| x + acc); - let ml = MultiLinear::new(values.to_vec()); - let mut mls = vec![ml.clone()]; + + let ml = MultiLinear::new(values.to_vec()).expect("should not fail"); + let mut mls = vec![ml]; let virtual_poly = ProjectionComposition::new(0); - let prover = - SumCheckProver::::new( - virtual_poly, - num_variables, - ); + // Prover + let prover = SumCheckProver::new(virtual_poly); + let mut coin = RpoRandomCoin::new(Word::default()); + let proof = prover.prove(claim, &mut mls, num_variables, &mut coin).unwrap(); + + // Verifier + let plain_query_builder = PlainQueryBuilder; + let verifier = SumCheckVerifier::new(virtual_poly, plain_query_builder); let mut coin = RpoRandomCoin::new(Word::default()); - let (final_claim, round_proofs) = prover.prove(claim, &mut mls, &mut coin); + let result = verifier.verify(claim, proof, &mut coin); + + assert!(result.is_ok()) + } + + #[test] + fn test_sum_check_product() { + let num_variables = 14; + let values_0 = rand_vector(1 << num_variables); + let values_1 = rand_vector(1 << num_variables); + let claim = values_0.iter().zip(values_1.iter()).fold(ZERO, |acc, (x, y)| *x * *y + acc); + + let ml_0 = MultiLinear::new(values_0.to_vec()).expect("should not fail"); + let ml_1 = MultiLinear::new(values_1.to_vec()).expect("should not fail"); + let mut mls = vec![ml_0, ml_1]; + let virtual_poly = ProductComposition; + + // Prover + let prover = SumCheckProver::new(virtual_poly); + let mut coin = RpoRandomCoin::new(Word::default()); + let proof = prover.prove(claim, &mut mls, num_variables, &mut coin).unwrap(); + + // Verifier + let plain_query_builder = PlainQueryBuilder; + let verifier = SumCheckVerifier::new(virtual_poly, plain_query_builder); + let mut coin = RpoRandomCoin::new(Word::default()); + let result = verifier.verify(claim, proof, &mut coin); + + assert!(result.is_ok()) + } - let verifier = - SumCheckVerifier::::new( - virtual_poly, - num_variables, - ); + #[test] + fn test_sum_check_product_failure() { + let num_variables = 14; + let values_0 = rand_vector(1 << num_variables); + let values_1 = rand_vector(1 << num_variables); + let mut claim = + values_0.iter().zip(values_1.iter()).fold(ZERO, |acc, (x, y)| *x * *y + acc); + + // modifying the claim should make the Verifier reject the proof + claim += ONE; + + let ml_0 = MultiLinear::new(values_0.to_vec()).expect("should not fail"); + let ml_1 = MultiLinear::new(values_1.to_vec()).expect("should not fail"); + let mut mls = vec![ml_0, ml_1]; + let virtual_poly = ProductComposition; + // Prover + let prover = SumCheckProver::new(virtual_poly); let mut coin = RpoRandomCoin::new(Word::default()); - verifier.verify(claim, round_proofs, &mut coin); + let proof = prover.prove(claim, &mut mls, num_variables, &mut coin).unwrap(); + + // Verifier + let plain_query_builder = PlainQueryBuilder; + let verifier = SumCheckVerifier::new(virtual_poly, plain_query_builder); + let mut coin = RpoRandomCoin::new(Word::default()); + let result = verifier.verify(claim, proof, &mut coin); + + assert!(result.is_err()) + } + + struct PlainQueryBuilder; + + impl FinalQueryBuilder for PlainQueryBuilder { + type Field = Felt; - let RoundClaim { - partial_eval_point, - current_claim, - } = final_claim; + fn build_query( + &self, + openings: &[Self::Field], + _evaluation_point: &[Self::Field], + ) -> Vec { + openings.to_vec() + } + } + + #[derive(Clone, Copy, Debug)] + pub struct ProjectionComposition { + coordinate: usize, + } + + impl ProjectionComposition { + pub fn new(coordinate: usize) -> Self { + Self { coordinate } + } + } + + impl CompositionPolynomial for ProjectionComposition + where + E: FieldElement, + { + fn num_variables(&self) -> usize { + 1 + } + + fn max_degree(&self) -> usize { + 1 + } + + fn evaluate(&self, query: &[E]) -> E { + query[self.coordinate] + } + } + + #[derive(Clone, Copy, Debug, Default)] + pub struct ProductComposition; + + impl CompositionPolynomial for ProductComposition + where + E: FieldElement, + { + fn num_variables(&self) -> usize { + 2 + } + + fn max_degree(&self) -> usize { + 2 + } + + fn evaluate(&self, query: &[E]) -> E { + assert_eq!(query.len(), 2); + query[0] * query[1] + } + } - assert_eq!(ml.evaluate(&partial_eval_point), current_claim); + pub fn eval(p: &[E], x: E) -> E + where + E: FieldElement, + { + p.iter().rev().fold(E::ZERO, |acc, &coeff| acc * x + coeff) } } diff --git a/processor/src/trace/virtual_bus/sum_check/prover/error.rs b/processor/src/trace/virtual_bus/sum_check/prover/error.rs new file mode 100644 index 0000000000..7858b6d1a1 --- /dev/null +++ b/processor/src/trace/virtual_bus/sum_check/prover/error.rs @@ -0,0 +1,21 @@ +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("number of rounds for sum-check must be greater than zero")] + NumRoundsZero, + #[error("sumcheck polynomial degree must be greater than zero")] + PolynomialDegreeIsZero, + #[error("the input was not well formed: {0}")] + ImproperInput(String), + #[error("the evaluation domain does not match the expected size")] + EvaluationDomainMismatch, + #[error("the number of rounds is greater than the number of variables")] + TooManyRounds, + #[error("should provide at least one multi-linear polynomial as input")] + NoMlsProvided, + #[error("failed to generate round challenge")] + FailedToGenerateChallenge, + #[error("the provided multi-linears have different arities")] + MlesDifferentArities, + #[error("multi-linears should have at least one variable")] + AtLeastOneVariable, +} diff --git a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs index 74fb650076..adf1e67952 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs @@ -1,26 +1,32 @@ +use self::error::Error; use super::{domain::EvaluationDomain, reduce_claim, Proof, RoundClaim, RoundProof}; use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinear}; use core::marker::PhantomData; use vm_core::{FieldElement, StarkField}; use winter_prover::crypto::{ElementHasher, RandomCoin}; +mod error; + /// A struct that contains relevant information for the execution of the multivariate sum-check -/// protocol. +/// protocol prover. /// The sum-check protocol is an interactive protocol (IP) for proving the following relation: /// -/// v = \sum_{(x_0,\cdots, x_{\nu - 1}) \in \{0 , 1\}^{2^{\nu}}} g(f_0((x_0,\cdots, x_{\nu - 1})), \cdots , f_c((x_0,\cdots, x_{\nu - 1}))) +/// v = \sum_{(x_0,\cdots, x_{\nu - 1}) \in \{0 , 1\}^{2^{\nu}}} +/// g(f_0((x_0,\cdots, x_{\nu - 1})), \cdots , f_c((x_0,\cdots, x_{\nu - 1}))) /// /// where: /// /// 1. v ∈ 𝔽 where 𝔽 is a finite field. -/// 2. f_i are multi-linear polynomials i.e., polynomials in 𝔽[X_i, \cdots ,X_{\nu - 1}] with degree at most one in each variable. +/// 2. f_i are multi-linear polynomials i.e., polynomials in 𝔽[X_i, \cdots ,X_{\nu - 1}] with degree +/// at most one in each variable. /// 3. g is a multivariate polynomial with degree at most d in each variable. /// -/// The verifier is given commitments to each `f_i` in addition to the claimed sum `v`. The Prover then engages in an IP -/// to convince the Verifier that the above relation holds for the given `f_i` and `v`. -/// More precisely: +/// The Verifier is given commitments to each `f_i` in addition to the claimed sum `v`. The Prover +/// then engages in an IP to convince the Verifier that the above relation holds for the given +/// `f_i` and `v`. More precisely: /// -/// 0. Denote by w(x_0,\cdots, x_{\nu - 1}) := g(f_0((x_0,\cdots, x_{\nu - 1})), \cdots , f_c((x_0,\cdots, x_{\nu - 1}))). +/// 0. Denote by w(x_0,\cdots, x_{\nu - 1}) := g(f_0((x_0,\cdots, x_{\nu - 1})), +/// \cdots , f_c((x_0,\cdots, x_{\nu - 1}))). /// /// 1. In the first round, the Prover sends the polynomial defined by: /// s_0(X_0) := \sum_{(x_{1},\cdots, x_{\nu - 1}) w(X_0, x_{1}, \cdots, x_{\nu - 1}) @@ -32,25 +38,30 @@ use winter_prover::crypto::{ElementHasher, RandomCoin}; /// 4. For each i in 1...(\nu - 1): /// a. The Prover sends the univariate polynomial defined by: /// -/// s_i(X_i) := \sum_{(x_{i + 1},\cdots, x_{\nu - 1}) w(r_0,\cdots, r_{i - 1}, X_i, x_{i + 1}, \cdots, x_{\nu - 1}) +/// s_i(X_i) := \sum_{(x_{i + 1},\cdots, x_{\nu - 1}) +/// w(r_0,\cdots, r_{i - 1}, X_i, x_{i + 1}, \cdots, x_{\nu - 1}). +/// /// b. The Verifier checks that s_{i - 1}(r_{i - 1}) = s_{i}(0) + s_{i}(1) rejecting if not. /// /// c. The Verifier samples a random challenge `r_i ∈ 𝔽` and sends it to the Prover. /// -/// 5. The Verifier now queries each of the oracles behind the commitments i.e., `f_i` at `(r_0, \cdots , r_{\nu - 1})` to get -/// u_i = f_i(r_0, \cdots , r_{\nu - 1}). The Verifier then accepts if and only if: +/// 5. The Verifier now queries each of the oracles behind the commitments i.e., `f_i` at +/// `(r_0, \cdots , r_{\nu - 1})` to get u_i = f_i(r_0, \cdots , r_{\nu - 1}). +/// The Verifier then accepts if and only if: /// /// s_{\nu - 1}(r_{\nu - 1}) = g(u_0, \cdots , u_{\nu - 1}) -/// +/// /// A few remarks: -/// -/// 1. The degree bound on `g` implies that each of the `s_i` polynomials is a univariate polynomial of degree at most `d`. -/// Thus, the Prover in each round sends `d + 1` values, either the coefficients or the evaluations of `s_i`. -/// +/// +/// 1. The degree bound on `g` implies that each of the `s_i` polynomials is a univariate polynomial +/// of degree at most `d`. Thus, the Prover in each round sends `d + 1` values, either +/// the coefficients or the evaluations of `s_i`. +/// /// 2. The Prover has each `f_i` in its evaluation form over the hyper-cube \{0 , 1\}^{2^{\nu}}. -/// -/// 3. An optimization is for the Prover to not send `s_i(0)` as it can be recoverd from the current reduced claim s_{i - 1}(r_{i - 1}) -/// using the relation s_{i}(0) = s_{i}(1) - s_{i - 1}(r_{i - 1}). This also means that the Verifier can skip point 4.b. +/// +/// 3. An optimization is for the Prover to not send `s_i(0)` as it can be recoverd from the current +/// reduced claim s_{i - 1}(r_{i - 1}) using the relation s_{i}(0) = s_{i}(1) - s_{i - 1}(r_{i - 1}). +/// This also means that the Verifier can skip point 4.b. pub struct SumCheckProver where B: StarkField, @@ -58,9 +69,8 @@ where C: RandomCoin, H: ElementHasher, { - pub virtual_poly: P, - pub eval_domain: EvaluationDomain, - num_rounds: usize, + composition_poly: P, + eval_domain: EvaluationDomain, _challenger: PhantomData, _hasher: PhantomData, } @@ -73,69 +83,160 @@ where C: RandomCoin, H: ElementHasher, { - pub fn new(virtual_poly: P, num_rounds: usize) -> Self { - let max_degree = virtual_poly.max_degree(); + /// Constructs a new [SumCheckProver] given a multivariate composition polynomial. + /// The multivariate composition polynomial corresponds to the `g` polynomial in the + /// description of the [SumCheckProver] struct. + pub fn new(composition_poly: P) -> Self { + let max_degree = composition_poly.max_degree(); let eval_domain = EvaluationDomain::new(max_degree); Self { - virtual_poly, + composition_poly, eval_domain, - num_rounds, _challenger: PhantomData, _hasher: PhantomData, } } + /// Given an initial claim `claim`, a mutable vector of multi-linear polynomials `mls` and + /// a number of rounds `num_rounds`, computes `num_rounds` iterations of the sum-check protocol + /// starting from claim `claim`. + /// + /// # Errors + /// Returns an error if: + /// - No multi-linears were provided. + /// - Number of rounds is zero or is greater than the number of variables of the multilinears. + /// - The provided multi-linears have different arities. pub fn prove( &self, claim: E, - mls: &mut Vec>, + mls: &mut [MultiLinear], + num_rounds: usize, coin: &mut C, - ) -> (RoundClaim, Proof) { - // Setup first round - let mut prev_claim = RoundClaim { - partial_eval_point: vec![], - current_claim: claim, - }; + //) -> Result<(RoundClaim, Proof), Error> { + ) -> Result, Error> { + // there should be at least one multi-linear polynomial provided + if mls.is_empty() { + return Err(Error::NoMlsProvided); + } + + // there should be at least one round to prove + if num_rounds == 0 { + return Err(Error::NumRoundsZero); + } + + // there can not be more rounds than variables of the multi-linears + let ml_variables = mls[0].num_variables(); + if num_rounds > ml_variables { + return Err(Error::TooManyRounds); + } + + // there should at least be one variable for the protocol to be non-trivial + if ml_variables < 2 { + return Err(Error::AtLeastOneVariable); + } + + // all multi-linears should have the same arity + if !mls.iter().all(|ml| ml.num_variables() == ml_variables) { + return Err(Error::MlesDifferentArities); + } + let mut round_proofs = vec![]; - let mut output = sumcheck_round(&self.virtual_poly, mls); - round_proofs.push(output); + // setup first round claim + let mut current_round_claim = RoundClaim { + eval_point: vec![], + claim, + }; + + // run the first round of the protocol + let mut round_proof = sumcheck_round(&self.composition_poly, mls); + round_proofs.push(round_proof); + // reseed with the s_0 polynomial + coin.reseed(H::hash_elements(&round_proofs[0].poly_evals)); - for i in 1..self.num_rounds { - let round_challenge = coin.draw().unwrap(); - let new_claim = - reduce_claim(&self.eval_domain, &round_proofs[i - 1], prev_claim, round_challenge); - mls.into_iter().for_each(|ml| ml.bind_assign(round_challenge)); + for i in 1..num_rounds { + // generate random challenge r_i for the i-th round + let round_challenge = coin.draw().map_err(|_| Error::FailedToGenerateChallenge)?; - output = sumcheck_round(&self.virtual_poly, mls); - round_proofs.push(output); + // compute the new reduced round claim + let new_round_claim = reduce_claim( + &self.eval_domain, + &round_proofs[i - 1], + current_round_claim, + round_challenge, + ); - prev_claim = new_claim; + // fold each multi-linear using the round challenge + mls.iter_mut().for_each(|ml| ml.bind_assign(round_challenge)); - let poly_evals = &round_proofs[i].poly_evals; - coin.reseed(H::hash_elements(poly_evals)); + // run the i-th round of the protocol using the folded multi-linears for the new reduced + // claim. This basically computes the s_i polynomial. + round_proof = sumcheck_round(&self.composition_poly, mls); + round_proofs.push(round_proof); + + // update the claim + current_round_claim = new_round_claim; + + // reseed with the s_i polynomial + coin.reseed(H::hash_elements(&round_proofs[i].poly_evals)); } - let round_challenge = coin.draw().unwrap(); - mls.into_iter().for_each(|ml| ml.bind_assign(round_challenge)); - - let final_round_claim = reduce_claim( - &self.eval_domain, - &round_proofs[self.num_rounds - 1], - prev_claim, - round_challenge, - ); - let round_proofs = Proof { round_proofs }; - - (final_round_claim, round_proofs) + + // generate the last random challenge + let round_challenge = coin.draw().map_err(|_| Error::FailedToGenerateChallenge)?; + // fold each multi-linear using the last random challenge + mls.iter_mut().for_each(|ml| ml.bind_assign(round_challenge)); + + let openings = { + if mls[0].num_evaluations() != 1 { + None + } else { + Some(mls.iter_mut().map(|ml| ml.evaluations()[0]).collect()) + } + }; + + Ok(Proof { + openings, + round_proofs, + }) } } +/// Computes the polynomial +/// +/// s_i(X_i) := \sum_{(x_{i + 1},\cdots, x_{\nu - 1}) +/// w(r_0,\cdots, r_{i - 1}, X_i, x_{i + 1}, \cdots, x_{\nu - 1}). +/// where +/// +/// w(x_0,\cdots, x_{\nu - 1}) := g(f_0((x_0,\cdots, x_{\nu - 1})), +/// \cdots , f_c((x_0,\cdots, x_{\nu - 1}))). +/// +/// Given a degree bound `d_max` for all variables, it suffices to compute the evaluations of `s_i` +/// at `d_max + 1` points. Given that `s_{i}(0) = s_{i}(1) - s_{i - 1}(r_{i - 1})` it is sufficient +/// to compute the evaluations on only `d_max` points. +/// +/// The algorithm works by iterating over the variables (x_{i + 1}, \cdots, x_{\nu - 1}) in +/// {0, 1}^{\nu - 1 - i}. For each such tuple, we store the evaluations of the (folded) +/// multi-linears at (0, x_{i + 1}, \cdots, x_{\nu - 1}) and +/// (1, x_{i + 1}, \cdots, x_{\nu - 1}) in two arrays, `evals_zero` and `evals_one`. +/// Using `evals_one`, remember that we optimize evaluating at 0 away, we get the first evaluation +/// i.e., `s_i(1)`. +/// +/// For the remaining evaluations, we use the fact that the folded `f_i` is multi-linear and hence +/// we can write +/// +/// f_i(X_i, x_{i + 1}, \cdots, x_{\nu - 1}) = +/// (1 - X_i) . f_i(0, x_{i + 1}, \cdots, x_{\nu - 1}) + X_i . f_i(1, x_{i + 1}, \cdots, x_{\nu - 1}) +/// +/// Note that we omitted writing the folding randomness for readability. +/// Since the evaluation domain is {0, 1, ... , d_max}, we can compute the evaluations based on +/// the previous one using only additions. This is the purpose of `deltas`, to hold the increments +/// added to each multi-linear to compute the evaluation at the next point, and `evals_x` to hold +/// the current evaluation at `x` in {2, ... , d_max}. fn sumcheck_round( - composer: &dyn CompositionPolynomial, - mls: &mut Vec>, + polynomial: &dyn CompositionPolynomial, + mls: &mut [MultiLinear], ) -> RoundProof { - let polynomial = composer; let num_ml = mls.len(); let num_vars = mls[0].num_variables(); let num_rounds = num_vars - 1; @@ -145,13 +246,15 @@ fn sumcheck_round( let mut deltas = vec![E::ZERO; num_ml]; let mut evals_x = vec![E::ZERO; num_ml]; - let total_evals = (0..1 << num_rounds).into_iter().map(|i| { + let total_evals = (0..1 << num_rounds).map(|i| { for (j, ml) in mls.iter().enumerate() { - evals_zero[j] = ml.evaluations()[(i << 1) as usize]; + evals_zero[j] = ml.evaluations()[i << 1]; evals_one[j] = ml.evaluations()[(i << 1) + 1]; } + let mut total_evals = vec![E::ZERO; polynomial.max_degree()]; total_evals[0] = polynomial.evaluate(&evals_one); + evals_zero .iter() .zip(evals_one.iter().zip(deltas.iter_mut().zip(evals_x.iter_mut()))) @@ -159,6 +262,7 @@ fn sumcheck_round( *delta = *a1 - *a0; *evx = *a1; }); + total_evals.iter_mut().skip(1).for_each(|e| { evals_x.iter_mut().zip(deltas.iter()).for_each(|(evx, delta)| { *evx += *delta; @@ -167,10 +271,12 @@ fn sumcheck_round( }); total_evals }); + let evaluations = total_evals.fold(vec![E::ZERO; polynomial.max_degree()], |mut acc, evals| { acc.iter_mut().zip(evals.iter()).for_each(|(a, ev)| *a += *ev); acc }); + RoundProof { poly_evals: evaluations, } diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/error.rs b/processor/src/trace/virtual_bus/sum_check/verifier/error.rs new file mode 100644 index 0000000000..299c2092f6 --- /dev/null +++ b/processor/src/trace/virtual_bus/sum_check/verifier/error.rs @@ -0,0 +1,9 @@ +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("the final evaluation check of sum-check failed")] + FinalEvaluationCheckFailed, + #[error("the proof doesn't contain openings of the multi-linears")] + NoOpeningsProvided, + #[error("failed to generate round challenge")] + FailedToGenerateChallenge, +} diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index b8c2f07418..b74d3e40a1 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -1,67 +1,151 @@ +use self::error::Error; +use super::{domain::EvaluationDomain, FinalOpeningClaim, Proof}; use crate::trace::virtual_bus::multilinear::CompositionPolynomial; - -use super::{domain::EvaluationDomain, FinalEvaluationClaim, Proof}; use core::marker::PhantomData; use vm_core::{FieldElement, StarkField}; use winter_prover::crypto::{ElementHasher, RandomCoin}; -pub struct SumCheckVerifier +mod error; + +/// A struct that contains relevant information for the execution of the multivariate sum-check +/// protocol verifier. The protocol is described in [`SumCheckProver`]. +/// The sum-check Verifier is composed of two parts: +/// +/// 1. Multi-round interaction where it sends challenges and receives polynomials. For each +/// polynomial received it uses the sent randomness to reduce the current claim to a new one. +/// +/// 2. A final round where the Verifier queries the multi-linear oracles it received at the outset +/// of the protocol (i.e., commitments) for their evaluations the random point +/// `(r_0, ... , r_{\nu - 1})` where $\nu$ is the number of rounds of the sum-check protocol and +/// `r_i` is the randomness sent by the Verifier at each round. +pub struct SumCheckVerifier where B: StarkField, E: FieldElement, C: RandomCoin, P: CompositionPolynomial, H: ElementHasher, + V: FinalQueryBuilder, { - pub virtual_poly: P, - pub eval_domain: EvaluationDomain, - pub num_rounds: usize, + composition_poly: P, + eval_domain: EvaluationDomain, + final_query_builder: V, _channel: PhantomData, } -impl SumCheckVerifier +impl SumCheckVerifier where B: StarkField, E: FieldElement, C: RandomCoin, P: CompositionPolynomial, H: ElementHasher, + V: FinalQueryBuilder, { - pub fn new(virtual_poly: P, num_rounds: usize) -> Self { - let max_degree = virtual_poly.max_degree(); + /// Create a new [SumCheckVerifier] from a composition polynomial and final query builder. + pub fn new(composition_poly: P, final_query_builder: V) -> Self { + let max_degree = composition_poly.max_degree(); let eval_domain = EvaluationDomain::new(max_degree); Self { - virtual_poly, + composition_poly, eval_domain, - num_rounds, + final_query_builder, _channel: PhantomData, } } + /// Verifies a sum-check proof [Proof] and returns a claim on the openings of the mult-linear + /// oracles that are part of the statement being proven. + /// + /// More precisely, the method: + /// + /// 1. Generates a `claimed_evaluation` from the round proof polynomials and the round challenge + /// randomness. + /// + /// 2. Computes a query that is built using the [FinalQueryBuilder] from the multi-linear + /// openings and the round challenges. + /// + /// 3. Evaluates the composition polynomial at the query and checks that it is equal + /// `claimed_evaluation`. + /// + /// 4. Outputs a `FinalOpeningClaim` on the multi-linear oracles. + /// + /// Thus, the proof is correct if the method outputs a [FinalOpeningClaim] and this latter is + /// a valid claim on the multi-linear oracles i.e., each multi-linear oracle opens to the + /// claimed value at the specified opening point. + /// + /// # Errors + /// Returns an error if: + /// - No openings were provided. + /// - Draw the round challenge fails. + /// - The final evaluation check fails. pub fn verify( &self, claim: E, - round_proofs: Proof, + proof: Proof, coin: &mut C, - ) -> FinalEvaluationClaim { + ) -> Result, Error> { + let Proof { + openings, + round_proofs, + } = proof; + let mut claimed_evaluation = claim; let mut evaluation_point = vec![]; - for proof in round_proofs.round_proofs { - let partial_evals = proof.poly_evals.clone(); + for round_proof in round_proofs { + let partial_evals = round_proof.poly_evals.clone(); coin.reseed(H::hash_elements(&partial_evals)); - let evals = proof.to_evals(claimed_evaluation); + let evals = round_proof.to_evals(claimed_evaluation); - let r = coin.draw().unwrap(); + let r = coin.draw().map_err(|_| Error::FailedToGenerateChallenge)?; let reduced_evaluation = self.eval_domain.evaluate(&evals, r); claimed_evaluation = reduced_evaluation; evaluation_point.push(r); } - FinalEvaluationClaim { - evaluation_point, - claimed_evaluation, + if let Some(openings) = openings { + let query = self.final_query_builder.build_query(&openings, &evaluation_point); + if self.composition_poly.evaluate(&query) != claimed_evaluation { + Err(Error::FinalEvaluationCheckFailed) + } else { + Ok(FinalOpeningClaim { + evaluation_point, + openings, + }) + } + } else { + Err(Error::NoOpeningsProvided) } } } + +/// Contains the logic for building the final query made to the virtual polynomial. +/// +/// During the last step of the sum-check protocol, the Verifier must evaluate the virtual +/// polynomial at a random point `(r_0, ... ,r_{\nu - 1})`. To do this, the Verifier asks the Prover +/// for the openings of the mult-linear oracles at `(r_0, ... ,r_{\nu - 1})` i.e., +/// `v_i = f_i(r_0, ... ,r_{\nu - 1})`. The Verifier then evaluates `g(v_0, ... , v_{\nu - 1})` and +/// compares it to the reduced claim resulting from the round proofs and challenges. +/// At this point, for the Verifier to accept the proof, it needs to check that indeed +/// `v_i = f_i(r_0, ... ,r_{\nu - 1})`, this is the exact content of [FinalOpeningClaim], which can +/// be either answered by a direct query to the oracles (i.e., in the complied protocol this would +/// be answered with an opening proof against the commitment) or through further interaction (as +/// in the case of the GKR protocol). +/// +/// The purpose of [FinalQueryBuilder] is to abstract the logic for evaluating multi-linears which +/// the Verifier can do by herself. For example, this is the case for periodic columns where given +/// `(r_0, ... ,r_{\nu - 1})` the Verifier can evaluate `f_i(r_0, ... ,r_{\nu - 1})` un-assisted. +/// +/// In the case where there are no `f_i(r_0, ... ,r_{\nu - 1})` which can be computed by the +/// Verifier alone, the output of [Self::build_query] will be just the provided openings. +pub trait FinalQueryBuilder { + type Field; + + fn build_query( + &self, + openings: &[Self::Field], + evaluation_point: &[Self::Field], + ) -> Vec; +} From 3b2acf5ebfff91f6abe9d09ef625f0c40cdd8307 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:04:42 +0100 Subject: [PATCH 05/32] fix: no-std --- processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs | 2 ++ processor/src/trace/virtual_bus/multilinear/mod.rs | 1 + processor/src/trace/virtual_bus/sum_check/domain.rs | 1 + processor/src/trace/virtual_bus/sum_check/mod.rs | 2 ++ processor/src/trace/virtual_bus/sum_check/prover/error.rs | 2 -- processor/src/trace/virtual_bus/sum_check/verifier/mod.rs | 1 + 6 files changed, 7 insertions(+), 2 deletions(-) diff --git a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs index 4fe7e048d8..f6ee1a476d 100644 --- a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs +++ b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs @@ -1,3 +1,5 @@ +use alloc::vec::Vec; + use super::{tensorize, FieldElement, MultiLinear}; /// The EQ (equality) function is the binary function defined by diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index 5ad5c10ab1..703b66326c 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -1,4 +1,5 @@ use core::ops::Index; +use alloc::{borrow::ToOwned, vec::Vec}; use vm_core::FieldElement; use winter_prover::math::log2; diff --git a/processor/src/trace/virtual_bus/sum_check/domain.rs b/processor/src/trace/virtual_bus/sum_check/domain.rs index 6d346b954d..3f0a79f0d8 100644 --- a/processor/src/trace/virtual_bus/sum_check/domain.rs +++ b/processor/src/trace/virtual_bus/sum_check/domain.rs @@ -1,3 +1,4 @@ +use alloc::vec::Vec; use vm_core::FieldElement; use winter_prover::math::batch_inversion; diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index b5c1ab15d1..bc9b43c94c 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -1,4 +1,5 @@ use self::domain::EvaluationDomain; +use alloc::vec::Vec; use vm_core::FieldElement; mod domain; @@ -99,6 +100,7 @@ mod test { verifier::{FinalQueryBuilder, SumCheckVerifier}, }; use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinear}; + use alloc::vec::Vec; use test_utils::rand::{rand_array, rand_value, rand_vector}; use vm_core::{crypto::random::RpoRandomCoin, Felt, FieldElement, Word, ONE, ZERO}; diff --git a/processor/src/trace/virtual_bus/sum_check/prover/error.rs b/processor/src/trace/virtual_bus/sum_check/prover/error.rs index 7858b6d1a1..a985f5f2d7 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/error.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/error.rs @@ -4,8 +4,6 @@ pub enum Error { NumRoundsZero, #[error("sumcheck polynomial degree must be greater than zero")] PolynomialDegreeIsZero, - #[error("the input was not well formed: {0}")] - ImproperInput(String), #[error("the evaluation domain does not match the expected size")] EvaluationDomainMismatch, #[error("the number of rounds is greater than the number of variables")] diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index b74d3e40a1..d15b202d8b 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -2,6 +2,7 @@ use self::error::Error; use super::{domain::EvaluationDomain, FinalOpeningClaim, Proof}; use crate::trace::virtual_bus::multilinear::CompositionPolynomial; use core::marker::PhantomData; +use alloc::vec::Vec; use vm_core::{FieldElement, StarkField}; use winter_prover::crypto::{ElementHasher, RandomCoin}; From 431b9374c87b5bea142eb784c25f3b20e77c5bbb Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:17:53 +0100 Subject: [PATCH 06/32] chore: fmt --- processor/src/trace/virtual_bus/multilinear/mod.rs | 2 +- processor/src/trace/virtual_bus/sum_check/verifier/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index 703b66326c..6bd4552465 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -1,5 +1,5 @@ -use core::ops::Index; use alloc::{borrow::ToOwned, vec::Vec}; +use core::ops::Index; use vm_core::FieldElement; use winter_prover::math::log2; diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index d15b202d8b..c8ac2bcf43 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -1,8 +1,8 @@ use self::error::Error; use super::{domain::EvaluationDomain, FinalOpeningClaim, Proof}; use crate::trace::virtual_bus::multilinear::CompositionPolynomial; -use core::marker::PhantomData; use alloc::vec::Vec; +use core::marker::PhantomData; use vm_core::{FieldElement, StarkField}; use winter_prover::crypto::{ElementHasher, RandomCoin}; From 2686d6589318f24406625de2140379042fd5260f Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:35:37 +0100 Subject: [PATCH 07/32] fix: remove challenger marker --- processor/src/trace/virtual_bus/sum_check/prover/mod.rs | 2 -- processor/src/trace/virtual_bus/sum_check/verifier/mod.rs | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs index adf1e67952..574fbe986a 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs @@ -72,7 +72,6 @@ where composition_poly: P, eval_domain: EvaluationDomain, _challenger: PhantomData, - _hasher: PhantomData, } impl SumCheckProver @@ -94,7 +93,6 @@ where composition_poly, eval_domain, _challenger: PhantomData, - _hasher: PhantomData, } } diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index c8ac2bcf43..102230e9f6 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -31,7 +31,7 @@ where composition_poly: P, eval_domain: EvaluationDomain, final_query_builder: V, - _channel: PhantomData, + _challenger: PhantomData, } impl SumCheckVerifier @@ -52,7 +52,7 @@ where composition_poly, eval_domain, final_query_builder, - _channel: PhantomData, + _challenger: PhantomData, } } From de541c5af1dea62dd5bac4bee46479d206d4833f Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:43:58 +0100 Subject: [PATCH 08/32] chore: add evaluation point to final eval claim output of prover --- .../src/trace/virtual_bus/sum_check/mod.rs | 8 ++++---- .../trace/virtual_bus/sum_check/prover/mod.rs | 12 ++++++++++-- .../virtual_bus/sum_check/verifier/error.rs | 2 ++ .../virtual_bus/sum_check/verifier/mod.rs | 18 +++++++++--------- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index bc9b43c94c..2be1a820bf 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -43,8 +43,8 @@ impl RoundProof { /// protocol for a certain number of rounds that is less than the number of variables of the /// multi-linears. This is the case for example when we have a merged polynomial. #[derive(Debug, Clone)] -pub struct Proof { - pub openings: Option>, +pub struct Proof { + pub openings: Option>, pub round_proofs: Vec>, } @@ -212,10 +212,10 @@ mod test { fn build_query( &self, - openings: &[Self::Field], + openings_claim: &super::FinalOpeningClaim, _evaluation_point: &[Self::Field], ) -> Vec { - openings.to_vec() + openings_claim.openings.to_vec() } } diff --git a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs index 574fbe986a..eeeaa71372 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs @@ -1,5 +1,7 @@ use self::error::Error; -use super::{domain::EvaluationDomain, reduce_claim, Proof, RoundClaim, RoundProof}; +use super::{ + domain::EvaluationDomain, reduce_claim, FinalOpeningClaim, Proof, RoundClaim, RoundProof, +}; use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinear}; use core::marker::PhantomData; use vm_core::{FieldElement, StarkField}; @@ -189,7 +191,13 @@ where if mls[0].num_evaluations() != 1 { None } else { - Some(mls.iter_mut().map(|ml| ml.evaluations()[0]).collect()) + let openings = mls.iter_mut().map(|ml| ml.evaluations()[0]).collect(); + let mut evaluation_point = current_round_claim.eval_point; + evaluation_point.push(round_challenge); + Some(FinalOpeningClaim { + evaluation_point, + openings, + }) } }; diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/error.rs b/processor/src/trace/virtual_bus/sum_check/verifier/error.rs index 299c2092f6..97eccfc042 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/error.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/error.rs @@ -6,4 +6,6 @@ pub enum Error { NoOpeningsProvided, #[error("failed to generate round challenge")] FailedToGenerateChallenge, + #[error("wrong opening point for the oracles")] + WrongOpeningPoint, } diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index 102230e9f6..1886b237fa 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -88,7 +88,7 @@ where coin: &mut C, ) -> Result, Error> { let Proof { - openings, + openings: openings_claim, round_proofs, } = proof; @@ -106,15 +106,15 @@ where evaluation_point.push(r); } - if let Some(openings) = openings { - let query = self.final_query_builder.build_query(&openings, &evaluation_point); + if let Some(openings_claim) = openings_claim { + if openings_claim.evaluation_point != evaluation_point { + return Err(Error::WrongOpeningPoint); + } + let query = self.final_query_builder.build_query(&openings_claim, &evaluation_point); if self.composition_poly.evaluate(&query) != claimed_evaluation { Err(Error::FinalEvaluationCheckFailed) } else { - Ok(FinalOpeningClaim { - evaluation_point, - openings, - }) + Ok(openings_claim) } } else { Err(Error::NoOpeningsProvided) @@ -142,11 +142,11 @@ where /// In the case where there are no `f_i(r_0, ... ,r_{\nu - 1})` which can be computed by the /// Verifier alone, the output of [Self::build_query] will be just the provided openings. pub trait FinalQueryBuilder { - type Field; + type Field: FieldElement; fn build_query( &self, - openings: &[Self::Field], + openings_claim: &FinalOpeningClaim, evaluation_point: &[Self::Field], ) -> Vec; } From 5110915be047e9a568678739638eb799a01c99b1 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 5 Apr 2024 14:57:22 +0200 Subject: [PATCH 09/32] chore: fix typos and comments --- .../virtual_bus/multilinear/lagrange_ker.rs | 71 ------------------- .../src/trace/virtual_bus/multilinear/mod.rs | 54 ++++++-------- .../src/trace/virtual_bus/sum_check/domain.rs | 8 +-- .../src/trace/virtual_bus/sum_check/mod.rs | 22 +++--- .../trace/virtual_bus/sum_check/prover/mod.rs | 18 ++--- .../virtual_bus/sum_check/verifier/mod.rs | 26 +++---- 6 files changed, 59 insertions(+), 140 deletions(-) delete mode 100644 processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs diff --git a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs deleted file mode 100644 index f6ee1a476d..0000000000 --- a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs +++ /dev/null @@ -1,71 +0,0 @@ -use alloc::vec::Vec; - -use super::{tensorize, FieldElement, MultiLinear}; - -/// The EQ (equality) function is the binary function defined by -/// -/// EQ: {0 , 1}^ν ⛌ {0 , 1}^ν ⇾ {0 , 1} -/// ((x_0, ..., x_{ν - 1}), (y_0, ..., y_{ν - 1})) ↦ \prod_{i = 0}^{ν - 1} (x_i * y_i + (1 - x_i) * (1 - y_i)) -/// -/// Taking It's multi-linear extension EQ^{~}, we can define a basis for the set of multi-linear -/// polynomials in ν variables by -/// {EQ^{~}(., (y_0, ..., y_{ν - 1})): (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν} -/// where each basis function is a function of its first argument. This is called the Lagrange or -/// evaluation basis with evaluation set {0 , 1}^ν. -/// -/// Given a function (f: {0 , 1}^ν ⇾ 𝔽), its multi-linear extension (i.e., the unique -/// mult-linear polynomial extending f to (f^{~}: 𝔽^ν ⇾ 𝔽) and agrees with it on {0 , 1}^ν) is -/// defined as the summation of the evaluations of f against the Lagrange basis. -/// More specifically, given (r_0, ..., r_{ν - 1}) ∈ 𝔽^ν, then: -/// -/// f^{~}(r_0, ..., r_{ν - 1}) = \sum_{(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν} -/// f(y_0, ..., y_{ν - 1}) EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) -/// -/// We call the Lagrange kernel the evaluation of the EQ^{~} function at -/// ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) for all (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν for -/// a fixed (r_0, ..., r_{ν - 1}) ∈ 𝔽^ν. -/// -/// [EqFunction] represents EQ^{~} the mult-linear extension of -/// -/// ((y_0, ..., y_{ν - 1}) ↦ EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))) -/// -/// and contains a method to generate the Lagrange kernel for defining evaluations of multi-linear -/// extensions of arbitrary functions (f: {0 , 1}^ν ⇾ 𝔽) at a given point (r_0, ..., r_{ν - 1}) -/// as well as a method to evaluate EQ^{~}((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1}))) for -/// (t_0, ..., t_{ν - 1}) ∈ 𝔽^ν. -pub struct EqFunction { - r: Vec, -} - -impl EqFunction { - /// Creates a new [EqFunction]. - pub fn new(r: Vec) -> Self { - let mut tmp = r.clone(); - tmp.reverse(); - EqFunction { r: tmp } - } - - /// Computes EQ((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1}))). - pub fn evaluate(&self, t: &[E]) -> E { - assert_eq!(self.r.len(), t.len()); - - (0..self.r.len()) - .map(|i| self.r[i] * t[i] + (E::ONE - self.r[i]) * (E::ONE - t[i])) - .fold(E::ONE, |acc, term| acc * term) - } - - /// Computes EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) for all - /// (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν i.e., the Lagrange kernel at r = (r_0, ..., r_{ν - 1}). - pub fn evaluations(&self) -> Vec { - tensorize(&self.r) - } - - /// Returns the evaluations of - /// ((y_0, ..., y_{ν - 1}) ↦ EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))) - /// over {0 , 1}^ν. - pub fn ml_at(evaluation_point: Vec) -> MultiLinear { - let eq_evals = EqFunction::new(evaluation_point.clone()).evaluations(); - MultiLinear::from_values(&eq_evals) - .expect("should not fail because evaluations is a power of two") - } -} diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index 6bd4552465..7981a5c83e 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -3,46 +3,35 @@ use core::ops::Index; use vm_core::FieldElement; use winter_prover::math::log2; -#[allow(dead_code)] -mod lagrange_ker; - mod error; use self::error::Error; // MULTI-LINEAR POLYNOMIAL // ================================================================================================ -/// The evaluations of a mult-linear polynomial over the boolean hyper-cube {0 , 1}^ν. +/// Represents a multi-linear polynomial. +/// +/// The representation stores the evaluations of the polynomial over the boolean hyper-cube +/// {0 , 1}^ν. #[derive(Clone, Debug)] -pub struct MultiLinear { +pub struct MultiLinearPoly { num_variables: usize, evaluations: Vec, } -impl MultiLinear { - /// Constructs a [MultiLinear] from an array of values in the field. - pub fn new(values: Vec) -> Result { - if !values.len().is_power_of_two() { +impl MultiLinearPoly { + /// Constructs a [MultiLinearPoly] from its evaluations over the boolean hyper-cube {0 , 1}^ν. + pub fn from_evaluations(evaluations: Vec) -> Result { + if !evaluations.len().is_power_of_two() { return Err(Error::EvaluationsNotPowerOfTwo); } Ok(Self { - num_variables: log2(values.len()) as usize, - evaluations: values, + num_variables: log2(evaluations.len()) as usize, + evaluations, }) } - /// Constructs a [MultiLinear] from a slice of values in the field. - pub fn from_values(values: &[E]) -> Result { - if !values.len().is_power_of_two() { - return Err(Error::EvaluationsNotPowerOfTwo); - } - Ok(Self { - num_variables: log2(values.len()) as usize, - evaluations: values.to_owned(), - }) - } - - /// Returns the number of the multi-linear polynomial. + /// Returns the number of variables of the multi-linear polynomial. pub fn num_variables(&self) -> usize { self.num_variables } @@ -77,7 +66,8 @@ impl MultiLinear { *res = self.evaluations[i << 1] + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); } - Self::from_values(&result).expect("should not fail given that it is a multi-linear") + Self::from_evaluations(result.to_owned()) + .expect("should not fail given that it is a multi-linear") } /// Computes f(r_0, y_1, ..., y_{ν - 1}) using the linear interpolation formula @@ -89,12 +79,12 @@ impl MultiLinear { *res = self.evaluations[i << 1] + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); } - *self = - Self::from_values(&result).expect("should not fail given that it is a multi-linear"); + *self = Self::from_evaluations(result.to_owned()) + .expect("should not fail given that it is a multi-linear"); } } -impl Index for MultiLinear { +impl Index for MultiLinearPoly { type Output = E; fn index(&self, index: usize) -> &E { @@ -108,16 +98,16 @@ impl Index for MultiLinear { /// A multi-variate polynomial for composing individual multi-linear polynomials. pub trait CompositionPolynomial: Sync + Send { /// The number of variables when interpreted as a multi-variate polynomial. - fn num_variables(&self) -> usize; + fn num_variables(&self) -> u32; /// Maximum degree in all variables. - fn max_degree(&self) -> usize; + fn max_degree(&self) -> u32; /// Given a query, of length equal the number of variables, evaluates [Self] at this query. fn evaluate(&self, query: &[E]) -> E; } -// COMPOSITION POLYNOMIAL +// HELPER // ================================================================================================ /// Computes the inner product of two vectors of the same length. @@ -139,11 +129,11 @@ pub fn tensorize(query: &[E]) -> Vec { let mut evals: Vec = vec![E::ONE; n]; let mut size = 1; - for query in query.iter() { + for r_i in query.iter() { size *= 2; for i in (0..size).rev().step_by(2) { let scalar = evals[i / 2]; - evals[i] = scalar * *query; + evals[i] = scalar * *r_i; evals[i - 1] = scalar - evals[i]; } } diff --git a/processor/src/trace/virtual_bus/sum_check/domain.rs b/processor/src/trace/virtual_bus/sum_check/domain.rs index 3f0a79f0d8..3f86122924 100644 --- a/processor/src/trace/virtual_bus/sum_check/domain.rs +++ b/processor/src/trace/virtual_bus/sum_check/domain.rs @@ -16,9 +16,9 @@ impl EvaluationDomain where E: FieldElement, { - pub fn new(max_degree: usize) -> Self { - let interpolation_points: Vec = (0..=max_degree).map(|x| E::from(x as u32)).collect(); - let barycentric_weights = barycentric_weights(&interpolation_points); + pub fn new(max_degree: u32) -> Self { + let interpolation_points: Vec = (0..=max_degree).map(E::from).collect(); + let barycentric_weights = barycentric_weights_denominators(&interpolation_points); Self { interpolation_points, @@ -32,7 +32,7 @@ where } /// Computes the barycentric weights for a set of interpolation points. -pub fn barycentric_weights(points: &[E]) -> Vec { +pub fn barycentric_weights_denominators(points: &[E]) -> Vec { let n = points.len(); let tmp = (0..n) .map(|i| (0..n).filter(|&j| j != i).fold(E::ONE, |acc, j| acc * (points[i] - points[j]))) diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index 2be1a820bf..968789f502 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -41,7 +41,7 @@ impl RoundProof { /// challenges. /// Openings is an [Option] as there are situations where we would like to run the sum-check /// protocol for a certain number of rounds that is less than the number of variables of the -/// multi-linears. This is the case for example when we have a merged polynomial. +/// multi-linears. #[derive(Debug, Clone)] pub struct Proof { pub openings: Option>, @@ -99,7 +99,7 @@ mod test { prover::SumCheckProver, verifier::{FinalQueryBuilder, SumCheckVerifier}, }; - use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinear}; + use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinearPoly}; use alloc::vec::Vec; use test_utils::rand::{rand_array, rand_value, rand_vector}; use vm_core::{crypto::random::RpoRandomCoin, Felt, FieldElement, Word, ONE, ZERO}; @@ -131,7 +131,7 @@ mod test { let values = rand_vector(1 << num_variables); let claim = values.iter().fold(ZERO, |acc, &x| x + acc); - let ml = MultiLinear::new(values.to_vec()).expect("should not fail"); + let ml = MultiLinearPoly::from_evaluations(values.to_vec()).expect("should not fail"); let mut mls = vec![ml]; let virtual_poly = ProjectionComposition::new(0); @@ -156,8 +156,8 @@ mod test { let values_1 = rand_vector(1 << num_variables); let claim = values_0.iter().zip(values_1.iter()).fold(ZERO, |acc, (x, y)| *x * *y + acc); - let ml_0 = MultiLinear::new(values_0.to_vec()).expect("should not fail"); - let ml_1 = MultiLinear::new(values_1.to_vec()).expect("should not fail"); + let ml_0 = MultiLinearPoly::from_evaluations(values_0.to_vec()).expect("should not fail"); + let ml_1 = MultiLinearPoly::from_evaluations(values_1.to_vec()).expect("should not fail"); let mut mls = vec![ml_0, ml_1]; let virtual_poly = ProductComposition; @@ -186,8 +186,8 @@ mod test { // modifying the claim should make the Verifier reject the proof claim += ONE; - let ml_0 = MultiLinear::new(values_0.to_vec()).expect("should not fail"); - let ml_1 = MultiLinear::new(values_1.to_vec()).expect("should not fail"); + let ml_0 = MultiLinearPoly::from_evaluations(values_0.to_vec()).expect("should not fail"); + let ml_1 = MultiLinearPoly::from_evaluations(values_1.to_vec()).expect("should not fail"); let mut mls = vec![ml_0, ml_1]; let virtual_poly = ProductComposition; @@ -234,11 +234,11 @@ mod test { where E: FieldElement, { - fn num_variables(&self) -> usize { + fn num_variables(&self) -> u32 { 1 } - fn max_degree(&self) -> usize { + fn max_degree(&self) -> u32 { 1 } @@ -254,11 +254,11 @@ mod test { where E: FieldElement, { - fn num_variables(&self) -> usize { + fn num_variables(&self) -> u32 { 2 } - fn max_degree(&self) -> usize { + fn max_degree(&self) -> u32 { 2 } diff --git a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs index eeeaa71372..83d595b908 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs @@ -2,7 +2,7 @@ use self::error::Error; use super::{ domain::EvaluationDomain, reduce_claim, FinalOpeningClaim, Proof, RoundClaim, RoundProof, }; -use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinear}; +use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinearPoly}; use core::marker::PhantomData; use vm_core::{FieldElement, StarkField}; use winter_prover::crypto::{ElementHasher, RandomCoin}; @@ -13,13 +13,13 @@ mod error; /// protocol prover. /// The sum-check protocol is an interactive protocol (IP) for proving the following relation: /// -/// v = \sum_{(x_0,\cdots, x_{\nu - 1}) \in \{0 , 1\}^{2^{\nu}}} +/// v = \sum_{(x_0,\cdots, x_{\nu - 1}) \in \{0 , 1\}^{\nu}} /// g(f_0((x_0,\cdots, x_{\nu - 1})), \cdots , f_c((x_0,\cdots, x_{\nu - 1}))) /// /// where: /// /// 1. v ∈ 𝔽 where 𝔽 is a finite field. -/// 2. f_i are multi-linear polynomials i.e., polynomials in 𝔽[X_i, \cdots ,X_{\nu - 1}] with degree +/// 2. f_i are multi-linear polynomials i.e., polynomials in 𝔽[X_0, \cdots ,X_{\nu - 1}] with degree /// at most one in each variable. /// 3. g is a multivariate polynomial with degree at most d in each variable. /// @@ -59,9 +59,9 @@ mod error; /// of degree at most `d`. Thus, the Prover in each round sends `d + 1` values, either /// the coefficients or the evaluations of `s_i`. /// -/// 2. The Prover has each `f_i` in its evaluation form over the hyper-cube \{0 , 1\}^{2^{\nu}}. +/// 2. The Prover has each `f_i` in its evaluation form over the hyper-cube \{0 , 1\}^{\nu}. /// -/// 3. An optimization is for the Prover to not send `s_i(0)` as it can be recoverd from the current +/// 3. An optimization is for the Prover to not send `s_i(0)` as it can be recovered from the current /// reduced claim s_{i - 1}(r_{i - 1}) using the relation s_{i}(0) = s_{i}(1) - s_{i - 1}(r_{i - 1}). /// This also means that the Verifier can skip point 4.b. pub struct SumCheckProver @@ -110,7 +110,7 @@ where pub fn prove( &self, claim: E, - mls: &mut [MultiLinear], + mls: &mut [MultiLinearPoly], num_rounds: usize, coin: &mut C, //) -> Result<(RoundClaim, Proof), Error> { @@ -241,7 +241,7 @@ where /// the current evaluation at `x` in {2, ... , d_max}. fn sumcheck_round( polynomial: &dyn CompositionPolynomial, - mls: &mut [MultiLinear], + mls: &mut [MultiLinearPoly], ) -> RoundProof { let num_ml = mls.len(); let num_vars = mls[0].num_variables(); @@ -258,7 +258,7 @@ fn sumcheck_round( evals_one[j] = ml.evaluations()[(i << 1) + 1]; } - let mut total_evals = vec![E::ZERO; polynomial.max_degree()]; + let mut total_evals = vec![E::ZERO; polynomial.max_degree() as usize]; total_evals[0] = polynomial.evaluate(&evals_one); evals_zero @@ -278,7 +278,7 @@ fn sumcheck_round( total_evals }); - let evaluations = total_evals.fold(vec![E::ZERO; polynomial.max_degree()], |mut acc, evals| { + let evaluations = total_evals.fold(vec![E::ZERO; polynomial.max_degree() as usize], |mut acc, evals| { acc.iter_mut().zip(evals.iter()).for_each(|(a, ev)| *a += *ev); acc }); diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index 1886b237fa..09485ffa82 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -92,17 +92,17 @@ where round_proofs, } = proof; - let mut claimed_evaluation = claim; + let mut round_claim = claim; let mut evaluation_point = vec![]; for round_proof in round_proofs { let partial_evals = round_proof.poly_evals.clone(); coin.reseed(H::hash_elements(&partial_evals)); - let evals = round_proof.to_evals(claimed_evaluation); + let evals = round_proof.to_evals(round_claim); let r = coin.draw().map_err(|_| Error::FailedToGenerateChallenge)?; let reduced_evaluation = self.eval_domain.evaluate(&evals, r); - claimed_evaluation = reduced_evaluation; + round_claim = reduced_evaluation; evaluation_point.push(r); } @@ -111,7 +111,7 @@ where return Err(Error::WrongOpeningPoint); } let query = self.final_query_builder.build_query(&openings_claim, &evaluation_point); - if self.composition_poly.evaluate(&query) != claimed_evaluation { + if self.composition_poly.evaluate(&query) != round_claim { Err(Error::FinalEvaluationCheckFailed) } else { Ok(openings_claim) @@ -125,22 +125,22 @@ where /// Contains the logic for building the final query made to the virtual polynomial. /// /// During the last step of the sum-check protocol, the Verifier must evaluate the virtual -/// polynomial at a random point `(r_0, ... ,r_{\nu - 1})`. To do this, the Verifier asks the Prover -/// for the openings of the mult-linear oracles at `(r_0, ... ,r_{\nu - 1})` i.e., +/// polynomial at a random point `(r_0, ... ,r_{\nu - 1})`. To do this, the Verifier asks +/// the Prover for the openings of the mult-linear oracles at `(r_0, ... ,r_{\nu - 1})` i.e., /// `v_i = f_i(r_0, ... ,r_{\nu - 1})`. The Verifier then evaluates `g(v_0, ... , v_{\nu - 1})` and /// compares it to the reduced claim resulting from the round proofs and challenges. /// At this point, for the Verifier to accept the proof, it needs to check that indeed -/// `v_i = f_i(r_0, ... ,r_{\nu - 1})`, this is the exact content of [FinalOpeningClaim], which can -/// be either answered by a direct query to the oracles (i.e., in the complied protocol this would -/// be answered with an opening proof against the commitment) or through further interaction (as -/// in the case of the GKR protocol). +/// `v_i = f_i(r_0, ... ,r_{\nu - 1})`, this is the exact content of [`FinalOpeningClaim`], which +/// can be either answered by a direct query to the oracles (i.e., in the compiled protocol this +/// would be answered with an opening proof against the commitment) or through further interaction +/// (as in the case of the GKR protocol). /// -/// The purpose of [FinalQueryBuilder] is to abstract the logic for evaluating multi-linears which +/// The purpose of [`FinalQueryBuilder`] is to abstract the logic for evaluating multi-linears which /// the Verifier can do by herself. For example, this is the case for periodic columns where given -/// `(r_0, ... ,r_{\nu - 1})` the Verifier can evaluate `f_i(r_0, ... ,r_{\nu - 1})` un-assisted. +/// `(r_0, ... ,r_{\nu - 1})` the Verifier can evaluate `f_i(r_0, ... ,r_{\nu - 1})` unassisted. /// /// In the case where there are no `f_i(r_0, ... ,r_{\nu - 1})` which can be computed by the -/// Verifier alone, the output of [Self::build_query] will be just the provided openings. +/// Verifier alone, the output of [`Self::build_query`] will be just the provided openings. pub trait FinalQueryBuilder { type Field: FieldElement; From 7537be3f01112b5c076099319889a00cc194cefd Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:27:04 +0200 Subject: [PATCH 10/32] chore: nits and renaming --- .../src/trace/virtual_bus/multilinear/mod.rs | 15 +---- .../src/trace/virtual_bus/sum_check/mod.rs | 37 ++++++------- .../trace/virtual_bus/sum_check/prover/mod.rs | 54 +++++++++--------- .../virtual_bus/sum_check/verifier/mod.rs | 55 ++++++++----------- 4 files changed, 68 insertions(+), 93 deletions(-) diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index 7981a5c83e..6a56d6bfcb 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -57,23 +57,10 @@ impl MultiLinearPoly { inner_product(&self.evaluations, &tensored_query) } - /// Computes f(r_0, y_1, ..., y_{ν - 1}) using the linear interpolation formula - /// (1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1}). The resulting - /// returned multi-linear is defined over a domain of half the size. - pub fn bind(&self, round_challenge: E) -> Self { - let mut result = vec![E::ZERO; 1 << (self.num_variables() - 1)]; - for (i, res) in result.iter_mut().enumerate() { - *res = self.evaluations[i << 1] - + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); - } - Self::from_evaluations(result.to_owned()) - .expect("should not fail given that it is a multi-linear") - } - /// Computes f(r_0, y_1, ..., y_{ν - 1}) using the linear interpolation formula /// (1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1}) and assigns /// the resulting multi-linear, defined over a domain of half the size, to `self`. - pub fn bind_assign(&mut self, round_challenge: E) { + pub fn bind(&mut self, round_challenge: E) { let mut result = vec![E::ZERO; 1 << (self.num_variables() - 1)]; for (i, res) in result.iter_mut().enumerate() { *res = self.evaluations[i << 1] diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index 968789f502..34e15400e7 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -11,12 +11,12 @@ pub use verifier::SumCheckVerifier; /// A sum-check round proof. /// -/// This represents the polynomial sent by the Prover during one of the rounds of the sum-check -/// protocol. The polynomial is in evaluation form and excludes the zero-th coefficient as -/// the Verifier can recover it from the first coefficient and the current reduced claim. +/// This represents the partial polynomial sent by the Prover during one of the rounds of the +/// sum-check protocol. The polynomial is in evaluation form and excludes the zero-th coefficient +/// as the Verifier can recover it from the first coefficient and the current reduced claim. #[derive(Debug, Clone)] pub struct RoundProof { - pub poly_evals: Vec, + pub partial_poly_evals: Vec, } impl RoundProof { @@ -26,10 +26,10 @@ impl RoundProof { let mut result = vec![]; // s(0) + s(1) = claim - let c0 = claim - self.poly_evals[0]; + let c0 = claim - self.partial_poly_evals[0]; result.push(c0); - result.extend_from_slice(&self.poly_evals); + result.extend_from_slice(&self.partial_poly_evals); result } } @@ -88,16 +88,16 @@ pub fn reduce_claim( pub struct FinalOpeningClaim { pub evaluation_point: Vec, pub openings: Vec, - // TODO: add a Vec to give more information on which main trace columns we would like - // to open. } #[cfg(test)] mod test { + use core::marker::PhantomData; + use super::{ domain::EvaluationDomain, prover::SumCheckProver, - verifier::{FinalQueryBuilder, SumCheckVerifier}, + verifier::{CompositionPolyQueryBuilder, SumCheckVerifier}, }; use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinearPoly}; use alloc::vec::Vec; @@ -141,7 +141,7 @@ mod test { let proof = prover.prove(claim, &mut mls, num_variables, &mut coin).unwrap(); // Verifier - let plain_query_builder = PlainQueryBuilder; + let plain_query_builder = ProjectionPolyQueryBuilder::default(); let verifier = SumCheckVerifier::new(virtual_poly, plain_query_builder); let mut coin = RpoRandomCoin::new(Word::default()); let result = verifier.verify(claim, proof, &mut coin); @@ -167,7 +167,7 @@ mod test { let proof = prover.prove(claim, &mut mls, num_variables, &mut coin).unwrap(); // Verifier - let plain_query_builder = PlainQueryBuilder; + let plain_query_builder = ProjectionPolyQueryBuilder::default(); let verifier = SumCheckVerifier::new(virtual_poly, plain_query_builder); let mut coin = RpoRandomCoin::new(Word::default()); let result = verifier.verify(claim, proof, &mut coin); @@ -197,7 +197,7 @@ mod test { let proof = prover.prove(claim, &mut mls, num_variables, &mut coin).unwrap(); // Verifier - let plain_query_builder = PlainQueryBuilder; + let plain_query_builder = ProjectionPolyQueryBuilder::default(); let verifier = SumCheckVerifier::new(virtual_poly, plain_query_builder); let mut coin = RpoRandomCoin::new(Word::default()); let result = verifier.verify(claim, proof, &mut coin); @@ -205,16 +205,15 @@ mod test { assert!(result.is_err()) } - struct PlainQueryBuilder; - - impl FinalQueryBuilder for PlainQueryBuilder { - type Field = Felt; + #[derive(Default)] + struct ProjectionPolyQueryBuilder(PhantomData); + impl CompositionPolyQueryBuilder for ProjectionPolyQueryBuilder { fn build_query( &self, - openings_claim: &super::FinalOpeningClaim, - _evaluation_point: &[Self::Field], - ) -> Vec { + openings_claim: &super::FinalOpeningClaim, + _evaluation_point: &[E], + ) -> Vec { openings_claim.openings.to_vec() } } diff --git a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs index 83d595b908..68dbb0f845 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinearPoly}; use core::marker::PhantomData; -use vm_core::{FieldElement, StarkField}; +use vm_core::FieldElement; use winter_prover::crypto::{ElementHasher, RandomCoin}; mod error; @@ -64,25 +64,23 @@ mod error; /// 3. An optimization is for the Prover to not send `s_i(0)` as it can be recovered from the current /// reduced claim s_{i - 1}(r_{i - 1}) using the relation s_{i}(0) = s_{i}(1) - s_{i - 1}(r_{i - 1}). /// This also means that the Verifier can skip point 4.b. -pub struct SumCheckProver +pub struct SumCheckProver where - B: StarkField, - E: FieldElement, - C: RandomCoin, - H: ElementHasher, + E: FieldElement, + C: RandomCoin, + H: ElementHasher, { composition_poly: P, eval_domain: EvaluationDomain, _challenger: PhantomData, } -impl SumCheckProver +impl SumCheckProver where - B: StarkField, - E: FieldElement, + E: FieldElement, P: CompositionPolynomial, - C: RandomCoin, - H: ElementHasher, + C: RandomCoin, + H: ElementHasher, { /// Constructs a new [SumCheckProver] given a multivariate composition polynomial. /// The multivariate composition polynomial corresponds to the `g` polynomial in the @@ -113,7 +111,6 @@ where mls: &mut [MultiLinearPoly], num_rounds: usize, coin: &mut C, - //) -> Result<(RoundClaim, Proof), Error> { ) -> Result, Error> { // there should be at least one multi-linear polynomial provided if mls.is_empty() { @@ -151,9 +148,9 @@ where // run the first round of the protocol let mut round_proof = sumcheck_round(&self.composition_poly, mls); - round_proofs.push(round_proof); // reseed with the s_0 polynomial - coin.reseed(H::hash_elements(&round_proofs[0].poly_evals)); + coin.reseed(H::hash_elements(&round_proof.partial_poly_evals)); + round_proofs.push(round_proof); for i in 1..num_rounds { // generate random challenge r_i for the i-th round @@ -168,24 +165,24 @@ where ); // fold each multi-linear using the round challenge - mls.iter_mut().for_each(|ml| ml.bind_assign(round_challenge)); + mls.iter_mut().for_each(|ml| ml.bind(round_challenge)); // run the i-th round of the protocol using the folded multi-linears for the new reduced // claim. This basically computes the s_i polynomial. round_proof = sumcheck_round(&self.composition_poly, mls); - round_proofs.push(round_proof); // update the claim current_round_claim = new_round_claim; // reseed with the s_i polynomial - coin.reseed(H::hash_elements(&round_proofs[i].poly_evals)); + coin.reseed(H::hash_elements(&round_proof.partial_poly_evals)); + round_proofs.push(round_proof); } // generate the last random challenge let round_challenge = coin.draw().map_err(|_| Error::FailedToGenerateChallenge)?; // fold each multi-linear using the last random challenge - mls.iter_mut().for_each(|ml| ml.bind_assign(round_challenge)); + mls.iter_mut().for_each(|ml| ml.bind(round_challenge)); let openings = { if mls[0].num_evaluations() != 1 { @@ -240,7 +237,7 @@ where /// added to each multi-linear to compute the evaluation at the next point, and `evals_x` to hold /// the current evaluation at `x` in {2, ... , d_max}. fn sumcheck_round( - polynomial: &dyn CompositionPolynomial, + composition_poly: &dyn CompositionPolynomial, mls: &mut [MultiLinearPoly], ) -> RoundProof { let num_ml = mls.len(); @@ -258,8 +255,8 @@ fn sumcheck_round( evals_one[j] = ml.evaluations()[(i << 1) + 1]; } - let mut total_evals = vec![E::ZERO; polynomial.max_degree() as usize]; - total_evals[0] = polynomial.evaluate(&evals_one); + let mut total_evals = vec![E::ZERO; composition_poly.max_degree() as usize]; + total_evals[0] = composition_poly.evaluate(&evals_one); evals_zero .iter() @@ -273,17 +270,20 @@ fn sumcheck_round( evals_x.iter_mut().zip(deltas.iter()).for_each(|(evx, delta)| { *evx += *delta; }); - *e = polynomial.evaluate(&evals_x); + *e = composition_poly.evaluate(&evals_x); }); total_evals }); - let evaluations = total_evals.fold(vec![E::ZERO; polynomial.max_degree() as usize], |mut acc, evals| { - acc.iter_mut().zip(evals.iter()).for_each(|(a, ev)| *a += *ev); - acc - }); + let evaluations = total_evals.fold( + vec![E::ZERO; composition_poly.max_degree() as usize], + |mut acc, evals| { + acc.iter_mut().zip(evals.iter()).for_each(|(a, ev)| *a += *ev); + acc + }, + ); RoundProof { - poly_evals: evaluations, + partial_poly_evals: evaluations, } } diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index 09485ffa82..5c0f98cbaa 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -3,7 +3,7 @@ use super::{domain::EvaluationDomain, FinalOpeningClaim, Proof}; use crate::trace::virtual_bus::multilinear::CompositionPolynomial; use alloc::vec::Vec; use core::marker::PhantomData; -use vm_core::{FieldElement, StarkField}; +use vm_core::FieldElement; use winter_prover::crypto::{ElementHasher, RandomCoin}; mod error; @@ -19,14 +19,13 @@ mod error; /// of the protocol (i.e., commitments) for their evaluations the random point /// `(r_0, ... , r_{\nu - 1})` where $\nu$ is the number of rounds of the sum-check protocol and /// `r_i` is the randomness sent by the Verifier at each round. -pub struct SumCheckVerifier +pub struct SumCheckVerifier where - B: StarkField, - E: FieldElement, - C: RandomCoin, + E: FieldElement, + C: RandomCoin, P: CompositionPolynomial, - H: ElementHasher, - V: FinalQueryBuilder, + H: ElementHasher, + V: CompositionPolyQueryBuilder, { composition_poly: P, eval_domain: EvaluationDomain, @@ -34,14 +33,13 @@ where _challenger: PhantomData, } -impl SumCheckVerifier +impl SumCheckVerifier where - B: StarkField, - E: FieldElement, - C: RandomCoin, + E: FieldElement, + C: RandomCoin, P: CompositionPolynomial, - H: ElementHasher, - V: FinalQueryBuilder, + H: ElementHasher, + V: CompositionPolyQueryBuilder, { /// Create a new [SumCheckVerifier] from a composition polynomial and final query builder. pub fn new(composition_poly: P, final_query_builder: V) -> Self { @@ -95,14 +93,13 @@ where let mut round_claim = claim; let mut evaluation_point = vec![]; for round_proof in round_proofs { - let partial_evals = round_proof.poly_evals.clone(); + let partial_evals = round_proof.partial_poly_evals.clone(); coin.reseed(H::hash_elements(&partial_evals)); let evals = round_proof.to_evals(round_claim); let r = coin.draw().map_err(|_| Error::FailedToGenerateChallenge)?; - let reduced_evaluation = self.eval_domain.evaluate(&evals, r); - round_claim = reduced_evaluation; + round_claim = self.eval_domain.evaluate(&evals, r); evaluation_point.push(r); } @@ -124,9 +121,9 @@ where /// Contains the logic for building the final query made to the virtual polynomial. /// -/// During the last step of the sum-check protocol, the Verifier must evaluate the virtual -/// polynomial at a random point `(r_0, ... ,r_{\nu - 1})`. To do this, the Verifier asks -/// the Prover for the openings of the mult-linear oracles at `(r_0, ... ,r_{\nu - 1})` i.e., +/// During the last step of the sum-check protocol, the Verifier must evaluate the composed +/// multilinear polynomials at a random point `(r_0, ... ,r_{\nu - 1})`. To do this, the Verifier +/// asks the Prover for the openings of the mult-linear oracles at `(r_0, ... ,r_{\nu - 1})` i.e., /// `v_i = f_i(r_0, ... ,r_{\nu - 1})`. The Verifier then evaluates `g(v_0, ... , v_{\nu - 1})` and /// compares it to the reduced claim resulting from the round proofs and challenges. /// At this point, for the Verifier to accept the proof, it needs to check that indeed @@ -135,18 +132,10 @@ where /// would be answered with an opening proof against the commitment) or through further interaction /// (as in the case of the GKR protocol). /// -/// The purpose of [`FinalQueryBuilder`] is to abstract the logic for evaluating multi-linears which -/// the Verifier can do by herself. For example, this is the case for periodic columns where given -/// `(r_0, ... ,r_{\nu - 1})` the Verifier can evaluate `f_i(r_0, ... ,r_{\nu - 1})` unassisted. -/// -/// In the case where there are no `f_i(r_0, ... ,r_{\nu - 1})` which can be computed by the -/// Verifier alone, the output of [`Self::build_query`] will be just the provided openings. -pub trait FinalQueryBuilder { - type Field: FieldElement; - - fn build_query( - &self, - openings_claim: &FinalOpeningClaim, - evaluation_point: &[Self::Field], - ) -> Vec; +/// The purpose of [`CompositionPolyQueryBuilder`] is to abstract the logic for evaluating the +/// multi-linear polynomials that the Verifier can do by herself. For example, this is the case +/// for periodic columns where given `(r_0, ... ,r_{\nu - 1})` the Verifier can evaluate +/// it at `(r_0, ... ,r_{\nu - 1})` unassisted. +pub trait CompositionPolyQueryBuilder { + fn build_query(&self, openings_claim: &FinalOpeningClaim, evaluation_point: &[E]) -> Vec; } From 87e3c4945e21c8a0c70873fcb199659ab4612a3f Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:54:05 +0200 Subject: [PATCH 11/32] refactor: remove Option for FinalOpeningClaim --- .../src/trace/virtual_bus/sum_check/mod.rs | 35 ++++++--- .../trace/virtual_bus/sum_check/prover/mod.rs | 75 +++++++++++++------ .../virtual_bus/sum_check/verifier/mod.rs | 49 +++++++----- 3 files changed, 110 insertions(+), 49 deletions(-) diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index 34e15400e7..2dc39e4a96 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -44,7 +44,7 @@ impl RoundProof { /// multi-linears. #[derive(Debug, Clone)] pub struct Proof { - pub openings: Option>, + pub openings_claim: FinalOpeningClaim, pub round_proofs: Vec>, } @@ -96,11 +96,11 @@ mod test { use super::{ domain::EvaluationDomain, - prover::SumCheckProver, + prover::{FinalClaimBuilder, SumCheckProver}, verifier::{CompositionPolyQueryBuilder, SumCheckVerifier}, }; use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinearPoly}; - use alloc::vec::Vec; + use alloc::{borrow::ToOwned, vec::Vec}; use test_utils::rand::{rand_array, rand_value, rand_vector}; use vm_core::{crypto::random::RpoRandomCoin, Felt, FieldElement, Word, ONE, ZERO}; @@ -136,9 +136,9 @@ mod test { let virtual_poly = ProjectionComposition::new(0); // Prover - let prover = SumCheckProver::new(virtual_poly); + let prover = SumCheckProver::new(virtual_poly, PlainClaimBuilder); let mut coin = RpoRandomCoin::new(Word::default()); - let proof = prover.prove(claim, &mut mls, num_variables, &mut coin).unwrap(); + let proof = prover.prove(claim, &mut mls, &mut coin).unwrap(); // Verifier let plain_query_builder = ProjectionPolyQueryBuilder::default(); @@ -162,9 +162,9 @@ mod test { let virtual_poly = ProductComposition; // Prover - let prover = SumCheckProver::new(virtual_poly); + let prover = SumCheckProver::new(virtual_poly, PlainClaimBuilder); let mut coin = RpoRandomCoin::new(Word::default()); - let proof = prover.prove(claim, &mut mls, num_variables, &mut coin).unwrap(); + let proof = prover.prove(claim, &mut mls, &mut coin).unwrap(); // Verifier let plain_query_builder = ProjectionPolyQueryBuilder::default(); @@ -192,9 +192,9 @@ mod test { let virtual_poly = ProductComposition; // Prover - let prover = SumCheckProver::new(virtual_poly); + let prover = SumCheckProver::new(virtual_poly, PlainClaimBuilder); let mut coin = RpoRandomCoin::new(Word::default()); - let proof = prover.prove(claim, &mut mls, num_variables, &mut coin).unwrap(); + let proof = prover.prove(claim, &mut mls, &mut coin).unwrap(); // Verifier let plain_query_builder = ProjectionPolyQueryBuilder::default(); @@ -205,6 +205,23 @@ mod test { assert!(result.is_err()) } + struct PlainClaimBuilder; + + impl FinalClaimBuilder for PlainClaimBuilder { + type Field = Felt; + + fn build_claim( + &self, + openings: Vec, + evaluation_point: &[Self::Field], + ) -> super::FinalOpeningClaim { + super::FinalOpeningClaim { + evaluation_point: evaluation_point.to_owned(), + openings, + } + } + } + #[derive(Default)] struct ProjectionPolyQueryBuilder(PhantomData); diff --git a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs index 68dbb0f845..f076c85d70 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs @@ -3,6 +3,7 @@ use super::{ domain::EvaluationDomain, reduce_claim, FinalOpeningClaim, Proof, RoundClaim, RoundProof, }; use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinearPoly}; +use alloc::vec::Vec; use core::marker::PhantomData; use vm_core::FieldElement; use winter_prover::crypto::{ElementHasher, RandomCoin}; @@ -64,34 +65,38 @@ mod error; /// 3. An optimization is for the Prover to not send `s_i(0)` as it can be recovered from the current /// reduced claim s_{i - 1}(r_{i - 1}) using the relation s_{i}(0) = s_{i}(1) - s_{i - 1}(r_{i - 1}). /// This also means that the Verifier can skip point 4.b. -pub struct SumCheckProver +pub struct SumCheckProver where E: FieldElement, C: RandomCoin, H: ElementHasher, + V: FinalClaimBuilder, { composition_poly: P, eval_domain: EvaluationDomain, + final_claim_builder: V, _challenger: PhantomData, } -impl SumCheckProver +impl SumCheckProver where E: FieldElement, P: CompositionPolynomial, C: RandomCoin, H: ElementHasher, + V: FinalClaimBuilder, { /// Constructs a new [SumCheckProver] given a multivariate composition polynomial. /// The multivariate composition polynomial corresponds to the `g` polynomial in the /// description of the [SumCheckProver] struct. - pub fn new(composition_poly: P) -> Self { + pub fn new(composition_poly: P, final_claim_builder: V) -> Self { let max_degree = composition_poly.max_degree(); let eval_domain = EvaluationDomain::new(max_degree); Self { composition_poly, eval_domain, + final_claim_builder, _challenger: PhantomData, } } @@ -109,9 +114,34 @@ where &self, claim: E, mls: &mut [MultiLinearPoly], - num_rounds: usize, coin: &mut C, ) -> Result, Error> { + let num_rounds = mls[0].num_variables(); + let ( + RoundClaim { + eval_point, + claim: _claim, + }, + round_proofs, + ) = self.prove_rounds(claim, mls, num_rounds, coin)?; + + let openings = mls.iter_mut().map(|ml| ml.evaluations()[0]).collect(); + let openings_claim = self.final_claim_builder.build_claim(openings, &eval_point); + + Ok(Proof { + openings_claim, + round_proofs, + }) + } + + /// Proves a round of the sum-check protocol. + pub fn prove_rounds( + &self, + claim: E, + mls: &mut [MultiLinearPoly], + num_rounds: usize, + coin: &mut C, + ) -> Result<(RoundClaim, Vec>), Error> { // there should be at least one multi-linear polynomial provided if mls.is_empty() { return Err(Error::NoMlsProvided); @@ -129,7 +159,7 @@ where } // there should at least be one variable for the protocol to be non-trivial - if ml_variables < 2 { + if ml_variables < 1 { return Err(Error::AtLeastOneVariable); } @@ -184,24 +214,13 @@ where // fold each multi-linear using the last random challenge mls.iter_mut().for_each(|ml| ml.bind(round_challenge)); - let openings = { - if mls[0].num_evaluations() != 1 { - None - } else { - let openings = mls.iter_mut().map(|ml| ml.evaluations()[0]).collect(); - let mut evaluation_point = current_round_claim.eval_point; - evaluation_point.push(round_challenge); - Some(FinalOpeningClaim { - evaluation_point, - openings, - }) - } - }; - - Ok(Proof { - openings, - round_proofs, - }) + let round_claim = reduce_claim( + &self.eval_domain, + &round_proofs[num_rounds - 1], + current_round_claim, + round_challenge, + ); + Ok((round_claim, round_proofs)) } } @@ -287,3 +306,13 @@ fn sumcheck_round( partial_poly_evals: evaluations, } } + +pub trait FinalClaimBuilder { + type Field: FieldElement; + + fn build_claim( + &self, + openings: alloc::vec::Vec, + evaluation_point: &[Self::Field], + ) -> FinalOpeningClaim; +} diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index 5c0f98cbaa..b91e05390e 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -1,5 +1,5 @@ use self::error::Error; -use super::{domain::EvaluationDomain, FinalOpeningClaim, Proof}; +use super::{domain::EvaluationDomain, FinalOpeningClaim, Proof, RoundClaim, RoundProof}; use crate::trace::virtual_bus::multilinear::CompositionPolynomial; use alloc::vec::Vec; use core::marker::PhantomData; @@ -12,11 +12,11 @@ mod error; /// protocol verifier. The protocol is described in [`SumCheckProver`]. /// The sum-check Verifier is composed of two parts: /// -/// 1. Multi-round interaction where it sends challenges and receives polynomials. For each +/// 1. A multi-round interaction where it sends challenges and receives polynomials. For each /// polynomial received it uses the sent randomness to reduce the current claim to a new one. /// /// 2. A final round where the Verifier queries the multi-linear oracles it received at the outset -/// of the protocol (i.e., commitments) for their evaluations the random point +/// of the protocol (i.e., commitments) for their evaluations at the random point /// `(r_0, ... , r_{\nu - 1})` where $\nu$ is the number of rounds of the sum-check protocol and /// `r_i` is the randomness sent by the Verifier at each round. pub struct SumCheckVerifier @@ -86,10 +86,34 @@ where coin: &mut C, ) -> Result, Error> { let Proof { - openings: openings_claim, + openings_claim, round_proofs, } = proof; + let RoundClaim { + eval_point: evaluation_point, + claim: claimed_evaluation, + } = self.verify_rounds(claim, round_proofs, coin)?; + + if openings_claim.evaluation_point != evaluation_point { + return Err(Error::WrongOpeningPoint); + } + let query = self.final_query_builder.build_query(&openings_claim, &evaluation_point); + + if self.composition_poly.evaluate(&query) != claimed_evaluation { + Err(Error::FinalEvaluationCheckFailed) + } else { + Ok(openings_claim) + } + } + + /// Verifies a round of the sum-check protocol. + pub fn verify_rounds( + &self, + claim: E, + round_proofs: Vec>, + coin: &mut C, + ) -> Result, Error> { let mut round_claim = claim; let mut evaluation_point = vec![]; for round_proof in round_proofs { @@ -103,19 +127,10 @@ where evaluation_point.push(r); } - if let Some(openings_claim) = openings_claim { - if openings_claim.evaluation_point != evaluation_point { - return Err(Error::WrongOpeningPoint); - } - let query = self.final_query_builder.build_query(&openings_claim, &evaluation_point); - if self.composition_poly.evaluate(&query) != round_claim { - Err(Error::FinalEvaluationCheckFailed) - } else { - Ok(openings_claim) - } - } else { - Err(Error::NoOpeningsProvided) - } + Ok(RoundClaim { + eval_point: evaluation_point, + claim: round_claim, + }) } } From 720ac33060faed31eccfb92e2d9c0d107dbc4a77 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 10 Apr 2024 07:01:41 +0200 Subject: [PATCH 12/32] chore: namings comments and misc. --- processor/src/trace/virtual_bus/sum_check/domain.rs | 8 ++++---- processor/src/trace/virtual_bus/sum_check/mod.rs | 8 +++----- .../src/trace/virtual_bus/sum_check/prover/mod.rs | 11 +++++++++++ .../src/trace/virtual_bus/sum_check/verifier/mod.rs | 12 ++++++++---- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/processor/src/trace/virtual_bus/sum_check/domain.rs b/processor/src/trace/virtual_bus/sum_check/domain.rs index 3f86122924..25e9db4f59 100644 --- a/processor/src/trace/virtual_bus/sum_check/domain.rs +++ b/processor/src/trace/virtual_bus/sum_check/domain.rs @@ -18,7 +18,7 @@ where { pub fn new(max_degree: u32) -> Self { let interpolation_points: Vec = (0..=max_degree).map(E::from).collect(); - let barycentric_weights = barycentric_weights_denominators(&interpolation_points); + let barycentric_weights = barycentric_weights(&interpolation_points); Self { interpolation_points, @@ -32,12 +32,12 @@ where } /// Computes the barycentric weights for a set of interpolation points. -pub fn barycentric_weights_denominators(points: &[E]) -> Vec { +pub fn barycentric_weights(points: &[E]) -> Vec { let n = points.len(); - let tmp = (0..n) + let denominators = (0..n) .map(|i| (0..n).filter(|&j| j != i).fold(E::ONE, |acc, j| acc * (points[i] - points[j]))) .collect::>(); - batch_inversion(&tmp) + batch_inversion(&denominators) } /// Computes the value of the polynomial with minimal degree interpolating the set `{(x_i, y_i)}` diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index 2dc39e4a96..f27d0c79f5 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -92,8 +92,6 @@ pub struct FinalOpeningClaim { #[cfg(test)] mod test { - use core::marker::PhantomData; - use super::{ domain::EvaluationDomain, prover::{FinalClaimBuilder, SumCheckProver}, @@ -223,10 +221,10 @@ mod test { } #[derive(Default)] - struct ProjectionPolyQueryBuilder(PhantomData); + struct ProjectionPolyQueryBuilder; - impl CompositionPolyQueryBuilder for ProjectionPolyQueryBuilder { - fn build_query( + impl CompositionPolyQueryBuilder for ProjectionPolyQueryBuilder { + fn build_query( &self, openings_claim: &super::FinalOpeningClaim, _evaluation_point: &[E], diff --git a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs index f076c85d70..2f6cf12709 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs @@ -105,6 +105,17 @@ where /// a number of rounds `num_rounds`, computes `num_rounds` iterations of the sum-check protocol /// starting from claim `claim`. /// + /// More specifically, executes the sum-check protocol for the following relation + /// + /// v = \sum_{(x_0,\cdots, x_{\nu - 1}) \in \{0 , 1\}^{\nu}} + /// g(f_0((x_0,\cdots, x_{\nu - 1})), \cdots , f_c((x_0,\cdots, x_{\nu - 1}))) + /// + /// where: + /// + /// 1. `claim` is v. + /// 2. `mls` is [f_0, ..., f_c]. + /// 3. `self.composition_poly` is g. + /// /// # Errors /// Returns an error if: /// - No multi-linears were provided. diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index b91e05390e..46ac4223bc 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -25,7 +25,7 @@ where C: RandomCoin, P: CompositionPolynomial, H: ElementHasher, - V: CompositionPolyQueryBuilder, + V: CompositionPolyQueryBuilder, { composition_poly: P, eval_domain: EvaluationDomain, @@ -39,7 +39,7 @@ where C: RandomCoin, P: CompositionPolynomial, H: ElementHasher, - V: CompositionPolyQueryBuilder, + V: CompositionPolyQueryBuilder, { /// Create a new [SumCheckVerifier] from a composition polynomial and final query builder. pub fn new(composition_poly: P, final_query_builder: V) -> Self { @@ -151,6 +151,10 @@ where /// multi-linear polynomials that the Verifier can do by herself. For example, this is the case /// for periodic columns where given `(r_0, ... ,r_{\nu - 1})` the Verifier can evaluate /// it at `(r_0, ... ,r_{\nu - 1})` unassisted. -pub trait CompositionPolyQueryBuilder { - fn build_query(&self, openings_claim: &FinalOpeningClaim, evaluation_point: &[E]) -> Vec; +pub trait CompositionPolyQueryBuilder { + fn build_query( + &self, + openings_claim: &FinalOpeningClaim, + evaluation_point: &[E], + ) -> Vec; } From cea681ad8b620d7755131ed4a703615e695c7f5f Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:06:54 +0200 Subject: [PATCH 13/32] wip: move to coefficient form --- processor/src/trace/virtual_bus/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/processor/src/trace/virtual_bus/mod.rs b/processor/src/trace/virtual_bus/mod.rs index c95e0f4ba8..76af154545 100644 --- a/processor/src/trace/virtual_bus/mod.rs +++ b/processor/src/trace/virtual_bus/mod.rs @@ -1,3 +1,4 @@ +mod univariate; mod multilinear; mod sum_check; pub use sum_check::{SumCheckProver, SumCheckVerifier}; From 0fed9cd7e487806d4b7759b6c24f337d62b33c87 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:02:35 +0200 Subject: [PATCH 14/32] feat: move sum-check round polys to coefficient form representation --- processor/src/trace/virtual_bus/mod.rs | 2 +- .../src/trace/virtual_bus/sum_check/mod.rs | 77 ++++--------------- .../trace/virtual_bus/sum_check/prover/mod.rs | 50 ++++++------ .../virtual_bus/sum_check/verifier/mod.rs | 16 ++-- 4 files changed, 43 insertions(+), 102 deletions(-) diff --git a/processor/src/trace/virtual_bus/mod.rs b/processor/src/trace/virtual_bus/mod.rs index 76af154545..173ad4a0c2 100644 --- a/processor/src/trace/virtual_bus/mod.rs +++ b/processor/src/trace/virtual_bus/mod.rs @@ -1,4 +1,4 @@ -mod univariate; mod multilinear; mod sum_check; +mod univariate; pub use sum_check::{SumCheckProver, SumCheckVerifier}; diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index f27d0c79f5..315a65e4c8 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -1,9 +1,7 @@ -use self::domain::EvaluationDomain; +use super::univariate::UnivariatePolyCoef; use alloc::vec::Vec; use vm_core::FieldElement; -mod domain; - mod prover; pub use prover::SumCheckProver; mod verifier; @@ -12,26 +10,12 @@ pub use verifier::SumCheckVerifier; /// A sum-check round proof. /// /// This represents the partial polynomial sent by the Prover during one of the rounds of the -/// sum-check protocol. The polynomial is in evaluation form and excludes the zero-th coefficient -/// as the Verifier can recover it from the first coefficient and the current reduced claim. +/// sum-check protocol. The polynomial is in coefficient form and excludes the coefficient for +/// the linear term as the Verifier can recover it from the other coefficients and the current +/// (reduced) claim. #[derive(Debug, Clone)] -pub struct RoundProof { - pub partial_poly_evals: Vec, -} - -impl RoundProof { - /// Completes the evaluations of the round polynomial by computing the zero-th coefficient - /// using the round claim. - pub fn to_evals(&self, claim: E) -> Vec { - let mut result = vec![]; - - // s(0) + s(1) = claim - let c0 = claim - self.partial_poly_evals[0]; - - result.push(c0); - result.extend_from_slice(&self.partial_poly_evals); - result - } +pub struct RoundProof { + pub round_poly_coefs: UnivariatePolyCoef, } /// A sum-check proof. @@ -39,9 +23,6 @@ impl RoundProof { /// Composed of the round proofs i.e., the polynomials sent by the Prover at each round as well as /// the (claimed) openings of the multi-linear oracles at the evaluation point given by the round /// challenges. -/// Openings is an [Option] as there are situations where we would like to run the sum-check -/// protocol for a certain number of rounds that is less than the number of variables of the -/// multi-linears. #[derive(Debug, Clone)] pub struct Proof { pub openings_claim: FinalOpeningClaim, @@ -58,15 +39,14 @@ pub struct RoundClaim { /// Reduces an old claim to a new claim using the round challenge. pub fn reduce_claim( - domain: &EvaluationDomain, current_poly: &RoundProof, current_round_claim: RoundClaim, round_challenge: E, ) -> RoundClaim { - // construct the round polynomial using the current claim - let poly_evals = current_poly.to_evals(current_round_claim.claim); // evaluate the round polynomial at the round challenge to obtain the new claim - let new_claim = domain.evaluate(&poly_evals, round_challenge); + let new_claim = current_poly + .round_poly_coefs + .evaluate_using_claim(¤t_round_claim.claim, &round_challenge); // update the evaluation point using the round challenge let mut new_partial_eval_point = current_round_claim.eval_point; @@ -80,10 +60,10 @@ pub fn reduce_claim( /// Represents an opening claim at an evaluation point against a batch of oracles. /// -/// After verifying [Proof], the verifier is left with a final question being whether a number -/// of oracles open to some value at some given point. This question is answered either using -/// further interaction with the Prover or using a polynomial commitment opening proof in -/// the compiled protocol. +/// After verifying [`Proof`], the verifier is left with a question on the validity of a final +/// claim on a number of oracles open to a given set of values at some given point. +/// This question is answered either using further interaction with the Prover or using +/// a polynomial commitment opening proof in the compiled protocol. #[derive(Clone, Debug)] pub struct FinalOpeningClaim { pub evaluation_point: Vec, @@ -93,36 +73,14 @@ pub struct FinalOpeningClaim { #[cfg(test)] mod test { use super::{ - domain::EvaluationDomain, prover::{FinalClaimBuilder, SumCheckProver}, verifier::{CompositionPolyQueryBuilder, SumCheckVerifier}, }; use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinearPoly}; use alloc::{borrow::ToOwned, vec::Vec}; - use test_utils::rand::{rand_array, rand_value, rand_vector}; + use test_utils::rand::rand_vector; use vm_core::{crypto::random::RpoRandomCoin, Felt, FieldElement, Word, ONE, ZERO}; - #[test] - fn test_evaluation_domain() { - let max_degree = 5; - let eval_domain = EvaluationDomain::::new(max_degree); - - let r = rand_value(); - let coefficients: [Felt; 6] = rand_array(); - - let evaluations: Vec = (0..=max_degree) - .into_iter() - .map(|x| eval(&coefficients, Felt::from(x as u8))) - .collect(); - - assert_eq!(coefficients.len(), evaluations.len()); - - let result = eval_domain.evaluate(&evaluations, r); - let expected = eval(&coefficients, r); - - assert_eq!(result, expected); - } - #[test] fn test_sum_check_sum() { let num_variables = 14; @@ -281,11 +239,4 @@ mod test { query[0] * query[1] } } - - pub fn eval(p: &[E], x: E) -> E - where - E: FieldElement, - { - p.iter().rev().fold(E::ZERO, |acc, &coeff| acc * x + coeff) - } } diff --git a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs index 2f6cf12709..7477c24d36 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs @@ -1,8 +1,9 @@ use self::error::Error; -use super::{ - domain::EvaluationDomain, reduce_claim, FinalOpeningClaim, Proof, RoundClaim, RoundProof, +use super::{reduce_claim, FinalOpeningClaim, Proof, RoundClaim, RoundProof}; +use crate::trace::virtual_bus::{ + multilinear::{CompositionPolynomial, MultiLinearPoly}, + univariate::UnivariatePolyEvals, }; -use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinearPoly}; use alloc::vec::Vec; use core::marker::PhantomData; use vm_core::FieldElement; @@ -73,8 +74,8 @@ where V: FinalClaimBuilder, { composition_poly: P, - eval_domain: EvaluationDomain, final_claim_builder: V, + _challenger: PhantomData, } @@ -90,12 +91,8 @@ where /// The multivariate composition polynomial corresponds to the `g` polynomial in the /// description of the [SumCheckProver] struct. pub fn new(composition_poly: P, final_claim_builder: V) -> Self { - let max_degree = composition_poly.max_degree(); - let eval_domain = EvaluationDomain::new(max_degree); - Self { composition_poly, - eval_domain, final_claim_builder, _challenger: PhantomData, } @@ -188,35 +185,36 @@ where }; // run the first round of the protocol - let mut round_proof = sumcheck_round(&self.composition_poly, mls); + let round_poly_evals = sumcheck_round(&self.composition_poly, mls); + let round_poly_coefs = round_poly_evals.to_poly(current_round_claim.claim); + // reseed with the s_0 polynomial - coin.reseed(H::hash_elements(&round_proof.partial_poly_evals)); - round_proofs.push(round_proof); + coin.reseed(H::hash_elements(&round_poly_coefs.coefficients)); + round_proofs.push(RoundProof { round_poly_coefs }); for i in 1..num_rounds { // generate random challenge r_i for the i-th round let round_challenge = coin.draw().map_err(|_| Error::FailedToGenerateChallenge)?; // compute the new reduced round claim - let new_round_claim = reduce_claim( - &self.eval_domain, - &round_proofs[i - 1], - current_round_claim, - round_challenge, - ); + let new_round_claim = + reduce_claim(&round_proofs[i - 1], current_round_claim, round_challenge); // fold each multi-linear using the round challenge mls.iter_mut().for_each(|ml| ml.bind(round_challenge)); // run the i-th round of the protocol using the folded multi-linears for the new reduced // claim. This basically computes the s_i polynomial. - round_proof = sumcheck_round(&self.composition_poly, mls); + let round_poly_evals = sumcheck_round(&self.composition_poly, mls); // update the claim current_round_claim = new_round_claim; + let round_poly_coefs = round_poly_evals.to_poly(current_round_claim.claim); + // reseed with the s_i polynomial - coin.reseed(H::hash_elements(&round_proof.partial_poly_evals)); + coin.reseed(H::hash_elements(&round_poly_coefs.coefficients)); + let round_proof = RoundProof { round_poly_coefs }; round_proofs.push(round_proof); } @@ -225,12 +223,8 @@ where // fold each multi-linear using the last random challenge mls.iter_mut().for_each(|ml| ml.bind(round_challenge)); - let round_claim = reduce_claim( - &self.eval_domain, - &round_proofs[num_rounds - 1], - current_round_claim, - round_challenge, - ); + let round_claim = + reduce_claim(&round_proofs[num_rounds - 1], current_round_claim, round_challenge); Ok((round_claim, round_proofs)) } } @@ -269,7 +263,7 @@ where fn sumcheck_round( composition_poly: &dyn CompositionPolynomial, mls: &mut [MultiLinearPoly], -) -> RoundProof { +) -> UnivariatePolyEvals { let num_ml = mls.len(); let num_vars = mls[0].num_variables(); let num_rounds = num_vars - 1; @@ -313,8 +307,8 @@ fn sumcheck_round( }, ); - RoundProof { - partial_poly_evals: evaluations, + UnivariatePolyEvals { + partial_evaluations: evaluations, } } diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index 46ac4223bc..6d080c2304 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -1,5 +1,5 @@ use self::error::Error; -use super::{domain::EvaluationDomain, FinalOpeningClaim, Proof, RoundClaim, RoundProof}; +use super::{FinalOpeningClaim, Proof, RoundClaim, RoundProof}; use crate::trace::virtual_bus::multilinear::CompositionPolynomial; use alloc::vec::Vec; use core::marker::PhantomData; @@ -28,8 +28,8 @@ where V: CompositionPolyQueryBuilder, { composition_poly: P, - eval_domain: EvaluationDomain, final_query_builder: V, + _field: PhantomData, _challenger: PhantomData, } @@ -43,13 +43,10 @@ where { /// Create a new [SumCheckVerifier] from a composition polynomial and final query builder. pub fn new(composition_poly: P, final_query_builder: V) -> Self { - let max_degree = composition_poly.max_degree(); - let eval_domain = EvaluationDomain::new(max_degree); - Self { composition_poly, - eval_domain, final_query_builder, + _field: PhantomData, _challenger: PhantomData, } } @@ -117,13 +114,12 @@ where let mut round_claim = claim; let mut evaluation_point = vec![]; for round_proof in round_proofs { - let partial_evals = round_proof.partial_poly_evals.clone(); - coin.reseed(H::hash_elements(&partial_evals)); - let evals = round_proof.to_evals(round_claim); + let round_poly_coefs = round_proof.round_poly_coefs.clone(); + coin.reseed(H::hash_elements(&round_poly_coefs.coefficients)); let r = coin.draw().map_err(|_| Error::FailedToGenerateChallenge)?; - round_claim = self.eval_domain.evaluate(&evals, r); + round_claim = round_proof.round_poly_coefs.evaluate_using_claim(&round_claim, &r); evaluation_point.push(r); } From cb503d1b7706dabde4cd3840cfba23d1f08cbc7b Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:11:32 +0200 Subject: [PATCH 15/32] fix: add file --- processor/src/trace/virtual_bus/univariate.rs | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 processor/src/trace/virtual_bus/univariate.rs diff --git a/processor/src/trace/virtual_bus/univariate.rs b/processor/src/trace/virtual_bus/univariate.rs new file mode 100644 index 0000000000..dfa6a818d4 --- /dev/null +++ b/processor/src/trace/virtual_bus/univariate.rs @@ -0,0 +1,239 @@ +use alloc::vec::Vec; +use vm_core::{polynom, FieldElement}; +use winter_prover::math::batch_inversion; + +/// The evaluations of a univariate polynomial of degree n at 0, 1, ..., n with the evaluation at 0 omitted. +#[derive(Clone, Debug)] +pub struct UnivariatePolyEvals { + pub(crate) partial_evaluations: Vec, +} + +impl UnivariatePolyEvals { + /// Gives the coefficient representation of a polynomial represented in evaluation form. + /// + /// Since the evaluation at 0 is omitted, we need to use the round claim to recover + /// the evaluation at 0 using the identity p(0) + p(1) = claim. + /// Now, we have that for any polynomial p(x) = c0 + c1 * x + ... + c_{n-1} * x^{n - 1}: + /// + /// 1. p(0) = c0. + /// 2. p(x) = c0 + x * q(x) where q(x) = c1 + ... + c_{n-1} * x^{n - 2}. + /// + /// This means that we can compute the evaluations of q at 1, ..., n - 1 using the evaluations + /// of p and thus reduce by 1 the size of the interpolation problem. + /// Once the coefficient of q are recovered, the c0 coefficient is appended to these and this + /// is precisely the coefficient representation of the original polynomial q. + /// Note that the coefficient of the linear term is removed as this coefficient can be recovered + /// from the remaining coefficients, again, using the round claim using the relation + /// 2 * c0 + c1 + ... c_{n - 1} = claim. + pub fn to_poly(&self, round_claim: E) -> UnivariatePolyCoef { + // construct the vector of interpolation points 1, ..., n + let n_minus_1 = self.partial_evaluations.len(); + let mut points = vec![E::BaseField::ZERO; n_minus_1]; + + // construct their inverses. These will be needed for computing the evaluations + // of the q polynomial as well as for doing the interpolation on q + points + .iter_mut() + .enumerate() + .for_each(|(i, node)| *node = E::BaseField::from(i as u32 + 1)); + let points_inv = batch_inversion(&points); + + // compute the zeroth coefficient + let c0 = round_claim - self.partial_evaluations[0]; + + // compute the evaluations of q + let mut q_evals: Vec = vec![E::ZERO; n_minus_1]; + q_evals.iter_mut().zip(self.partial_evaluations.iter()).enumerate().for_each( + |(i, (normalized, evals))| *normalized = (*evals - c0).mul_base(points_inv[i]), + ); + + // interpolate q + let q_coefs = multiply_by_inverse_vandermonde(&q_evals, &points_inv); + + // append c0 to the coefficients of q to get the coefficients of p. The linear term + // coefficient is removed as this can be recovered from the other coefficients using + // the reduced claim. + let mut coefficients = Vec::with_capacity(self.partial_evaluations.len() + 1); + coefficients.push(c0); + coefficients.extend_from_slice(&q_coefs[1..]); + + UnivariatePolyCoef { coefficients } + } +} + +/// The coefficients of a univariate polynomial of degree n with the linear term coefficient omitted. +#[derive(Clone, Debug)] +pub struct UnivariatePolyCoef { + pub(crate) coefficients: Vec, +} + +impl UnivariatePolyCoef { + /// Evaluates a polynomial at a challenge point using a round claim. + /// + /// The round claim is used to recover the coefficient of the linear term using the relation + /// 2 * c0 + c1 + ... c_{n - 1} = claim. Using the complete list of coefficients, the polynomial + /// is then evaluated using Horner's method. + pub fn evaluate_using_claim(&self, claim: &E, challenge: &E) -> E { + // recover the coefficient of the linear term + let c1 = *claim + - self.coefficients.iter().fold(E::ZERO, |acc, term| acc + *term) + - self.coefficients[0]; + + // construct the full coefficient list + let mut complete_coefficients = vec![self.coefficients[0], c1]; + complete_coefficients.extend_from_slice(&self.coefficients[1..]); + + // evaluate + polynom::eval(&complete_coefficients, *challenge) + } +} + +/// Given a (row) vector `v`, computes the vector-matrix product `v * V^{-1}` where `V` is +/// the Vandermonde matrix over the points `1, ..., n` where `n` is the length of `v`. +/// The resulting vector will then be the coefficients of the minimal interpolating polynomial +/// through the points `(i+1, v[i])` for `i` in `0, ..., n - 1` +/// +/// The naive way would be to invert the matrix `V` and then compute the vector-matrix product +/// this will cost `O(n^3)` operations and `O(n^2)` memory. We can also try Gaussian elimination +/// but this is also worst case `O(n^3)` operations and `O(n^2)` memory. +/// In the following implementation, we use the fact that the points over which we are interpolating +/// is a set of equidistant points and thus both the Vandermonde matrix and its inverse can be +/// described by sparse linear recurrence equations. +/// More specifically, we use the representation given in [1], where `V^{-1}` is represented as +/// `U * M` where: +/// +/// 1. `M` is a lower triangular matrix where its entries are given by +/// M(i, j) = M(i - 1, j) - M(i - 1, j - 1) / (i - 1) +/// with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. +/// +/// 2. `U` is an upper triangular (involutory) matrix where its entries are given by +/// U(i, j) = U(i, j - 1) - U(i - 1, j - 1) +/// with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. +/// +/// Note that the matrix indexing in the formulas above matches the one in the reference and starts +/// from 1. +/// +/// The above implies that we can do the vector-matrix multiplication in `O(n^2)` and using only +/// `O(n)` space. +/// +/// [1]: https://link.springer.com/article/10.1007/s002110050360 +fn multiply_by_inverse_vandermonde( + vector: &[E], + nodes_inv: &[E::BaseField], +) -> Vec { + let res = multiply_by_u(vector); + multiply_by_m(&res, nodes_inv) +} + +/// Multiplies a (row) vector `v` by an upper triangular matrix `U` to compute `v * U`. +/// +/// `U` is an upper triangular (involutory) matrix with its entries given by +/// U(i, j) = U(i, j - 1) - U(i - 1, j - 1) +/// with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. +fn multiply_by_u(vector: &[E]) -> Vec { + let n = vector.len(); + let mut previous_u_col = vec![E::BaseField::ZERO; n]; + previous_u_col[0] = E::BaseField::ONE; + let mut current_u_col = vec![E::BaseField::ZERO; n]; + current_u_col[0] = E::BaseField::ONE; + + let mut result: Vec = vec![E::ZERO; n]; + for (i, res) in result.iter_mut().enumerate() { + *res = vector[0]; + + for (j, v) in vector.iter().enumerate().take(i + 1).skip(1) { + let u_entry: E::BaseField = + compute_u_entry::(j, &mut previous_u_col, &mut current_u_col); + *res += v.mul_base(u_entry); + } + previous_u_col.clone_from(¤t_u_col); + } + + result +} + +/// Multiplies a (row) vector `v` by a lower triangular matrix `M` to compute `v * M`. +/// +/// `M` is a lower triangular matrix with its entries given by +/// M(i, j) = M(i - 1, j) - M(i - 1, j - 1) / (i - 1) +/// with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. +fn multiply_by_m(vector: &[E], nodes_inv: &[E::BaseField]) -> Vec { + let n = vector.len(); + let mut previous_m_col = vec![E::BaseField::ONE; n]; + let mut current_m_col = vec![E::BaseField::ZERO; n]; + current_m_col[0] = E::BaseField::ONE; + + let mut result: Vec = vec![E::ZERO; n]; + result[0] = vector.iter().fold(E::ZERO, |acc, term| acc + *term); + for (i, res) in result.iter_mut().enumerate().skip(1) { + current_m_col = vec![E::BaseField::ZERO; n]; + + for (j, v) in vector.iter().enumerate().skip(i) { + let m_entry: E::BaseField = + compute_m_entry::(j, &mut previous_m_col, &mut current_m_col, nodes_inv[j - 1]); + *res += v.mul_base(m_entry); + } + previous_m_col.clone_from(¤t_m_col); + } + + result +} + +/// Returns the j-th entry of the i-th column of matrix `U` given the values of the (i - 1)-th +/// column. The i-th column is also updated with the just computed `U(i, j)` entry. +/// +/// `U` is an upper triangular (involutory) matrix with its entries given by +/// U(i, j) = U(i, j - 1) - U(i - 1, j - 1) +/// with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. +fn compute_u_entry( + j: usize, + col_prev: &mut [E::BaseField], + col_cur: &mut [E::BaseField], +) -> E::BaseField { + let value = col_prev[j] - col_prev[j - 1]; + col_cur[j] = value; + value +} + +/// Returns the j-th entry of the i-th column of matrix `M` given the values of the (i - 1)-th +/// and the i-th columns. The i-th column is also updated with the just computed `M(i, j)` entry. +/// +/// `M` is a lower triangular matrix with its entries given by +/// M(i, j) = M(i - 1, j) - M(i - 1, j - 1) / (i - 1) +/// with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. +fn compute_m_entry( + j: usize, + col_previous: &mut [E::BaseField], + col_current: &mut [E::BaseField], + node_inv: E::BaseField, +) -> E::BaseField { + let value = col_current[j - 1] - node_inv * col_previous[j - 1]; + col_current[j] = value; + value +} + +#[test] +fn test_poly_partial() { + use vm_core::Felt; + + use test_utils::rand; + let degree = 1000; + let mut points: Vec = vec![Felt::ZERO; degree]; + points.iter_mut().enumerate().for_each(|(i, node)| *node = Felt::from(i as u32)); + + let p: Vec = rand::rand_vector(degree); + let evals = vm_core::polynom::eval_many(&p, &points); + + let mut partial_evals = evals.clone(); + partial_evals.remove(0); + + let partial_poly = UnivariatePolyEvals { + partial_evaluations: partial_evals, + }; + let claim = evals[0] + evals[1]; + let poly_coeff = partial_poly.to_poly(claim); + + let r = rand::rand_vector(1); + + assert_eq!(vm_core::polynom::eval(&p, r[0]), poly_coeff.evaluate_using_claim(&claim, &r[0])) +} From 5b734d148daace1a0a5a00a04ba248d1634b6434 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Thu, 4 Apr 2024 23:59:35 +0200 Subject: [PATCH 16/32] wip: GKR protocol for virtual bus --- processor/src/trace/mod.rs | 2 +- .../src/trace/virtual_bus/circuit/error.rs | 23 + .../src/trace/virtual_bus/circuit/mod.rs | 238 +++++++++ .../src/trace/virtual_bus/circuit/prover.rs | 471 ++++++++++++++++++ .../src/trace/virtual_bus/circuit/verifier.rs | 237 +++++++++ processor/src/trace/virtual_bus/error.rs | 7 + processor/src/trace/virtual_bus/mod.rs | 171 +++++++ .../virtual_bus/multilinear/lagrange_ker.rs | 69 +++ .../src/trace/virtual_bus/multilinear/mod.rs | 24 +- processor/src/trace/virtual_bus/prover.rs | 103 ++++ .../src/trace/virtual_bus/sub_bus/mod.rs | 14 + .../virtual_bus/sub_bus/range_checker.rs | 239 +++++++++ .../src/trace/virtual_bus/sum_check/mod.rs | 11 +- .../virtual_bus/sum_check/prover/error.rs | 4 - .../virtual_bus/sum_check/verifier/error.rs | 2 - .../virtual_bus/sum_check/verifier/mod.rs | 6 +- processor/src/trace/virtual_bus/tests.rs | 59 +++ processor/src/trace/virtual_bus/verifier.rs | 57 +++ 18 files changed, 1720 insertions(+), 17 deletions(-) create mode 100644 processor/src/trace/virtual_bus/circuit/error.rs create mode 100644 processor/src/trace/virtual_bus/circuit/mod.rs create mode 100644 processor/src/trace/virtual_bus/circuit/prover.rs create mode 100644 processor/src/trace/virtual_bus/circuit/verifier.rs create mode 100644 processor/src/trace/virtual_bus/error.rs create mode 100644 processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs create mode 100644 processor/src/trace/virtual_bus/prover.rs create mode 100644 processor/src/trace/virtual_bus/sub_bus/mod.rs create mode 100644 processor/src/trace/virtual_bus/sub_bus/range_checker.rs create mode 100644 processor/src/trace/virtual_bus/tests.rs create mode 100644 processor/src/trace/virtual_bus/verifier.rs diff --git a/processor/src/trace/mod.rs b/processor/src/trace/mod.rs index 51c176f844..071b958395 100644 --- a/processor/src/trace/mod.rs +++ b/processor/src/trace/mod.rs @@ -19,7 +19,7 @@ mod utils; pub use utils::{AuxColumnBuilder, ChipletsLengths, TraceFragment, TraceLenSummary}; mod virtual_bus; -pub use virtual_bus::{SumCheckProver, SumCheckVerifier}; +pub use virtual_bus::{VirtualBusProver, VirtualBusVerifier}; #[cfg(test)] mod tests; diff --git a/processor/src/trace/virtual_bus/circuit/error.rs b/processor/src/trace/virtual_bus/circuit/error.rs new file mode 100644 index 0000000000..64390684fd --- /dev/null +++ b/processor/src/trace/virtual_bus/circuit/error.rs @@ -0,0 +1,23 @@ +#[derive(Debug, thiserror::Error)] +pub enum ProverError { + #[error("failed to generate multi-linear from the given evaluations")] + FailedGenerateML, + #[error("the inputs to the circuit's input layer have incompatible lengths")] + MismatchingLengthsCircuitInputs, + #[error("the inputs to the circuit's input layer must have power-of-two lengths")] + InputsMustBePowerTwo, + #[error("the inputs to the circuit's input layer must have at least two evaluations")] + InputsAtLeastTwo, +} + +#[derive(Debug, thiserror::Error)] +pub enum VerifierError { + #[error("one of the claimed circuit denominators is zero")] + ZeroOutputDenominator, + #[error("the output of the fraction circuit is not equal to the expected value")] + MismatchingCircuitOutput, + #[error("failed to generate the random challenge")] + FailedGenerateRandomness, + #[error("failed to verify the sum-check proof")] + FailedToVerifySumCheck, +} diff --git a/processor/src/trace/virtual_bus/circuit/mod.rs b/processor/src/trace/virtual_bus/circuit/mod.rs new file mode 100644 index 0000000000..4e7b6e94f3 --- /dev/null +++ b/processor/src/trace/virtual_bus/circuit/mod.rs @@ -0,0 +1,238 @@ +use crate::trace::virtual_bus::{ + multilinear::{CompositionPolynomial, EqFunction}, + sum_check::RoundProof, +}; +use alloc::{borrow::ToOwned, sync::Arc, vec::Vec}; +use miden_air::trace::TRACE_WIDTH; +use vm_core::{Felt, FieldElement}; + +mod error; +mod prover; +pub use prover::prove; + +mod verifier; +pub use verifier::verify; + +use super::sum_check::Proof as SumCheckProof; + +/// A GKR proof for the correct evaluation of the sum of fractions circuit. +#[derive(Debug)] +pub struct GkrCircuitProof { + circuit_outputs: [E; 4], + before_final_layer_proofs: BeforeFinalLayerProof, + final_layer_proof: FinalLayerProof, +} + +/// A set of sum-check proofs for all GKR layers but for the input circuit layer. +#[derive(Debug)] +pub struct BeforeFinalLayerProof { + pub proof: Vec>, +} + +/// A proof for the input circuit layer i.e., the final layer in the GKR protocol. +#[derive(Debug)] +pub struct FinalLayerProof { + before_merge_proof: Vec>, + after_merge_proof: SumCheckProof, +} + +/// Represents a claim to be proven by next sum-check protocol. +#[derive(Debug)] +pub struct GkrClaim { + pub evaluation_point: Vec, + pub claimed_evaluation: (E, E), +} + +/// A composition polynomial that projects into a specific component. +#[derive(Clone, Copy, Debug)] +pub struct ProjectionComposition { + coordinate: usize, +} + +impl ProjectionComposition { + pub fn new(coordinate: usize) -> Self { + Self { coordinate } + } +} + +impl CompositionPolynomial for ProjectionComposition +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + 1 + } + + fn max_degree(&self) -> usize { + 1 + } + + fn evaluate(&self, query: &[E]) -> E { + query[self.coordinate] + } +} + +/// A composition polynomial used in the GKR protocol for all of its sum-checks except the final one. +#[derive(Clone)] +pub struct GkrComposition +where + E: FieldElement, +{ + pub combining_randomness: E, +} + +impl GkrComposition +where + E: FieldElement, +{ + pub fn new(combining_randomness: E) -> Self { + Self { + combining_randomness, + } + } +} + +impl CompositionPolynomial for GkrComposition +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + 5 + } + + fn max_degree(&self) -> usize { + 3 + } + + fn evaluate(&self, query: &[E]) -> E { + let eval_left_numerator = query[0]; + let eval_right_numerator = query[1]; + let eval_left_denominator = query[2]; + let eval_right_denominator = query[3]; + let eq_eval = query[4]; + eq_eval + * ((eval_left_numerator * eval_right_denominator + + eval_right_numerator * eval_left_denominator) + + eval_left_denominator * eval_right_denominator * self.combining_randomness) + } +} + +/// A composition polynomial used in the GKR protocol for its final sum-check. +#[derive(Clone)] +pub struct GkrCompositionMerge +where + E: FieldElement, +{ + pub sum_check_combining_randomness: E, + pub tensored_merge_randomness: Vec, + pub degree: usize, + + pub eq_composer: Arc>, + pub right_numerator_composer: Vec>>, + pub left_numerator_composer: Vec>>, + pub right_denominator_composer: Vec>>, + pub left_denominator_composer: Vec>>, +} + +impl GkrCompositionMerge +where + E: FieldElement, +{ + pub fn new( + combining_randomness: E, + merge_randomness: Vec, + eq_composer: Arc>, + right_numerator_composer: Vec>>, + left_numerator_composer: Vec>>, + right_denominator_composer: Vec>>, + left_denominator_composer: Vec>>, + ) -> Self { + let tensored_merge_randomness = + EqFunction::ml_at(merge_randomness.clone()).evaluations().to_vec(); + + let max_left_num = left_numerator_composer.iter().map(|c| c.max_degree()).max().unwrap(); + let max_right_num = right_numerator_composer.iter().map(|c| c.max_degree()).max().unwrap(); + let max_left_denom = + left_denominator_composer.iter().map(|c| c.max_degree()).max().unwrap(); + let max_right_denom = + right_denominator_composer.iter().map(|c| c.max_degree()).max().unwrap(); + let degree = + 1 + core::cmp::max(max_left_num + max_right_denom, max_right_num + max_left_denom); + + Self { + sum_check_combining_randomness: combining_randomness, + eq_composer, + degree, + right_numerator_composer, + left_numerator_composer, + right_denominator_composer, + left_denominator_composer, + tensored_merge_randomness, + } + } +} + +impl CompositionPolynomial for GkrCompositionMerge +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + TRACE_WIDTH + } + + fn max_degree(&self) -> usize { + self.degree + } + + fn evaluate(&self, query: &[E]) -> E { + let eval_right_numerator = + self.right_numerator_composer.iter().enumerate().fold(E::ZERO, |acc, (i, ml)| { + acc + ml.evaluate(query) * self.tensored_merge_randomness[i] + }); + let eval_left_numerator = + self.left_numerator_composer.iter().enumerate().fold(E::ZERO, |acc, (i, ml)| { + acc + ml.evaluate(query) * self.tensored_merge_randomness[i] + }); + let eval_right_denominator = self + .right_denominator_composer + .iter() + .enumerate() + .fold(E::ZERO, |acc, (i, ml)| { + acc + ml.evaluate(query) * self.tensored_merge_randomness[i] + }); + let eval_left_denominator = + self.left_denominator_composer.iter().enumerate().fold(E::ZERO, |acc, (i, ml)| { + acc + ml.evaluate(query) * self.tensored_merge_randomness[i] + }); + let eq_eval = self.eq_composer.evaluate(query); + eq_eval + * ((eval_left_numerator * eval_right_denominator + + eval_right_numerator * eval_left_denominator) + + eval_left_denominator + * eval_right_denominator + * self.sum_check_combining_randomness) + } +} + +/// Generates a [GkrCompositionMerge] given the sum-check randomness so-far and the random value +/// for batching two sum-checks to one. +pub fn gkr_merge_composition_from_composition_polys + 'static>( + composition_polys: &[Vec>>], + sum_check_batch_randomness: E, + merge_randomness: Vec, +) -> GkrCompositionMerge { + let eq_composer = Arc::new(ProjectionComposition::new(TRACE_WIDTH)); + let left_numerator = composition_polys[0].to_owned(); + let right_numerator = composition_polys[1].to_owned(); + let left_denominator = composition_polys[2].to_owned(); + let right_denominator = composition_polys[3].to_owned(); + GkrCompositionMerge::new( + sum_check_batch_randomness, + merge_randomness, + eq_composer, + right_numerator, + left_numerator, + right_denominator, + left_denominator, + ) +} diff --git a/processor/src/trace/virtual_bus/circuit/prover.rs b/processor/src/trace/virtual_bus/circuit/prover.rs new file mode 100644 index 0000000000..2e37aa01a0 --- /dev/null +++ b/processor/src/trace/virtual_bus/circuit/prover.rs @@ -0,0 +1,471 @@ +use super::{ + super::sum_check::Proof as SumCheckProof, error::ProverError, + gkr_merge_composition_from_composition_polys, BeforeFinalLayerProof, FinalLayerProof, + GkrCircuitProof, GkrClaim, GkrComposition, +}; +use crate::trace::virtual_bus::{ + multilinear::{CompositionPolynomial, EqFunction, MultiLinear}, + sum_check::{FinalClaimBuilder, FinalOpeningClaim, RoundClaim, RoundProof}, + SumCheckProver, +}; +use alloc::{borrow::ToOwned, sync::Arc, vec::Vec}; +use core::marker::PhantomData; +use vm_core::{Felt, FieldElement}; +use winter_prover::crypto::{ElementHasher, RandomCoin}; + +/// Layered circuit for computing a sum of fractions. +/// +/// The circuit computes a sum of fractions based on the formula a / c + b / d = (a * d + b * c) / (c * d) +/// which defines a "gate" ((a, b), (c, d)) --> (a * d + b * c, c * d) upon which the `FractionalSumCircuit` +/// is built. Due to the uniformity of the circuit, each of the circuit layers collect all the: +/// +/// 1. `a`'s into a [MultiLinear] called `p_0`. +/// 2. `b`'s into a [MultiLinear] called `p_1`. +/// 3. `c`'s into a [MultiLinear] called `q_0`. +/// 4. `d`'s into a [MultiLinear] called `q_1`. +/// +/// The relation between two subsequent layers is given by the formula +/// +/// p_0[layer + 1](x_0, x_1, ..., x_{ν - 2}) = p_0[layer](x_0, x_1, ..., x_{ν - 2}, 0) * q_1[layer](x_0, x_1, ..., x_{ν - 2}, 0) +/// + p_1[layer](x_0, x_1, ..., x_{ν - 2}, 0) * q_0[layer](x_0, x_1, ..., x_{ν - 2}, 0) +/// +/// p_1[layer + 1](x_0, x_1, ..., x_{ν - 2}) = p_0[layer](x_0, x_1, ..., x_{ν - 2}, 1) * q_1[layer](x_0, x_1, ..., x_{ν - 2}, 1) +/// + p_1[layer](x_0, x_1, ..., x_{ν - 2}, 1) * q_0[layer](x_0, x_1, ..., x_{ν - 2}, 1) +/// +/// and +/// +/// q_0[layer + 1](x_0, x_1, ..., x_{ν - 2}) = q_0[layer](x_0, x_1, ..., x_{ν - 2}, 0) * q_1[layer](x_0, x_1, ..., x_{ν - 1}, 0) +/// +/// q_1[layer + 1](x_0, x_1, ..., x_{ν - 2}) = q_0[layer](x_0, x_1, ..., x_{ν - 2}, 1) * q_1[layer](x_0, x_1, ..., x_{ν - 1}, 1) +/// +/// This means that layer ν will be the output layer and will consist of four values +/// (p_0[ν - 1], p_1[ν - 1], p_0[ν - 1], p_1[ν - 1]) ∈ 𝔽^ν. +#[derive(Debug)] +pub struct FractionalSumCircuit { + p_0_vec: Vec>, + p_1_vec: Vec>, + q_0_vec: Vec>, + q_1_vec: Vec>, +} + +impl FractionalSumCircuit { + /// Computes The values of the gate outputs for each of the layers of the fractional sum circuit. + pub fn new(num_den: Vec>) -> Result { + let num_evaluations = num_den[0].len(); + + if !num_den.iter().all(|t| t.len() == num_evaluations) { + return Err(ProverError::MismatchingLengthsCircuitInputs); + } + + if !num_evaluations.is_power_of_two() { + return Err(ProverError::InputsMustBePowerTwo); + } + + if num_evaluations < 2 { + return Err(ProverError::InputsAtLeastTwo); + } + + let num_layers = num_evaluations.ilog2() as usize; + let mut p_0_vec: Vec> = Vec::with_capacity(num_layers); + let mut p_1_vec: Vec> = Vec::with_capacity(num_layers); + let mut q_0_vec: Vec> = Vec::with_capacity(num_layers); + let mut q_1_vec: Vec> = Vec::with_capacity(num_layers); + + let p_0 = + MultiLinear::from_values(&num_den[0]).map_err(|_| ProverError::FailedGenerateML)?; + let p_1 = + MultiLinear::from_values(&num_den[1]).map_err(|_| ProverError::FailedGenerateML)?; + let q_0 = + MultiLinear::from_values(&num_den[2]).map_err(|_| ProverError::FailedGenerateML)?; + let q_1 = + MultiLinear::from_values(&num_den[3]).map_err(|_| ProverError::FailedGenerateML)?; + p_0_vec.push(p_0); + p_1_vec.push(p_1); + q_0_vec.push(q_0); + q_1_vec.push(q_1); + + for i in 0..num_layers { + let (output_p_0, output_p_1, output_q_0, output_q_1) = + FractionalSumCircuit::compute_layer( + &p_0_vec[i], + &p_1_vec[i], + &q_0_vec[i], + &q_1_vec[i], + )?; + p_0_vec.push(output_p_0); + p_1_vec.push(output_p_1); + q_0_vec.push(output_q_0); + q_1_vec.push(output_q_1); + } + + Ok(FractionalSumCircuit { + p_0_vec, + p_1_vec, + q_0_vec, + q_1_vec, + }) + } + + /// Computes the output values of the layer given a set of input values + #[allow(clippy::type_complexity)] + fn compute_layer( + inp_p_0: &MultiLinear, + inp_p_1: &MultiLinear, + inp_q_0: &MultiLinear, + inp_q_1: &MultiLinear, + ) -> Result<(MultiLinear, MultiLinear, MultiLinear, MultiLinear), ProverError> { + let len = inp_q_0.num_evaluations(); + let outp_p_0 = (0..len / 2) + .map(|i| inp_p_0[i] * inp_q_1[i] + inp_p_1[i] * inp_q_0[i]) + .collect::>(); + let outp_p_1 = (len / 2..len) + .map(|i| inp_p_0[i] * inp_q_1[i] + inp_p_1[i] * inp_q_0[i]) + .collect::>(); + let outp_q_0 = (0..len / 2).map(|i| inp_q_0[i] * inp_q_1[i]).collect::>(); + let outp_q_1 = (len / 2..len).map(|i| inp_q_0[i] * inp_q_1[i]).collect::>(); + + Ok(( + MultiLinear::new(outp_p_0).map_err(|_| ProverError::FailedGenerateML)?, + MultiLinear::new(outp_p_1).map_err(|_| ProverError::FailedGenerateML)?, + MultiLinear::new(outp_q_0).map_err(|_| ProverError::FailedGenerateML)?, + MultiLinear::new(outp_q_1).map_err(|_| ProverError::FailedGenerateML)?, + )) + } + + /// Given a value r, computes the evaluation of the last layer at r when interpreted as (two) + /// multilinear polynomials. + pub fn evaluate_output_layer(&self, r: E) -> (E, E) { + let len = self.p_0_vec.len(); + assert_eq!(self.p_0_vec[len - 1].num_variables(), 0); + assert_eq!(self.p_1_vec[len - 1].num_variables(), 0); + assert_eq!(self.q_0_vec[len - 1].num_variables(), 0); + assert_eq!(self.q_1_vec[len - 1].num_variables(), 0); + + let mut p_ = self.p_0_vec[len - 1].clone(); + p_.extend(&self.p_1_vec[len - 1]); + let mut q_ = self.q_0_vec[len - 1].clone(); + q_.extend(&self.q_1_vec[len - 1]); + + (p_.evaluate(&[r]), q_.evaluate(&[r])) + } + + /// Outputs the value of the circuit output layer. + pub fn output_layer(&self) -> [E; 4] { + let len = self.p_0_vec.len(); + let poly_a = self.p_0_vec[len - 1][0]; + let poly_b = self.p_1_vec[len - 1][0]; + let poly_c = self.q_0_vec[len - 1][0]; + let poly_d = self.q_1_vec[len - 1][0]; + [poly_a, poly_b, poly_c, poly_d] + } +} + +/// Evaluates and proves a fractional sum circuit given a set of composition polynomials. +/// +/// Each individual component of the quadruple [p_0, p_1, q_0, q_1] is of the form: +/// +/// m(z_0, ... , z_{μ - 1}, x_0, ... , x_{ν - 1}) = +/// \sum_{y ∈ {0,1}^μ} EQ(z, y) * g_{[y]}(f_0(x_0, ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , x_{ν - 1})) +/// +/// where: +/// +/// 1. μ is the log_2 of the number of different numerator/denominator expressions divided by two. +/// 2. [y] := \sum_{j = 0}^{μ - 1} y_j * 2^j +/// 3. κ is the number of multi-linears (i.e., main trace columns) involved in the computation +/// of the circuit (i.e., virtual bus). +/// 4. ν is the log_2 of the trace length. +/// +/// The above `m` is usually referred to as the merge of the individual composed multi-linear +/// polynomials g_{[y]}(f_0(x_0, ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , x_{ν - 1})). +/// +/// The composition polynomials `g` are provided as inputs and then used in order to compute +/// the evaluations of each of the four merge polynomials over {0, 1}^{μ + ν}. The resulting +/// evaluations are then used in order to evaluate [FractionalSumCircuit]. +/// At this point, the GKR protocol is used to prove the correctness of circuit evaluation. It +/// should be noted that the input layer, which corresponds to the last layer treated by the GKR +/// protocol, is handled differently from the other layers. +/// More specifically, the sum-check protocol used for the input layer is composed of two sum-check +/// protocols, the first one works directly with the evaluations of the `m`'s over {0, 1}^{μ + ν} +/// and runs for μ rounds. +/// After these μ rounds, and using the resulting [RoundClaim], we run the second and final +/// sum-check protocol for ν rounds on the composed multi-linear polynomial given by +/// +/// \sum_{y ∈ {0,1}^μ} EQ(ρ', y) * g_{[y]}(f_0(x_0, ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , x_{ν - 1})) +/// +/// where ρ' is the randomness sampled during the first sum-check protocol. +/// +/// As part of the final sum-check protocol, the openings {f_j(ρ)} are provided as part of +/// a [FinalOpeningClaim]. This latter claim will be proven by the STARK prover later on using +/// the auxiliary trace. +pub fn prove< + E: FieldElement + 'static, + C: RandomCoin, + H: ElementHasher, +>( + composition_polys: Vec>>>, + mls: &mut Vec>, + transcript: &mut C, +) -> GkrCircuitProof { + // evaluate the numerators and denominators over the boolean hyper-cube {0, 1}^{μ + ν} + let input: Vec> = evaluate_composition_polys(mls, &composition_polys); + + // evaluate the GKR fractional sum circuit + let mut circuit = FractionalSumCircuit::new(input).unwrap(); + + // run the GKR prover for all layers except the input layer + let (before_final_layer_proofs, gkr_claim) = + prove_before_final_circuit_layers(&mut circuit, transcript); + + // run the GKR prover for the input layer + let num_rounds_before_merge = composition_polys[0].len().ilog2() as usize; + let final_layer_proof = prove_final_circuit_layer( + composition_polys, + mls, + num_rounds_before_merge, + gkr_claim, + &mut circuit, + transcript, + ); + + // include the circuit output as part of the final proof + let circuit_outputs = circuit.output_layer(); + + GkrCircuitProof { + circuit_outputs, + before_final_layer_proofs, + final_layer_proof, + } +} + +/// Proves the final GKR layer which corresponds to the input circuit layer. +fn prove_final_circuit_layer< + E: FieldElement + 'static, + C: RandomCoin, + H: ElementHasher, +>( + composition_polys: Vec>>>, + mls: &mut Vec>, + num_rounds_merge: usize, + gkr_claim: GkrClaim, + circuit: &mut FractionalSumCircuit, + transcript: &mut C, +) -> FinalLayerProof { + // parse the [GkrClaim] resulting from the previous GKR layer + let GkrClaim { + evaluation_point, + claimed_evaluation, + } = gkr_claim; + + // compute the EQ function at the evaluation point + let poly_x = EqFunction::ml_at(evaluation_point.clone()); + + // get the multi-linears of the 4 merge polynomials + let poly_a = circuit.p_0_vec[0].to_owned(); + let poly_b = circuit.p_1_vec[0].to_owned(); + let poly_c = circuit.q_0_vec[0].to_owned(); + let poly_d = circuit.q_1_vec[0].to_owned(); + let mut merged_mls = vec![poly_a, poly_b, poly_c, poly_d, poly_x]; + + // run the first sum-check protocol + let ((round_claim, before_merge_proof), r_sum_check) = sum_check_prover_plain_partial( + claimed_evaluation, + num_rounds_merge, + &mut merged_mls, + transcript, + ); + + // parse the output of the first sum-check protocol + let RoundClaim { + eval_point: rand_merge, + claim, + } = round_claim; + + // create the composed multi-linear for the second sum-check protocol using the randomness + // sampled during the first one + let gkr_composition = + gkr_merge_composition_from_composition_polys(&composition_polys, r_sum_check, rand_merge); + + // include the partially evaluated at the first sum-check randomness EQ multi-linear + mls.push(merged_mls[4].clone()); + + // run the second sum-check protocol + let main_prover = SumCheckProver::new(gkr_composition, SimpleGkrFinalClaimBuilder(PhantomData)); + let after_merge_proof = main_prover.prove(claim, mls, transcript).unwrap(); + + FinalLayerProof { + before_merge_proof, + after_merge_proof, + } +} + +/// Proves all GKR layers except for input layer. +fn prove_before_final_circuit_layers< + E: FieldElement + 'static, + C: RandomCoin, + H: ElementHasher, +>( + circuit: &mut FractionalSumCircuit, + transcript: &mut C, +) -> (BeforeFinalLayerProof, GkrClaim) { + // absorb the circuit output layer. This corresponds to sending the four values of the output + // layer to the verifier. The verifier then replies with a challenge `r` in order to evaluate + // `p` and `q` at `r` as multi-linears. + let num_layers = circuit.p_0_vec.len(); + let data = vec![ + circuit.p_0_vec[num_layers - 1][0], + circuit.p_1_vec[num_layers - 1][0], + circuit.q_0_vec[num_layers - 1][0], + circuit.q_1_vec[num_layers - 1][0], + ]; + // generate the challenge `r` + transcript.reseed(H::hash_elements(&data)); + + // generate the challenge and reduce [p0, p1, q0, q1] to [pr, qr] + let r = transcript.draw().unwrap(); + let mut claim = circuit.evaluate_output_layer(r); + + let mut proof_layers: Vec> = Vec::new(); + let mut rand = vec![r]; + for layer_id in (1..num_layers - 1).rev() { + // construct the Lagrange kernel evaluated at the previous GKR round randomness + let poly_x = EqFunction::ml_at(rand.clone()); + + // construct the vector of multi-linear polynomials + // TODO: avoid unnecessary allocation + let poly_a = circuit.p_0_vec[layer_id].to_owned(); + let poly_b = circuit.p_1_vec[layer_id].to_owned(); + let poly_c = circuit.q_0_vec[layer_id].to_owned(); + let poly_d = circuit.q_1_vec[layer_id].to_owned(); + let mut mls = vec![poly_a, poly_b, poly_c, poly_d, poly_x]; + + // run the sumcheck protocol + let (proof, _) = sum_check_prover_plain_full(claim, &mut mls, transcript); + + // sample a random challenge to reduce claims + transcript.reseed(H::hash_elements(&proof.openings_claim.openings)); + let r_layer = transcript.draw().unwrap(); + + // reduce the claim + let p0 = proof.openings_claim.openings[0]; + let p1 = proof.openings_claim.openings[1]; + let q0 = proof.openings_claim.openings[2]; + let q1 = proof.openings_claim.openings[3]; + claim = (p0 + r_layer * (p1 - p0), q0 + r_layer * (q1 - q0)); + + // collect the randomness used for the current layer + let mut ext = proof.openings_claim.eval_point.clone(); + ext.push(r_layer); + rand = ext; + + proof_layers.push(proof); + } + + ( + BeforeFinalLayerProof { + proof: proof_layers, + }, + GkrClaim { + evaluation_point: rand, + claimed_evaluation: claim, + }, + ) +} + +/// Runs the first sum-check prover for the input layer. +fn sum_check_prover_plain_partial< + E: FieldElement + 'static, + C: RandomCoin, + H: ElementHasher, +>( + claim: (E, E), + num_rounds: usize, + ml_polys: &mut [MultiLinear], + transcript: &mut C, +) -> ((RoundClaim, Vec>), E) { + // generate challenge to batch two sumchecks + let data = vec![claim.0, claim.1]; + transcript.reseed(H::hash_elements(&data)); + let r_batch = transcript.draw().unwrap(); + let claim = claim.0 + claim.1 * r_batch; + + // generate the composition polynomial + let composer = GkrComposition::new(r_batch); + + // run the sum-check protocol + let main_prover = SumCheckProver::new(composer, SimpleGkrFinalClaimBuilder(PhantomData)); + let proof = main_prover.prove_rounds(claim, ml_polys, num_rounds, transcript).unwrap(); + + (proof, r_batch) +} + +/// Runs the sum-check prover used in all but the input layer. +fn sum_check_prover_plain_full< + E: FieldElement + 'static, + C: RandomCoin, + H: ElementHasher, +>( + claim: (E, E), + ml_polys: &mut [MultiLinear], + transcript: &mut C, +) -> (SumCheckProof, E) { + // generate challenge to batch two sumchecks + let data = vec![claim.0, claim.1]; + transcript.reseed(H::hash_elements(&data)); + let r_batch = transcript.draw().unwrap(); + let claim_ = claim.0 + claim.1 * r_batch; + + // generate the composition polynomial + let composer = GkrComposition::new(r_batch); + + // run the sum-check protocol + let main_prover = SumCheckProver::new(composer, SimpleGkrFinalClaimBuilder(PhantomData)); + let proof = main_prover.prove(claim_, ml_polys, transcript).unwrap(); + + (proof, r_batch) +} + +/// Computes the evaluations over {0, 1}^{μ + ν} of +/// +/// m(z_0, ... , z_{μ - 1}, x_0, ... , x_{ν - 1}) = +/// \sum_{y ∈ {0,1}^μ} EQ(z, y) * g_{[y]}(f_0(x_0, ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , x_{ν - 1})) +fn evaluate_composition_polys + 'static>( + mls: &[MultiLinear], + composition_polys: &[Vec>>], +) -> Vec> { + let num_evaluations = 1 << mls[0].num_variables(); + let mut num_den: Vec> = + (0..4).map(|_| Vec::with_capacity(num_evaluations)).collect::>(); + + for i in 0..num_evaluations { + for j in 0..4 { + let query: Vec = mls.iter().map(|ml| ml[i]).collect(); + + composition_polys[j].iter().for_each(|c| { + let evaluation = c.as_ref().evaluate(&query); + num_den[j].push(evaluation); + }); + } + } + num_den +} + +/// Constructs [FinalOpeningClaim] for the sum-checks used in the GKR protocol. +/// +/// TODO: currently, this just removes the EQ evaluation as it can computed by the verifier. +/// This should be generalized for other "transparent" multi-linears e.g., periodic columns. +struct SimpleGkrFinalClaimBuilder(PhantomData); + +impl FinalClaimBuilder for SimpleGkrFinalClaimBuilder { + type Field = E; + + fn build_claim( + &self, + openings: Vec, + evaluation_point: &[Self::Field], + ) -> FinalOpeningClaim { + FinalOpeningClaim { + eval_point: evaluation_point.to_vec(), + openings: (openings[..openings.len() - 1]).to_vec(), + } + } +} diff --git a/processor/src/trace/virtual_bus/circuit/verifier.rs b/processor/src/trace/virtual_bus/circuit/verifier.rs new file mode 100644 index 0000000000..48da26e63c --- /dev/null +++ b/processor/src/trace/virtual_bus/circuit/verifier.rs @@ -0,0 +1,237 @@ +use super::{ + error::VerifierError, gkr_merge_composition_from_composition_polys, FinalLayerProof, + GkrCircuitProof, GkrComposition, +}; +use crate::trace::virtual_bus::{ + multilinear::{CompositionPolynomial, EqFunction}, + sum_check::{FinalOpeningClaim, FinalQueryBuilder, Proof as SumCheckFullProof, RoundClaim}, + SumCheckVerifier, +}; +use alloc::{borrow::ToOwned, sync::Arc, vec::Vec}; +use vm_core::{Felt, FieldElement}; +use winter_prover::crypto::{ElementHasher, RandomCoin}; + +/// Verifies the validity of a GKR proof for the correct evaluation of a fractional sum circuit. +pub fn verify< + E: FieldElement + 'static, + C: RandomCoin, + H: ElementHasher, +>( + claim: E, + proof: GkrCircuitProof, + composition_polys: Vec>>>, + transcript: &mut C, +) -> Result, VerifierError> { + let GkrCircuitProof { + circuit_outputs, + before_final_layer_proofs, + final_layer_proof, + } = proof; + + let p0 = circuit_outputs[0]; + let p1 = circuit_outputs[1]; + let q0 = circuit_outputs[2]; + let q1 = circuit_outputs[3]; + + // make sure that both denominators are not equal to E::ZERO + if q0 == E::ZERO || q1 == E::ZERO { + return Err(VerifierError::ZeroOutputDenominator); + } + + // check that the output matches the expected `claim` + if (p0 * q1 + p1 * q0) / (q0 * q1) != claim { + return Err(VerifierError::MismatchingCircuitOutput); + } + + // generate the random challenge to reduce two claims into a single claim + transcript.reseed(H::hash_elements(&circuit_outputs)); + let r = transcript.draw().map_err(|_| VerifierError::FailedGenerateRandomness)?; + + // reduce the claim + let p_r = p0 + r * (p1 - p0); + let q_r = q0 + r * (q1 - q0); + let mut reduced_claim = (p_r, q_r); + + // verify all GKR layers but for the last one + let num_layers = before_final_layer_proofs.proof.len(); + let mut rand = vec![r]; + for i in 0..num_layers { + let FinalOpeningClaim { + eval_point, + openings, + } = verify_sum_check_proof_before_last( + &before_final_layer_proofs.proof[i], + &rand, + reduced_claim, + transcript, + )?; + + // generate the random challenge to reduce two claims into a single claim + transcript.reseed(H::hash_elements(&openings)); + let r_layer = transcript.draw().unwrap(); + + let p0 = openings[0]; + let p1 = openings[1]; + let q0 = openings[2]; + let q1 = openings[3]; + reduced_claim = (p0 + r_layer * (p1 - p0), q0 + r_layer * (q1 - q0)); + + // collect the randomness used for the current layer + let rand_sumcheck = eval_point; + let mut ext = rand_sumcheck; + ext.push(r_layer); + rand = ext; + } + + // verify the proof of the final GKR layer and pass final opening claim for verification + // to the STARK + verify_sum_check_proof_last( + composition_polys, + final_layer_proof, + &rand, + reduced_claim, + transcript, + ) +} + +/// Verifies sum-check proofs, as part of the GKR proof, for all GKR layers except for the last one +/// i.e., the circuit input layer. +pub fn verify_sum_check_proof_before_last< + E: FieldElement + 'static, + C: RandomCoin, + H: ElementHasher, +>( + proof: &SumCheckFullProof, + gkr_eval_point: &[E], + claim: (E, E), + transcript: &mut C, +) -> Result, VerifierError> { + // generate challenge to batch sum-checks + transcript.reseed(H::hash_elements(&[claim.0, claim.1])); + let r_batch: E = transcript.draw().unwrap(); + + // compute the claim for the batched sum-check + let reduced_claim = claim.0 + claim.1 * r_batch; + + // verify the sum-check protocol + let composition_poly = GkrComposition::new(r_batch); + let verifier = + SumCheckVerifier::new(composition_poly, GkrQueryBuilder::new(gkr_eval_point.to_owned())); + verifier + .verify(reduced_claim, proof.clone(), transcript) + .map_err(|_| VerifierError::FailedToVerifySumCheck) +} + +/// Verifies the final sum-check proof as part of the GKR proof. +pub fn verify_sum_check_proof_last< + E: FieldElement + 'static, + C: RandomCoin, + H: ElementHasher, +>( + composition_polys: Vec>>>, + proof: FinalLayerProof, + gkr_eval_point: &[E], + claim: (E, E), + transcript: &mut C, +) -> Result, VerifierError> { + let FinalLayerProof { + before_merge_proof, + after_merge_proof, + } = proof; + + // generate challenge to batch sum-checks + transcript.reseed(H::hash_elements(&[claim.0, claim.1])); + let r_sum_check: E = transcript.draw().unwrap(); + + // compute the claim for the batched sum-check + let reduced_claim = claim.0 + claim.1 * r_sum_check; + + // verify the first part of the sum-check protocol + let composition_poly = GkrComposition::new(r_sum_check); + let verifier = + SumCheckVerifier::new(composition_poly, GkrQueryBuilder::new(gkr_eval_point.to_owned())); + let RoundClaim { + eval_point: rand_merge, + claim, + } = verifier + .verify_rounds(reduced_claim, before_merge_proof, transcript) + .map_err(|_| VerifierError::FailedToVerifySumCheck)?; + + // verify the second part of the sum-check protocol + let gkr_composition = gkr_merge_composition_from_composition_polys( + &composition_polys, + r_sum_check, + rand_merge.clone(), + ); + let verifier = SumCheckVerifier::new( + gkr_composition, + GkrMergeQueryBuilder::new(gkr_eval_point.to_owned(), rand_merge), + ); + verifier + .verify(claim, after_merge_proof, transcript) + .map_err(|_| VerifierError::FailedToVerifySumCheck) +} + +/// A [FinalQueryBuilder] for the sum-check verifier used for all sum-checks but for the final one. +#[derive(Default)] +struct GkrQueryBuilder { + gkr_eval_point: Vec, +} + +impl GkrQueryBuilder { + fn new(gkr_eval_point: Vec) -> Self { + Self { gkr_eval_point } + } +} + +impl FinalQueryBuilder for GkrQueryBuilder { + type Field = E; + + fn build_query( + &self, + openings_claim: &FinalOpeningClaim, + evaluation_point: &[Self::Field], + ) -> Vec { + let rand_sumcheck = evaluation_point; + let eq_at_gkr_eval_point = EqFunction::new(self.gkr_eval_point.clone()); + let eq = eq_at_gkr_eval_point.evaluate(rand_sumcheck); + + let mut query = openings_claim.openings.clone(); + query.push(eq); + query + } +} + +/// A [FinalQueryBuilder] for the sum-check verifier used for the final sum-check. +#[derive(Default)] +struct GkrMergeQueryBuilder { + gkr_eval_point: Vec, + merge_rand: Vec, +} + +impl GkrMergeQueryBuilder { + fn new(gkr_eval_point: Vec, merge_rand: Vec) -> Self { + Self { + gkr_eval_point, + merge_rand, + } + } +} + +impl FinalQueryBuilder for GkrMergeQueryBuilder { + type Field = E; + + fn build_query( + &self, + openings_claim: &FinalOpeningClaim, + evaluation_point: &[Self::Field], + ) -> Vec { + let eq_at_gkr_eval_point = EqFunction::new(self.gkr_eval_point.clone()); + let mut rand_sumcheck = self.merge_rand.clone(); + rand_sumcheck.extend_from_slice(evaluation_point); + let eq = eq_at_gkr_eval_point.evaluate(&rand_sumcheck); + let mut query = openings_claim.openings.clone(); + query.push(eq); + query + } +} diff --git a/processor/src/trace/virtual_bus/error.rs b/processor/src/trace/virtual_bus/error.rs new file mode 100644 index 0000000000..1a79e1540d --- /dev/null +++ b/processor/src/trace/virtual_bus/error.rs @@ -0,0 +1,7 @@ +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("number of numerators and denominators is different")] + NumeratorDenominatorLengthMismatch, + #[error("number of numerators and denominators should be at least two")] + NumeratorDenominatorLessThanTwo, +} diff --git a/processor/src/trace/virtual_bus/mod.rs b/processor/src/trace/virtual_bus/mod.rs index 173ad4a0c2..c31d260cf1 100644 --- a/processor/src/trace/virtual_bus/mod.rs +++ b/processor/src/trace/virtual_bus/mod.rs @@ -1,4 +1,175 @@ +//! Represents a global virtual bus for the Miden VM. +//! +//! A global bus is a single bus which encompasses several sub-buses, each representing +//! a communication channel between two or more components of the VM. +//! A bus represents a client-server relationship between some VM components. The server is +//! usually one specific component of the VM, e.g., hasher chiplet, and the client can be one or +//! several other components of the VM, e.g., the decoder. The communication between the clients +//! and the server is composed of `request` messages made by the clients and corresponding +//! `reply` messages by the server. +//! The purpose of the sub-bus then, from the verifiable computation point of view, is to ensure +//! the consistency between the `request` and `reply` messages exchanged by the clients and +//! the server. +//! The global bus uses a per-sub-bus address in order to ensure correct routing of the `request` +//! messages and their matching `reply` messages. +//! Miden VM uses a virtual global bus in the sense that neither the global bus nor the individual +//! sub-buses are fully materialized as part of the (auxiliary) trace. This is replaced by a layered +//! circuit which computes the global bus relation. The correct evaluation of this circuit is then +//! proved using the GKR protocol of Goldwasser, Kalai and Rothblum [1] using the protocol in +//! GKR-LogUp [2]. +//! +//! [1]: https://dl.acm.org/doi/10.1145/2699436 +//! [2]: https://eprint.iacr.org/2023/1284 + +use self::{ + error::Error, + multilinear::CompositionPolynomial, + sub_bus::{BusBuilder, RangeCheckerBus}, +}; +use alloc::{sync::Arc, vec::Vec}; +use core::marker::PhantomData; +use vm_core::FieldElement; + +mod circuit; mod multilinear; +pub use circuit::{prove, verify}; mod sum_check; mod univariate; pub use sum_check::{SumCheckProver, SumCheckVerifier}; + +mod prover; +pub use prover::VirtualBusProver; +mod verifier; +pub use verifier::VirtualBusVerifier; +mod error; +mod sub_bus; +mod tests; + +/// Generates the composition polynomials describing the virtual bus. +#[allow(clippy::type_complexity)] +fn generate( + log_up_randomness: Vec, +) -> Result<(E, Vec>>>), Error> { + // build each sub-bus of the virtual bus + // TODO: This implements only the range checker bus and should be generalized to iterate + // over all the remaining sub-buses + let range_checker_bus_builder = RangeCheckerBus::new(&log_up_randomness); + let claim = range_checker_bus_builder.compute_initial_claim(); + let numerators = range_checker_bus_builder.build_numerators(); + let denominators = range_checker_bus_builder.build_denominators(); + + // the numerators and denominators should have matching lengths + if numerators.len() != denominators.len() { + return Err(Error::NumeratorDenominatorLengthMismatch); + } + + // should have at least two numerator/denominator + if numerators.len() < 2 { + return Err(Error::NumeratorDenominatorLessThanTwo); + } + + // split the numerators and denominators into two subsets, a left and a right one + let mut numerators = pad_and_split(numerators, false); + let denominators = pad_and_split(denominators, true); + numerators.extend_from_slice(&denominators); + + Ok((claim, numerators)) +} + +/// Pads a set of composition polynomials to the next power of two and splits the resulting padded +/// into two sub-sets of equal size. +/// +/// The padding is done using the `ZeroNumerator` composition polynomial in the case when the set +/// is the set of composition polynomials appearing in the numerators, and is done using +/// the `OneDenominator` composition polynomial in the case of denominators. +fn pad_and_split( + vector: Vec>>, + denominator: bool, +) -> Vec>>> { + let length = vector.len(); + let padded_length = length.next_power_of_two(); + + let mut output = Vec::with_capacity(padded_length); + output.extend_from_slice(&vector); + + if denominator { + for _i in length..padded_length { + output.push(Arc::new(OneDenominator::default())); + } + } else { + for _i in length..padded_length { + output.push(Arc::new(ZeroNumerator::default())); + } + } + + let mut left = output; + let right = left.split_off(left.len() >> 1); + + vec![left, right] +} + +/// A polynomial representing the identically zero polynomial. +/// +/// This is used for padding to the next power of 2 as the current implementation of the sum-check +/// protocol requires that the number of numerators be a power of two. The same holds for the +/// denominators. +/// It should be noted that this padding has no effect on the sum computed by the GKR circuit as +/// adding a fraction with a zero numerator and a non-zero denominator doesn't change the value of +/// the sum. +#[derive(Default)] +pub struct ZeroNumerator +where + E: FieldElement, +{ + phantom: PhantomData, +} + +impl CompositionPolynomial for ZeroNumerator +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + 1 // TODO: Update + } + + fn max_degree(&self) -> usize { + 1 + } + + fn evaluate(&self, _query: &[E]) -> E { + E::ZERO + } +} + +/// A polynomial representing the identically `E::ONE` polynomial. +/// +/// This is used for padding to the next power of 2 as the current implementation of the sum-check +/// protocol requires that the number of denominators be a power of two. The same holds for the +/// numerators. +/// It should be noted that this padding has no effect on the sum computed by the GKR circuit as +/// adding a fraction with a zero numerator and a non-zero denominator doesn't change the value of +/// the sum. +#[derive(Default)] +pub struct OneDenominator +where + E: FieldElement, +{ + phantom: PhantomData, +} + +impl CompositionPolynomial for OneDenominator +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + 1 // TODO: Update + } + + fn max_degree(&self) -> usize { + 1 + } + + fn evaluate(&self, _query: &[E]) -> E { + E::ONE + } +} diff --git a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs new file mode 100644 index 0000000000..73dc1d1d65 --- /dev/null +++ b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs @@ -0,0 +1,69 @@ +use super::{tensorize, FieldElement, MultiLinear}; +use alloc::vec::Vec; + +/// The EQ (equality) function is the binary function defined by +/// +/// EQ: {0 , 1}^ν ⛌ {0 , 1}^ν ⇾ {0 , 1} +/// ((x_0, ..., x_{ν - 1}), (y_0, ..., y_{ν - 1})) ↦ \prod_{i = 0}^{ν - 1} (x_i * y_i + (1 - x_i) * (1 - y_i)) +/// +/// Taking It's multi-linear extension EQ^{~}, we can define a basis for the set of multi-linear +/// polynomials in ν variables by +/// {EQ^{~}(., (y_0, ..., y_{ν - 1})): (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν} +/// where each basis function is a function of its first argument. This is called the Lagrange or +/// evaluation basis with evaluation set {0 , 1}^ν. +/// +/// Given a function (f: {0 , 1}^ν ⇾ 𝔽), its multi-linear extension (i.e., the unique +/// mult-linear polynomial extending f to (f^{~}: 𝔽^ν ⇾ 𝔽) and agrees with it on {0 , 1}^ν) is +/// defined as the summation of the evaluations of f against the Lagrange basis. +/// More specifically, given (r_0, ..., r_{ν - 1}) ∈ 𝔽^ν, then: +/// +/// f^{~}(r_0, ..., r_{ν - 1}) = \sum_{(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν} +/// f(y_0, ..., y_{ν - 1}) EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) +/// +/// We call the Lagrange kernel the evaluation of the EQ^{~} function at +/// ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) for all (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν for +/// a fixed (r_0, ..., r_{ν - 1}) ∈ 𝔽^ν. +/// +/// [EqFunction] represents EQ^{~} the mult-linear extension of +/// +/// ((y_0, ..., y_{ν - 1}) ↦ EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))) +/// +/// and contains a method to generate the Lagrange kernel for defining evaluations of multi-linear +/// extensions of arbitrary functions (f: {0 , 1}^ν ⇾ 𝔽) at a given point (r_0, ..., r_{ν - 1}) +/// as well as a method to evaluate EQ^{~}((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1}))) for +/// (t_0, ..., t_{ν - 1}) ∈ 𝔽^ν. +pub struct EqFunction { + r: Vec, +} + +impl EqFunction { + /// Creates a new [EqFunction]. + pub fn new(r: Vec) -> Self { + let tmp = r.clone(); + EqFunction { r: tmp } + } + + /// Computes EQ((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1}))). + pub fn evaluate(&self, t: &[E]) -> E { + assert_eq!(self.r.len(), t.len()); + + (0..self.r.len()) + .map(|i| self.r[i] * t[i] + (E::ONE - self.r[i]) * (E::ONE - t[i])) + .fold(E::ONE, |acc, term| acc * term) + } + + /// Computes EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) for all + /// (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν i.e., the Lagrange kernel at r = (r_0, ..., r_{ν - 1}). + pub fn evaluations(&self) -> Vec { + tensorize(&self.r) + } + + /// Returns the evaluations of + /// ((y_0, ..., y_{ν - 1}) ↦ EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))) + /// over {0 , 1}^ν. + pub fn ml_at(evaluation_point: Vec) -> MultiLinear { + let eq_evals = EqFunction::new(evaluation_point.clone()).evaluations(); + MultiLinear::from_values(&eq_evals) + .expect("should not fail because evaluations is a power of two") + } +} diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index 6a56d6bfcb..cc63534458 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -57,6 +57,19 @@ impl MultiLinearPoly { inner_product(&self.evaluations, &tensored_query) } + /// Computes f(r_0, y_1, ..., y_{ν - 1}) using the linear interpolation formula + /// (1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1}). The resulting + /// returned multi-linear is defined over a domain of half the size. + #[allow(dead_code)] + pub fn bind(&self, round_challenge: E) -> Self { + let mut result = vec![E::ZERO; 1 << (self.num_variables() - 1)]; + for (i, res) in result.iter_mut().enumerate() { + *res = self.evaluations[i << 1] + + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); + } + Self::from_values(&result).expect("should not fail given that it is a multi-linear") + } + /// Computes f(r_0, y_1, ..., y_{ν - 1}) using the linear interpolation formula /// (1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1}) and assigns /// the resulting multi-linear, defined over a domain of half the size, to `self`. @@ -69,6 +82,13 @@ impl MultiLinearPoly { *self = Self::from_evaluations(result.to_owned()) .expect("should not fail given that it is a multi-linear"); } + + pub fn extend(&mut self, other: &MultiLinear) { + let other_vec = other.evaluations.to_vec(); + assert_eq!(other_vec.len(), self.evaluations().len()); + self.evaluations.extend(other_vec); + self.num_variables += 1; + } } impl Index for MultiLinearPoly { @@ -83,7 +103,7 @@ impl Index for MultiLinearPoly { // ================================================================================================ /// A multi-variate polynomial for composing individual multi-linear polynomials. -pub trait CompositionPolynomial: Sync + Send { +pub trait CompositionPolynomial { /// The number of variables when interpreted as a multi-variate polynomial. fn num_variables(&self) -> u32; @@ -116,7 +136,7 @@ pub fn tensorize(query: &[E]) -> Vec { let mut evals: Vec = vec![E::ONE; n]; let mut size = 1; - for r_i in query.iter() { + for r_i in query.iter().rev() { size *= 2; for i in (0..size).rev().step_by(2) { let scalar = evals[i / 2]; diff --git a/processor/src/trace/virtual_bus/prover.rs b/processor/src/trace/virtual_bus/prover.rs new file mode 100644 index 0000000000..59504cc351 --- /dev/null +++ b/processor/src/trace/virtual_bus/prover.rs @@ -0,0 +1,103 @@ +use super::{ + circuit::GkrCircuitProof, + error::Error, + generate, + multilinear::{CompositionPolynomial, MultiLinear}, + prove, +}; +use alloc::{sync::Arc, vec::Vec}; +use core::marker::PhantomData; +use miden_air::trace::main_trace::MainTrace; +use vm_core::{Felt, FieldElement}; +use winter_prover::crypto::{ElementHasher, RandomCoin}; + +/// A struct which implements the logic for proving the correctness of the global virtual bus +/// relation. +/// +/// The global virtual bus relation is encoded as a layered circuit and is proven using the GKR +/// protocol for proving the correct evaluation of a circuit. The correctness of the resulting +/// GKR proof entails, together with that of the final evaluation claim (see further down), that +/// the current instance of the global virtual bus is consistant with overwhelming probability. +/// This last statement is conditioned on the correctness of what is called the final evaluation +/// claim. This is a claim on the openings of the multi-linear extensions of the (main) trace +/// columns involved in the global virtual bus relation at some random point. This random point +/// is the result of the challenges sent by the GKR verifier in the interactive version of the GKR +/// protocol. +/// As Miden VM uses FRI as its polynomial commitment scheme, we use the transformation in +/// section 5 of [1] for transforming a univariate polynomial commitment scheme into a multi-variate +/// one. This means that the (outer) STARK will take the final evaluation claim in +/// `GkrCircuitProof::FinalLayerProof::Proof::FinalOpeningClaim` and use it to construct two columns +/// in the auxiliary trace, one for the Lagrange kernel at `FinalOpeningClaim::eval_point` and the +/// other for computing the evaluation of the (batched) multi-linear extensions of the relevant +/// columns at `FinalOpeningClaim::eval_point` using the aforementioned Lagrange kernel. +/// This means, in a sense, that the opening proof for the final GKR evaluation is part of the STARK +/// proof. +/// +/// [1]: https://eprint.iacr.org/2023/1284 +pub struct VirtualBusProver +where + E: FieldElement + 'static, + C: RandomCoin, + H: ElementHasher, +{ + composition_polynomials: Vec>>>, + _challenger: PhantomData, +} + +impl VirtualBusProver +where + E: FieldElement + 'static, + C: RandomCoin, + H: ElementHasher, +{ + /// Constructs a new [VirtualBusProver] given a set of random values for the GKR-LogUp relation. + /// + /// The constructor uses the randomness to do two things: + /// + /// 1. Compute the claimed value of the GKR-LogUp relation i.e., the generalization of the 0 + /// constant in equation (3) in [1]. This constant can depend on the LogUp randomness as in + /// e.g., the overflow stack virtual table bus where such a constant represents entries in + /// the overflow stack which were present before the VM started executing. + /// + /// 2. The composition polynomials describing the numerators and denominators of the fractions + /// appearing on the left hand side of equation (3) in [1]. The denominators depend as well + /// on the LogUp randomness. + /// The composition polynomials are grouped into 4 sets that can be thought of as left/right + /// numerator/denominator. The reason for this can be understood by looking at the original + /// GKR protocol in [2] where the protocol therein uses the wiring predicates [\tilde{ADD}] + /// and [\tilde{MUL}] to encode the wiring of the circuit. For our purposes, we can do + /// away with wiring predicates using the left/right and numerator/denominator distinction. + /// + /// [1]: https://eprint.iacr.org/2023/1284 + /// [2]: https://dl.acm.org/doi/10.1145/2699436 + pub fn new(log_up_randomness: Vec) -> Result { + let (_claim, composition_polynomials) = generate(log_up_randomness)?; + + Ok(Self { + composition_polynomials, + _challenger: PhantomData, + }) + } + + /// Returns the composition polynomials of the left/right numerators/denominators of + /// the GKR-LogUp relation. + fn composition_polynomials(&self) -> Vec>>> { + self.composition_polynomials.clone() + } + + /// Proves the GKR-LogUp relation. + pub fn prove(&self, trace: MainTrace, transcript: &mut C) -> GkrCircuitProof { + // TODO: Optimize this so that we can work with base field element directly and thus save + // on memory usage. + let trace_len = trace.num_rows(); + let mut mls: Vec> = trace + .columns() + .map(|col| { + let mut values: Vec = col.iter().map(|value| E::from(*value)).collect(); + values[trace_len - 1] = E::ZERO; + MultiLinear::new(values).unwrap() + }) + .collect(); + prove(self.composition_polynomials(), &mut mls, transcript) + } +} diff --git a/processor/src/trace/virtual_bus/sub_bus/mod.rs b/processor/src/trace/virtual_bus/sub_bus/mod.rs new file mode 100644 index 0000000000..95385610fa --- /dev/null +++ b/processor/src/trace/virtual_bus/sub_bus/mod.rs @@ -0,0 +1,14 @@ +use super::multilinear::CompositionPolynomial; +use alloc::{sync::Arc, vec::Vec}; +use vm_core::FieldElement; + +mod range_checker; +pub use range_checker::*; + +pub trait BusBuilder { + fn compute_initial_claim(&self) -> E; + + fn build_numerators(&self) -> Vec>>; + + fn build_denominators(&self) -> Vec>>; +} diff --git a/processor/src/trace/virtual_bus/sub_bus/range_checker.rs b/processor/src/trace/virtual_bus/sub_bus/range_checker.rs new file mode 100644 index 0000000000..7cb4bd200d --- /dev/null +++ b/processor/src/trace/virtual_bus/sub_bus/range_checker.rs @@ -0,0 +1,239 @@ +use super::BusBuilder; +use crate::trace::virtual_bus::multilinear::CompositionPolynomial; +use alloc::{sync::Arc, vec::Vec}; +use core::marker::PhantomData; +use miden_air::trace::{ + chiplets::MEMORY_D0_COL_IDX, + decoder::{DECODER_OP_BITS_OFFSET, DECODER_USER_OP_HELPERS_OFFSET}, + range::{M_COL_IDX, V_COL_IDX}, + CHIPLETS_OFFSET, +}; +use vm_core::FieldElement; + +pub struct RangeCheckerBus { + alphas: Vec, +} + +impl RangeCheckerBus { + pub fn new(alphas: &[E]) -> Self { + Self { + alphas: alphas.to_vec(), + } + } +} + +impl BusBuilder for RangeCheckerBus { + fn compute_initial_claim(&self) -> E { + E::ZERO + } + + fn build_numerators(&self) -> Vec>> { + vec![ + Arc::new(RangeCheckMultiplicity::default()), + Arc::new(MemoryFlagChiplet::default()), + Arc::new(MemoryFlagChiplet::default()), + Arc::new(U32RangeCheckFlag::default()), + Arc::new(U32RangeCheckFlag::default()), + Arc::new(U32RangeCheckFlag::default()), + Arc::new(U32RangeCheckFlag::default()), + ] + } + + fn build_denominators(&self) -> Vec>> { + vec![ + Arc::new(TableValue::new(self.alphas.clone())), + Arc::new(MemoryValue::new(0, self.alphas.clone())), + Arc::new(MemoryValue::new(1, self.alphas.clone())), + Arc::new(StackValue::new(0, self.alphas.clone())), + Arc::new(StackValue::new(1, self.alphas.clone())), + Arc::new(StackValue::new(2, self.alphas.clone())), + Arc::new(StackValue::new(3, self.alphas.clone())), + ] + } +} + +#[derive(Default)] +pub struct U32RangeCheckFlag +where + E: FieldElement, +{ + phantom: PhantomData, +} + +impl CompositionPolynomial for U32RangeCheckFlag +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + 1 + } + + fn max_degree(&self) -> usize { + 3 + } + + fn evaluate(&self, query: &[E]) -> E { + let op_bit_4 = query[DECODER_OP_BITS_OFFSET + 4]; + let op_bit_5 = query[DECODER_OP_BITS_OFFSET + 5]; + let op_bit_6 = query[DECODER_OP_BITS_OFFSET + 6]; + + (E::ONE - op_bit_4) * (E::ONE - op_bit_5) * op_bit_6 + } +} + +#[derive(Default)] +pub struct MemoryFlagChiplet +where + E: FieldElement, +{ + phantom: PhantomData, +} + +impl CompositionPolynomial for MemoryFlagChiplet +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + 1 + } + + fn max_degree(&self) -> usize { + 3 + } + + fn evaluate(&self, query: &[E]) -> E { + let mem_selec0 = query[CHIPLETS_OFFSET]; + let mem_selec1 = query[CHIPLETS_OFFSET + 1]; + let mem_selec2 = query[CHIPLETS_OFFSET + 2]; + + mem_selec0 * mem_selec1 * (E::ONE - mem_selec2) + } +} + +#[derive(Default)] +pub struct RangeCheckMultiplicity +where + E: FieldElement, +{ + phantom: PhantomData, +} + +impl CompositionPolynomial for RangeCheckMultiplicity +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + 1 + } + + fn max_degree(&self) -> usize { + 1 + } + + fn evaluate(&self, query: &[E]) -> E { + query[M_COL_IDX] + } +} + +pub struct StackValue +where + E: FieldElement, +{ + i: usize, + alphas: Vec, +} + +impl StackValue +where + E: FieldElement, +{ + pub fn new(i: usize, alphas: Vec) -> Self { + assert!(i < 4); + Self { i, alphas } + } +} + +impl CompositionPolynomial for StackValue +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + 1 + } + + fn max_degree(&self) -> usize { + 1 + } + + fn evaluate(&self, query: &[E]) -> E { + -(self.alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET + self.i]) + } +} + +pub struct MemoryValue +where + E: FieldElement, +{ + i: usize, + alphas: Vec, +} + +impl MemoryValue +where + E: FieldElement, +{ + pub fn new(i: usize, alphas: Vec) -> Self { + assert!(i < 2); + Self { i, alphas } + } +} + +impl CompositionPolynomial for MemoryValue +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + 1 + } + + fn max_degree(&self) -> usize { + 1 + } + + fn evaluate(&self, query: &[E]) -> E { + -(self.alphas[0] - query[MEMORY_D0_COL_IDX + self.i]) + } +} + +pub struct TableValue +where + E: FieldElement, +{ + alphas: Vec, +} + +impl TableValue +where + E: FieldElement, +{ + pub fn new(alphas: Vec) -> Self { + Self { alphas } + } +} + +impl CompositionPolynomial for TableValue +where + E: FieldElement, +{ + fn num_variables(&self) -> usize { + 1 + } + + fn max_degree(&self) -> usize { + 1 + } + + fn evaluate(&self, query: &[E]) -> E { + self.alphas[0] - query[V_COL_IDX] + } +} diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index 315a65e4c8..b8836c31ed 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -3,9 +3,9 @@ use alloc::vec::Vec; use vm_core::FieldElement; mod prover; -pub use prover::SumCheckProver; +pub use prover::{FinalClaimBuilder, SumCheckProver}; mod verifier; -pub use verifier::SumCheckVerifier; +pub use verifier::{FinalQueryBuilder, SumCheckVerifier}; /// A sum-check round proof. /// @@ -65,8 +65,8 @@ pub fn reduce_claim( /// This question is answered either using further interaction with the Prover or using /// a polynomial commitment opening proof in the compiled protocol. #[derive(Clone, Debug)] -pub struct FinalOpeningClaim { - pub evaluation_point: Vec, +pub struct FinalOpeningClaim { + pub eval_point: Vec, pub openings: Vec, } @@ -77,6 +77,7 @@ mod test { verifier::{CompositionPolyQueryBuilder, SumCheckVerifier}, }; use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinearPoly}; + use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinear}; use alloc::{borrow::ToOwned, vec::Vec}; use test_utils::rand::rand_vector; use vm_core::{crypto::random::RpoRandomCoin, Felt, FieldElement, Word, ONE, ZERO}; @@ -172,7 +173,7 @@ mod test { evaluation_point: &[Self::Field], ) -> super::FinalOpeningClaim { super::FinalOpeningClaim { - evaluation_point: evaluation_point.to_owned(), + eval_point: evaluation_point.to_owned(), openings, } } diff --git a/processor/src/trace/virtual_bus/sum_check/prover/error.rs b/processor/src/trace/virtual_bus/sum_check/prover/error.rs index a985f5f2d7..e425bbfbfd 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/error.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/error.rs @@ -2,10 +2,6 @@ pub enum Error { #[error("number of rounds for sum-check must be greater than zero")] NumRoundsZero, - #[error("sumcheck polynomial degree must be greater than zero")] - PolynomialDegreeIsZero, - #[error("the evaluation domain does not match the expected size")] - EvaluationDomainMismatch, #[error("the number of rounds is greater than the number of variables")] TooManyRounds, #[error("should provide at least one multi-linear polynomial as input")] diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/error.rs b/processor/src/trace/virtual_bus/sum_check/verifier/error.rs index 97eccfc042..19163283cd 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/error.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/error.rs @@ -2,8 +2,6 @@ pub enum Error { #[error("the final evaluation check of sum-check failed")] FinalEvaluationCheckFailed, - #[error("the proof doesn't contain openings of the multi-linears")] - NoOpeningsProvided, #[error("failed to generate round challenge")] FailedToGenerateChallenge, #[error("wrong opening point for the oracles")] diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index 6d080c2304..6d4544dae3 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -92,7 +92,7 @@ where claim: claimed_evaluation, } = self.verify_rounds(claim, round_proofs, coin)?; - if openings_claim.evaluation_point != evaluation_point { + if openings_claim.eval_point != evaluation_point { return Err(Error::WrongOpeningPoint); } let query = self.final_query_builder.build_query(&openings_claim, &evaluation_point); @@ -111,7 +111,7 @@ where round_proofs: Vec>, coin: &mut C, ) -> Result, Error> { - let mut round_claim = claim; + let mut claimed_evaluation = claim; let mut evaluation_point = vec![]; for round_proof in round_proofs { let round_poly_coefs = round_proof.round_poly_coefs.clone(); @@ -125,7 +125,7 @@ where Ok(RoundClaim { eval_point: evaluation_point, - claim: round_claim, + claim: claimed_evaluation, }) } } diff --git a/processor/src/trace/virtual_bus/tests.rs b/processor/src/trace/virtual_bus/tests.rs new file mode 100644 index 0000000000..c00a1f0c22 --- /dev/null +++ b/processor/src/trace/virtual_bus/tests.rs @@ -0,0 +1,59 @@ +#[cfg(test)] +mod test { + use crate::{ + trace::virtual_bus::{prover::VirtualBusProver, verifier::VirtualBusVerifier}, + DefaultHost, ExecutionTrace, Process, + }; + use alloc::vec::Vec; + use miden_air::{trace::main_trace::MainTrace, ExecutionOptions}; + use vm_core::crypto::random::RpoRandomCoin; + use vm_core::{ + code_blocks::CodeBlock, CodeBlockTable, Felt, FieldElement, Kernel, Operation, StackInputs, + }; + + #[test] + fn test_vb_prover() { + let s = 5; + let e = 5; + let stack: Vec<_> = (0..(1 << s)).into_iter().collect(); + let operations: Vec<_> = (0..(1 << e)) + .flat_map(|_| { + vec![Operation::U32split, Operation::U32add, Operation::U32xor, Operation::MStoreW] + }) + .collect(); + + let trace = build_full_trace(&stack, operations, Kernel::default()); + + // TODO: this should be generated using the transcript up to when the prover sends the commitment + // of the main trace. + let alphas = vec![Felt::from(7898787_u32)]; + + let seed = [Felt::ZERO; 4]; // TODO: should be initialized with the appropriate transcript + let mut transcript = RpoRandomCoin::new(seed.into()); + + let vb_prover = VirtualBusProver::new(alphas.clone()).unwrap(); + + let proof = vb_prover.prove(trace, &mut transcript); + + let seed = [Felt::ZERO; 4]; // TODO: should be initialized with the appropriate transcript + let mut transcript = RpoRandomCoin::new(seed.into()); + let vb_verifier = VirtualBusVerifier::new(alphas).unwrap(); + let _final_opening_claim = vb_verifier.verify(proof, &mut transcript); + } + + fn build_full_trace( + stack_inputs: &[u64], + operations: Vec, + kernel: Kernel, + ) -> MainTrace { + let stack_inputs: Vec = stack_inputs.iter().map(|a| Felt::new(*a)).collect(); + let stack_inputs = StackInputs::new(stack_inputs).unwrap(); + let host = DefaultHost::default(); + let mut process = Process::new(kernel, stack_inputs, host, ExecutionOptions::default()); + let program = CodeBlock::new_span(operations); + process.execute_code_block(&program, &CodeBlockTable::default()).unwrap(); + let (trace, _, _): (MainTrace, _, _) = ExecutionTrace::test_finalize_trace(process); + + trace + } +} diff --git a/processor/src/trace/virtual_bus/verifier.rs b/processor/src/trace/virtual_bus/verifier.rs new file mode 100644 index 0000000000..5ee2766663 --- /dev/null +++ b/processor/src/trace/virtual_bus/verifier.rs @@ -0,0 +1,57 @@ +use super::{ + circuit::GkrCircuitProof, error::Error, generate, multilinear::CompositionPolynomial, + sum_check::FinalOpeningClaim, verify, +}; +use alloc::{sync::Arc, vec::Vec}; +use core::marker::PhantomData; +use vm_core::{Felt, FieldElement}; +use winter_prover::crypto::{ElementHasher, RandomCoin}; + +/// A struct which implements the logic for verification of the correctness of the global virtual +/// bus relation. +pub struct VirtualBusVerifier +where + E: FieldElement + 'static, + C: RandomCoin, + H: ElementHasher, +{ + claim: E, + composition_polynomials: Vec>>>, + _challenger: PhantomData, +} + +impl VirtualBusVerifier +where + E: FieldElement + 'static, + C: RandomCoin, + H: ElementHasher, +{ + /// Constructs a new [VirtualBusVerifier] given a set of random values for the GKR-LogUp relation. + pub fn new(log_up_randomness: Vec) -> Result { + let (claim, composition_polynomials) = generate(log_up_randomness)?; + + Ok(Self { + claim, + composition_polynomials, + _challenger: PhantomData, + }) + } + + /// Returns the claim of the GKR-LogUp relation. + pub fn claim(&self) -> Result { + Ok(self.claim) + } + + /// Returns the composition polynomials of the left/right numerators/denominators of + /// the GKR-LogUp relation. + pub fn composition_polynomials(&self) -> Vec>>> { + self.composition_polynomials.clone() + } + + /// Verifies the GKR-LogUp relation. This output, in the case the proof is accepted, + /// a [FinalOpeningClaim] which is passed on to the STARK verifier in order to check + /// the correctness of the claimed openings. + pub fn verify(&self, proof: GkrCircuitProof, transcript: &mut C) -> FinalOpeningClaim { + verify(self.claim, proof, self.composition_polynomials(), transcript).unwrap() + } +} From fa97e9e9d4083e3f0b2e2d4d97abee6d60386aa6 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 5 Apr 2024 13:37:39 +0200 Subject: [PATCH 17/32] chore: add accessors and re-org --- .../src/trace/virtual_bus/circuit/mod.rs | 8 +- processor/src/trace/virtual_bus/mod.rs | 2 + processor/src/trace/virtual_bus/prover.rs | 2 +- .../src/trace/virtual_bus/sub_bus/mod.rs | 1 + .../virtual_bus/sub_bus/range_checker.rs | 32 ++++++ processor/src/trace/virtual_bus/tests.rs | 103 ++++++++---------- 6 files changed, 90 insertions(+), 58 deletions(-) diff --git a/processor/src/trace/virtual_bus/circuit/mod.rs b/processor/src/trace/virtual_bus/circuit/mod.rs index 4e7b6e94f3..c851096235 100644 --- a/processor/src/trace/virtual_bus/circuit/mod.rs +++ b/processor/src/trace/virtual_bus/circuit/mod.rs @@ -13,7 +13,7 @@ pub use prover::prove; mod verifier; pub use verifier::verify; -use super::sum_check::Proof as SumCheckProof; +use super::sum_check::{FinalOpeningClaim, Proof as SumCheckProof}; /// A GKR proof for the correct evaluation of the sum of fractions circuit. #[derive(Debug)] @@ -23,6 +23,12 @@ pub struct GkrCircuitProof { final_layer_proof: FinalLayerProof, } +impl GkrCircuitProof { + pub fn get_final_opening_claim(&self) -> FinalOpeningClaim { + self.final_layer_proof.after_merge_proof.openings_claim.clone() + } +} + /// A set of sum-check proofs for all GKR layers but for the input circuit layer. #[derive(Debug)] pub struct BeforeFinalLayerProof { diff --git a/processor/src/trace/virtual_bus/mod.rs b/processor/src/trace/virtual_bus/mod.rs index c31d260cf1..a6784fc63c 100644 --- a/processor/src/trace/virtual_bus/mod.rs +++ b/processor/src/trace/virtual_bus/mod.rs @@ -43,6 +43,8 @@ mod verifier; pub use verifier::VirtualBusVerifier; mod error; mod sub_bus; + +#[cfg(test)] mod tests; /// Generates the composition polynomials describing the virtual bus. diff --git a/processor/src/trace/virtual_bus/prover.rs b/processor/src/trace/virtual_bus/prover.rs index 59504cc351..d7844eda9c 100644 --- a/processor/src/trace/virtual_bus/prover.rs +++ b/processor/src/trace/virtual_bus/prover.rs @@ -86,7 +86,7 @@ where } /// Proves the GKR-LogUp relation. - pub fn prove(&self, trace: MainTrace, transcript: &mut C) -> GkrCircuitProof { + pub fn prove(&self, trace: &MainTrace, transcript: &mut C) -> GkrCircuitProof { // TODO: Optimize this so that we can work with base field element directly and thus save // on memory usage. let trace_len = trace.num_rows(); diff --git a/processor/src/trace/virtual_bus/sub_bus/mod.rs b/processor/src/trace/virtual_bus/sub_bus/mod.rs index 95385610fa..2660ff3c64 100644 --- a/processor/src/trace/virtual_bus/sub_bus/mod.rs +++ b/processor/src/trace/virtual_bus/sub_bus/mod.rs @@ -5,6 +5,7 @@ use vm_core::FieldElement; mod range_checker; pub use range_checker::*; +/// Defines a sub-bus of the global virtual bus pub trait BusBuilder { fn compute_initial_claim(&self) -> E; diff --git a/processor/src/trace/virtual_bus/sub_bus/range_checker.rs b/processor/src/trace/virtual_bus/sub_bus/range_checker.rs index 7cb4bd200d..132eb53fac 100644 --- a/processor/src/trace/virtual_bus/sub_bus/range_checker.rs +++ b/processor/src/trace/virtual_bus/sub_bus/range_checker.rs @@ -10,6 +10,28 @@ use miden_air::trace::{ }; use vm_core::FieldElement; +/// Defines the range checker sub-bus. +/// +/// Define the following variables: +/// +/// - rc_value: the range checker value +/// - rc_multiplicity: the range checker multiplicity value +/// - flag_s: boolean flag indicating a stack operation with range checks. This flag is degree 3. +/// - sv0-sv3: stack value 0-3, the 4 values range-checked from the stack +/// - flag_m: boolean flag indicating the memory chiplet is active (i.e. range checks are required). +/// This flag is degree 3. +/// - mv0-mv1: memory value 0-1, the 2 values range-checked from the memory chiplet +/// +/// Let `col(x)` denote the multi-linear extension of trace column `col`. This means that +/// for x \in \{0 , 1\}^{\nu}, where \nu is the log_2 of the trace length, `col(x)` is the value +/// at row index [x] = \sum_{j=0}^{\nu - 1} x_j * 2^j of column `col`. +/// +/// Given the above, the range checker is implemented using the expression +/// +/// 0 = \sum_{x \in \{0 , 1\}^{\nu}} rc_multiplicity(x) / (alpha - rc_value)(x) +/// - flag_s(x) / (alpha - sv0)(x) - flag_s(x) / (alpha - sv1)(x) +/// - flag_s(x) / (alpha - sv2)(x) - flag_s(x) / (alpha - sv3)(x) +/// - flag_m(x) / (alpha - mv0)(x) - flag_m(x) / (alpha - mv1)(x) pub struct RangeCheckerBus { alphas: Vec, } @@ -29,9 +51,12 @@ impl BusBuilder for RangeCheckerBus { fn build_numerators(&self) -> Vec>> { vec![ + // rc_multiplicity(x) Arc::new(RangeCheckMultiplicity::default()), + // flag_m(x) Arc::new(MemoryFlagChiplet::default()), Arc::new(MemoryFlagChiplet::default()), + // flag_s(x) Arc::new(U32RangeCheckFlag::default()), Arc::new(U32RangeCheckFlag::default()), Arc::new(U32RangeCheckFlag::default()), @@ -41,12 +66,19 @@ impl BusBuilder for RangeCheckerBus { fn build_denominators(&self) -> Vec>> { vec![ + // (alpha - rc_value)(x) Arc::new(TableValue::new(self.alphas.clone())), + // (alpha - mv0)(x) Arc::new(MemoryValue::new(0, self.alphas.clone())), + // (alpha - mv1)(x) Arc::new(MemoryValue::new(1, self.alphas.clone())), + // (alpha - sv0)(x) Arc::new(StackValue::new(0, self.alphas.clone())), + // (alpha - sv1)(x) Arc::new(StackValue::new(1, self.alphas.clone())), + // (alpha - sv2)(x) Arc::new(StackValue::new(2, self.alphas.clone())), + // (alpha - sv3)(x) Arc::new(StackValue::new(3, self.alphas.clone())), ] } diff --git a/processor/src/trace/virtual_bus/tests.rs b/processor/src/trace/virtual_bus/tests.rs index c00a1f0c22..64129b5802 100644 --- a/processor/src/trace/virtual_bus/tests.rs +++ b/processor/src/trace/virtual_bus/tests.rs @@ -1,59 +1,50 @@ -#[cfg(test)] -mod test { - use crate::{ - trace::virtual_bus::{prover::VirtualBusProver, verifier::VirtualBusVerifier}, - DefaultHost, ExecutionTrace, Process, - }; - use alloc::vec::Vec; - use miden_air::{trace::main_trace::MainTrace, ExecutionOptions}; - use vm_core::crypto::random::RpoRandomCoin; - use vm_core::{ - code_blocks::CodeBlock, CodeBlockTable, Felt, FieldElement, Kernel, Operation, StackInputs, - }; - - #[test] - fn test_vb_prover() { - let s = 5; - let e = 5; - let stack: Vec<_> = (0..(1 << s)).into_iter().collect(); - let operations: Vec<_> = (0..(1 << e)) - .flat_map(|_| { - vec![Operation::U32split, Operation::U32add, Operation::U32xor, Operation::MStoreW] - }) - .collect(); - - let trace = build_full_trace(&stack, operations, Kernel::default()); - - // TODO: this should be generated using the transcript up to when the prover sends the commitment - // of the main trace. - let alphas = vec![Felt::from(7898787_u32)]; - - let seed = [Felt::ZERO; 4]; // TODO: should be initialized with the appropriate transcript - let mut transcript = RpoRandomCoin::new(seed.into()); - - let vb_prover = VirtualBusProver::new(alphas.clone()).unwrap(); - - let proof = vb_prover.prove(trace, &mut transcript); - - let seed = [Felt::ZERO; 4]; // TODO: should be initialized with the appropriate transcript - let mut transcript = RpoRandomCoin::new(seed.into()); - let vb_verifier = VirtualBusVerifier::new(alphas).unwrap(); - let _final_opening_claim = vb_verifier.verify(proof, &mut transcript); - } +use crate::{ + trace::virtual_bus::{prover::VirtualBusProver, verifier::VirtualBusVerifier}, + DefaultHost, ExecutionTrace, Process, +}; +use alloc::vec::Vec; +use miden_air::{trace::main_trace::MainTrace, ExecutionOptions}; +use vm_core::crypto::random::RpoRandomCoin; +use vm_core::{ + code_blocks::CodeBlock, CodeBlockTable, Felt, FieldElement, Kernel, Operation, StackInputs, +}; + +#[test] +fn test_vb_prover_verifier() { + let s = 6; + let o = 6; + let stack: Vec<_> = (0..(1 << s)).into_iter().collect(); + let operations: Vec<_> = (0..(1 << o)) + .flat_map(|_| { + vec![Operation::U32split, Operation::U32add, Operation::U32xor, Operation::MStoreW] + }) + .collect(); + + let trace = build_full_trace(&stack, operations, Kernel::default()); + + // this should be generated using the transcript up to when the prover sends the commitment + // to the main trace. + let alphas: Vec = vec![test_utils::rand::rand_value()]; + + let seed = [Felt::ZERO; 4]; // should be initialized with the appropriate transcript + let mut transcript = RpoRandomCoin::new(seed.into()); + let vb_prover = VirtualBusProver::new(alphas.clone()).unwrap(); + let proof = vb_prover.prove(&trace, &mut transcript); + + let seed = [Felt::ZERO; 4]; // should be initialized with the appropriate transcript + let mut transcript = RpoRandomCoin::new(seed.into()); + let vb_verifier = VirtualBusVerifier::new(alphas).unwrap(); + let _final_opening_claim = vb_verifier.verify(proof, &mut transcript); +} - fn build_full_trace( - stack_inputs: &[u64], - operations: Vec, - kernel: Kernel, - ) -> MainTrace { - let stack_inputs: Vec = stack_inputs.iter().map(|a| Felt::new(*a)).collect(); - let stack_inputs = StackInputs::new(stack_inputs).unwrap(); - let host = DefaultHost::default(); - let mut process = Process::new(kernel, stack_inputs, host, ExecutionOptions::default()); - let program = CodeBlock::new_span(operations); - process.execute_code_block(&program, &CodeBlockTable::default()).unwrap(); - let (trace, _, _): (MainTrace, _, _) = ExecutionTrace::test_finalize_trace(process); +fn build_full_trace(stack_inputs: &[u64], operations: Vec, kernel: Kernel) -> MainTrace { + let stack_inputs: Vec = stack_inputs.iter().map(|a| Felt::new(*a)).collect(); + let stack_inputs = StackInputs::new(stack_inputs).unwrap(); + let host = DefaultHost::default(); + let mut process = Process::new(kernel, stack_inputs, host, ExecutionOptions::default()); + let program = CodeBlock::new_span(operations); + process.execute_code_block(&program, &CodeBlockTable::default()).unwrap(); + let (trace, _, _): (MainTrace, _, _) = ExecutionTrace::test_finalize_trace(process); - trace - } + trace } From 63243a8de58514bace16d0b4aa2ebf6f72dd9a5b Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 5 Apr 2024 19:41:53 +0200 Subject: [PATCH 18/32] chore: fix merge conflicts --- .../src/trace/virtual_bus/circuit/mod.rs | 24 +++--- .../src/trace/virtual_bus/circuit/prover.rs | 81 ++++++++++--------- .../src/trace/virtual_bus/circuit/verifier.rs | 24 ++---- processor/src/trace/virtual_bus/mod.rs | 8 +- .../virtual_bus/multilinear/lagrange_ker.rs | 6 +- .../src/trace/virtual_bus/multilinear/mod.rs | 18 +---- processor/src/trace/virtual_bus/prover.rs | 6 +- .../virtual_bus/sub_bus/range_checker.rs | 24 +++--- .../src/trace/virtual_bus/sum_check/mod.rs | 3 +- .../virtual_bus/sum_check/verifier/mod.rs | 4 +- 10 files changed, 91 insertions(+), 107 deletions(-) diff --git a/processor/src/trace/virtual_bus/circuit/mod.rs b/processor/src/trace/virtual_bus/circuit/mod.rs index c851096235..dfb6d1c3f5 100644 --- a/processor/src/trace/virtual_bus/circuit/mod.rs +++ b/processor/src/trace/virtual_bus/circuit/mod.rs @@ -1,7 +1,5 @@ -use crate::trace::virtual_bus::{ - multilinear::{CompositionPolynomial, EqFunction}, - sum_check::RoundProof, -}; +use crate::trace::virtual_bus::multilinear::EqFunction; +use crate::trace::virtual_bus::{multilinear::CompositionPolynomial, sum_check::RoundProof}; use alloc::{borrow::ToOwned, sync::Arc, vec::Vec}; use miden_air::trace::TRACE_WIDTH; use vm_core::{Felt, FieldElement}; @@ -42,7 +40,7 @@ pub struct FinalLayerProof { after_merge_proof: SumCheckProof, } -/// Represents a claim to be proven by next sum-check protocol. +/// Represents a claim to be proven by a subsequent call to the sum-check protocol. #[derive(Debug)] pub struct GkrClaim { pub evaluation_point: Vec, @@ -65,11 +63,11 @@ impl CompositionPolynomial for ProjectionComposition where E: FieldElement, { - fn num_variables(&self) -> usize { + fn num_variables(&self) -> u32 { 1 } - fn max_degree(&self) -> usize { + fn max_degree(&self) -> u32 { 1 } @@ -102,11 +100,11 @@ impl CompositionPolynomial for GkrComposition where E: FieldElement, { - fn num_variables(&self) -> usize { + fn num_variables(&self) -> u32 { 5 } - fn max_degree(&self) -> usize { + fn max_degree(&self) -> u32 { 3 } @@ -131,7 +129,7 @@ where { pub sum_check_combining_randomness: E, pub tensored_merge_randomness: Vec, - pub degree: usize, + pub degree: u32, pub eq_composer: Arc>, pub right_numerator_composer: Vec>>, @@ -182,11 +180,11 @@ impl CompositionPolynomial for GkrCompositionMerge where E: FieldElement, { - fn num_variables(&self) -> usize { - TRACE_WIDTH + fn num_variables(&self) -> u32 { + TRACE_WIDTH as u32 } - fn max_degree(&self) -> usize { + fn max_degree(&self) -> u32 { self.degree } diff --git a/processor/src/trace/virtual_bus/circuit/prover.rs b/processor/src/trace/virtual_bus/circuit/prover.rs index 2e37aa01a0..0fafc0173e 100644 --- a/processor/src/trace/virtual_bus/circuit/prover.rs +++ b/processor/src/trace/virtual_bus/circuit/prover.rs @@ -4,7 +4,7 @@ use super::{ GkrCircuitProof, GkrClaim, GkrComposition, }; use crate::trace::virtual_bus::{ - multilinear::{CompositionPolynomial, EqFunction, MultiLinear}, + multilinear::{CompositionPolynomial, EqFunction, MultiLinearPoly}, sum_check::{FinalClaimBuilder, FinalOpeningClaim, RoundClaim, RoundProof}, SumCheckProver, }; @@ -16,13 +16,13 @@ use winter_prover::crypto::{ElementHasher, RandomCoin}; /// Layered circuit for computing a sum of fractions. /// /// The circuit computes a sum of fractions based on the formula a / c + b / d = (a * d + b * c) / (c * d) -/// which defines a "gate" ((a, b), (c, d)) --> (a * d + b * c, c * d) upon which the `FractionalSumCircuit` +/// which defines a "gate" ((a, b), (c, d)) --> (a * d + b * c, c * d) upon which the [`FractionalSumCircuit`] /// is built. Due to the uniformity of the circuit, each of the circuit layers collect all the: /// -/// 1. `a`'s into a [MultiLinear] called `p_0`. -/// 2. `b`'s into a [MultiLinear] called `p_1`. -/// 3. `c`'s into a [MultiLinear] called `q_0`. -/// 4. `d`'s into a [MultiLinear] called `q_1`. +/// 1. `a`'s into a [`MultiLinearPoly`] called `p_0`. +/// 2. `b`'s into a [`MultiLinearPoly`] called `p_1`. +/// 3. `c`'s into a [`MultiLinearPoly`] called `q_0`. +/// 4. `d`'s into a [`MultiLinearPoly`] called `q_1`. /// /// The relation between two subsequent layers is given by the formula /// @@ -42,10 +42,10 @@ use winter_prover::crypto::{ElementHasher, RandomCoin}; /// (p_0[ν - 1], p_1[ν - 1], p_0[ν - 1], p_1[ν - 1]) ∈ 𝔽^ν. #[derive(Debug)] pub struct FractionalSumCircuit { - p_0_vec: Vec>, - p_1_vec: Vec>, - q_0_vec: Vec>, - q_1_vec: Vec>, + p_0_vec: Vec>, + p_1_vec: Vec>, + q_0_vec: Vec>, + q_1_vec: Vec>, } impl FractionalSumCircuit { @@ -66,19 +66,19 @@ impl FractionalSumCircuit { } let num_layers = num_evaluations.ilog2() as usize; - let mut p_0_vec: Vec> = Vec::with_capacity(num_layers); - let mut p_1_vec: Vec> = Vec::with_capacity(num_layers); - let mut q_0_vec: Vec> = Vec::with_capacity(num_layers); - let mut q_1_vec: Vec> = Vec::with_capacity(num_layers); - - let p_0 = - MultiLinear::from_values(&num_den[0]).map_err(|_| ProverError::FailedGenerateML)?; - let p_1 = - MultiLinear::from_values(&num_den[1]).map_err(|_| ProverError::FailedGenerateML)?; - let q_0 = - MultiLinear::from_values(&num_den[2]).map_err(|_| ProverError::FailedGenerateML)?; - let q_1 = - MultiLinear::from_values(&num_den[3]).map_err(|_| ProverError::FailedGenerateML)?; + let mut p_0_vec: Vec> = Vec::with_capacity(num_layers); + let mut p_1_vec: Vec> = Vec::with_capacity(num_layers); + let mut q_0_vec: Vec> = Vec::with_capacity(num_layers); + let mut q_1_vec: Vec> = Vec::with_capacity(num_layers); + + let p_0 = MultiLinearPoly::from_evaluations(num_den[0].to_owned()) + .map_err(|_| ProverError::FailedGenerateML)?; + let p_1 = MultiLinearPoly::from_evaluations(num_den[1].to_owned()) + .map_err(|_| ProverError::FailedGenerateML)?; + let q_0 = MultiLinearPoly::from_evaluations(num_den[2].to_owned()) + .map_err(|_| ProverError::FailedGenerateML)?; + let q_1 = MultiLinearPoly::from_evaluations(num_den[3].to_owned()) + .map_err(|_| ProverError::FailedGenerateML)?; p_0_vec.push(p_0); p_1_vec.push(p_1); q_0_vec.push(q_0); @@ -109,11 +109,14 @@ impl FractionalSumCircuit { /// Computes the output values of the layer given a set of input values #[allow(clippy::type_complexity)] fn compute_layer( - inp_p_0: &MultiLinear, - inp_p_1: &MultiLinear, - inp_q_0: &MultiLinear, - inp_q_1: &MultiLinear, - ) -> Result<(MultiLinear, MultiLinear, MultiLinear, MultiLinear), ProverError> { + inp_p_0: &MultiLinearPoly, + inp_p_1: &MultiLinearPoly, + inp_q_0: &MultiLinearPoly, + inp_q_1: &MultiLinearPoly, + ) -> Result< + (MultiLinearPoly, MultiLinearPoly, MultiLinearPoly, MultiLinearPoly), + ProverError, + > { let len = inp_q_0.num_evaluations(); let outp_p_0 = (0..len / 2) .map(|i| inp_p_0[i] * inp_q_1[i] + inp_p_1[i] * inp_q_0[i]) @@ -125,10 +128,14 @@ impl FractionalSumCircuit { let outp_q_1 = (len / 2..len).map(|i| inp_q_0[i] * inp_q_1[i]).collect::>(); Ok(( - MultiLinear::new(outp_p_0).map_err(|_| ProverError::FailedGenerateML)?, - MultiLinear::new(outp_p_1).map_err(|_| ProverError::FailedGenerateML)?, - MultiLinear::new(outp_q_0).map_err(|_| ProverError::FailedGenerateML)?, - MultiLinear::new(outp_q_1).map_err(|_| ProverError::FailedGenerateML)?, + MultiLinearPoly::from_evaluations(outp_p_0) + .map_err(|_| ProverError::FailedGenerateML)?, + MultiLinearPoly::from_evaluations(outp_p_1) + .map_err(|_| ProverError::FailedGenerateML)?, + MultiLinearPoly::from_evaluations(outp_q_0) + .map_err(|_| ProverError::FailedGenerateML)?, + MultiLinearPoly::from_evaluations(outp_q_1) + .map_err(|_| ProverError::FailedGenerateML)?, )) } @@ -203,7 +210,7 @@ pub fn prove< H: ElementHasher, >( composition_polys: Vec>>>, - mls: &mut Vec>, + mls: &mut Vec>, transcript: &mut C, ) -> GkrCircuitProof { // evaluate the numerators and denominators over the boolean hyper-cube {0, 1}^{μ + ν} @@ -244,7 +251,7 @@ fn prove_final_circuit_layer< H: ElementHasher, >( composition_polys: Vec>>>, - mls: &mut Vec>, + mls: &mut Vec>, num_rounds_merge: usize, gkr_claim: GkrClaim, circuit: &mut FractionalSumCircuit, @@ -379,7 +386,7 @@ fn sum_check_prover_plain_partial< >( claim: (E, E), num_rounds: usize, - ml_polys: &mut [MultiLinear], + ml_polys: &mut [MultiLinearPoly], transcript: &mut C, ) -> ((RoundClaim, Vec>), E) { // generate challenge to batch two sumchecks @@ -405,7 +412,7 @@ fn sum_check_prover_plain_full< H: ElementHasher, >( claim: (E, E), - ml_polys: &mut [MultiLinear], + ml_polys: &mut [MultiLinearPoly], transcript: &mut C, ) -> (SumCheckProof, E) { // generate challenge to batch two sumchecks @@ -429,7 +436,7 @@ fn sum_check_prover_plain_full< /// m(z_0, ... , z_{μ - 1}, x_0, ... , x_{ν - 1}) = /// \sum_{y ∈ {0,1}^μ} EQ(z, y) * g_{[y]}(f_0(x_0, ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , x_{ν - 1})) fn evaluate_composition_polys + 'static>( - mls: &[MultiLinear], + mls: &[MultiLinearPoly], composition_polys: &[Vec>>], ) -> Vec> { let num_evaluations = 1 << mls[0].num_variables(); diff --git a/processor/src/trace/virtual_bus/circuit/verifier.rs b/processor/src/trace/virtual_bus/circuit/verifier.rs index 48da26e63c..640796ce4f 100644 --- a/processor/src/trace/virtual_bus/circuit/verifier.rs +++ b/processor/src/trace/virtual_bus/circuit/verifier.rs @@ -4,7 +4,9 @@ use super::{ }; use crate::trace::virtual_bus::{ multilinear::{CompositionPolynomial, EqFunction}, - sum_check::{FinalOpeningClaim, FinalQueryBuilder, Proof as SumCheckFullProof, RoundClaim}, + sum_check::{ + CompositionPolyQueryBuilder, FinalOpeningClaim, Proof as SumCheckFullProof, RoundClaim, + }, SumCheckVerifier, }; use alloc::{borrow::ToOwned, sync::Arc, vec::Vec}; @@ -184,14 +186,8 @@ impl GkrQueryBuilder { } } -impl FinalQueryBuilder for GkrQueryBuilder { - type Field = E; - - fn build_query( - &self, - openings_claim: &FinalOpeningClaim, - evaluation_point: &[Self::Field], - ) -> Vec { +impl CompositionPolyQueryBuilder for GkrQueryBuilder { + fn build_query(&self, openings_claim: &FinalOpeningClaim, evaluation_point: &[E]) -> Vec { let rand_sumcheck = evaluation_point; let eq_at_gkr_eval_point = EqFunction::new(self.gkr_eval_point.clone()); let eq = eq_at_gkr_eval_point.evaluate(rand_sumcheck); @@ -218,14 +214,8 @@ impl GkrMergeQueryBuilder { } } -impl FinalQueryBuilder for GkrMergeQueryBuilder { - type Field = E; - - fn build_query( - &self, - openings_claim: &FinalOpeningClaim, - evaluation_point: &[Self::Field], - ) -> Vec { +impl CompositionPolyQueryBuilder for GkrMergeQueryBuilder { + fn build_query(&self, openings_claim: &FinalOpeningClaim, evaluation_point: &[E]) -> Vec { let eq_at_gkr_eval_point = EqFunction::new(self.gkr_eval_point.clone()); let mut rand_sumcheck = self.merge_rand.clone(); rand_sumcheck.extend_from_slice(evaluation_point); diff --git a/processor/src/trace/virtual_bus/mod.rs b/processor/src/trace/virtual_bus/mod.rs index a6784fc63c..a7b17a182c 100644 --- a/processor/src/trace/virtual_bus/mod.rs +++ b/processor/src/trace/virtual_bus/mod.rs @@ -130,11 +130,11 @@ impl CompositionPolynomial for ZeroNumerator where E: FieldElement, { - fn num_variables(&self) -> usize { + fn num_variables(&self) -> u32 { 1 // TODO: Update } - fn max_degree(&self) -> usize { + fn max_degree(&self) -> u32 { 1 } @@ -163,11 +163,11 @@ impl CompositionPolynomial for OneDenominator where E: FieldElement, { - fn num_variables(&self) -> usize { + fn num_variables(&self) -> u32 { 1 // TODO: Update } - fn max_degree(&self) -> usize { + fn max_degree(&self) -> u32 { 1 } diff --git a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs index 73dc1d1d65..ac4b749d38 100644 --- a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs +++ b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs @@ -1,4 +1,4 @@ -use super::{tensorize, FieldElement, MultiLinear}; +use super::{tensorize, FieldElement, MultiLinearPoly}; use alloc::vec::Vec; /// The EQ (equality) function is the binary function defined by @@ -61,9 +61,9 @@ impl EqFunction { /// Returns the evaluations of /// ((y_0, ..., y_{ν - 1}) ↦ EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))) /// over {0 , 1}^ν. - pub fn ml_at(evaluation_point: Vec) -> MultiLinear { + pub fn ml_at(evaluation_point: Vec) -> MultiLinearPoly { let eq_evals = EqFunction::new(evaluation_point.clone()).evaluations(); - MultiLinear::from_values(&eq_evals) + MultiLinearPoly::from_evaluations(eq_evals) .expect("should not fail because evaluations is a power of two") } } diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index cc63534458..e33fc098b3 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -3,6 +3,9 @@ use core::ops::Index; use vm_core::FieldElement; use winter_prover::math::log2; +mod lagrange_ker; +pub use lagrange_ker::EqFunction; + mod error; use self::error::Error; @@ -57,19 +60,6 @@ impl MultiLinearPoly { inner_product(&self.evaluations, &tensored_query) } - /// Computes f(r_0, y_1, ..., y_{ν - 1}) using the linear interpolation formula - /// (1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1}). The resulting - /// returned multi-linear is defined over a domain of half the size. - #[allow(dead_code)] - pub fn bind(&self, round_challenge: E) -> Self { - let mut result = vec![E::ZERO; 1 << (self.num_variables() - 1)]; - for (i, res) in result.iter_mut().enumerate() { - *res = self.evaluations[i << 1] - + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); - } - Self::from_values(&result).expect("should not fail given that it is a multi-linear") - } - /// Computes f(r_0, y_1, ..., y_{ν - 1}) using the linear interpolation formula /// (1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1}) and assigns /// the resulting multi-linear, defined over a domain of half the size, to `self`. @@ -83,7 +73,7 @@ impl MultiLinearPoly { .expect("should not fail given that it is a multi-linear"); } - pub fn extend(&mut self, other: &MultiLinear) { + pub fn extend(&mut self, other: &MultiLinearPoly) { let other_vec = other.evaluations.to_vec(); assert_eq!(other_vec.len(), self.evaluations().len()); self.evaluations.extend(other_vec); diff --git a/processor/src/trace/virtual_bus/prover.rs b/processor/src/trace/virtual_bus/prover.rs index d7844eda9c..dc947882d7 100644 --- a/processor/src/trace/virtual_bus/prover.rs +++ b/processor/src/trace/virtual_bus/prover.rs @@ -2,7 +2,7 @@ use super::{ circuit::GkrCircuitProof, error::Error, generate, - multilinear::{CompositionPolynomial, MultiLinear}, + multilinear::{CompositionPolynomial, MultiLinearPoly}, prove, }; use alloc::{sync::Arc, vec::Vec}; @@ -90,12 +90,12 @@ where // TODO: Optimize this so that we can work with base field element directly and thus save // on memory usage. let trace_len = trace.num_rows(); - let mut mls: Vec> = trace + let mut mls: Vec> = trace .columns() .map(|col| { let mut values: Vec = col.iter().map(|value| E::from(*value)).collect(); values[trace_len - 1] = E::ZERO; - MultiLinear::new(values).unwrap() + MultiLinearPoly::from_evaluations(values).unwrap() }) .collect(); prove(self.composition_polynomials(), &mut mls, transcript) diff --git a/processor/src/trace/virtual_bus/sub_bus/range_checker.rs b/processor/src/trace/virtual_bus/sub_bus/range_checker.rs index 132eb53fac..6c66b9c6e2 100644 --- a/processor/src/trace/virtual_bus/sub_bus/range_checker.rs +++ b/processor/src/trace/virtual_bus/sub_bus/range_checker.rs @@ -96,11 +96,11 @@ impl CompositionPolynomial for U32RangeCheckFlag where E: FieldElement, { - fn num_variables(&self) -> usize { + fn num_variables(&self) -> u32 { 1 } - fn max_degree(&self) -> usize { + fn max_degree(&self) -> u32 { 3 } @@ -125,11 +125,11 @@ impl CompositionPolynomial for MemoryFlagChiplet where E: FieldElement, { - fn num_variables(&self) -> usize { + fn num_variables(&self) -> u32 { 1 } - fn max_degree(&self) -> usize { + fn max_degree(&self) -> u32 { 3 } @@ -154,11 +154,11 @@ impl CompositionPolynomial for RangeCheckMultiplicity where E: FieldElement, { - fn num_variables(&self) -> usize { + fn num_variables(&self) -> u32 { 1 } - fn max_degree(&self) -> usize { + fn max_degree(&self) -> u32 { 1 } @@ -189,11 +189,11 @@ impl CompositionPolynomial for StackValue where E: FieldElement, { - fn num_variables(&self) -> usize { + fn num_variables(&self) -> u32 { 1 } - fn max_degree(&self) -> usize { + fn max_degree(&self) -> u32 { 1 } @@ -224,11 +224,11 @@ impl CompositionPolynomial for MemoryValue where E: FieldElement, { - fn num_variables(&self) -> usize { + fn num_variables(&self) -> u32 { 1 } - fn max_degree(&self) -> usize { + fn max_degree(&self) -> u32 { 1 } @@ -257,11 +257,11 @@ impl CompositionPolynomial for TableValue where E: FieldElement, { - fn num_variables(&self) -> usize { + fn num_variables(&self) -> u32 { 1 } - fn max_degree(&self) -> usize { + fn max_degree(&self) -> u32 { 1 } diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index b8836c31ed..82ef500a89 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -5,7 +5,7 @@ use vm_core::FieldElement; mod prover; pub use prover::{FinalClaimBuilder, SumCheckProver}; mod verifier; -pub use verifier::{FinalQueryBuilder, SumCheckVerifier}; +pub use verifier::{CompositionPolyQueryBuilder, SumCheckVerifier}; /// A sum-check round proof. /// @@ -77,7 +77,6 @@ mod test { verifier::{CompositionPolyQueryBuilder, SumCheckVerifier}, }; use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinearPoly}; - use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinear}; use alloc::{borrow::ToOwned, vec::Vec}; use test_utils::rand::rand_vector; use vm_core::{crypto::random::RpoRandomCoin, Felt, FieldElement, Word, ONE, ZERO}; diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index 6d4544dae3..a5e9d4f118 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -111,7 +111,7 @@ where round_proofs: Vec>, coin: &mut C, ) -> Result, Error> { - let mut claimed_evaluation = claim; + let mut round_claim = claim; let mut evaluation_point = vec![]; for round_proof in round_proofs { let round_poly_coefs = round_proof.round_poly_coefs.clone(); @@ -125,7 +125,7 @@ where Ok(RoundClaim { eval_point: evaluation_point, - claim: claimed_evaluation, + claim: round_claim, }) } } From a275df46dec2c91ac3772895f235c366af96319f Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 5 Apr 2024 19:48:02 +0200 Subject: [PATCH 19/32] chore: re-shuffle --- .../virtual_bus/multilinear/lagrange_ker.rs | 39 +++++++++++++++-- .../src/trace/virtual_bus/multilinear/mod.rs | 42 +++---------------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs index ac4b749d38..ece573d099 100644 --- a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs +++ b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs @@ -1,4 +1,4 @@ -use super::{tensorize, FieldElement, MultiLinearPoly}; +use super::{FieldElement, MultiLinearPoly}; use alloc::vec::Vec; /// The EQ (equality) function is the binary function defined by @@ -24,7 +24,7 @@ use alloc::vec::Vec; /// ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) for all (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν for /// a fixed (r_0, ..., r_{ν - 1}) ∈ 𝔽^ν. /// -/// [EqFunction] represents EQ^{~} the mult-linear extension of +/// [`EqFunction`] represents EQ^{~} the mult-linear extension of /// /// ((y_0, ..., y_{ν - 1}) ↦ EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))) /// @@ -55,7 +55,7 @@ impl EqFunction { /// Computes EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) for all /// (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν i.e., the Lagrange kernel at r = (r_0, ..., r_{ν - 1}). pub fn evaluations(&self) -> Vec { - tensorize(&self.r) + compute_lagrange_basis_evals_at(&self.r) } /// Returns the evaluations of @@ -67,3 +67,36 @@ impl EqFunction { .expect("should not fail because evaluations is a power of two") } } + +// HELPER +// ================================================================================================ + +/// Computes the inner product of two vectors of the same length. +/// +/// Panics if the vectors have different lengths. +pub fn inner_product(evaluations: &[E], tensored_query: &[E]) -> E { + assert_eq!(evaluations.len(), tensored_query.len()); + evaluations + .iter() + .zip(tensored_query.iter()) + .fold(E::ZERO, |acc, (x_i, y_i)| acc + *x_i * *y_i) +} + +/// Computes the evaluations of the Lagrange basis polynomials over the interpolating +/// set {0 , 1}^ν at (r_0, ..., r_{ν - 1}) i.e., the Lagrange kernel at (r_0, ..., r_{ν - 1}). +pub fn compute_lagrange_basis_evals_at(query: &[E]) -> Vec { + let nu = query.len(); + let n = 1 << nu; + + let mut evals: Vec = vec![E::ONE; n]; + let mut size = 1; + for r_i in query.iter().rev() { + size *= 2; + for i in (0..size).rev().step_by(2) { + let scalar = evals[i / 2]; + evals[i] = scalar * *r_i; + evals[i - 1] = scalar - evals[i]; + } + } + evals +} diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index e33fc098b3..7d5cd34a78 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -7,7 +7,10 @@ mod lagrange_ker; pub use lagrange_ker::EqFunction; mod error; -use self::error::Error; +use self::{ + error::Error, + lagrange_ker::{compute_lagrange_basis_evals_at, inner_product}, +}; // MULTI-LINEAR POLYNOMIAL // ================================================================================================ @@ -23,7 +26,7 @@ pub struct MultiLinearPoly { } impl MultiLinearPoly { - /// Constructs a [MultiLinearPoly] from its evaluations over the boolean hyper-cube {0 , 1}^ν. + /// Constructs a [`MultiLinearPoly`] from its evaluations over the boolean hyper-cube {0 , 1}^ν. pub fn from_evaluations(evaluations: Vec) -> Result { if !evaluations.len().is_power_of_two() { return Err(Error::EvaluationsNotPowerOfTwo); @@ -56,7 +59,7 @@ impl MultiLinearPoly { /// The evaluation then is the inner product, indexed by {0 , 1}^ν, of the vector of /// evaluations times the Lagrange kernel. pub fn evaluate(&self, query: &[E]) -> E { - let tensored_query = tensorize(query); + let tensored_query = compute_lagrange_basis_evals_at(query); inner_product(&self.evaluations, &tensored_query) } @@ -103,36 +106,3 @@ pub trait CompositionPolynomial { /// Given a query, of length equal the number of variables, evaluates [Self] at this query. fn evaluate(&self, query: &[E]) -> E; } - -// HELPER -// ================================================================================================ - -/// Computes the inner product of two vectors of the same length. -/// -/// Panics if the vectors have different lengths. -fn inner_product(evaluations: &[E], tensored_query: &[E]) -> E { - assert_eq!(evaluations.len(), tensored_query.len()); - evaluations - .iter() - .zip(tensored_query.iter()) - .fold(E::ZERO, |acc, (x_i, y_i)| acc + *x_i * *y_i) -} - -/// Computes the evaluations of the Lagrange basis polynomials over the interpolating -/// set {0 , 1}^ν at (r_0, ..., r_{ν - 1}) i.e., the Lagrange kernel at (r_0, ..., r_{ν - 1}). -pub fn tensorize(query: &[E]) -> Vec { - let nu = query.len(); - let n = 1 << nu; - - let mut evals: Vec = vec![E::ONE; n]; - let mut size = 1; - for r_i in query.iter().rev() { - size *= 2; - for i in (0..size).rev().step_by(2) { - let scalar = evals[i / 2]; - evals[i] = scalar * *r_i; - evals[i - 1] = scalar - evals[i]; - } - } - evals -} From d6cd1973474568d461c627e96a2b47f77e12c7dd Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Sun, 7 Apr 2024 20:25:51 +0200 Subject: [PATCH 20/32] chore: add vb errors and failing test --- air/src/trace/main_trace.rs | 7 ++ .../src/trace/virtual_bus/circuit/error.rs | 8 +- .../src/trace/virtual_bus/circuit/prover.rs | 93 ++++++++++--------- .../src/trace/virtual_bus/circuit/verifier.rs | 12 +-- processor/src/trace/virtual_bus/error.rs | 12 +++ processor/src/trace/virtual_bus/prover.rs | 12 ++- processor/src/trace/virtual_bus/tests.rs | 42 ++++++++- processor/src/trace/virtual_bus/verifier.rs | 19 +++- 8 files changed, 143 insertions(+), 62 deletions(-) diff --git a/air/src/trace/main_trace.rs b/air/src/trace/main_trace.rs index dc4db3ca88..029871f3b2 100644 --- a/air/src/trace/main_trace.rs +++ b/air/src/trace/main_trace.rs @@ -41,6 +41,13 @@ impl Deref for MainTrace { } } +#[cfg(any(test, feature = "internals"))] +impl core::ops::DerefMut for MainTrace { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.columns + } +} + impl MainTrace { pub fn new(main_trace: ColMatrix) -> Self { Self { diff --git a/processor/src/trace/virtual_bus/circuit/error.rs b/processor/src/trace/virtual_bus/circuit/error.rs index 64390684fd..0d89880d94 100644 --- a/processor/src/trace/virtual_bus/circuit/error.rs +++ b/processor/src/trace/virtual_bus/circuit/error.rs @@ -1,13 +1,17 @@ #[derive(Debug, thiserror::Error)] pub enum ProverError { #[error("failed to generate multi-linear from the given evaluations")] - FailedGenerateML, + FailedToGenerateML, #[error("the inputs to the circuit's input layer have incompatible lengths")] MismatchingLengthsCircuitInputs, #[error("the inputs to the circuit's input layer must have power-of-two lengths")] InputsMustBePowerTwo, #[error("the inputs to the circuit's input layer must have at least two evaluations")] InputsAtLeastTwo, + #[error("failed to generate the sum-check proof")] + FailedToProveSumCheck, + #[error("failed to generate the random challenge")] + FailedToGenerateChallenge, } #[derive(Debug, thiserror::Error)] @@ -17,7 +21,7 @@ pub enum VerifierError { #[error("the output of the fraction circuit is not equal to the expected value")] MismatchingCircuitOutput, #[error("failed to generate the random challenge")] - FailedGenerateRandomness, + FailedToGenerateChallenge, #[error("failed to verify the sum-check proof")] FailedToVerifySumCheck, } diff --git a/processor/src/trace/virtual_bus/circuit/prover.rs b/processor/src/trace/virtual_bus/circuit/prover.rs index 0fafc0173e..dbccafe411 100644 --- a/processor/src/trace/virtual_bus/circuit/prover.rs +++ b/processor/src/trace/virtual_bus/circuit/prover.rs @@ -72,13 +72,13 @@ impl FractionalSumCircuit { let mut q_1_vec: Vec> = Vec::with_capacity(num_layers); let p_0 = MultiLinearPoly::from_evaluations(num_den[0].to_owned()) - .map_err(|_| ProverError::FailedGenerateML)?; + .map_err(|_| ProverError::FailedToGenerateML)?; let p_1 = MultiLinearPoly::from_evaluations(num_den[1].to_owned()) - .map_err(|_| ProverError::FailedGenerateML)?; + .map_err(|_| ProverError::FailedToGenerateML)?; let q_0 = MultiLinearPoly::from_evaluations(num_den[2].to_owned()) - .map_err(|_| ProverError::FailedGenerateML)?; + .map_err(|_| ProverError::FailedToGenerateML)?; let q_1 = MultiLinearPoly::from_evaluations(num_den[3].to_owned()) - .map_err(|_| ProverError::FailedGenerateML)?; + .map_err(|_| ProverError::FailedToGenerateML)?; p_0_vec.push(p_0); p_1_vec.push(p_1); q_0_vec.push(q_0); @@ -129,13 +129,13 @@ impl FractionalSumCircuit { Ok(( MultiLinearPoly::from_evaluations(outp_p_0) - .map_err(|_| ProverError::FailedGenerateML)?, + .map_err(|_| ProverError::FailedToGenerateML)?, MultiLinearPoly::from_evaluations(outp_p_1) - .map_err(|_| ProverError::FailedGenerateML)?, + .map_err(|_| ProverError::FailedToGenerateML)?, MultiLinearPoly::from_evaluations(outp_q_0) - .map_err(|_| ProverError::FailedGenerateML)?, + .map_err(|_| ProverError::FailedToGenerateML)?, MultiLinearPoly::from_evaluations(outp_q_1) - .map_err(|_| ProverError::FailedGenerateML)?, + .map_err(|_| ProverError::FailedToGenerateML)?, )) } @@ -148,12 +148,12 @@ impl FractionalSumCircuit { assert_eq!(self.q_0_vec[len - 1].num_variables(), 0); assert_eq!(self.q_1_vec[len - 1].num_variables(), 0); - let mut p_ = self.p_0_vec[len - 1].clone(); - p_.extend(&self.p_1_vec[len - 1]); - let mut q_ = self.q_0_vec[len - 1].clone(); - q_.extend(&self.q_1_vec[len - 1]); + let mut p = self.p_0_vec[len - 1].clone(); + p.extend(&self.p_1_vec[len - 1]); + let mut q = self.q_0_vec[len - 1].clone(); + q.extend(&self.q_1_vec[len - 1]); - (p_.evaluate(&[r]), q_.evaluate(&[r])) + (p.evaluate(&[r]), q.evaluate(&[r])) } /// Outputs the value of the circuit output layer. @@ -187,14 +187,14 @@ impl FractionalSumCircuit { /// /// The composition polynomials `g` are provided as inputs and then used in order to compute /// the evaluations of each of the four merge polynomials over {0, 1}^{μ + ν}. The resulting -/// evaluations are then used in order to evaluate [FractionalSumCircuit]. +/// evaluations are then used in order to evaluate [`FractionalSumCircuit`]. /// At this point, the GKR protocol is used to prove the correctness of circuit evaluation. It /// should be noted that the input layer, which corresponds to the last layer treated by the GKR /// protocol, is handled differently from the other layers. /// More specifically, the sum-check protocol used for the input layer is composed of two sum-check /// protocols, the first one works directly with the evaluations of the `m`'s over {0, 1}^{μ + ν} /// and runs for μ rounds. -/// After these μ rounds, and using the resulting [RoundClaim], we run the second and final +/// After these μ rounds, and using the resulting [`RoundClaim`], we run the second and final /// sum-check protocol for ν rounds on the composed multi-linear polynomial given by /// /// \sum_{y ∈ {0,1}^μ} EQ(ρ', y) * g_{[y]}(f_0(x_0, ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , x_{ν - 1})) @@ -202,7 +202,7 @@ impl FractionalSumCircuit { /// where ρ' is the randomness sampled during the first sum-check protocol. /// /// As part of the final sum-check protocol, the openings {f_j(ρ)} are provided as part of -/// a [FinalOpeningClaim]. This latter claim will be proven by the STARK prover later on using +/// a [`FinalOpeningClaim`]. This latter claim will be proven by the STARK prover later on using /// the auxiliary trace. pub fn prove< E: FieldElement + 'static, @@ -212,16 +212,16 @@ pub fn prove< composition_polys: Vec>>>, mls: &mut Vec>, transcript: &mut C, -) -> GkrCircuitProof { +) -> Result, ProverError> { // evaluate the numerators and denominators over the boolean hyper-cube {0, 1}^{μ + ν} let input: Vec> = evaluate_composition_polys(mls, &composition_polys); // evaluate the GKR fractional sum circuit - let mut circuit = FractionalSumCircuit::new(input).unwrap(); + let mut circuit = FractionalSumCircuit::new(input)?; // run the GKR prover for all layers except the input layer let (before_final_layer_proofs, gkr_claim) = - prove_before_final_circuit_layers(&mut circuit, transcript); + prove_before_final_circuit_layers(&mut circuit, transcript)?; // run the GKR prover for the input layer let num_rounds_before_merge = composition_polys[0].len().ilog2() as usize; @@ -232,16 +232,16 @@ pub fn prove< gkr_claim, &mut circuit, transcript, - ); + )?; // include the circuit output as part of the final proof let circuit_outputs = circuit.output_layer(); - GkrCircuitProof { + Ok(GkrCircuitProof { circuit_outputs, before_final_layer_proofs, final_layer_proof, - } + }) } /// Proves the final GKR layer which corresponds to the input circuit layer. @@ -256,7 +256,7 @@ fn prove_final_circuit_layer< gkr_claim: GkrClaim, circuit: &mut FractionalSumCircuit, transcript: &mut C, -) -> FinalLayerProof { +) -> Result, ProverError> { // parse the [GkrClaim] resulting from the previous GKR layer let GkrClaim { evaluation_point, @@ -279,7 +279,7 @@ fn prove_final_circuit_layer< num_rounds_merge, &mut merged_mls, transcript, - ); + )?; // parse the output of the first sum-check protocol let RoundClaim { @@ -297,12 +297,14 @@ fn prove_final_circuit_layer< // run the second sum-check protocol let main_prover = SumCheckProver::new(gkr_composition, SimpleGkrFinalClaimBuilder(PhantomData)); - let after_merge_proof = main_prover.prove(claim, mls, transcript).unwrap(); + let after_merge_proof = main_prover + .prove(claim, mls, transcript) + .map_err(|_| ProverError::FailedToProveSumCheck)?; - FinalLayerProof { + Ok(FinalLayerProof { before_merge_proof, after_merge_proof, - } + }) } /// Proves all GKR layers except for input layer. @@ -313,7 +315,7 @@ fn prove_before_final_circuit_layers< >( circuit: &mut FractionalSumCircuit, transcript: &mut C, -) -> (BeforeFinalLayerProof, GkrClaim) { +) -> Result<(BeforeFinalLayerProof, GkrClaim), ProverError> { // absorb the circuit output layer. This corresponds to sending the four values of the output // layer to the verifier. The verifier then replies with a challenge `r` in order to evaluate // `p` and `q` at `r` as multi-linears. @@ -328,7 +330,7 @@ fn prove_before_final_circuit_layers< transcript.reseed(H::hash_elements(&data)); // generate the challenge and reduce [p0, p1, q0, q1] to [pr, qr] - let r = transcript.draw().unwrap(); + let r = transcript.draw().map_err(|_| ProverError::FailedToGenerateChallenge)?; let mut claim = circuit.evaluate_output_layer(r); let mut proof_layers: Vec> = Vec::new(); @@ -346,11 +348,11 @@ fn prove_before_final_circuit_layers< let mut mls = vec![poly_a, poly_b, poly_c, poly_d, poly_x]; // run the sumcheck protocol - let (proof, _) = sum_check_prover_plain_full(claim, &mut mls, transcript); + let (proof, _) = sum_check_prover_plain_full(claim, &mut mls, transcript)?; // sample a random challenge to reduce claims transcript.reseed(H::hash_elements(&proof.openings_claim.openings)); - let r_layer = transcript.draw().unwrap(); + let r_layer = transcript.draw().map_err(|_| ProverError::FailedToGenerateChallenge)?; // reduce the claim let p0 = proof.openings_claim.openings[0]; @@ -367,7 +369,7 @@ fn prove_before_final_circuit_layers< proof_layers.push(proof); } - ( + Ok(( BeforeFinalLayerProof { proof: proof_layers, }, @@ -375,10 +377,11 @@ fn prove_before_final_circuit_layers< evaluation_point: rand, claimed_evaluation: claim, }, - ) + )) } /// Runs the first sum-check prover for the input layer. +#[allow(clippy::type_complexity)] fn sum_check_prover_plain_partial< E: FieldElement + 'static, C: RandomCoin, @@ -388,11 +391,11 @@ fn sum_check_prover_plain_partial< num_rounds: usize, ml_polys: &mut [MultiLinearPoly], transcript: &mut C, -) -> ((RoundClaim, Vec>), E) { +) -> Result<((RoundClaim, Vec>), E), ProverError> { // generate challenge to batch two sumchecks let data = vec![claim.0, claim.1]; transcript.reseed(H::hash_elements(&data)); - let r_batch = transcript.draw().unwrap(); + let r_batch = transcript.draw().map_err(|_| ProverError::FailedToGenerateChallenge)?; let claim = claim.0 + claim.1 * r_batch; // generate the composition polynomial @@ -400,9 +403,11 @@ fn sum_check_prover_plain_partial< // run the sum-check protocol let main_prover = SumCheckProver::new(composer, SimpleGkrFinalClaimBuilder(PhantomData)); - let proof = main_prover.prove_rounds(claim, ml_polys, num_rounds, transcript).unwrap(); + let proof = main_prover + .prove_rounds(claim, ml_polys, num_rounds, transcript) + .map_err(|_| ProverError::FailedToProveSumCheck)?; - (proof, r_batch) + Ok((proof, r_batch)) } /// Runs the sum-check prover used in all but the input layer. @@ -414,11 +419,11 @@ fn sum_check_prover_plain_full< claim: (E, E), ml_polys: &mut [MultiLinearPoly], transcript: &mut C, -) -> (SumCheckProof, E) { +) -> Result<(SumCheckProof, E), ProverError> { // generate challenge to batch two sumchecks let data = vec![claim.0, claim.1]; transcript.reseed(H::hash_elements(&data)); - let r_batch = transcript.draw().unwrap(); + let r_batch = transcript.draw().map_err(|_| ProverError::FailedToGenerateChallenge)?; let claim_ = claim.0 + claim.1 * r_batch; // generate the composition polynomial @@ -426,9 +431,11 @@ fn sum_check_prover_plain_full< // run the sum-check protocol let main_prover = SumCheckProver::new(composer, SimpleGkrFinalClaimBuilder(PhantomData)); - let proof = main_prover.prove(claim_, ml_polys, transcript).unwrap(); + let proof = main_prover + .prove(claim_, ml_polys, transcript) + .map_err(|_| ProverError::FailedToProveSumCheck)?; - (proof, r_batch) + Ok((proof, r_batch)) } /// Computes the evaluations over {0, 1}^{μ + ν} of @@ -456,9 +463,9 @@ fn evaluate_composition_polys + 'static>( num_den } -/// Constructs [FinalOpeningClaim] for the sum-checks used in the GKR protocol. +/// Constructs [`FinalOpeningClaim`] for the sum-checks used in the GKR protocol. /// -/// TODO: currently, this just removes the EQ evaluation as it can computed by the verifier. +/// TODO: currently, this just removes the EQ evaluation as it can be computed by the verifier. /// This should be generalized for other "transparent" multi-linears e.g., periodic columns. struct SimpleGkrFinalClaimBuilder(PhantomData); diff --git a/processor/src/trace/virtual_bus/circuit/verifier.rs b/processor/src/trace/virtual_bus/circuit/verifier.rs index 640796ce4f..5be8efb8fc 100644 --- a/processor/src/trace/virtual_bus/circuit/verifier.rs +++ b/processor/src/trace/virtual_bus/circuit/verifier.rs @@ -47,7 +47,7 @@ pub fn verify< // generate the random challenge to reduce two claims into a single claim transcript.reseed(H::hash_elements(&circuit_outputs)); - let r = transcript.draw().map_err(|_| VerifierError::FailedGenerateRandomness)?; + let r = transcript.draw().map_err(|_| VerifierError::FailedToGenerateChallenge)?; // reduce the claim let p_r = p0 + r * (p1 - p0); @@ -70,7 +70,7 @@ pub fn verify< // generate the random challenge to reduce two claims into a single claim transcript.reseed(H::hash_elements(&openings)); - let r_layer = transcript.draw().unwrap(); + let r_layer = transcript.draw().map_err(|_| VerifierError::FailedToGenerateChallenge)?; let p0 = openings[0]; let p1 = openings[1]; @@ -110,7 +110,7 @@ pub fn verify_sum_check_proof_before_last< ) -> Result, VerifierError> { // generate challenge to batch sum-checks transcript.reseed(H::hash_elements(&[claim.0, claim.1])); - let r_batch: E = transcript.draw().unwrap(); + let r_batch: E = transcript.draw().map_err(|_| VerifierError::FailedToGenerateChallenge)?; // compute the claim for the batched sum-check let reduced_claim = claim.0 + claim.1 * r_batch; @@ -143,7 +143,7 @@ pub fn verify_sum_check_proof_last< // generate challenge to batch sum-checks transcript.reseed(H::hash_elements(&[claim.0, claim.1])); - let r_sum_check: E = transcript.draw().unwrap(); + let r_sum_check: E = transcript.draw().map_err(|_| VerifierError::FailedToGenerateChallenge)?; // compute the claim for the batched sum-check let reduced_claim = claim.0 + claim.1 * r_sum_check; @@ -174,7 +174,7 @@ pub fn verify_sum_check_proof_last< .map_err(|_| VerifierError::FailedToVerifySumCheck) } -/// A [FinalQueryBuilder] for the sum-check verifier used for all sum-checks but for the final one. +/// A [`FinalQueryBuilder`] for the sum-check verifier used for all sum-checks but for the final one. #[derive(Default)] struct GkrQueryBuilder { gkr_eval_point: Vec, @@ -198,7 +198,7 @@ impl CompositionPolyQueryBuilder for GkrQueryBuilder { } } -/// A [FinalQueryBuilder] for the sum-check verifier used for the final sum-check. +/// A [`FinalQueryBuilder`] for the sum-check verifier used for the final sum-check. #[derive(Default)] struct GkrMergeQueryBuilder { gkr_eval_point: Vec, diff --git a/processor/src/trace/virtual_bus/error.rs b/processor/src/trace/virtual_bus/error.rs index 1a79e1540d..ba9ade0052 100644 --- a/processor/src/trace/virtual_bus/error.rs +++ b/processor/src/trace/virtual_bus/error.rs @@ -5,3 +5,15 @@ pub enum Error { #[error("number of numerators and denominators should be at least two")] NumeratorDenominatorLessThanTwo, } + +#[derive(Debug, thiserror::Error)] +pub enum ProverError { + #[error("failed to generate a proof for the virtual bus relation")] + FailedToGenerateProof, +} + +#[derive(Debug, thiserror::Error)] +pub enum VerifierError { + #[error("failed to generate a proof for the virtual bus relation")] + FailedToVerifyProof, +} diff --git a/processor/src/trace/virtual_bus/prover.rs b/processor/src/trace/virtual_bus/prover.rs index dc947882d7..8ca3778e0e 100644 --- a/processor/src/trace/virtual_bus/prover.rs +++ b/processor/src/trace/virtual_bus/prover.rs @@ -1,6 +1,6 @@ use super::{ circuit::GkrCircuitProof, - error::Error, + error::{Error, ProverError}, generate, multilinear::{CompositionPolynomial, MultiLinearPoly}, prove, @@ -50,7 +50,7 @@ where C: RandomCoin, H: ElementHasher, { - /// Constructs a new [VirtualBusProver] given a set of random values for the GKR-LogUp relation. + /// Constructs a new [`VirtualBusProver`] given a set of random values for the GKR-LogUp relation. /// /// The constructor uses the randomness to do two things: /// @@ -86,7 +86,11 @@ where } /// Proves the GKR-LogUp relation. - pub fn prove(&self, trace: &MainTrace, transcript: &mut C) -> GkrCircuitProof { + pub fn prove( + &self, + trace: &MainTrace, + transcript: &mut C, + ) -> Result, ProverError> { // TODO: Optimize this so that we can work with base field element directly and thus save // on memory usage. let trace_len = trace.num_rows(); @@ -98,6 +102,8 @@ where MultiLinearPoly::from_evaluations(values).unwrap() }) .collect(); + prove(self.composition_polynomials(), &mut mls, transcript) + .map_err(|_| ProverError::FailedToGenerateProof) } } diff --git a/processor/src/trace/virtual_bus/tests.rs b/processor/src/trace/virtual_bus/tests.rs index 64129b5802..5a36fd53a5 100644 --- a/processor/src/trace/virtual_bus/tests.rs +++ b/processor/src/trace/virtual_bus/tests.rs @@ -3,7 +3,10 @@ use crate::{ DefaultHost, ExecutionTrace, Process, }; use alloc::vec::Vec; -use miden_air::{trace::main_trace::MainTrace, ExecutionOptions}; +use miden_air::{ + trace::{main_trace::MainTrace, range::M_COL_IDX}, + ExecutionOptions, +}; use vm_core::crypto::random::RpoRandomCoin; use vm_core::{ code_blocks::CodeBlock, CodeBlockTable, Felt, FieldElement, Kernel, Operation, StackInputs, @@ -29,12 +32,45 @@ fn test_vb_prover_verifier() { let seed = [Felt::ZERO; 4]; // should be initialized with the appropriate transcript let mut transcript = RpoRandomCoin::new(seed.into()); let vb_prover = VirtualBusProver::new(alphas.clone()).unwrap(); - let proof = vb_prover.prove(&trace, &mut transcript); + let proof = vb_prover.prove(&trace, &mut transcript).unwrap(); + + let seed = [Felt::ZERO; 4]; // should be initialized with the appropriate transcript + let mut transcript = RpoRandomCoin::new(seed.into()); + let vb_verifier = VirtualBusVerifier::new(alphas).unwrap(); + let final_opening_claim = vb_verifier.verify(proof, &mut transcript); + assert!(final_opening_claim.is_ok()) +} + +#[test] +fn test_vb_prover_verifier_failure() { + let s = 6; + let o = 6; + let stack: Vec<_> = (0..(1 << s)).into_iter().collect(); + let operations: Vec<_> = (0..(1 << o)) + .flat_map(|_| { + vec![Operation::U32split, Operation::U32add, Operation::U32xor, Operation::MStoreW] + }) + .collect(); + + // modifying the multiplicity + let mut trace = build_full_trace(&stack, operations, Kernel::default()); + let index = trace.get_column(M_COL_IDX).iter().position(|v| *v != Felt::ZERO).unwrap(); + trace.get_column_mut(M_COL_IDX)[index] = Felt::ONE; + + // this should be generated using the transcript up to when the prover sends the commitment + // to the main trace. + let alphas: Vec = vec![test_utils::rand::rand_value()]; + + let seed = [Felt::ZERO; 4]; // should be initialized with the appropriate transcript + let mut transcript = RpoRandomCoin::new(seed.into()); + let vb_prover = VirtualBusProver::new(alphas.clone()).unwrap(); + let proof = vb_prover.prove(&trace, &mut transcript).unwrap(); let seed = [Felt::ZERO; 4]; // should be initialized with the appropriate transcript let mut transcript = RpoRandomCoin::new(seed.into()); let vb_verifier = VirtualBusVerifier::new(alphas).unwrap(); - let _final_opening_claim = vb_verifier.verify(proof, &mut transcript); + let final_opening_claim = vb_verifier.verify(proof, &mut transcript); + assert!(final_opening_claim.is_err()) } fn build_full_trace(stack_inputs: &[u64], operations: Vec, kernel: Kernel) -> MainTrace { diff --git a/processor/src/trace/virtual_bus/verifier.rs b/processor/src/trace/virtual_bus/verifier.rs index 5ee2766663..1f4754afcb 100644 --- a/processor/src/trace/virtual_bus/verifier.rs +++ b/processor/src/trace/virtual_bus/verifier.rs @@ -1,6 +1,10 @@ use super::{ - circuit::GkrCircuitProof, error::Error, generate, multilinear::CompositionPolynomial, - sum_check::FinalOpeningClaim, verify, + circuit::GkrCircuitProof, + error::{Error, VerifierError}, + generate, + multilinear::CompositionPolynomial, + sum_check::FinalOpeningClaim, + verify, }; use alloc::{sync::Arc, vec::Vec}; use core::marker::PhantomData; @@ -26,7 +30,7 @@ where C: RandomCoin, H: ElementHasher, { - /// Constructs a new [VirtualBusVerifier] given a set of random values for the GKR-LogUp relation. + /// Constructs a new [`VirtualBusVerifier`] given a set of random values for the GKR-LogUp relation. pub fn new(log_up_randomness: Vec) -> Result { let (claim, composition_polynomials) = generate(log_up_randomness)?; @@ -51,7 +55,12 @@ where /// Verifies the GKR-LogUp relation. This output, in the case the proof is accepted, /// a [FinalOpeningClaim] which is passed on to the STARK verifier in order to check /// the correctness of the claimed openings. - pub fn verify(&self, proof: GkrCircuitProof, transcript: &mut C) -> FinalOpeningClaim { - verify(self.claim, proof, self.composition_polynomials(), transcript).unwrap() + pub fn verify( + &self, + proof: GkrCircuitProof, + transcript: &mut C, + ) -> Result, VerifierError> { + verify(self.claim, proof, self.composition_polynomials(), transcript) + .map_err(|_| VerifierError::FailedToVerifyProof) } } From 016bc957da271bd2c38b44f95d1a86046d5f6732 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Thu, 18 Apr 2024 14:59:14 +0200 Subject: [PATCH 21/32] fix: update trait def after rebase --- processor/src/trace/virtual_bus/sum_check/mod.rs | 4 ++-- .../src/trace/virtual_bus/sum_check/verifier/mod.rs | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index 82ef500a89..c2f41f0040 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -181,8 +181,8 @@ mod test { #[derive(Default)] struct ProjectionPolyQueryBuilder; - impl CompositionPolyQueryBuilder for ProjectionPolyQueryBuilder { - fn build_query( + impl CompositionPolyQueryBuilder for ProjectionPolyQueryBuilder { + fn build_query( &self, openings_claim: &super::FinalOpeningClaim, _evaluation_point: &[E], diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index a5e9d4f118..ff3036cc68 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -25,7 +25,7 @@ where C: RandomCoin, P: CompositionPolynomial, H: ElementHasher, - V: CompositionPolyQueryBuilder, + V: CompositionPolyQueryBuilder, { composition_poly: P, final_query_builder: V, @@ -39,7 +39,7 @@ where C: RandomCoin, P: CompositionPolynomial, H: ElementHasher, - V: CompositionPolyQueryBuilder, + V: CompositionPolyQueryBuilder, { /// Create a new [SumCheckVerifier] from a composition polynomial and final query builder. pub fn new(composition_poly: P, final_query_builder: V) -> Self { @@ -147,10 +147,6 @@ where /// multi-linear polynomials that the Verifier can do by herself. For example, this is the case /// for periodic columns where given `(r_0, ... ,r_{\nu - 1})` the Verifier can evaluate /// it at `(r_0, ... ,r_{\nu - 1})` unassisted. -pub trait CompositionPolyQueryBuilder { - fn build_query( - &self, - openings_claim: &FinalOpeningClaim, - evaluation_point: &[E], - ) -> Vec; +pub trait CompositionPolyQueryBuilder { + fn build_query(&self, openings_claim: &FinalOpeningClaim, evaluation_point: &[E]) -> Vec; } From f46ac0c4efff33757d3216937792c9a719f01399 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:29:41 +0200 Subject: [PATCH 22/32] fix: use last_mut --- processor/src/trace/virtual_bus/prover.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/processor/src/trace/virtual_bus/prover.rs b/processor/src/trace/virtual_bus/prover.rs index 8ca3778e0e..46e7925ac9 100644 --- a/processor/src/trace/virtual_bus/prover.rs +++ b/processor/src/trace/virtual_bus/prover.rs @@ -93,12 +93,13 @@ where ) -> Result, ProverError> { // TODO: Optimize this so that we can work with base field element directly and thus save // on memory usage. - let trace_len = trace.num_rows(); let mut mls: Vec> = trace .columns() .map(|col| { let mut values: Vec = col.iter().map(|value| E::from(*value)).collect(); - values[trace_len - 1] = E::ZERO; + if let Some(value) = values.last_mut(){ + *value = E::ZERO + } MultiLinearPoly::from_evaluations(values).unwrap() }) .collect(); From 31609c59e6acd952adafc24fb12cc1edac997eb2 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:11:06 +0200 Subject: [PATCH 23/32] fix: address some comments --- processor/src/trace/virtual_bus/circuit/prover.rs | 3 +-- processor/src/trace/virtual_bus/multilinear/mod.rs | 9 +++++++++ processor/src/trace/virtual_bus/prover.rs | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/processor/src/trace/virtual_bus/circuit/prover.rs b/processor/src/trace/virtual_bus/circuit/prover.rs index dbccafe411..706e1021e6 100644 --- a/processor/src/trace/virtual_bus/circuit/prover.rs +++ b/processor/src/trace/virtual_bus/circuit/prover.rs @@ -421,8 +421,7 @@ fn sum_check_prover_plain_full< transcript: &mut C, ) -> Result<(SumCheckProof, E), ProverError> { // generate challenge to batch two sumchecks - let data = vec![claim.0, claim.1]; - transcript.reseed(H::hash_elements(&data)); + transcript.reseed(H::hash_elements(&[claim.0, claim.1])); let r_batch = transcript.draw().map_err(|_| ProverError::FailedToGenerateChallenge)?; let claim_ = claim.0 + claim.1 * r_batch; diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index 7d5cd34a78..341337c78f 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -76,6 +76,15 @@ impl MultiLinearPoly { .expect("should not fail given that it is a multi-linear"); } + /// Given two instances of [`MultiLinearPoly`], f(x_0, x_1, ..., x_{ν - 1}) and + /// g(x_0, x_1, ..., x_{ν - 1}), constructs the following polynomial defined by + /// + /// merge(f, g)(x_0, x_1, ..., x_{ν - 1}, z) := (1 - z) * f(x_0, x_1, ..., x_{ν - 1}) + /// + z * g(x_0, x_1, ..., x_{ν - 1}) + /// Notice that: + /// + /// 1. merge(f, g)(x_0, x_1, ..., x_{ν - 1}, 0) = f(x_0, x_1, ..., x_{ν - 1}) + /// 2. merge(f, g)(x_0, x_1, ..., x_{ν - 1}, 1) = g(x_0, x_1, ..., x_{ν - 1}) pub fn extend(&mut self, other: &MultiLinearPoly) { let other_vec = other.evaluations.to_vec(); assert_eq!(other_vec.len(), self.evaluations().len()); diff --git a/processor/src/trace/virtual_bus/prover.rs b/processor/src/trace/virtual_bus/prover.rs index 46e7925ac9..0f4be7ceb5 100644 --- a/processor/src/trace/virtual_bus/prover.rs +++ b/processor/src/trace/virtual_bus/prover.rs @@ -97,7 +97,7 @@ where .columns() .map(|col| { let mut values: Vec = col.iter().map(|value| E::from(*value)).collect(); - if let Some(value) = values.last_mut(){ + if let Some(value) = values.last_mut() { *value = E::ZERO } MultiLinearPoly::from_evaluations(values).unwrap() From 7567a10070d2a896ffeabf1b1790668d4c3ac9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Laferri=C3=A8re?= Date: Fri, 3 May 2024 10:40:41 -0400 Subject: [PATCH 24/32] Change suggestions to GKR implementation (#1323) * change prover * change verifier * cleanup * Remove `VirtualBusVerifier` * Remove `VirtualBusProver` * reorganize exports * `LayerGatesInputs` * use constant * docstrings * add TODO * remove TODO * clippy * add static_assertions * add padding comment * add back `claim` in verifier --- processor/Cargo.toml | 3 +- processor/src/trace/mod.rs | 2 +- .../src/trace/virtual_bus/circuit/error.rs | 6 - .../src/trace/virtual_bus/circuit/mod.rs | 210 +++++++------- .../src/trace/virtual_bus/circuit/prover.rs | 165 ++++++----- .../src/trace/virtual_bus/circuit/verifier.rs | 20 +- processor/src/trace/virtual_bus/error.rs | 19 -- processor/src/trace/virtual_bus/mod.rs | 150 +--------- .../src/trace/virtual_bus/multilinear/mod.rs | 7 +- processor/src/trace/virtual_bus/prover.rs | 110 ------- .../src/trace/virtual_bus/sub_bus/mod.rs | 15 - .../virtual_bus/sub_bus/range_checker.rs | 271 ------------------ .../src/trace/virtual_bus/sum_check/mod.rs | 12 +- .../trace/virtual_bus/sum_check/prover/mod.rs | 4 +- processor/src/trace/virtual_bus/tests.rs | 23 +- processor/src/trace/virtual_bus/verifier.rs | 66 ----- 16 files changed, 237 insertions(+), 846 deletions(-) delete mode 100644 processor/src/trace/virtual_bus/error.rs delete mode 100644 processor/src/trace/virtual_bus/prover.rs delete mode 100644 processor/src/trace/virtual_bus/sub_bus/mod.rs delete mode 100644 processor/src/trace/virtual_bus/sub_bus/range_checker.rs delete mode 100644 processor/src/trace/virtual_bus/verifier.rs diff --git a/processor/Cargo.toml b/processor/Cargo.toml index 5ff16e7c07..6ddb853c92 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -26,8 +26,9 @@ std = ["vm-core/std", "winter-prover/std"] tracing = { version = "0.1", default-features = false, features = [ "attributes", ] } -thiserror = { version = "1.0", default-features = false } miden-air = { package = "miden-air", path = "../air", version = "0.8", default-features = false } +static_assertions = "1.1.0" +thiserror = { version = "1.0", default-features = false } vm-core = { package = "miden-core", path = "../core", version = "0.8", default-features = false } winter-prover = { package = "winter-prover", version = "0.9", default-features = false } diff --git a/processor/src/trace/mod.rs b/processor/src/trace/mod.rs index 071b958395..1d4ace0f73 100644 --- a/processor/src/trace/mod.rs +++ b/processor/src/trace/mod.rs @@ -19,7 +19,7 @@ mod utils; pub use utils::{AuxColumnBuilder, ChipletsLengths, TraceFragment, TraceLenSummary}; mod virtual_bus; -pub use virtual_bus::{VirtualBusProver, VirtualBusVerifier}; +pub use virtual_bus::{prove as prove_virtual_bus, verify as verify_virtual_bus}; #[cfg(test)] mod tests; diff --git a/processor/src/trace/virtual_bus/circuit/error.rs b/processor/src/trace/virtual_bus/circuit/error.rs index 0d89880d94..1e713a2027 100644 --- a/processor/src/trace/virtual_bus/circuit/error.rs +++ b/processor/src/trace/virtual_bus/circuit/error.rs @@ -2,12 +2,6 @@ pub enum ProverError { #[error("failed to generate multi-linear from the given evaluations")] FailedToGenerateML, - #[error("the inputs to the circuit's input layer have incompatible lengths")] - MismatchingLengthsCircuitInputs, - #[error("the inputs to the circuit's input layer must have power-of-two lengths")] - InputsMustBePowerTwo, - #[error("the inputs to the circuit's input layer must have at least two evaluations")] - InputsAtLeastTwo, #[error("failed to generate the sum-check proof")] FailedToProveSumCheck, #[error("failed to generate the random challenge")] diff --git a/processor/src/trace/virtual_bus/circuit/mod.rs b/processor/src/trace/virtual_bus/circuit/mod.rs index dfb6d1c3f5..6a6f1031ee 100644 --- a/processor/src/trace/virtual_bus/circuit/mod.rs +++ b/processor/src/trace/virtual_bus/circuit/mod.rs @@ -1,7 +1,11 @@ use crate::trace::virtual_bus::multilinear::EqFunction; use crate::trace::virtual_bus::{multilinear::CompositionPolynomial, sum_check::RoundProof}; -use alloc::{borrow::ToOwned, sync::Arc, vec::Vec}; -use miden_air::trace::TRACE_WIDTH; +use alloc::vec::Vec; +use miden_air::trace::chiplets::{MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX}; +use miden_air::trace::decoder::{DECODER_OP_BITS_OFFSET, DECODER_USER_OP_HELPERS_OFFSET}; +use miden_air::trace::range::{M_COL_IDX, V_COL_IDX}; +use miden_air::trace::{CHIPLETS_OFFSET, TRACE_WIDTH}; +use static_assertions::const_assert; use vm_core::{Felt, FieldElement}; mod error; @@ -11,8 +15,90 @@ pub use prover::prove; mod verifier; pub use verifier::verify; +use super::multilinear::inner_product; use super::sum_check::{FinalOpeningClaim, Proof as SumCheckProof}; +/// Defines the number of elements for the partial left/right numerator/denominators of +/// [`LayerGatesInputs`]. +const NUM_ELEMENTS_PER_GATE_INPUT: usize = 4; +const_assert!(NUM_ELEMENTS_PER_GATE_INPUT.is_power_of_two()); + +/// Holds the contribution of one main trace row to the input layer's gates inputs. +struct LayerGatesInputs { + pub partial_left_numerator: [E; NUM_ELEMENTS_PER_GATE_INPUT], + pub partial_right_numerator: [E; NUM_ELEMENTS_PER_GATE_INPUT], + pub partial_left_denominator: [E; NUM_ELEMENTS_PER_GATE_INPUT], + pub partial_right_denominator: [E; NUM_ELEMENTS_PER_GATE_INPUT], +} + +impl LayerGatesInputs { + pub fn from_main_trace_query(query: &[E], log_up_randomness: &[E]) -> Self { + Self { + partial_left_numerator: Self::left_numerator(query), + partial_right_numerator: Self::right_numerator(query), + partial_left_denominator: Self::left_denominator(query, log_up_randomness), + partial_right_denominator: Self::right_denominator(query, log_up_randomness), + } + } + + fn left_numerator(query: &[E]) -> [E; NUM_ELEMENTS_PER_GATE_INPUT] { + let f_m = { + let mem_selec0 = query[CHIPLETS_OFFSET]; + let mem_selec1 = query[CHIPLETS_OFFSET + 1]; + let mem_selec2 = query[CHIPLETS_OFFSET + 2]; + mem_selec0 * mem_selec1 * (E::ONE - mem_selec2) + }; + let f_rc = { + let op_bit_4 = query[DECODER_OP_BITS_OFFSET + 4]; + let op_bit_5 = query[DECODER_OP_BITS_OFFSET + 5]; + let op_bit_6 = query[DECODER_OP_BITS_OFFSET + 6]; + + (E::ONE - op_bit_4) * (E::ONE - op_bit_5) * op_bit_6 + }; + + [query[M_COL_IDX], f_m, f_m, f_rc] + } + + fn right_numerator(query: &[E]) -> [E; NUM_ELEMENTS_PER_GATE_INPUT] { + let f_rc = { + let op_bit_4 = query[DECODER_OP_BITS_OFFSET + 4]; + let op_bit_5 = query[DECODER_OP_BITS_OFFSET + 5]; + let op_bit_6 = query[DECODER_OP_BITS_OFFSET + 6]; + + (E::ONE - op_bit_4) * (E::ONE - op_bit_5) * op_bit_6 + }; + + let padding = E::ZERO; + + // the last numerator/denominator pair is unused, so is padded with 0 and 1, respectively. + [f_rc, f_rc, f_rc, padding] + } + + fn left_denominator(query: &[E], log_up_randomness: &[E]) -> [E; NUM_ELEMENTS_PER_GATE_INPUT] { + let alphas = log_up_randomness; + + let table_denom = alphas[0] - query[V_COL_IDX]; + let memory_denom_0 = -(alphas[0] - query[MEMORY_D0_COL_IDX]); + let memory_denom_1 = -(alphas[0] - query[MEMORY_D1_COL_IDX]); + let stack_value_denom_0 = -(alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET]); + + [table_denom, memory_denom_0, memory_denom_1, stack_value_denom_0] + } + + fn right_denominator(query: &[E], log_up_randomness: &[E]) -> [E; NUM_ELEMENTS_PER_GATE_INPUT] { + let alphas = log_up_randomness; + + let stack_value_denom_1 = -(alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET + 1]); + let stack_value_denom_2 = -(alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET + 2]); + let stack_value_denom_3 = -(alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET + 3]); + + let padding = E::ONE; + + // the last numerator/denominator pair is unused, so is padded with 0 and 1, respectively. + [stack_value_denom_1, stack_value_denom_2, stack_value_denom_3, padding] + } +} + /// A GKR proof for the correct evaluation of the sum of fractions circuit. #[derive(Debug)] pub struct GkrCircuitProof { @@ -47,35 +133,6 @@ pub struct GkrClaim { pub claimed_evaluation: (E, E), } -/// A composition polynomial that projects into a specific component. -#[derive(Clone, Copy, Debug)] -pub struct ProjectionComposition { - coordinate: usize, -} - -impl ProjectionComposition { - pub fn new(coordinate: usize) -> Self { - Self { coordinate } - } -} - -impl CompositionPolynomial for ProjectionComposition -where - E: FieldElement, -{ - fn num_variables(&self) -> u32 { - 1 - } - - fn max_degree(&self) -> u32 { - 1 - } - - fn evaluate(&self, query: &[E]) -> E { - query[self.coordinate] - } -} - /// A composition polynomial used in the GKR protocol for all of its sum-checks except the final one. #[derive(Clone)] pub struct GkrComposition @@ -129,13 +186,7 @@ where { pub sum_check_combining_randomness: E, pub tensored_merge_randomness: Vec, - pub degree: u32, - - pub eq_composer: Arc>, - pub right_numerator_composer: Vec>>, - pub left_numerator_composer: Vec>>, - pub right_denominator_composer: Vec>>, - pub left_denominator_composer: Vec>>, + pub log_up_randomness: Vec, } impl GkrCompositionMerge @@ -145,33 +196,15 @@ where pub fn new( combining_randomness: E, merge_randomness: Vec, - eq_composer: Arc>, - right_numerator_composer: Vec>>, - left_numerator_composer: Vec>>, - right_denominator_composer: Vec>>, - left_denominator_composer: Vec>>, + log_up_randomness: Vec, ) -> Self { let tensored_merge_randomness = EqFunction::ml_at(merge_randomness.clone()).evaluations().to_vec(); - let max_left_num = left_numerator_composer.iter().map(|c| c.max_degree()).max().unwrap(); - let max_right_num = right_numerator_composer.iter().map(|c| c.max_degree()).max().unwrap(); - let max_left_denom = - left_denominator_composer.iter().map(|c| c.max_degree()).max().unwrap(); - let max_right_denom = - right_denominator_composer.iter().map(|c| c.max_degree()).max().unwrap(); - let degree = - 1 + core::cmp::max(max_left_num + max_right_denom, max_right_num + max_left_denom); - Self { sum_check_combining_randomness: combining_randomness, - eq_composer, - degree, - right_numerator_composer, - left_numerator_composer, - right_denominator_composer, - left_denominator_composer, tensored_merge_randomness, + log_up_randomness, } } } @@ -185,30 +218,30 @@ where } fn max_degree(&self) -> u32 { - self.degree + // Computed as: + // 1 + max(left_numerator_degree + right_denom_degree, right_numerator_degree + left_denom_degree) + 5 } fn evaluate(&self, query: &[E]) -> E { - let eval_right_numerator = - self.right_numerator_composer.iter().enumerate().fold(E::ZERO, |acc, (i, ml)| { - acc + ml.evaluate(query) * self.tensored_merge_randomness[i] - }); + let LayerGatesInputs { + partial_left_numerator, + partial_right_numerator, + partial_left_denominator, + partial_right_denominator, + } = LayerGatesInputs::from_main_trace_query(query, &self.log_up_randomness); + let eval_left_numerator = - self.left_numerator_composer.iter().enumerate().fold(E::ZERO, |acc, (i, ml)| { - acc + ml.evaluate(query) * self.tensored_merge_randomness[i] - }); - let eval_right_denominator = self - .right_denominator_composer - .iter() - .enumerate() - .fold(E::ZERO, |acc, (i, ml)| { - acc + ml.evaluate(query) * self.tensored_merge_randomness[i] - }); + inner_product(&partial_left_numerator, &self.tensored_merge_randomness); + let eval_right_numerator = + inner_product(&partial_right_numerator, &self.tensored_merge_randomness); let eval_left_denominator = - self.left_denominator_composer.iter().enumerate().fold(E::ZERO, |acc, (i, ml)| { - acc + ml.evaluate(query) * self.tensored_merge_randomness[i] - }); - let eq_eval = self.eq_composer.evaluate(query); + inner_product(&partial_left_denominator, &self.tensored_merge_randomness); + let eval_right_denominator = + inner_product(&partial_right_denominator, &self.tensored_merge_randomness); + + let eq_eval = query[TRACE_WIDTH]; + eq_eval * ((eval_left_numerator * eval_right_denominator + eval_right_numerator * eval_left_denominator) @@ -217,26 +250,3 @@ where * self.sum_check_combining_randomness) } } - -/// Generates a [GkrCompositionMerge] given the sum-check randomness so-far and the random value -/// for batching two sum-checks to one. -pub fn gkr_merge_composition_from_composition_polys + 'static>( - composition_polys: &[Vec>>], - sum_check_batch_randomness: E, - merge_randomness: Vec, -) -> GkrCompositionMerge { - let eq_composer = Arc::new(ProjectionComposition::new(TRACE_WIDTH)); - let left_numerator = composition_polys[0].to_owned(); - let right_numerator = composition_polys[1].to_owned(); - let left_denominator = composition_polys[2].to_owned(); - let right_denominator = composition_polys[3].to_owned(); - GkrCompositionMerge::new( - sum_check_batch_randomness, - merge_randomness, - eq_composer, - right_numerator, - left_numerator, - right_denominator, - left_denominator, - ) -} diff --git a/processor/src/trace/virtual_bus/circuit/prover.rs b/processor/src/trace/virtual_bus/circuit/prover.rs index 706e1021e6..e8661f9684 100644 --- a/processor/src/trace/virtual_bus/circuit/prover.rs +++ b/processor/src/trace/virtual_bus/circuit/prover.rs @@ -1,15 +1,16 @@ use super::{ - super::sum_check::Proof as SumCheckProof, error::ProverError, - gkr_merge_composition_from_composition_polys, BeforeFinalLayerProof, FinalLayerProof, - GkrCircuitProof, GkrClaim, GkrComposition, + super::sum_check::Proof as SumCheckProof, error::ProverError, BeforeFinalLayerProof, + FinalLayerProof, GkrCircuitProof, GkrClaim, GkrComposition, GkrCompositionMerge, + LayerGatesInputs, NUM_ELEMENTS_PER_GATE_INPUT, }; use crate::trace::virtual_bus::{ - multilinear::{CompositionPolynomial, EqFunction, MultiLinearPoly}, + multilinear::{EqFunction, MultiLinearPoly}, sum_check::{FinalClaimBuilder, FinalOpeningClaim, RoundClaim, RoundProof}, SumCheckProver, }; -use alloc::{borrow::ToOwned, sync::Arc, vec::Vec}; +use alloc::{borrow::ToOwned, vec::Vec}; use core::marker::PhantomData; +use miden_air::trace::main_trace::MainTrace; use vm_core::{Felt, FieldElement}; use winter_prover::crypto::{ElementHasher, RandomCoin}; @@ -50,39 +51,22 @@ pub struct FractionalSumCircuit { impl FractionalSumCircuit { /// Computes The values of the gate outputs for each of the layers of the fractional sum circuit. - pub fn new(num_den: Vec>) -> Result { - let num_evaluations = num_den[0].len(); + pub fn new( + columns: &[MultiLinearPoly], + log_up_randomness: &[E], + ) -> Result { + let circuit_inputs = CircuitInputs::new(columns, log_up_randomness)?; - if !num_den.iter().all(|t| t.len() == num_evaluations) { - return Err(ProverError::MismatchingLengthsCircuitInputs); - } - - if !num_evaluations.is_power_of_two() { - return Err(ProverError::InputsMustBePowerTwo); - } - - if num_evaluations < 2 { - return Err(ProverError::InputsAtLeastTwo); - } - - let num_layers = num_evaluations.ilog2() as usize; + let num_layers = circuit_inputs.num_variables(); let mut p_0_vec: Vec> = Vec::with_capacity(num_layers); let mut p_1_vec: Vec> = Vec::with_capacity(num_layers); let mut q_0_vec: Vec> = Vec::with_capacity(num_layers); let mut q_1_vec: Vec> = Vec::with_capacity(num_layers); - let p_0 = MultiLinearPoly::from_evaluations(num_den[0].to_owned()) - .map_err(|_| ProverError::FailedToGenerateML)?; - let p_1 = MultiLinearPoly::from_evaluations(num_den[1].to_owned()) - .map_err(|_| ProverError::FailedToGenerateML)?; - let q_0 = MultiLinearPoly::from_evaluations(num_den[2].to_owned()) - .map_err(|_| ProverError::FailedToGenerateML)?; - let q_1 = MultiLinearPoly::from_evaluations(num_den[3].to_owned()) - .map_err(|_| ProverError::FailedToGenerateML)?; - p_0_vec.push(p_0); - p_1_vec.push(p_1); - q_0_vec.push(q_0); - q_1_vec.push(q_1); + p_0_vec.push(circuit_inputs.left_numerator); + p_1_vec.push(circuit_inputs.right_numerator); + q_0_vec.push(circuit_inputs.left_denominator); + q_1_vec.push(circuit_inputs.right_denominator); for i in 0..num_layers { let (output_p_0, output_p_1, output_q_0, output_q_1) = @@ -167,6 +151,58 @@ impl FractionalSumCircuit { } } +/// Holds the inputs to [`FractionalSumCircuit`] +struct CircuitInputs { + left_numerator: MultiLinearPoly, + right_numerator: MultiLinearPoly, + left_denominator: MultiLinearPoly, + right_denominator: MultiLinearPoly, +} + +impl CircuitInputs { + fn new(columns: &[MultiLinearPoly], log_up_randomness: &[E]) -> Result { + let num_evaluations = columns[0].num_evaluations(); + let mut left_numerator = Vec::with_capacity(num_evaluations * NUM_ELEMENTS_PER_GATE_INPUT); + let mut right_numerator = Vec::with_capacity(num_evaluations * NUM_ELEMENTS_PER_GATE_INPUT); + let mut left_denominator = + Vec::with_capacity(num_evaluations * NUM_ELEMENTS_PER_GATE_INPUT); + let mut right_denominator = + Vec::with_capacity(num_evaluations * NUM_ELEMENTS_PER_GATE_INPUT); + + for i in 0..num_evaluations { + let query: Vec = columns.iter().map(|ml| ml[i]).collect(); + + let LayerGatesInputs { + partial_left_numerator, + partial_right_numerator, + partial_left_denominator, + partial_right_denominator, + } = LayerGatesInputs::from_main_trace_query(&query, log_up_randomness); + + left_numerator.extend(partial_left_numerator); + right_numerator.extend(partial_right_numerator); + left_denominator.extend(partial_left_denominator); + right_denominator.extend(partial_right_denominator); + } + + Ok(Self { + left_numerator: MultiLinearPoly::from_evaluations(left_numerator) + .map_err(|_| ProverError::FailedToGenerateML)?, + right_numerator: MultiLinearPoly::from_evaluations(right_numerator) + .map_err(|_| ProverError::FailedToGenerateML)?, + left_denominator: MultiLinearPoly::from_evaluations(left_denominator) + .map_err(|_| ProverError::FailedToGenerateML)?, + + right_denominator: MultiLinearPoly::from_evaluations(right_denominator) + .map_err(|_| ProverError::FailedToGenerateML)?, + }) + } + + fn num_variables(&self) -> usize { + self.left_numerator.num_variables() + } +} + /// Evaluates and proves a fractional sum circuit given a set of composition polynomials. /// /// Each individual component of the quadruple [p_0, p_1, q_0, q_1] is of the form: @@ -209,25 +245,35 @@ pub fn prove< C: RandomCoin, H: ElementHasher, >( - composition_polys: Vec>>>, - mls: &mut Vec>, + trace: &MainTrace, + log_up_randomness: Vec, transcript: &mut C, ) -> Result, ProverError> { - // evaluate the numerators and denominators over the boolean hyper-cube {0, 1}^{μ + ν} - let input: Vec> = evaluate_composition_polys(mls, &composition_polys); + // TODO: Optimize this so that we can work with base field element directly and thus save + // on memory usage. + let main_trace_columns: Vec> = trace + .columns() + .map(|col| { + let mut values: Vec = col.iter().map(|value| E::from(*value)).collect(); + if let Some(value) = values.last_mut() { + *value = E::ZERO + } + MultiLinearPoly::from_evaluations(values).unwrap() + }) + .collect(); // evaluate the GKR fractional sum circuit - let mut circuit = FractionalSumCircuit::new(input)?; + let mut circuit = FractionalSumCircuit::new(&main_trace_columns, &log_up_randomness)?; // run the GKR prover for all layers except the input layer let (before_final_layer_proofs, gkr_claim) = prove_before_final_circuit_layers(&mut circuit, transcript)?; // run the GKR prover for the input layer - let num_rounds_before_merge = composition_polys[0].len().ilog2() as usize; + let num_rounds_before_merge = NUM_ELEMENTS_PER_GATE_INPUT.ilog2() as usize; let final_layer_proof = prove_final_circuit_layer( - composition_polys, - mls, + log_up_randomness, + main_trace_columns, num_rounds_before_merge, gkr_claim, &mut circuit, @@ -250,8 +296,8 @@ fn prove_final_circuit_layer< C: RandomCoin, H: ElementHasher, >( - composition_polys: Vec>>>, - mls: &mut Vec>, + log_up_randomness: Vec, + mut mls: Vec>, num_rounds_merge: usize, gkr_claim: GkrClaim, circuit: &mut FractionalSumCircuit, @@ -289,10 +335,10 @@ fn prove_final_circuit_layer< // create the composed multi-linear for the second sum-check protocol using the randomness // sampled during the first one - let gkr_composition = - gkr_merge_composition_from_composition_polys(&composition_polys, r_sum_check, rand_merge); + let gkr_composition = GkrCompositionMerge::new(r_sum_check, rand_merge, log_up_randomness); // include the partially evaluated at the first sum-check randomness EQ multi-linear + // TODO: Find a better way than to push the evaluation of `EqFunction` here. mls.push(merged_mls[4].clone()); // run the second sum-check protocol @@ -345,10 +391,10 @@ fn prove_before_final_circuit_layers< let poly_b = circuit.p_1_vec[layer_id].to_owned(); let poly_c = circuit.q_0_vec[layer_id].to_owned(); let poly_d = circuit.q_1_vec[layer_id].to_owned(); - let mut mls = vec![poly_a, poly_b, poly_c, poly_d, poly_x]; + let mls = vec![poly_a, poly_b, poly_c, poly_d, poly_x]; // run the sumcheck protocol - let (proof, _) = sum_check_prover_plain_full(claim, &mut mls, transcript)?; + let (proof, _) = sum_check_prover_plain_full(claim, mls, transcript)?; // sample a random challenge to reduce claims transcript.reseed(H::hash_elements(&proof.openings_claim.openings)); @@ -417,7 +463,7 @@ fn sum_check_prover_plain_full< H: ElementHasher, >( claim: (E, E), - ml_polys: &mut [MultiLinearPoly], + ml_polys: Vec>, transcript: &mut C, ) -> Result<(SumCheckProof, E), ProverError> { // generate challenge to batch two sumchecks @@ -437,31 +483,6 @@ fn sum_check_prover_plain_full< Ok((proof, r_batch)) } -/// Computes the evaluations over {0, 1}^{μ + ν} of -/// -/// m(z_0, ... , z_{μ - 1}, x_0, ... , x_{ν - 1}) = -/// \sum_{y ∈ {0,1}^μ} EQ(z, y) * g_{[y]}(f_0(x_0, ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , x_{ν - 1})) -fn evaluate_composition_polys + 'static>( - mls: &[MultiLinearPoly], - composition_polys: &[Vec>>], -) -> Vec> { - let num_evaluations = 1 << mls[0].num_variables(); - let mut num_den: Vec> = - (0..4).map(|_| Vec::with_capacity(num_evaluations)).collect::>(); - - for i in 0..num_evaluations { - for j in 0..4 { - let query: Vec = mls.iter().map(|ml| ml[i]).collect(); - - composition_polys[j].iter().for_each(|c| { - let evaluation = c.as_ref().evaluate(&query); - num_den[j].push(evaluation); - }); - } - } - num_den -} - /// Constructs [`FinalOpeningClaim`] for the sum-checks used in the GKR protocol. /// /// TODO: currently, this just removes the EQ evaluation as it can be computed by the verifier. diff --git a/processor/src/trace/virtual_bus/circuit/verifier.rs b/processor/src/trace/virtual_bus/circuit/verifier.rs index 5be8efb8fc..e7d20c5275 100644 --- a/processor/src/trace/virtual_bus/circuit/verifier.rs +++ b/processor/src/trace/virtual_bus/circuit/verifier.rs @@ -1,15 +1,14 @@ use super::{ - error::VerifierError, gkr_merge_composition_from_composition_polys, FinalLayerProof, - GkrCircuitProof, GkrComposition, + error::VerifierError, FinalLayerProof, GkrCircuitProof, GkrComposition, GkrCompositionMerge, }; use crate::trace::virtual_bus::{ - multilinear::{CompositionPolynomial, EqFunction}, + multilinear::EqFunction, sum_check::{ CompositionPolyQueryBuilder, FinalOpeningClaim, Proof as SumCheckFullProof, RoundClaim, }, SumCheckVerifier, }; -use alloc::{borrow::ToOwned, sync::Arc, vec::Vec}; +use alloc::{borrow::ToOwned, vec::Vec}; use vm_core::{Felt, FieldElement}; use winter_prover::crypto::{ElementHasher, RandomCoin}; @@ -21,7 +20,7 @@ pub fn verify< >( claim: E, proof: GkrCircuitProof, - composition_polys: Vec>>>, + log_up_randomness: Vec, transcript: &mut C, ) -> Result, VerifierError> { let GkrCircuitProof { @@ -88,8 +87,8 @@ pub fn verify< // verify the proof of the final GKR layer and pass final opening claim for verification // to the STARK verify_sum_check_proof_last( - composition_polys, final_layer_proof, + log_up_randomness, &rand, reduced_claim, transcript, @@ -130,8 +129,8 @@ pub fn verify_sum_check_proof_last< C: RandomCoin, H: ElementHasher, >( - composition_polys: Vec>>>, proof: FinalLayerProof, + log_up_randomness: Vec, gkr_eval_point: &[E], claim: (E, E), transcript: &mut C, @@ -160,11 +159,8 @@ pub fn verify_sum_check_proof_last< .map_err(|_| VerifierError::FailedToVerifySumCheck)?; // verify the second part of the sum-check protocol - let gkr_composition = gkr_merge_composition_from_composition_polys( - &composition_polys, - r_sum_check, - rand_merge.clone(), - ); + let gkr_composition = + GkrCompositionMerge::new(r_sum_check, rand_merge.clone(), log_up_randomness); let verifier = SumCheckVerifier::new( gkr_composition, GkrMergeQueryBuilder::new(gkr_eval_point.to_owned(), rand_merge), diff --git a/processor/src/trace/virtual_bus/error.rs b/processor/src/trace/virtual_bus/error.rs deleted file mode 100644 index ba9ade0052..0000000000 --- a/processor/src/trace/virtual_bus/error.rs +++ /dev/null @@ -1,19 +0,0 @@ -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("number of numerators and denominators is different")] - NumeratorDenominatorLengthMismatch, - #[error("number of numerators and denominators should be at least two")] - NumeratorDenominatorLessThanTwo, -} - -#[derive(Debug, thiserror::Error)] -pub enum ProverError { - #[error("failed to generate a proof for the virtual bus relation")] - FailedToGenerateProof, -} - -#[derive(Debug, thiserror::Error)] -pub enum VerifierError { - #[error("failed to generate a proof for the virtual bus relation")] - FailedToVerifyProof, -} diff --git a/processor/src/trace/virtual_bus/mod.rs b/processor/src/trace/virtual_bus/mod.rs index a7b17a182c..9ebcfea082 100644 --- a/processor/src/trace/virtual_bus/mod.rs +++ b/processor/src/trace/virtual_bus/mod.rs @@ -21,157 +21,15 @@ //! [1]: https://dl.acm.org/doi/10.1145/2699436 //! [2]: https://eprint.iacr.org/2023/1284 -use self::{ - error::Error, - multilinear::CompositionPolynomial, - sub_bus::{BusBuilder, RangeCheckerBus}, -}; -use alloc::{sync::Arc, vec::Vec}; -use core::marker::PhantomData; -use vm_core::FieldElement; - mod circuit; -mod multilinear; pub use circuit::{prove, verify}; + +mod multilinear; + mod sum_check; -mod univariate; pub use sum_check::{SumCheckProver, SumCheckVerifier}; -mod prover; -pub use prover::VirtualBusProver; -mod verifier; -pub use verifier::VirtualBusVerifier; -mod error; -mod sub_bus; +mod univariate; #[cfg(test)] mod tests; - -/// Generates the composition polynomials describing the virtual bus. -#[allow(clippy::type_complexity)] -fn generate( - log_up_randomness: Vec, -) -> Result<(E, Vec>>>), Error> { - // build each sub-bus of the virtual bus - // TODO: This implements only the range checker bus and should be generalized to iterate - // over all the remaining sub-buses - let range_checker_bus_builder = RangeCheckerBus::new(&log_up_randomness); - let claim = range_checker_bus_builder.compute_initial_claim(); - let numerators = range_checker_bus_builder.build_numerators(); - let denominators = range_checker_bus_builder.build_denominators(); - - // the numerators and denominators should have matching lengths - if numerators.len() != denominators.len() { - return Err(Error::NumeratorDenominatorLengthMismatch); - } - - // should have at least two numerator/denominator - if numerators.len() < 2 { - return Err(Error::NumeratorDenominatorLessThanTwo); - } - - // split the numerators and denominators into two subsets, a left and a right one - let mut numerators = pad_and_split(numerators, false); - let denominators = pad_and_split(denominators, true); - numerators.extend_from_slice(&denominators); - - Ok((claim, numerators)) -} - -/// Pads a set of composition polynomials to the next power of two and splits the resulting padded -/// into two sub-sets of equal size. -/// -/// The padding is done using the `ZeroNumerator` composition polynomial in the case when the set -/// is the set of composition polynomials appearing in the numerators, and is done using -/// the `OneDenominator` composition polynomial in the case of denominators. -fn pad_and_split( - vector: Vec>>, - denominator: bool, -) -> Vec>>> { - let length = vector.len(); - let padded_length = length.next_power_of_two(); - - let mut output = Vec::with_capacity(padded_length); - output.extend_from_slice(&vector); - - if denominator { - for _i in length..padded_length { - output.push(Arc::new(OneDenominator::default())); - } - } else { - for _i in length..padded_length { - output.push(Arc::new(ZeroNumerator::default())); - } - } - - let mut left = output; - let right = left.split_off(left.len() >> 1); - - vec![left, right] -} - -/// A polynomial representing the identically zero polynomial. -/// -/// This is used for padding to the next power of 2 as the current implementation of the sum-check -/// protocol requires that the number of numerators be a power of two. The same holds for the -/// denominators. -/// It should be noted that this padding has no effect on the sum computed by the GKR circuit as -/// adding a fraction with a zero numerator and a non-zero denominator doesn't change the value of -/// the sum. -#[derive(Default)] -pub struct ZeroNumerator -where - E: FieldElement, -{ - phantom: PhantomData, -} - -impl CompositionPolynomial for ZeroNumerator -where - E: FieldElement, -{ - fn num_variables(&self) -> u32 { - 1 // TODO: Update - } - - fn max_degree(&self) -> u32 { - 1 - } - - fn evaluate(&self, _query: &[E]) -> E { - E::ZERO - } -} - -/// A polynomial representing the identically `E::ONE` polynomial. -/// -/// This is used for padding to the next power of 2 as the current implementation of the sum-check -/// protocol requires that the number of denominators be a power of two. The same holds for the -/// numerators. -/// It should be noted that this padding has no effect on the sum computed by the GKR circuit as -/// adding a fraction with a zero numerator and a non-zero denominator doesn't change the value of -/// the sum. -#[derive(Default)] -pub struct OneDenominator -where - E: FieldElement, -{ - phantom: PhantomData, -} - -impl CompositionPolynomial for OneDenominator -where - E: FieldElement, -{ - fn num_variables(&self) -> u32 { - 1 // TODO: Update - } - - fn max_degree(&self) -> u32 { - 1 - } - - fn evaluate(&self, _query: &[E]) -> E { - E::ONE - } -} diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index 341337c78f..970ece863d 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -4,13 +4,10 @@ use vm_core::FieldElement; use winter_prover::math::log2; mod lagrange_ker; -pub use lagrange_ker::EqFunction; +pub use lagrange_ker::{inner_product, EqFunction}; mod error; -use self::{ - error::Error, - lagrange_ker::{compute_lagrange_basis_evals_at, inner_product}, -}; +use self::{error::Error, lagrange_ker::compute_lagrange_basis_evals_at}; // MULTI-LINEAR POLYNOMIAL // ================================================================================================ diff --git a/processor/src/trace/virtual_bus/prover.rs b/processor/src/trace/virtual_bus/prover.rs deleted file mode 100644 index 0f4be7ceb5..0000000000 --- a/processor/src/trace/virtual_bus/prover.rs +++ /dev/null @@ -1,110 +0,0 @@ -use super::{ - circuit::GkrCircuitProof, - error::{Error, ProverError}, - generate, - multilinear::{CompositionPolynomial, MultiLinearPoly}, - prove, -}; -use alloc::{sync::Arc, vec::Vec}; -use core::marker::PhantomData; -use miden_air::trace::main_trace::MainTrace; -use vm_core::{Felt, FieldElement}; -use winter_prover::crypto::{ElementHasher, RandomCoin}; - -/// A struct which implements the logic for proving the correctness of the global virtual bus -/// relation. -/// -/// The global virtual bus relation is encoded as a layered circuit and is proven using the GKR -/// protocol for proving the correct evaluation of a circuit. The correctness of the resulting -/// GKR proof entails, together with that of the final evaluation claim (see further down), that -/// the current instance of the global virtual bus is consistant with overwhelming probability. -/// This last statement is conditioned on the correctness of what is called the final evaluation -/// claim. This is a claim on the openings of the multi-linear extensions of the (main) trace -/// columns involved in the global virtual bus relation at some random point. This random point -/// is the result of the challenges sent by the GKR verifier in the interactive version of the GKR -/// protocol. -/// As Miden VM uses FRI as its polynomial commitment scheme, we use the transformation in -/// section 5 of [1] for transforming a univariate polynomial commitment scheme into a multi-variate -/// one. This means that the (outer) STARK will take the final evaluation claim in -/// `GkrCircuitProof::FinalLayerProof::Proof::FinalOpeningClaim` and use it to construct two columns -/// in the auxiliary trace, one for the Lagrange kernel at `FinalOpeningClaim::eval_point` and the -/// other for computing the evaluation of the (batched) multi-linear extensions of the relevant -/// columns at `FinalOpeningClaim::eval_point` using the aforementioned Lagrange kernel. -/// This means, in a sense, that the opening proof for the final GKR evaluation is part of the STARK -/// proof. -/// -/// [1]: https://eprint.iacr.org/2023/1284 -pub struct VirtualBusProver -where - E: FieldElement + 'static, - C: RandomCoin, - H: ElementHasher, -{ - composition_polynomials: Vec>>>, - _challenger: PhantomData, -} - -impl VirtualBusProver -where - E: FieldElement + 'static, - C: RandomCoin, - H: ElementHasher, -{ - /// Constructs a new [`VirtualBusProver`] given a set of random values for the GKR-LogUp relation. - /// - /// The constructor uses the randomness to do two things: - /// - /// 1. Compute the claimed value of the GKR-LogUp relation i.e., the generalization of the 0 - /// constant in equation (3) in [1]. This constant can depend on the LogUp randomness as in - /// e.g., the overflow stack virtual table bus where such a constant represents entries in - /// the overflow stack which were present before the VM started executing. - /// - /// 2. The composition polynomials describing the numerators and denominators of the fractions - /// appearing on the left hand side of equation (3) in [1]. The denominators depend as well - /// on the LogUp randomness. - /// The composition polynomials are grouped into 4 sets that can be thought of as left/right - /// numerator/denominator. The reason for this can be understood by looking at the original - /// GKR protocol in [2] where the protocol therein uses the wiring predicates [\tilde{ADD}] - /// and [\tilde{MUL}] to encode the wiring of the circuit. For our purposes, we can do - /// away with wiring predicates using the left/right and numerator/denominator distinction. - /// - /// [1]: https://eprint.iacr.org/2023/1284 - /// [2]: https://dl.acm.org/doi/10.1145/2699436 - pub fn new(log_up_randomness: Vec) -> Result { - let (_claim, composition_polynomials) = generate(log_up_randomness)?; - - Ok(Self { - composition_polynomials, - _challenger: PhantomData, - }) - } - - /// Returns the composition polynomials of the left/right numerators/denominators of - /// the GKR-LogUp relation. - fn composition_polynomials(&self) -> Vec>>> { - self.composition_polynomials.clone() - } - - /// Proves the GKR-LogUp relation. - pub fn prove( - &self, - trace: &MainTrace, - transcript: &mut C, - ) -> Result, ProverError> { - // TODO: Optimize this so that we can work with base field element directly and thus save - // on memory usage. - let mut mls: Vec> = trace - .columns() - .map(|col| { - let mut values: Vec = col.iter().map(|value| E::from(*value)).collect(); - if let Some(value) = values.last_mut() { - *value = E::ZERO - } - MultiLinearPoly::from_evaluations(values).unwrap() - }) - .collect(); - - prove(self.composition_polynomials(), &mut mls, transcript) - .map_err(|_| ProverError::FailedToGenerateProof) - } -} diff --git a/processor/src/trace/virtual_bus/sub_bus/mod.rs b/processor/src/trace/virtual_bus/sub_bus/mod.rs deleted file mode 100644 index 2660ff3c64..0000000000 --- a/processor/src/trace/virtual_bus/sub_bus/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -use super::multilinear::CompositionPolynomial; -use alloc::{sync::Arc, vec::Vec}; -use vm_core::FieldElement; - -mod range_checker; -pub use range_checker::*; - -/// Defines a sub-bus of the global virtual bus -pub trait BusBuilder { - fn compute_initial_claim(&self) -> E; - - fn build_numerators(&self) -> Vec>>; - - fn build_denominators(&self) -> Vec>>; -} diff --git a/processor/src/trace/virtual_bus/sub_bus/range_checker.rs b/processor/src/trace/virtual_bus/sub_bus/range_checker.rs deleted file mode 100644 index 6c66b9c6e2..0000000000 --- a/processor/src/trace/virtual_bus/sub_bus/range_checker.rs +++ /dev/null @@ -1,271 +0,0 @@ -use super::BusBuilder; -use crate::trace::virtual_bus::multilinear::CompositionPolynomial; -use alloc::{sync::Arc, vec::Vec}; -use core::marker::PhantomData; -use miden_air::trace::{ - chiplets::MEMORY_D0_COL_IDX, - decoder::{DECODER_OP_BITS_OFFSET, DECODER_USER_OP_HELPERS_OFFSET}, - range::{M_COL_IDX, V_COL_IDX}, - CHIPLETS_OFFSET, -}; -use vm_core::FieldElement; - -/// Defines the range checker sub-bus. -/// -/// Define the following variables: -/// -/// - rc_value: the range checker value -/// - rc_multiplicity: the range checker multiplicity value -/// - flag_s: boolean flag indicating a stack operation with range checks. This flag is degree 3. -/// - sv0-sv3: stack value 0-3, the 4 values range-checked from the stack -/// - flag_m: boolean flag indicating the memory chiplet is active (i.e. range checks are required). -/// This flag is degree 3. -/// - mv0-mv1: memory value 0-1, the 2 values range-checked from the memory chiplet -/// -/// Let `col(x)` denote the multi-linear extension of trace column `col`. This means that -/// for x \in \{0 , 1\}^{\nu}, where \nu is the log_2 of the trace length, `col(x)` is the value -/// at row index [x] = \sum_{j=0}^{\nu - 1} x_j * 2^j of column `col`. -/// -/// Given the above, the range checker is implemented using the expression -/// -/// 0 = \sum_{x \in \{0 , 1\}^{\nu}} rc_multiplicity(x) / (alpha - rc_value)(x) -/// - flag_s(x) / (alpha - sv0)(x) - flag_s(x) / (alpha - sv1)(x) -/// - flag_s(x) / (alpha - sv2)(x) - flag_s(x) / (alpha - sv3)(x) -/// - flag_m(x) / (alpha - mv0)(x) - flag_m(x) / (alpha - mv1)(x) -pub struct RangeCheckerBus { - alphas: Vec, -} - -impl RangeCheckerBus { - pub fn new(alphas: &[E]) -> Self { - Self { - alphas: alphas.to_vec(), - } - } -} - -impl BusBuilder for RangeCheckerBus { - fn compute_initial_claim(&self) -> E { - E::ZERO - } - - fn build_numerators(&self) -> Vec>> { - vec![ - // rc_multiplicity(x) - Arc::new(RangeCheckMultiplicity::default()), - // flag_m(x) - Arc::new(MemoryFlagChiplet::default()), - Arc::new(MemoryFlagChiplet::default()), - // flag_s(x) - Arc::new(U32RangeCheckFlag::default()), - Arc::new(U32RangeCheckFlag::default()), - Arc::new(U32RangeCheckFlag::default()), - Arc::new(U32RangeCheckFlag::default()), - ] - } - - fn build_denominators(&self) -> Vec>> { - vec![ - // (alpha - rc_value)(x) - Arc::new(TableValue::new(self.alphas.clone())), - // (alpha - mv0)(x) - Arc::new(MemoryValue::new(0, self.alphas.clone())), - // (alpha - mv1)(x) - Arc::new(MemoryValue::new(1, self.alphas.clone())), - // (alpha - sv0)(x) - Arc::new(StackValue::new(0, self.alphas.clone())), - // (alpha - sv1)(x) - Arc::new(StackValue::new(1, self.alphas.clone())), - // (alpha - sv2)(x) - Arc::new(StackValue::new(2, self.alphas.clone())), - // (alpha - sv3)(x) - Arc::new(StackValue::new(3, self.alphas.clone())), - ] - } -} - -#[derive(Default)] -pub struct U32RangeCheckFlag -where - E: FieldElement, -{ - phantom: PhantomData, -} - -impl CompositionPolynomial for U32RangeCheckFlag -where - E: FieldElement, -{ - fn num_variables(&self) -> u32 { - 1 - } - - fn max_degree(&self) -> u32 { - 3 - } - - fn evaluate(&self, query: &[E]) -> E { - let op_bit_4 = query[DECODER_OP_BITS_OFFSET + 4]; - let op_bit_5 = query[DECODER_OP_BITS_OFFSET + 5]; - let op_bit_6 = query[DECODER_OP_BITS_OFFSET + 6]; - - (E::ONE - op_bit_4) * (E::ONE - op_bit_5) * op_bit_6 - } -} - -#[derive(Default)] -pub struct MemoryFlagChiplet -where - E: FieldElement, -{ - phantom: PhantomData, -} - -impl CompositionPolynomial for MemoryFlagChiplet -where - E: FieldElement, -{ - fn num_variables(&self) -> u32 { - 1 - } - - fn max_degree(&self) -> u32 { - 3 - } - - fn evaluate(&self, query: &[E]) -> E { - let mem_selec0 = query[CHIPLETS_OFFSET]; - let mem_selec1 = query[CHIPLETS_OFFSET + 1]; - let mem_selec2 = query[CHIPLETS_OFFSET + 2]; - - mem_selec0 * mem_selec1 * (E::ONE - mem_selec2) - } -} - -#[derive(Default)] -pub struct RangeCheckMultiplicity -where - E: FieldElement, -{ - phantom: PhantomData, -} - -impl CompositionPolynomial for RangeCheckMultiplicity -where - E: FieldElement, -{ - fn num_variables(&self) -> u32 { - 1 - } - - fn max_degree(&self) -> u32 { - 1 - } - - fn evaluate(&self, query: &[E]) -> E { - query[M_COL_IDX] - } -} - -pub struct StackValue -where - E: FieldElement, -{ - i: usize, - alphas: Vec, -} - -impl StackValue -where - E: FieldElement, -{ - pub fn new(i: usize, alphas: Vec) -> Self { - assert!(i < 4); - Self { i, alphas } - } -} - -impl CompositionPolynomial for StackValue -where - E: FieldElement, -{ - fn num_variables(&self) -> u32 { - 1 - } - - fn max_degree(&self) -> u32 { - 1 - } - - fn evaluate(&self, query: &[E]) -> E { - -(self.alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET + self.i]) - } -} - -pub struct MemoryValue -where - E: FieldElement, -{ - i: usize, - alphas: Vec, -} - -impl MemoryValue -where - E: FieldElement, -{ - pub fn new(i: usize, alphas: Vec) -> Self { - assert!(i < 2); - Self { i, alphas } - } -} - -impl CompositionPolynomial for MemoryValue -where - E: FieldElement, -{ - fn num_variables(&self) -> u32 { - 1 - } - - fn max_degree(&self) -> u32 { - 1 - } - - fn evaluate(&self, query: &[E]) -> E { - -(self.alphas[0] - query[MEMORY_D0_COL_IDX + self.i]) - } -} - -pub struct TableValue -where - E: FieldElement, -{ - alphas: Vec, -} - -impl TableValue -where - E: FieldElement, -{ - pub fn new(alphas: Vec) -> Self { - Self { alphas } - } -} - -impl CompositionPolynomial for TableValue -where - E: FieldElement, -{ - fn num_variables(&self) -> u32 { - 1 - } - - fn max_degree(&self) -> u32 { - 1 - } - - fn evaluate(&self, query: &[E]) -> E { - self.alphas[0] - query[V_COL_IDX] - } -} diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index c2f41f0040..2ab088c21f 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -88,13 +88,13 @@ mod test { let claim = values.iter().fold(ZERO, |acc, &x| x + acc); let ml = MultiLinearPoly::from_evaluations(values.to_vec()).expect("should not fail"); - let mut mls = vec![ml]; + let mls = vec![ml]; let virtual_poly = ProjectionComposition::new(0); // Prover let prover = SumCheckProver::new(virtual_poly, PlainClaimBuilder); let mut coin = RpoRandomCoin::new(Word::default()); - let proof = prover.prove(claim, &mut mls, &mut coin).unwrap(); + let proof = prover.prove(claim, mls, &mut coin).unwrap(); // Verifier let plain_query_builder = ProjectionPolyQueryBuilder::default(); @@ -114,13 +114,13 @@ mod test { let ml_0 = MultiLinearPoly::from_evaluations(values_0.to_vec()).expect("should not fail"); let ml_1 = MultiLinearPoly::from_evaluations(values_1.to_vec()).expect("should not fail"); - let mut mls = vec![ml_0, ml_1]; + let mls = vec![ml_0, ml_1]; let virtual_poly = ProductComposition; // Prover let prover = SumCheckProver::new(virtual_poly, PlainClaimBuilder); let mut coin = RpoRandomCoin::new(Word::default()); - let proof = prover.prove(claim, &mut mls, &mut coin).unwrap(); + let proof = prover.prove(claim, mls, &mut coin).unwrap(); // Verifier let plain_query_builder = ProjectionPolyQueryBuilder::default(); @@ -144,13 +144,13 @@ mod test { let ml_0 = MultiLinearPoly::from_evaluations(values_0.to_vec()).expect("should not fail"); let ml_1 = MultiLinearPoly::from_evaluations(values_1.to_vec()).expect("should not fail"); - let mut mls = vec![ml_0, ml_1]; + let mls = vec![ml_0, ml_1]; let virtual_poly = ProductComposition; // Prover let prover = SumCheckProver::new(virtual_poly, PlainClaimBuilder); let mut coin = RpoRandomCoin::new(Word::default()); - let proof = prover.prove(claim, &mut mls, &mut coin).unwrap(); + let proof = prover.prove(claim, mls, &mut coin).unwrap(); // Verifier let plain_query_builder = ProjectionPolyQueryBuilder::default(); diff --git a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs index 7477c24d36..09bdd029d4 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs @@ -121,7 +121,7 @@ where pub fn prove( &self, claim: E, - mls: &mut [MultiLinearPoly], + mut mls: Vec>, coin: &mut C, ) -> Result, Error> { let num_rounds = mls[0].num_variables(); @@ -131,7 +131,7 @@ where claim: _claim, }, round_proofs, - ) = self.prove_rounds(claim, mls, num_rounds, coin)?; + ) = self.prove_rounds(claim, &mut mls, num_rounds, coin)?; let openings = mls.iter_mut().map(|ml| ml.evaluations()[0]).collect(); let openings_claim = self.final_claim_builder.build_claim(openings, &eval_point); diff --git a/processor/src/trace/virtual_bus/tests.rs b/processor/src/trace/virtual_bus/tests.rs index 5a36fd53a5..6492c9a946 100644 --- a/processor/src/trace/virtual_bus/tests.rs +++ b/processor/src/trace/virtual_bus/tests.rs @@ -1,7 +1,4 @@ -use crate::{ - trace::virtual_bus::{prover::VirtualBusProver, verifier::VirtualBusVerifier}, - DefaultHost, ExecutionTrace, Process, -}; +use crate::{prove_virtual_bus, verify_virtual_bus, DefaultHost, ExecutionTrace, Process}; use alloc::vec::Vec; use miden_air::{ trace::{main_trace::MainTrace, range::M_COL_IDX}, @@ -27,17 +24,16 @@ fn test_vb_prover_verifier() { // this should be generated using the transcript up to when the prover sends the commitment // to the main trace. - let alphas: Vec = vec![test_utils::rand::rand_value()]; + let log_up_randomness: Vec = vec![test_utils::rand::rand_value()]; let seed = [Felt::ZERO; 4]; // should be initialized with the appropriate transcript let mut transcript = RpoRandomCoin::new(seed.into()); - let vb_prover = VirtualBusProver::new(alphas.clone()).unwrap(); - let proof = vb_prover.prove(&trace, &mut transcript).unwrap(); + let proof = prove_virtual_bus(&trace, log_up_randomness.clone(), &mut transcript).unwrap(); let seed = [Felt::ZERO; 4]; // should be initialized with the appropriate transcript let mut transcript = RpoRandomCoin::new(seed.into()); - let vb_verifier = VirtualBusVerifier::new(alphas).unwrap(); - let final_opening_claim = vb_verifier.verify(proof, &mut transcript); + let final_opening_claim = + verify_virtual_bus(Felt::ZERO, proof, log_up_randomness, &mut transcript); assert!(final_opening_claim.is_ok()) } @@ -59,17 +55,16 @@ fn test_vb_prover_verifier_failure() { // this should be generated using the transcript up to when the prover sends the commitment // to the main trace. - let alphas: Vec = vec![test_utils::rand::rand_value()]; + let log_up_randomness: Vec = vec![test_utils::rand::rand_value()]; let seed = [Felt::ZERO; 4]; // should be initialized with the appropriate transcript let mut transcript = RpoRandomCoin::new(seed.into()); - let vb_prover = VirtualBusProver::new(alphas.clone()).unwrap(); - let proof = vb_prover.prove(&trace, &mut transcript).unwrap(); + let proof = prove_virtual_bus(&trace, log_up_randomness.clone(), &mut transcript).unwrap(); let seed = [Felt::ZERO; 4]; // should be initialized with the appropriate transcript let mut transcript = RpoRandomCoin::new(seed.into()); - let vb_verifier = VirtualBusVerifier::new(alphas).unwrap(); - let final_opening_claim = vb_verifier.verify(proof, &mut transcript); + let final_opening_claim = + verify_virtual_bus(Felt::ZERO, proof, log_up_randomness, &mut transcript); assert!(final_opening_claim.is_err()) } diff --git a/processor/src/trace/virtual_bus/verifier.rs b/processor/src/trace/virtual_bus/verifier.rs deleted file mode 100644 index 1f4754afcb..0000000000 --- a/processor/src/trace/virtual_bus/verifier.rs +++ /dev/null @@ -1,66 +0,0 @@ -use super::{ - circuit::GkrCircuitProof, - error::{Error, VerifierError}, - generate, - multilinear::CompositionPolynomial, - sum_check::FinalOpeningClaim, - verify, -}; -use alloc::{sync::Arc, vec::Vec}; -use core::marker::PhantomData; -use vm_core::{Felt, FieldElement}; -use winter_prover::crypto::{ElementHasher, RandomCoin}; - -/// A struct which implements the logic for verification of the correctness of the global virtual -/// bus relation. -pub struct VirtualBusVerifier -where - E: FieldElement + 'static, - C: RandomCoin, - H: ElementHasher, -{ - claim: E, - composition_polynomials: Vec>>>, - _challenger: PhantomData, -} - -impl VirtualBusVerifier -where - E: FieldElement + 'static, - C: RandomCoin, - H: ElementHasher, -{ - /// Constructs a new [`VirtualBusVerifier`] given a set of random values for the GKR-LogUp relation. - pub fn new(log_up_randomness: Vec) -> Result { - let (claim, composition_polynomials) = generate(log_up_randomness)?; - - Ok(Self { - claim, - composition_polynomials, - _challenger: PhantomData, - }) - } - - /// Returns the claim of the GKR-LogUp relation. - pub fn claim(&self) -> Result { - Ok(self.claim) - } - - /// Returns the composition polynomials of the left/right numerators/denominators of - /// the GKR-LogUp relation. - pub fn composition_polynomials(&self) -> Vec>>> { - self.composition_polynomials.clone() - } - - /// Verifies the GKR-LogUp relation. This output, in the case the proof is accepted, - /// a [FinalOpeningClaim] which is passed on to the STARK verifier in order to check - /// the correctness of the claimed openings. - pub fn verify( - &self, - proof: GkrCircuitProof, - transcript: &mut C, - ) -> Result, VerifierError> { - verify(self.claim, proof, self.composition_polynomials(), transcript) - .map_err(|_| VerifierError::FailedToVerifyProof) - } -} From 37af90ba7848d45d1e6f445df27e49b9c45d631c Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 21 May 2024 07:55:34 +0200 Subject: [PATCH 25/32] chore: rebase --- processor/src/lib.rs | 2 +- processor/src/trace/virtual_bus/multilinear/mod.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 7946a2a73d..82cf0a4b7f 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -57,7 +57,7 @@ use chiplets::Chiplets; mod trace; use trace::TraceFragment; -pub use trace::{ChipletsLengths, ExecutionTrace, TraceLenSummary, NUM_RAND_ROWS}; +pub use trace::{prove_virtual_bus, verify_virtual_bus, ChipletsLengths, ExecutionTrace, TraceLenSummary, NUM_RAND_ROWS}; mod errors; pub use errors::{ExecutionError, Ext2InttError}; diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index 970ece863d..3eadb9df4a 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -1,7 +1,6 @@ use alloc::{borrow::ToOwned, vec::Vec}; use core::ops::Index; use vm_core::FieldElement; -use winter_prover::math::log2; mod lagrange_ker; pub use lagrange_ker::{inner_product, EqFunction}; @@ -29,7 +28,7 @@ impl MultiLinearPoly { return Err(Error::EvaluationsNotPowerOfTwo); } Ok(Self { - num_variables: log2(evaluations.len()) as usize, + num_variables: (evaluations.len().ilog2()) as usize, evaluations, }) } From 456ebecc20b2ce83f20cef1bfddab938c6c267da Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 21 May 2024 09:47:22 +0200 Subject: [PATCH 26/32] chore: pacify warning --- processor/src/trace/virtual_bus/multilinear/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index 3eadb9df4a..775fc1032f 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -103,6 +103,7 @@ impl Index for MultiLinearPoly { /// A multi-variate polynomial for composing individual multi-linear polynomials. pub trait CompositionPolynomial { /// The number of variables when interpreted as a multi-variate polynomial. + #[allow(dead_code)] fn num_variables(&self) -> u32; /// Maximum degree in all variables. From 173e26a7dedbb2fa04daba7c390d0eea8201aabd Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 24 May 2024 09:24:19 +0200 Subject: [PATCH 27/32] chore: change sumcheck_round signature --- processor/src/trace/virtual_bus/sum_check/prover/mod.rs | 6 +++--- prover/src/lib.rs | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs index 09bdd029d4..24d379afa7 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs @@ -260,9 +260,9 @@ where /// the previous one using only additions. This is the purpose of `deltas`, to hold the increments /// added to each multi-linear to compute the evaluation at the next point, and `evals_x` to hold /// the current evaluation at `x` in {2, ... , d_max}. -fn sumcheck_round( - composition_poly: &dyn CompositionPolynomial, - mls: &mut [MultiLinearPoly], +fn sumcheck_round>( + composition_poly: &P, + mls: &[MultiLinearPoly], ) -> UnivariatePolyEvals { let num_ml = mls.len(); let num_vars = mls[0].num_variables(); diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 40fb3a2705..38cc68f054 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -19,7 +19,6 @@ use processor::{ ExecutionTrace, }; use tracing::instrument; -use winter_air::AuxRandElements; use winter_prover::{ matrix::ColMatrix, ConstraintCompositionCoefficients, DefaultConstraintEvaluator, DefaultTraceLde, ProofOptions as WinterProofOptions, Prover, StarkDomain, TraceInfo, From 4206910bd90b2e9467c8feca936e6616f9f57749 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 24 May 2024 09:43:03 +0200 Subject: [PATCH 28/32] chore: remove to_owned --- processor/src/trace/virtual_bus/multilinear/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index 775fc1032f..b52dcc07d3 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -1,4 +1,4 @@ -use alloc::{borrow::ToOwned, vec::Vec}; +use alloc::vec::Vec; use core::ops::Index; use vm_core::FieldElement; @@ -68,7 +68,7 @@ impl MultiLinearPoly { *res = self.evaluations[i << 1] + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); } - *self = Self::from_evaluations(result.to_owned()) + *self = Self::from_evaluations(result) .expect("should not fail given that it is a multi-linear"); } From 09deed7d5e2248de4e7d4c767b174b4aef383d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Laferri=C3=A8re?= Date: Sun, 9 Jun 2024 11:08:47 -0400 Subject: [PATCH 29/32] GKR: Refactor circuit evaluation (#1345) * `ProjectiveCoordinates` * Refactor `LayerGatesInputs` * FractionalSumCircuit2 WIP * Use `FractionalSumCircuit2` in prover * SumCheckProverError * fix number of layers computed * SumCheckVerifierError * Remove old `FractionalSumCircuit` * Remove old `LayerGatesInputs` * Remove `CircuitGateInputs` * Document and rename * docs * Document `ProjectiveCoordinates` * sumcheck_round variable ordering * fix direction of sum-check * Simplify and clean up (#1347) * wip: change terminology of circuit eval * wip: remove left/right num/denom * chore: remove static * chore: remove old file and comments * Document `project_lower_variable` * chore: minor nits and renaming --------- Co-authored-by: Philippe Laferriere * add TODOP * Use `Wire` terminology * Remove `CompositionPolynomial::num_variables()` * fix capacity * rename `NUM_WIRES_PER_TRACE_ROW` constant * TODOP * Introduce `reduce_layer_claim` * use left/right terminology in GkrCompositionMerge * adjust TODOP * fix docs * docs * clippy * clippy * cleanup prove_before_final_circuit_layers loop No longer uses an index * adjust docs * fmt * doc nit * nodes -> wires * doc nit * cleanup doc * document loop * fmt --------- Co-authored-by: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> --- air/src/trace/main_trace.rs | 3 +- processor/src/lib.rs | 5 +- .../src/trace/virtual_bus/circuit/error.rs | 7 +- .../src/trace/virtual_bus/circuit/mod.rs | 241 +++++---- .../src/trace/virtual_bus/circuit/prover.rs | 511 ++++++++++-------- .../src/trace/virtual_bus/circuit/verifier.rs | 40 +- .../virtual_bus/multilinear/lagrange_ker.rs | 3 +- .../src/trace/virtual_bus/multilinear/mod.rs | 44 +- .../src/trace/virtual_bus/sum_check/domain.rs | 65 --- .../src/trace/virtual_bus/sum_check/mod.rs | 12 +- .../trace/virtual_bus/sum_check/prover/mod.rs | 39 +- .../virtual_bus/sum_check/verifier/mod.rs | 2 +- processor/src/trace/virtual_bus/univariate.rs | 14 +- 13 files changed, 521 insertions(+), 465 deletions(-) delete mode 100644 processor/src/trace/virtual_bus/sum_check/domain.rs diff --git a/air/src/trace/main_trace.rs b/air/src/trace/main_trace.rs index 029871f3b2..5f36e1f5b9 100644 --- a/air/src/trace/main_trace.rs +++ b/air/src/trace/main_trace.rs @@ -16,7 +16,6 @@ use super::{ CHIPLETS_OFFSET, CLK_COL_IDX, CTX_COL_IDX, DECODER_TRACE_OFFSET, FMP_COL_IDX, FN_HASH_OFFSET, STACK_TRACE_OFFSET, }; -use alloc::vec::Vec; use core::ops::{Deref, Range}; use vm_core::{utils::range, Felt, ONE, ZERO}; @@ -60,7 +59,7 @@ impl MainTrace { } #[cfg(any(test, feature = "internals"))] - pub fn get_column_range(&self, range: Range) -> Vec> { + pub fn get_column_range(&self, range: Range) -> alloc::vec::Vec> { range.fold(vec![], |mut acc, col_idx| { acc.push(self.get_column(col_idx).to_vec()); acc diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 82cf0a4b7f..8fd84fdfa9 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -57,7 +57,10 @@ use chiplets::Chiplets; mod trace; use trace::TraceFragment; -pub use trace::{prove_virtual_bus, verify_virtual_bus, ChipletsLengths, ExecutionTrace, TraceLenSummary, NUM_RAND_ROWS}; +pub use trace::{ + prove_virtual_bus, verify_virtual_bus, ChipletsLengths, ExecutionTrace, TraceLenSummary, + NUM_RAND_ROWS, +}; mod errors; pub use errors::{ExecutionError, Ext2InttError}; diff --git a/processor/src/trace/virtual_bus/circuit/error.rs b/processor/src/trace/virtual_bus/circuit/error.rs index 1e713a2027..bfc2a35f91 100644 --- a/processor/src/trace/virtual_bus/circuit/error.rs +++ b/processor/src/trace/virtual_bus/circuit/error.rs @@ -1,9 +1,12 @@ +use crate::trace::virtual_bus::sum_check::SumCheckProverError; +use crate::trace::virtual_bus::sum_check::SumCheckVerifierError; + #[derive(Debug, thiserror::Error)] pub enum ProverError { #[error("failed to generate multi-linear from the given evaluations")] FailedToGenerateML, #[error("failed to generate the sum-check proof")] - FailedToProveSumCheck, + FailedToProveSumCheck(#[from] SumCheckProverError), #[error("failed to generate the random challenge")] FailedToGenerateChallenge, } @@ -17,5 +20,5 @@ pub enum VerifierError { #[error("failed to generate the random challenge")] FailedToGenerateChallenge, #[error("failed to verify the sum-check proof")] - FailedToVerifySumCheck, + FailedToVerifySumCheck(#[from] SumCheckVerifierError), } diff --git a/processor/src/trace/virtual_bus/circuit/mod.rs b/processor/src/trace/virtual_bus/circuit/mod.rs index 6a6f1031ee..fa5f0a495c 100644 --- a/processor/src/trace/virtual_bus/circuit/mod.rs +++ b/processor/src/trace/virtual_bus/circuit/mod.rs @@ -1,3 +1,5 @@ +use core::ops::Add; + use crate::trace::virtual_bus::multilinear::EqFunction; use crate::trace::virtual_bus::{multilinear::CompositionPolynomial, sum_check::RoundProof}; use alloc::vec::Vec; @@ -5,6 +7,7 @@ use miden_air::trace::chiplets::{MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX}; use miden_air::trace::decoder::{DECODER_OP_BITS_OFFSET, DECODER_USER_OP_HELPERS_OFFSET}; use miden_air::trace::range::{M_COL_IDX, V_COL_IDX}; use miden_air::trace::{CHIPLETS_OFFSET, TRACE_WIDTH}; +use prover::CircuitLayerPolys; use static_assertions::const_assert; use vm_core::{Felt, FieldElement}; @@ -15,99 +18,133 @@ pub use prover::prove; mod verifier; pub use verifier::verify; -use super::multilinear::inner_product; +use super::multilinear::MultiLinearPoly; use super::sum_check::{FinalOpeningClaim, Proof as SumCheckProof}; -/// Defines the number of elements for the partial left/right numerator/denominators of -/// [`LayerGatesInputs`]. -const NUM_ELEMENTS_PER_GATE_INPUT: usize = 4; -const_assert!(NUM_ELEMENTS_PER_GATE_INPUT.is_power_of_two()); - -/// Holds the contribution of one main trace row to the input layer's gates inputs. -struct LayerGatesInputs { - pub partial_left_numerator: [E; NUM_ELEMENTS_PER_GATE_INPUT], - pub partial_right_numerator: [E; NUM_ELEMENTS_PER_GATE_INPUT], - pub partial_left_denominator: [E; NUM_ELEMENTS_PER_GATE_INPUT], - pub partial_right_denominator: [E; NUM_ELEMENTS_PER_GATE_INPUT], +/// Defines the number of wires in the input layer that are generated from a single main trace row. +const NUM_WIRES_PER_TRACE_ROW: usize = 8; +const_assert!(NUM_WIRES_PER_TRACE_ROW.is_power_of_two()); + +/// Represents a fraction `numerator / denominator` as a pair `(numerator, denominator)`. This is +/// the type for the gates' inputs in [`prover::EvaluatedCircuit`]. +/// +/// Hence, addition is defined in the natural way fractions are added together: `a/b + c/d = (ad + +/// bc) / bd`. +#[derive(Debug, Clone, Copy)] +pub struct CircuitWire { + numerator: E, + denominator: E, } -impl LayerGatesInputs { - pub fn from_main_trace_query(query: &[E], log_up_randomness: &[E]) -> Self { +impl CircuitWire +where + E: FieldElement, +{ + /// Creates new projective coordinates from a numerator and a denominator. + pub fn new(numerator: E, denominator: E) -> Self { + assert_ne!(denominator, E::ZERO); + Self { - partial_left_numerator: Self::left_numerator(query), - partial_right_numerator: Self::right_numerator(query), - partial_left_denominator: Self::left_denominator(query, log_up_randomness), - partial_right_denominator: Self::right_denominator(query, log_up_randomness), + numerator, + denominator, } } +} - fn left_numerator(query: &[E]) -> [E; NUM_ELEMENTS_PER_GATE_INPUT] { - let f_m = { - let mem_selec0 = query[CHIPLETS_OFFSET]; - let mem_selec1 = query[CHIPLETS_OFFSET + 1]; - let mem_selec2 = query[CHIPLETS_OFFSET + 2]; - mem_selec0 * mem_selec1 * (E::ONE - mem_selec2) - }; - let f_rc = { - let op_bit_4 = query[DECODER_OP_BITS_OFFSET + 4]; - let op_bit_5 = query[DECODER_OP_BITS_OFFSET + 5]; - let op_bit_6 = query[DECODER_OP_BITS_OFFSET + 6]; - - (E::ONE - op_bit_4) * (E::ONE - op_bit_5) * op_bit_6 - }; - - [query[M_COL_IDX], f_m, f_m, f_rc] - } - - fn right_numerator(query: &[E]) -> [E; NUM_ELEMENTS_PER_GATE_INPUT] { - let f_rc = { - let op_bit_4 = query[DECODER_OP_BITS_OFFSET + 4]; - let op_bit_5 = query[DECODER_OP_BITS_OFFSET + 5]; - let op_bit_6 = query[DECODER_OP_BITS_OFFSET + 6]; - - (E::ONE - op_bit_4) * (E::ONE - op_bit_5) * op_bit_6 - }; - - let padding = E::ZERO; - - // the last numerator/denominator pair is unused, so is padded with 0 and 1, respectively. - [f_rc, f_rc, f_rc, padding] - } - - fn left_denominator(query: &[E], log_up_randomness: &[E]) -> [E; NUM_ELEMENTS_PER_GATE_INPUT] { - let alphas = log_up_randomness; +impl Add for CircuitWire +where + E: FieldElement, +{ + type Output = Self; - let table_denom = alphas[0] - query[V_COL_IDX]; - let memory_denom_0 = -(alphas[0] - query[MEMORY_D0_COL_IDX]); - let memory_denom_1 = -(alphas[0] - query[MEMORY_D1_COL_IDX]); - let stack_value_denom_0 = -(alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET]); + fn add(self, other: Self) -> Self { + let numerator = self.numerator * other.denominator + other.numerator * self.denominator; + let denominator = self.denominator * other.denominator; - [table_denom, memory_denom_0, memory_denom_1, stack_value_denom_0] + Self::new(numerator, denominator) } +} - fn right_denominator(query: &[E], log_up_randomness: &[E]) -> [E; NUM_ELEMENTS_PER_GATE_INPUT] { - let alphas = log_up_randomness; - - let stack_value_denom_1 = -(alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET + 1]); - let stack_value_denom_2 = -(alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET + 2]); - let stack_value_denom_3 = -(alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET + 3]); - - let padding = E::ONE; +/// Converts a main trace row (or more generally "query") to numerators and denominators of the +/// input layer. +fn evaluate_fractions_at_main_trace_query( + query: &[E], + log_up_randomness: &[E], +) -> [[E; NUM_WIRES_PER_TRACE_ROW]; 2] +where + E: FieldElement, +{ + // numerators + let multiplicity = query[M_COL_IDX]; + let f_m = { + let mem_selec0 = query[CHIPLETS_OFFSET]; + let mem_selec1 = query[CHIPLETS_OFFSET + 1]; + let mem_selec2 = query[CHIPLETS_OFFSET + 2]; + mem_selec0 * mem_selec1 * (E::ONE - mem_selec2) + }; + + let f_rc = { + let op_bit_4 = query[DECODER_OP_BITS_OFFSET + 4]; + let op_bit_5 = query[DECODER_OP_BITS_OFFSET + 5]; + let op_bit_6 = query[DECODER_OP_BITS_OFFSET + 6]; + + (E::ONE - op_bit_4) * (E::ONE - op_bit_5) * op_bit_6 + }; + + // denominators + let alphas = log_up_randomness; + + let table_denom = alphas[0] - query[V_COL_IDX]; + let memory_denom_0 = -(alphas[0] - query[MEMORY_D0_COL_IDX]); + let memory_denom_1 = -(alphas[0] - query[MEMORY_D1_COL_IDX]); + let stack_value_denom_0 = -(alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET]); + let stack_value_denom_1 = -(alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET + 1]); + let stack_value_denom_2 = -(alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET + 2]); + let stack_value_denom_3 = -(alphas[0] - query[DECODER_USER_OP_HELPERS_OFFSET + 3]); + + [ + [multiplicity, f_m, f_m, f_rc, f_rc, f_rc, f_rc, E::ZERO], + [ + table_denom, + memory_denom_0, + memory_denom_1, + stack_value_denom_0, + stack_value_denom_1, + stack_value_denom_2, + stack_value_denom_3, + E::ONE, + ], + ] +} - // the last numerator/denominator pair is unused, so is padded with 0 and 1, respectively. - [stack_value_denom_1, stack_value_denom_2, stack_value_denom_3, padding] - } +/// Computes the wires added to the input layer that come from a given main trace row (or more +/// generally, "query"). +fn compute_input_layer_wires_at_main_trace_query( + query: &[E], + log_up_randomness: &[E], +) -> [CircuitWire; NUM_WIRES_PER_TRACE_ROW] +where + E: FieldElement, +{ + let [numerators, denominators] = + evaluate_fractions_at_main_trace_query(query, log_up_randomness); + let input_gates_values: Vec> = numerators + .into_iter() + .zip(denominators) + .map(|(numerator, denominator)| CircuitWire::new(numerator, denominator)) + .collect(); + input_gates_values.try_into().unwrap() } /// A GKR proof for the correct evaluation of the sum of fractions circuit. #[derive(Debug)] -pub struct GkrCircuitProof { - circuit_outputs: [E; 4], +pub struct GkrCircuitProof { + circuit_outputs: CircuitLayerPolys, before_final_layer_proofs: BeforeFinalLayerProof, final_layer_proof: FinalLayerProof, } -impl GkrCircuitProof { +impl GkrCircuitProof { pub fn get_final_opening_claim(&self) -> FinalOpeningClaim { self.final_layer_proof.after_merge_proof.openings_claim.clone() } @@ -115,7 +152,7 @@ impl GkrCircuitProof { /// A set of sum-check proofs for all GKR layers but for the input circuit layer. #[derive(Debug)] -pub struct BeforeFinalLayerProof { +pub struct BeforeFinalLayerProof { pub proof: Vec>, } @@ -128,12 +165,13 @@ pub struct FinalLayerProof { /// Represents a claim to be proven by a subsequent call to the sum-check protocol. #[derive(Debug)] -pub struct GkrClaim { +pub struct GkrClaim { pub evaluation_point: Vec, pub claimed_evaluation: (E, E), } -/// A composition polynomial used in the GKR protocol for all of its sum-checks except the final one. +/// A composition polynomial used in the GKR protocol for all of its sum-checks except the final +/// one. #[derive(Clone)] pub struct GkrComposition where @@ -157,10 +195,6 @@ impl CompositionPolynomial for GkrComposition where E: FieldElement, { - fn num_variables(&self) -> u32 { - 5 - } - fn max_degree(&self) -> u32 { 3 } @@ -213,40 +247,41 @@ impl CompositionPolynomial for GkrCompositionMerge where E: FieldElement, { - fn num_variables(&self) -> u32 { - TRACE_WIDTH as u32 - } - fn max_degree(&self) -> u32 { // Computed as: - // 1 + max(left_numerator_degree + right_denom_degree, right_numerator_degree + left_denom_degree) + // 1 + max(left_numerator_degree + right_denom_degree, right_numerator_degree + + // left_denom_degree) 5 } fn evaluate(&self, query: &[E]) -> E { - let LayerGatesInputs { - partial_left_numerator, - partial_right_numerator, - partial_left_denominator, - partial_right_denominator, - } = LayerGatesInputs::from_main_trace_query(query, &self.log_up_randomness); - - let eval_left_numerator = - inner_product(&partial_left_numerator, &self.tensored_merge_randomness); - let eval_right_numerator = - inner_product(&partial_right_numerator, &self.tensored_merge_randomness); - let eval_left_denominator = - inner_product(&partial_left_denominator, &self.tensored_merge_randomness); - let eval_right_denominator = - inner_product(&partial_right_denominator, &self.tensored_merge_randomness); + let [numerators, denominators] = + evaluate_fractions_at_main_trace_query(query, &self.log_up_randomness); + + let numerators = MultiLinearPoly::from_evaluations(numerators.to_vec()).unwrap(); + let denominators = MultiLinearPoly::from_evaluations(denominators.to_vec()).unwrap(); + + let (left_numerators, right_numerators) = numerators.project_least_significant_variable(); + let (left_denominators, right_denominators) = + denominators.project_least_significant_variable(); + + let eval_left_numerators = + left_numerators.evaluate_with_lagrange_kernel(&self.tensored_merge_randomness); + let eval_right_numerators = + right_numerators.evaluate_with_lagrange_kernel(&self.tensored_merge_randomness); + + let eval_left_denominators = + left_denominators.evaluate_with_lagrange_kernel(&self.tensored_merge_randomness); + let eval_right_denominators = + right_denominators.evaluate_with_lagrange_kernel(&self.tensored_merge_randomness); let eq_eval = query[TRACE_WIDTH]; eq_eval - * ((eval_left_numerator * eval_right_denominator - + eval_right_numerator * eval_left_denominator) - + eval_left_denominator - * eval_right_denominator + * ((eval_left_numerators * eval_right_denominators + + eval_right_numerators * eval_left_denominators) + + eval_left_denominators + * eval_right_denominators * self.sum_check_combining_randomness) } } diff --git a/processor/src/trace/virtual_bus/circuit/prover.rs b/processor/src/trace/virtual_bus/circuit/prover.rs index e8661f9684..7fadc95d0f 100644 --- a/processor/src/trace/virtual_bus/circuit/prover.rs +++ b/processor/src/trace/virtual_bus/circuit/prover.rs @@ -1,247 +1,262 @@ use super::{ - super::sum_check::Proof as SumCheckProof, error::ProverError, BeforeFinalLayerProof, - FinalLayerProof, GkrCircuitProof, GkrClaim, GkrComposition, GkrCompositionMerge, - LayerGatesInputs, NUM_ELEMENTS_PER_GATE_INPUT, + super::sum_check::Proof as SumCheckProof, compute_input_layer_wires_at_main_trace_query, + error::ProverError, BeforeFinalLayerProof, CircuitWire, FinalLayerProof, GkrCircuitProof, + GkrClaim, GkrComposition, GkrCompositionMerge, NUM_WIRES_PER_TRACE_ROW, }; use crate::trace::virtual_bus::{ multilinear::{EqFunction, MultiLinearPoly}, sum_check::{FinalClaimBuilder, FinalOpeningClaim, RoundClaim, RoundProof}, SumCheckProver, }; -use alloc::{borrow::ToOwned, vec::Vec}; +use alloc::vec::Vec; use core::marker::PhantomData; use miden_air::trace::main_trace::MainTrace; use vm_core::{Felt, FieldElement}; use winter_prover::crypto::{ElementHasher, RandomCoin}; -/// Layered circuit for computing a sum of fractions. +/// Evaluation of a layered circuit for computing a sum of fractions. /// -/// The circuit computes a sum of fractions based on the formula a / c + b / d = (a * d + b * c) / (c * d) -/// which defines a "gate" ((a, b), (c, d)) --> (a * d + b * c, c * d) upon which the [`FractionalSumCircuit`] -/// is built. Due to the uniformity of the circuit, each of the circuit layers collect all the: +/// The circuit computes a sum of fractions based on the formula a / c + b / d = (a * d + b * c) / +/// (c * d) which defines a "gate" ((a, b), (c, d)) --> (a * d + b * c, c * d) upon which the +/// [`EvaluatedCircuit`] is built. Due to the uniformity of the circuit, each of the circuit +/// layers collect all the: /// -/// 1. `a`'s into a [`MultiLinearPoly`] called `p_0`. -/// 2. `b`'s into a [`MultiLinearPoly`] called `p_1`. -/// 3. `c`'s into a [`MultiLinearPoly`] called `q_0`. -/// 4. `d`'s into a [`MultiLinearPoly`] called `q_1`. +/// 1. `a`'s into a [`MultiLinearPoly`] called `left_numerators`. +/// 2. `b`'s into a [`MultiLinearPoly`] called `right_numerators`. +/// 3. `c`'s into a [`MultiLinearPoly`] called `left_denominators`. +/// 4. `d`'s into a [`MultiLinearPoly`] called `right_denominators`. /// /// The relation between two subsequent layers is given by the formula /// -/// p_0[layer + 1](x_0, x_1, ..., x_{ν - 2}) = p_0[layer](x_0, x_1, ..., x_{ν - 2}, 0) * q_1[layer](x_0, x_1, ..., x_{ν - 2}, 0) -/// + p_1[layer](x_0, x_1, ..., x_{ν - 2}, 0) * q_0[layer](x_0, x_1, ..., x_{ν - 2}, 0) +/// p_0[layer + 1](x_0, x_1, ..., x_{ν - 2}) = p_0[layer](x_0, x_1, ..., x_{ν - 2}, 0) * +/// q_1[layer](x_0, x_1, ..., x_{ν - 2}, 0) +/// + p_1[layer](x_0, x_1, ..., x_{ν - 2}, 0) * q_0[layer](x_0, +/// x_1, ..., x_{ν - 2}, 0) /// -/// p_1[layer + 1](x_0, x_1, ..., x_{ν - 2}) = p_0[layer](x_0, x_1, ..., x_{ν - 2}, 1) * q_1[layer](x_0, x_1, ..., x_{ν - 2}, 1) -/// + p_1[layer](x_0, x_1, ..., x_{ν - 2}, 1) * q_0[layer](x_0, x_1, ..., x_{ν - 2}, 1) +/// p_1[layer + 1](x_0, x_1, ..., x_{ν - 2}) = p_0[layer](x_0, x_1, ..., x_{ν - 2}, 1) * +/// q_1[layer](x_0, x_1, ..., x_{ν - 2}, 1) +/// + p_1[layer](x_0, x_1, ..., x_{ν - 2}, 1) * q_0[layer](x_0, +/// x_1, ..., x_{ν - 2}, 1) /// /// and /// -/// q_0[layer + 1](x_0, x_1, ..., x_{ν - 2}) = q_0[layer](x_0, x_1, ..., x_{ν - 2}, 0) * q_1[layer](x_0, x_1, ..., x_{ν - 1}, 0) -/// -/// q_1[layer + 1](x_0, x_1, ..., x_{ν - 2}) = q_0[layer](x_0, x_1, ..., x_{ν - 2}, 1) * q_1[layer](x_0, x_1, ..., x_{ν - 1}, 1) +/// q_0[layer + 1](x_0, x_1, ..., x_{ν - 2}) = q_0[layer](x_0, x_1, ..., x_{ν - 2}, 0) * +/// q_1[layer](x_0, x_1, ..., x_{ν - 1}, 0) +/// q_1[layer + 1](x_0, x_1, ..., x_{ν - 2}) = q_0[layer](x_0, x_1, ..., x_{ν - 2}, 1) * +/// q_1[layer](x_0, x_1, ..., x_{ν - 1}, 1) +/// +/// This logic is encoded in [`CircuitWire`]. /// /// This means that layer ν will be the output layer and will consist of four values /// (p_0[ν - 1], p_1[ν - 1], p_0[ν - 1], p_1[ν - 1]) ∈ 𝔽^ν. -#[derive(Debug)] -pub struct FractionalSumCircuit { - p_0_vec: Vec>, - p_1_vec: Vec>, - q_0_vec: Vec>, - q_1_vec: Vec>, +pub struct EvaluatedCircuit { + layer_polys: Vec>, } -impl FractionalSumCircuit { - /// Computes The values of the gate outputs for each of the layers of the fractional sum circuit. +impl EvaluatedCircuit { + /// Creates a new [`EvaluatedCircuit`] by evaluating the circuit where the input layer is + /// defined from the main trace columns. pub fn new( - columns: &[MultiLinearPoly], + main_trace_columns: &[MultiLinearPoly], log_up_randomness: &[E], ) -> Result { - let circuit_inputs = CircuitInputs::new(columns, log_up_randomness)?; - - let num_layers = circuit_inputs.num_variables(); - let mut p_0_vec: Vec> = Vec::with_capacity(num_layers); - let mut p_1_vec: Vec> = Vec::with_capacity(num_layers); - let mut q_0_vec: Vec> = Vec::with_capacity(num_layers); - let mut q_1_vec: Vec> = Vec::with_capacity(num_layers); - - p_0_vec.push(circuit_inputs.left_numerator); - p_1_vec.push(circuit_inputs.right_numerator); - q_0_vec.push(circuit_inputs.left_denominator); - q_1_vec.push(circuit_inputs.right_denominator); - - for i in 0..num_layers { - let (output_p_0, output_p_1, output_q_0, output_q_1) = - FractionalSumCircuit::compute_layer( - &p_0_vec[i], - &p_1_vec[i], - &q_0_vec[i], - &q_1_vec[i], - )?; - p_0_vec.push(output_p_0); - p_1_vec.push(output_p_1); - q_0_vec.push(output_q_0); - q_1_vec.push(output_q_1); + let mut layer_polys = Vec::new(); + + let mut current_layer = Self::generate_input_layer(main_trace_columns, log_up_randomness); + while current_layer.num_wires() > 1 { + let next_layer = Self::compute_next_layer(¤t_layer); + + layer_polys.push(CircuitLayerPolys::from_circuit_layer(current_layer)); + + current_layer = next_layer; } - Ok(FractionalSumCircuit { - p_0_vec, - p_1_vec, - q_0_vec, - q_1_vec, - }) + Ok(Self { layer_polys }) } - /// Computes the output values of the layer given a set of input values - #[allow(clippy::type_complexity)] - fn compute_layer( - inp_p_0: &MultiLinearPoly, - inp_p_1: &MultiLinearPoly, - inp_q_0: &MultiLinearPoly, - inp_q_1: &MultiLinearPoly, - ) -> Result< - (MultiLinearPoly, MultiLinearPoly, MultiLinearPoly, MultiLinearPoly), - ProverError, - > { - let len = inp_q_0.num_evaluations(); - let outp_p_0 = (0..len / 2) - .map(|i| inp_p_0[i] * inp_q_1[i] + inp_p_1[i] * inp_q_0[i]) - .collect::>(); - let outp_p_1 = (len / 2..len) - .map(|i| inp_p_0[i] * inp_q_1[i] + inp_p_1[i] * inp_q_0[i]) - .collect::>(); - let outp_q_0 = (0..len / 2).map(|i| inp_q_0[i] * inp_q_1[i]).collect::>(); - let outp_q_1 = (len / 2..len).map(|i| inp_q_0[i] * inp_q_1[i]).collect::>(); - - Ok(( - MultiLinearPoly::from_evaluations(outp_p_0) - .map_err(|_| ProverError::FailedToGenerateML)?, - MultiLinearPoly::from_evaluations(outp_p_1) - .map_err(|_| ProverError::FailedToGenerateML)?, - MultiLinearPoly::from_evaluations(outp_q_0) - .map_err(|_| ProverError::FailedToGenerateML)?, - MultiLinearPoly::from_evaluations(outp_q_1) - .map_err(|_| ProverError::FailedToGenerateML)?, - )) + /// Returns a layer of the evaluated circuit. + /// + /// Note that the return type is [`LayerPolys`] as opposed to [`Layer`], since the evaluated + /// circuit is stored in a representation which can be proved using GKR. + pub fn get_layer(&self, layer_idx: usize) -> &CircuitLayerPolys { + &self.layer_polys[layer_idx] } - /// Given a value r, computes the evaluation of the last layer at r when interpreted as (two) - /// multilinear polynomials. - pub fn evaluate_output_layer(&self, r: E) -> (E, E) { - let len = self.p_0_vec.len(); - assert_eq!(self.p_0_vec[len - 1].num_variables(), 0); - assert_eq!(self.p_1_vec[len - 1].num_variables(), 0); - assert_eq!(self.q_0_vec[len - 1].num_variables(), 0); - assert_eq!(self.q_1_vec[len - 1].num_variables(), 0); - - let mut p = self.p_0_vec[len - 1].clone(); - p.extend(&self.p_1_vec[len - 1]); - let mut q = self.q_0_vec[len - 1].clone(); - q.extend(&self.q_1_vec[len - 1]); - - (p.evaluate(&[r]), q.evaluate(&[r])) + /// Returns all layers of the evaluated circuit, starting from the input layer. + /// + /// Note that the return type is a slice of [`CircuitLayerPolys`] as opposed to + /// [`CircuitLayer`], since the evaluated layers are stored in a representation which can be + /// proved using GKR. + pub fn layers(&self) -> &[CircuitLayerPolys] { + &self.layer_polys } - /// Outputs the value of the circuit output layer. - pub fn output_layer(&self) -> [E; 4] { - let len = self.p_0_vec.len(); - let poly_a = self.p_0_vec[len - 1][0]; - let poly_b = self.p_1_vec[len - 1][0]; - let poly_c = self.q_0_vec[len - 1][0]; - let poly_d = self.q_1_vec[len - 1][0]; - [poly_a, poly_b, poly_c, poly_d] + /// Returns the numerator/denominator polynomials representing the output layer of the circuit. + pub fn output_layer(&self) -> &CircuitLayerPolys { + self.layer_polys.last().expect("circuit has at least one layer") } -} -/// Holds the inputs to [`FractionalSumCircuit`] -struct CircuitInputs { - left_numerator: MultiLinearPoly, - right_numerator: MultiLinearPoly, - left_denominator: MultiLinearPoly, - right_denominator: MultiLinearPoly, -} + /// Evaluates the output layer at `query`, where the numerators of the output layer are treated + /// as evaluations of a multilinear polynomial, and similarly for the denominators. + pub fn evaluate_output_layer(&self, query: E) -> (E, E) { + let CircuitLayerPolys { + numerators, + denominators, + } = self.output_layer(); + + (numerators.evaluate(&[query]), denominators.evaluate(&[query])) + } -impl CircuitInputs { - fn new(columns: &[MultiLinearPoly], log_up_randomness: &[E]) -> Result { - let num_evaluations = columns[0].num_evaluations(); - let mut left_numerator = Vec::with_capacity(num_evaluations * NUM_ELEMENTS_PER_GATE_INPUT); - let mut right_numerator = Vec::with_capacity(num_evaluations * NUM_ELEMENTS_PER_GATE_INPUT); - let mut left_denominator = - Vec::with_capacity(num_evaluations * NUM_ELEMENTS_PER_GATE_INPUT); - let mut right_denominator = - Vec::with_capacity(num_evaluations * NUM_ELEMENTS_PER_GATE_INPUT); + // HELPERS + // ------------------------------------------------------------------------------------------- + + /// Generates the input layer of the circuit from the main trace columns and some randomness + /// provided by the verifier. + fn generate_input_layer( + main_trace_columns: &[MultiLinearPoly], + log_up_randomness: &[E], + ) -> CircuitLayer { + let num_evaluations = main_trace_columns[0].num_evaluations(); + let mut input_layer_wires = Vec::with_capacity(num_evaluations * NUM_WIRES_PER_TRACE_ROW); for i in 0..num_evaluations { - let query: Vec = columns.iter().map(|ml| ml[i]).collect(); - - let LayerGatesInputs { - partial_left_numerator, - partial_right_numerator, - partial_left_denominator, - partial_right_denominator, - } = LayerGatesInputs::from_main_trace_query(&query, log_up_randomness); - - left_numerator.extend(partial_left_numerator); - right_numerator.extend(partial_right_numerator); - left_denominator.extend(partial_left_denominator); - right_denominator.extend(partial_right_denominator); + let wires_from_trace_row = { + let query: Vec = main_trace_columns.iter().map(|ml| ml[i]).collect(); + compute_input_layer_wires_at_main_trace_query(&query, log_up_randomness) + }; + + input_layer_wires.extend(wires_from_trace_row); } - Ok(Self { - left_numerator: MultiLinearPoly::from_evaluations(left_numerator) - .map_err(|_| ProverError::FailedToGenerateML)?, - right_numerator: MultiLinearPoly::from_evaluations(right_numerator) - .map_err(|_| ProverError::FailedToGenerateML)?, - left_denominator: MultiLinearPoly::from_evaluations(left_denominator) - .map_err(|_| ProverError::FailedToGenerateML)?, + CircuitLayer::new(input_layer_wires) + } - right_denominator: MultiLinearPoly::from_evaluations(right_denominator) - .map_err(|_| ProverError::FailedToGenerateML)?, - }) + /// Computes the subsequent layer of the circuit from a given layer. + fn compute_next_layer(prev_layer: &CircuitLayer) -> CircuitLayer { + let next_layer_wires = prev_layer + .wires() + .chunks_exact(2) + .map(|input_wires| { + let left_input_wire = input_wires[0]; + let right_input_wire = input_wires[1]; + + // output wire + left_input_wire + right_input_wire + }) + .collect(); + + CircuitLayer::new(next_layer_wires) } +} + +/// Represents a layer in a [`EvaluatedCircuit`]. +/// +/// A layer is made up of a set of `n` wires, where `n` is a power of two. This is the natural +/// circuit representation of a layer, where each consecutive pair of wires are summed to yield a +/// wire in the subsequent layer of an [`EvaluatedCircuit`]. +/// +/// Note that a [`Layer`] needs to be first converted to a [`LayerPolys`] before the evaluation of +/// the layer can be proved using GKR. +struct CircuitLayer { + wires: Vec>, +} - fn num_variables(&self) -> usize { - self.left_numerator.num_variables() +impl CircuitLayer { + /// Creates a new [`Layer`] from a set of projective coordinates. + /// + /// Panics if the number of projective coordinates is not a power of two. + pub fn new(wires: Vec>) -> Self { + assert!(wires.len().is_power_of_two()); + + Self { wires } + } + + /// Returns the wires that make up this circuit layer. + pub fn wires(&self) -> &[CircuitWire] { + &self.wires + } + + /// Returns the number of wires in the layer. + pub fn num_wires(&self) -> usize { + self.wires.len() + } +} + +/// Holds a layer of an [`EvaluatedCircuit`] in a representation amenable to proving circuit +/// evaluation using GKR. +#[derive(Clone, Debug)] +pub struct CircuitLayerPolys { + pub numerators: MultiLinearPoly, + pub denominators: MultiLinearPoly, +} + +impl CircuitLayerPolys +where + E: FieldElement, +{ + fn from_circuit_layer(layer: CircuitLayer) -> Self { + Self::from_wires(layer.wires) + } + + pub fn from_wires(wires: Vec>) -> Self { + let mut numerators = Vec::new(); + let mut denominators = Vec::new(); + + for wire in wires { + numerators.push(wire.numerator); + denominators.push(wire.denominator); + } + + Self { + numerators: MultiLinearPoly::from_evaluations(numerators) + .expect("evaluations guaranteed to be a power of two"), + denominators: MultiLinearPoly::from_evaluations(denominators) + .expect("evaluations guaranteed to be a power of two"), + } } } /// Evaluates and proves a fractional sum circuit given a set of composition polynomials. /// -/// Each individual component of the quadruple [p_0, p_1, q_0, q_1] is of the form: +/// For the input layer of the circuit, each individual component of the quadruple +/// [p_0, p_1, q_0, q_1] is of the form: /// -/// m(z_0, ... , z_{μ - 1}, x_0, ... , x_{ν - 1}) = -/// \sum_{y ∈ {0,1}^μ} EQ(z, y) * g_{[y]}(f_0(x_0, ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , x_{ν - 1})) +/// m(z_0, ... , z_{μ - 1}, x_0, ... , x_{ν - 1}) = \sum_{y ∈ {0,1}^μ} EQ(z, y) * g_{[y]}(f_0(x_0, +/// ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , x_{ν +/// - 1})) /// /// where: /// /// 1. μ is the log_2 of the number of different numerator/denominator expressions divided by two. /// 2. [y] := \sum_{j = 0}^{μ - 1} y_j * 2^j -/// 3. κ is the number of multi-linears (i.e., main trace columns) involved in the computation -/// of the circuit (i.e., virtual bus). +/// 3. κ is the number of multi-linears (i.e., main trace columns) involved in the computation of +/// the circuit (i.e., virtual bus). /// 4. ν is the log_2 of the trace length. /// /// The above `m` is usually referred to as the merge of the individual composed multi-linear /// polynomials g_{[y]}(f_0(x_0, ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , x_{ν - 1})). /// -/// The composition polynomials `g` are provided as inputs and then used in order to compute -/// the evaluations of each of the four merge polynomials over {0, 1}^{μ + ν}. The resulting -/// evaluations are then used in order to evaluate [`FractionalSumCircuit`]. -/// At this point, the GKR protocol is used to prove the correctness of circuit evaluation. It -/// should be noted that the input layer, which corresponds to the last layer treated by the GKR -/// protocol, is handled differently from the other layers. -/// More specifically, the sum-check protocol used for the input layer is composed of two sum-check -/// protocols, the first one works directly with the evaluations of the `m`'s over {0, 1}^{μ + ν} -/// and runs for μ rounds. -/// After these μ rounds, and using the resulting [`RoundClaim`], we run the second and final -/// sum-check protocol for ν rounds on the composed multi-linear polynomial given by +/// The composition polynomials `g` are provided as inputs and then used in order to compute the +/// evaluations of each of the four merge polynomials over {0, 1}^{μ + ν}. The resulting evaluations +/// are then used in order to evaluate the circuit. At this point, the GKR protocol is used to prove +/// the correctness of circuit evaluation. It should be noted that the input layer, which +/// corresponds to the last layer treated by the GKR protocol, is handled differently from the other +/// layers. More specifically, the sum-check protocol used for the input layer is composed of two +/// sum-check protocols, the first one works directly with the evaluations of the `m`'s over {0, +/// 1}^{μ + ν} and runs for μ rounds. After these μ rounds, and using the resulting [`RoundClaim`], +/// we run the second and final sum-check protocol for ν rounds on the composed multi-linear +/// polynomial given by /// -/// \sum_{y ∈ {0,1}^μ} EQ(ρ', y) * g_{[y]}(f_0(x_0, ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , x_{ν - 1})) +/// \sum_{y ∈ {0,1}^μ} EQ(ρ', y) * g_{[y]}(f_0(x_0, ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , +/// x_{ν - 1})) /// /// where ρ' is the randomness sampled during the first sum-check protocol. /// -/// As part of the final sum-check protocol, the openings {f_j(ρ)} are provided as part of -/// a [`FinalOpeningClaim`]. This latter claim will be proven by the STARK prover later on using -/// the auxiliary trace. +/// As part of the final sum-check protocol, the openings {f_j(ρ)} are provided as part of a +/// [`FinalOpeningClaim`]. This latter claim will be proven by the STARK prover later on using the +/// auxiliary trace. pub fn prove< - E: FieldElement + 'static, + E: FieldElement, C: RandomCoin, H: ElementHasher, >( @@ -263,14 +278,14 @@ pub fn prove< .collect(); // evaluate the GKR fractional sum circuit - let mut circuit = FractionalSumCircuit::new(&main_trace_columns, &log_up_randomness)?; + let mut circuit = EvaluatedCircuit::new(&main_trace_columns, &log_up_randomness)?; // run the GKR prover for all layers except the input layer let (before_final_layer_proofs, gkr_claim) = prove_before_final_circuit_layers(&mut circuit, transcript)?; // run the GKR prover for the input layer - let num_rounds_before_merge = NUM_ELEMENTS_PER_GATE_INPUT.ilog2() as usize; + let num_rounds_before_merge = NUM_WIRES_PER_TRACE_ROW.ilog2() as usize - 1; let final_layer_proof = prove_final_circuit_layer( log_up_randomness, main_trace_columns, @@ -284,7 +299,7 @@ pub fn prove< let circuit_outputs = circuit.output_layer(); Ok(GkrCircuitProof { - circuit_outputs, + circuit_outputs: circuit_outputs.clone(), before_final_layer_proofs, final_layer_proof, }) @@ -292,7 +307,7 @@ pub fn prove< /// Proves the final GKR layer which corresponds to the input circuit layer. fn prove_final_circuit_layer< - E: FieldElement + 'static, + E: FieldElement, C: RandomCoin, H: ElementHasher, >( @@ -300,7 +315,7 @@ fn prove_final_circuit_layer< mut mls: Vec>, num_rounds_merge: usize, gkr_claim: GkrClaim, - circuit: &mut FractionalSumCircuit, + circuit: &mut EvaluatedCircuit, transcript: &mut C, ) -> Result, ProverError> { // parse the [GkrClaim] resulting from the previous GKR layer @@ -313,12 +328,12 @@ fn prove_final_circuit_layer< let poly_x = EqFunction::ml_at(evaluation_point.clone()); // get the multi-linears of the 4 merge polynomials - let poly_a = circuit.p_0_vec[0].to_owned(); - let poly_b = circuit.p_1_vec[0].to_owned(); - let poly_c = circuit.q_0_vec[0].to_owned(); - let poly_d = circuit.q_1_vec[0].to_owned(); - let mut merged_mls = vec![poly_a, poly_b, poly_c, poly_d, poly_x]; - + let layer = circuit.get_layer(0); + let (left_numerators, right_numerators) = layer.numerators.project_least_significant_variable(); + let (left_denominators, right_denominators) = + layer.denominators.project_least_significant_variable(); + let mut merged_mls = + vec![left_numerators, right_numerators, left_denominators, right_denominators, poly_x]; // run the first sum-check protocol let ((round_claim, before_merge_proof), r_sum_check) = sum_check_prover_plain_partial( claimed_evaluation, @@ -343,9 +358,7 @@ fn prove_final_circuit_layer< // run the second sum-check protocol let main_prover = SumCheckProver::new(gkr_composition, SimpleGkrFinalClaimBuilder(PhantomData)); - let after_merge_proof = main_prover - .prove(claim, mls, transcript) - .map_err(|_| ProverError::FailedToProveSumCheck)?; + let after_merge_proof = main_prover.prove(claim, mls, transcript)?; Ok(FinalLayerProof { before_merge_proof, @@ -355,25 +368,23 @@ fn prove_final_circuit_layer< /// Proves all GKR layers except for input layer. fn prove_before_final_circuit_layers< - E: FieldElement + 'static, + E: FieldElement, C: RandomCoin, H: ElementHasher, >( - circuit: &mut FractionalSumCircuit, + circuit: &mut EvaluatedCircuit, transcript: &mut C, ) -> Result<(BeforeFinalLayerProof, GkrClaim), ProverError> { // absorb the circuit output layer. This corresponds to sending the four values of the output // layer to the verifier. The verifier then replies with a challenge `r` in order to evaluate // `p` and `q` at `r` as multi-linears. - let num_layers = circuit.p_0_vec.len(); - let data = vec![ - circuit.p_0_vec[num_layers - 1][0], - circuit.p_1_vec[num_layers - 1][0], - circuit.q_0_vec[num_layers - 1][0], - circuit.q_1_vec[num_layers - 1][0], - ]; - // generate the challenge `r` - transcript.reseed(H::hash_elements(&data)); + let CircuitLayerPolys { + numerators, + denominators, + } = circuit.output_layer(); + let mut evaluations = numerators.evaluations().to_vec(); + evaluations.extend_from_slice(denominators.evaluations()); + transcript.reseed(H::hash_elements(&evaluations)); // generate the challenge and reduce [p0, p1, q0, q1] to [pr, qr] let r = transcript.draw().map_err(|_| ProverError::FailedToGenerateChallenge)?; @@ -381,17 +392,26 @@ fn prove_before_final_circuit_layers< let mut proof_layers: Vec> = Vec::new(); let mut rand = vec![r]; - for layer_id in (1..num_layers - 1).rev() { + + // Loop over all inner layers, from output to input. + // + // In a layered circuit, each layer is defined in terms of its predecessor. The first inner + // layer (starting from the output layer) is the first layer that has a predecessor. Here, we + // loop over all inner layers in order to iteratively reduce a layer in terms of its successor + // layer. Note that we don't include the input layer, since its predecessor layer will be + // reduced in terms of the input layer separately in `prove_final_circuit_layer`. + for inner_layer in circuit.layers().iter().skip(1).rev().skip(1) { // construct the Lagrange kernel evaluated at the previous GKR round randomness let poly_x = EqFunction::ml_at(rand.clone()); // construct the vector of multi-linear polynomials // TODO: avoid unnecessary allocation - let poly_a = circuit.p_0_vec[layer_id].to_owned(); - let poly_b = circuit.p_1_vec[layer_id].to_owned(); - let poly_c = circuit.q_0_vec[layer_id].to_owned(); - let poly_d = circuit.q_1_vec[layer_id].to_owned(); - let mls = vec![poly_a, poly_b, poly_c, poly_d, poly_x]; + let (left_numerators, right_numerators) = + inner_layer.numerators.project_least_significant_variable(); + let (left_denominators, right_denominators) = + inner_layer.denominators.project_least_significant_variable(); + let mls = + vec![left_numerators, right_numerators, left_denominators, right_denominators, poly_x]; // run the sumcheck protocol let (proof, _) = sum_check_prover_plain_full(claim, mls, transcript)?; @@ -401,15 +421,24 @@ fn prove_before_final_circuit_layers< let r_layer = transcript.draw().map_err(|_| ProverError::FailedToGenerateChallenge)?; // reduce the claim - let p0 = proof.openings_claim.openings[0]; - let p1 = proof.openings_claim.openings[1]; - let q0 = proof.openings_claim.openings[2]; - let q1 = proof.openings_claim.openings[3]; - claim = (p0 + r_layer * (p1 - p0), q0 + r_layer * (q1 - q0)); + claim = { + let left_numerators_opening = proof.openings_claim.openings[0]; + let right_numerators_opening = proof.openings_claim.openings[1]; + let left_denominators_opening = proof.openings_claim.openings[2]; + let right_denominators_opening = proof.openings_claim.openings[3]; + + reduce_layer_claim( + left_numerators_opening, + right_numerators_opening, + left_denominators_opening, + right_denominators_opening, + r_layer, + ) + }; // collect the randomness used for the current layer - let mut ext = proof.openings_claim.eval_point.clone(); - ext.push(r_layer); + let mut ext = vec![r_layer]; + ext.extend_from_slice(&proof.openings_claim.eval_point); rand = ext; proof_layers.push(proof); @@ -426,10 +455,52 @@ fn prove_before_final_circuit_layers< )) } +/// We receive our 4 multilinear polynomials which were evaluated at a random point: +/// `left_numerators` (or `p0`), `right_numerators` (or `p1`), `left_denominators` (or `q0`), and +/// `right_denominators` (or `q1`). We'll call the 4 evaluations at a random point `p0(r)`, `p1(r)`, +/// `q0(r)`, and `q1(r)`, respectively, where `r` is the random point. Note that `r` is a shorthand +/// for a tuple of random values `(r_0, ... r_{l-1})`, where `2^{l + 1}` is the number of wires in +/// the layer. +/// +/// It is important to recall how `p0` and `p1` were constructed (and analogously for `q0` and +/// `q1`). They are the `numerators` layer polynomial (or `p`) evaluations `p(0, r)` and `p(1, r)`, +/// obtained from [`MultiLinearPoly::project_least_significant_variable`]. Hence, `[p0, p1]` form +/// the evaluations of polynomial `p'(x_0) = p(x_0, r)`. Then, the round claim for `numerators`, +/// defined as `p(r_layer, r)`, is simply `p'(r_layer)`. +fn reduce_layer_claim( + left_numerators_opening: E, + right_numerators_opening: E, + left_denominators_opening: E, + right_denominators_opening: E, + r_layer: E, +) -> (E, E) +where + E: FieldElement, +{ + // This is the `numerators` layer polynomial `f(x_0) = numerators(x_0, rx_0, ..., rx_{l-1})`, + // where `rx_0, ..., rx_{l-1}` are the random variables that were sampled during the sumcheck + // round for this layer. + let numerators_univariate = + MultiLinearPoly::from_evaluations(vec![left_numerators_opening, right_numerators_opening]) + .unwrap(); + + // This is analogous to `numerators_univariate`, but for the `denominators` layer polynomial + let denominators_univariate = MultiLinearPoly::from_evaluations(vec![ + left_denominators_opening, + right_denominators_opening, + ]) + .unwrap(); + + ( + numerators_univariate.evaluate(&[r_layer]), + denominators_univariate.evaluate(&[r_layer]), + ) +} + /// Runs the first sum-check prover for the input layer. #[allow(clippy::type_complexity)] fn sum_check_prover_plain_partial< - E: FieldElement + 'static, + E: FieldElement, C: RandomCoin, H: ElementHasher, >( @@ -449,16 +520,14 @@ fn sum_check_prover_plain_partial< // run the sum-check protocol let main_prover = SumCheckProver::new(composer, SimpleGkrFinalClaimBuilder(PhantomData)); - let proof = main_prover - .prove_rounds(claim, ml_polys, num_rounds, transcript) - .map_err(|_| ProverError::FailedToProveSumCheck)?; + let proof = main_prover.prove_rounds(claim, ml_polys, num_rounds, transcript)?; Ok((proof, r_batch)) } /// Runs the sum-check prover used in all but the input layer. fn sum_check_prover_plain_full< - E: FieldElement + 'static, + E: FieldElement, C: RandomCoin, H: ElementHasher, >( @@ -476,9 +545,7 @@ fn sum_check_prover_plain_full< // run the sum-check protocol let main_prover = SumCheckProver::new(composer, SimpleGkrFinalClaimBuilder(PhantomData)); - let proof = main_prover - .prove(claim_, ml_polys, transcript) - .map_err(|_| ProverError::FailedToProveSumCheck)?; + let proof = main_prover.prove(claim_, ml_polys, transcript)?; Ok((proof, r_batch)) } diff --git a/processor/src/trace/virtual_bus/circuit/verifier.rs b/processor/src/trace/virtual_bus/circuit/verifier.rs index e7d20c5275..8a205f90e5 100644 --- a/processor/src/trace/virtual_bus/circuit/verifier.rs +++ b/processor/src/trace/virtual_bus/circuit/verifier.rs @@ -1,5 +1,6 @@ use super::{ - error::VerifierError, FinalLayerProof, GkrCircuitProof, GkrComposition, GkrCompositionMerge, + error::VerifierError, prover::CircuitLayerPolys, FinalLayerProof, GkrCircuitProof, + GkrComposition, GkrCompositionMerge, }; use crate::trace::virtual_bus::{ multilinear::EqFunction, @@ -14,7 +15,7 @@ use winter_prover::crypto::{ElementHasher, RandomCoin}; /// Verifies the validity of a GKR proof for the correct evaluation of a fractional sum circuit. pub fn verify< - E: FieldElement + 'static, + E: FieldElement, C: RandomCoin, H: ElementHasher, >( @@ -29,10 +30,14 @@ pub fn verify< final_layer_proof, } = proof; - let p0 = circuit_outputs[0]; - let p1 = circuit_outputs[1]; - let q0 = circuit_outputs[2]; - let q1 = circuit_outputs[3]; + let CircuitLayerPolys { + numerators, + denominators, + } = circuit_outputs; + let p0 = numerators.evaluations()[0]; + let p1 = numerators.evaluations()[1]; + let q0 = denominators.evaluations()[0]; + let q1 = denominators.evaluations()[1]; // make sure that both denominators are not equal to E::ZERO if q0 == E::ZERO || q1 == E::ZERO { @@ -45,7 +50,9 @@ pub fn verify< } // generate the random challenge to reduce two claims into a single claim - transcript.reseed(H::hash_elements(&circuit_outputs)); + let mut evaluations = numerators.evaluations().to_vec(); + evaluations.extend_from_slice(denominators.evaluations()); + transcript.reseed(H::hash_elements(&evaluations)); let r = transcript.draw().map_err(|_| VerifierError::FailedToGenerateChallenge)?; // reduce the claim @@ -79,8 +86,8 @@ pub fn verify< // collect the randomness used for the current layer let rand_sumcheck = eval_point; - let mut ext = rand_sumcheck; - ext.push(r_layer); + let mut ext = vec![r_layer]; + ext.extend_from_slice(&rand_sumcheck); rand = ext; } @@ -98,7 +105,7 @@ pub fn verify< /// Verifies sum-check proofs, as part of the GKR proof, for all GKR layers except for the last one /// i.e., the circuit input layer. pub fn verify_sum_check_proof_before_last< - E: FieldElement + 'static, + E: FieldElement, C: RandomCoin, H: ElementHasher, >( @@ -120,12 +127,12 @@ pub fn verify_sum_check_proof_before_last< SumCheckVerifier::new(composition_poly, GkrQueryBuilder::new(gkr_eval_point.to_owned())); verifier .verify(reduced_claim, proof.clone(), transcript) - .map_err(|_| VerifierError::FailedToVerifySumCheck) + .map_err(VerifierError::FailedToVerifySumCheck) } /// Verifies the final sum-check proof as part of the GKR proof. pub fn verify_sum_check_proof_last< - E: FieldElement + 'static, + E: FieldElement, C: RandomCoin, H: ElementHasher, >( @@ -154,9 +161,7 @@ pub fn verify_sum_check_proof_last< let RoundClaim { eval_point: rand_merge, claim, - } = verifier - .verify_rounds(reduced_claim, before_merge_proof, transcript) - .map_err(|_| VerifierError::FailedToVerifySumCheck)?; + } = verifier.verify_rounds(reduced_claim, before_merge_proof, transcript)?; // verify the second part of the sum-check protocol let gkr_composition = @@ -167,10 +172,11 @@ pub fn verify_sum_check_proof_last< ); verifier .verify(claim, after_merge_proof, transcript) - .map_err(|_| VerifierError::FailedToVerifySumCheck) + .map_err(VerifierError::FailedToVerifySumCheck) } -/// A [`FinalQueryBuilder`] for the sum-check verifier used for all sum-checks but for the final one. +/// A [`FinalQueryBuilder`] for the sum-check verifier used for all sum-checks but for the final +/// one. #[derive(Default)] struct GkrQueryBuilder { gkr_eval_point: Vec, diff --git a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs index ece573d099..4c309a6a56 100644 --- a/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs +++ b/processor/src/trace/virtual_bus/multilinear/lagrange_ker.rs @@ -4,7 +4,8 @@ use alloc::vec::Vec; /// The EQ (equality) function is the binary function defined by /// /// EQ: {0 , 1}^ν ⛌ {0 , 1}^ν ⇾ {0 , 1} -/// ((x_0, ..., x_{ν - 1}), (y_0, ..., y_{ν - 1})) ↦ \prod_{i = 0}^{ν - 1} (x_i * y_i + (1 - x_i) * (1 - y_i)) +/// ((x_0, ..., x_{ν - 1}), (y_0, ..., y_{ν - 1})) ↦ \prod_{i = 0}^{ν - 1} (x_i * y_i + (1 - x_i) +/// * (1 - y_i)) /// /// Taking It's multi-linear extension EQ^{~}, we can define a basis for the set of multi-linear /// polynomials in ν variables by diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index b52dcc07d3..2696b927f2 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -59,10 +59,20 @@ impl MultiLinearPoly { inner_product(&self.evaluations, &tensored_query) } + /// Similar to [`Self::evaluate`], except that the query was already turned into the Lagrange + /// kernel (i.e. the [`lagrange_ker::EqFunction`] evaluated at every point in the set + /// `{0 , 1}^ν`). + /// + /// This is more efficient than [`Self::evaluate`] when multiple different [`MultiLinearPoly`] + /// need to be evaluated at the same query point. + pub fn evaluate_with_lagrange_kernel(&self, lagrange_kernel: &[E]) -> E { + inner_product(&self.evaluations, lagrange_kernel) + } + /// Computes f(r_0, y_1, ..., y_{ν - 1}) using the linear interpolation formula /// (1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1}) and assigns /// the resulting multi-linear, defined over a domain of half the size, to `self`. - pub fn bind(&mut self, round_challenge: E) { + pub fn bind_least_significant_variable(&mut self, round_challenge: E) { let mut result = vec![E::ZERO; 1 << (self.num_variables() - 1)]; for (i, res) in result.iter_mut().enumerate() { *res = self.evaluations[i << 1] @@ -72,20 +82,20 @@ impl MultiLinearPoly { .expect("should not fail given that it is a multi-linear"); } - /// Given two instances of [`MultiLinearPoly`], f(x_0, x_1, ..., x_{ν - 1}) and - /// g(x_0, x_1, ..., x_{ν - 1}), constructs the following polynomial defined by - /// - /// merge(f, g)(x_0, x_1, ..., x_{ν - 1}, z) := (1 - z) * f(x_0, x_1, ..., x_{ν - 1}) - /// + z * g(x_0, x_1, ..., x_{ν - 1}) - /// Notice that: - /// - /// 1. merge(f, g)(x_0, x_1, ..., x_{ν - 1}, 0) = f(x_0, x_1, ..., x_{ν - 1}) - /// 2. merge(f, g)(x_0, x_1, ..., x_{ν - 1}, 1) = g(x_0, x_1, ..., x_{ν - 1}) - pub fn extend(&mut self, other: &MultiLinearPoly) { - let other_vec = other.evaluations.to_vec(); - assert_eq!(other_vec.len(), self.evaluations().len()); - self.evaluations.extend(other_vec); - self.num_variables += 1; + /// Given the multilinear polynomial f(y_0, y_1, ..., y_{ν - 1}), returns two polynomials: + /// f(0, y_1, ..., y_{ν - 1}) and f(1, y_1, ..., y_{ν - 1}). + pub fn project_least_significant_variable(&self) -> (Self, Self) { + let mut p0 = Vec::with_capacity(self.num_evaluations() / 2); + let mut p1 = Vec::with_capacity(self.num_evaluations() / 2); + for chunk in self.evaluations.chunks_exact(2) { + p0.push(chunk[0]); + p1.push(chunk[1]); + } + + ( + MultiLinearPoly::from_evaluations(p0).unwrap(), + MultiLinearPoly::from_evaluations(p1).unwrap(), + ) } } @@ -102,10 +112,6 @@ impl Index for MultiLinearPoly { /// A multi-variate polynomial for composing individual multi-linear polynomials. pub trait CompositionPolynomial { - /// The number of variables when interpreted as a multi-variate polynomial. - #[allow(dead_code)] - fn num_variables(&self) -> u32; - /// Maximum degree in all variables. fn max_degree(&self) -> u32; diff --git a/processor/src/trace/virtual_bus/sum_check/domain.rs b/processor/src/trace/virtual_bus/sum_check/domain.rs deleted file mode 100644 index 25e9db4f59..0000000000 --- a/processor/src/trace/virtual_bus/sum_check/domain.rs +++ /dev/null @@ -1,65 +0,0 @@ -use alloc::vec::Vec; -use vm_core::FieldElement; -use winter_prover::math::batch_inversion; - -/// Implements barycentric evaluation where the interpolation domain is `0, 1, ..., max_degree` -/// where `max_degree` is maximal polynomial supported degree. -pub struct EvaluationDomain -where - E: FieldElement, -{ - interpolation_points: Vec, - barycentric_weights: Vec, -} - -impl EvaluationDomain -where - E: FieldElement, -{ - pub fn new(max_degree: u32) -> Self { - let interpolation_points: Vec = (0..=max_degree).map(E::from).collect(); - let barycentric_weights = barycentric_weights(&interpolation_points); - - Self { - interpolation_points, - barycentric_weights, - } - } - - pub fn evaluate(&self, evaluations: &[E], r: E) -> E { - evaluate_barycentric(&self.interpolation_points, evaluations, r, &self.barycentric_weights) - } -} - -/// Computes the barycentric weights for a set of interpolation points. -pub fn barycentric_weights(points: &[E]) -> Vec { - let n = points.len(); - let denominators = (0..n) - .map(|i| (0..n).filter(|&j| j != i).fold(E::ONE, |acc, j| acc * (points[i] - points[j]))) - .collect::>(); - batch_inversion(&denominators) -} - -/// Computes the value of the polynomial with minimal degree interpolating the set `{(x_i, y_i)}` -/// at the point `r` given pre-computed barycentric weights. -pub fn evaluate_barycentric( - x_i: &[E], - y_i: &[E], - r: E, - barycentric_weights: &[E], -) -> E { - for (&x_i, &y_i) in x_i.iter().zip(y_i.iter()) { - if x_i == r { - return y_i; - } - } - - let l_x: E = x_i.iter().fold(E::ONE, |acc, &x_i| acc * (r - x_i)); - - let sum = (0..x_i.len()).fold(E::ZERO, |acc, i| { - let w_i = barycentric_weights[i]; - acc + (w_i / (r - x_i[i]) * y_i[i]) - }); - - l_x * sum -} diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index 2ab088c21f..8346886203 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -3,9 +3,9 @@ use alloc::vec::Vec; use vm_core::FieldElement; mod prover; -pub use prover::{FinalClaimBuilder, SumCheckProver}; +pub use prover::{Error as SumCheckProverError, FinalClaimBuilder, SumCheckProver}; mod verifier; -pub use verifier::{CompositionPolyQueryBuilder, SumCheckVerifier}; +pub use verifier::{CompositionPolyQueryBuilder, Error as SumCheckVerifierError, SumCheckVerifier}; /// A sum-check round proof. /// @@ -206,10 +206,6 @@ mod test { where E: FieldElement, { - fn num_variables(&self) -> u32 { - 1 - } - fn max_degree(&self) -> u32 { 1 } @@ -226,10 +222,6 @@ mod test { where E: FieldElement, { - fn num_variables(&self) -> u32 { - 2 - } - fn max_degree(&self) -> u32 { 2 } diff --git a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs index 24d379afa7..7e5ec4c16c 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs @@ -1,4 +1,3 @@ -use self::error::Error; use super::{reduce_claim, FinalOpeningClaim, Proof, RoundClaim, RoundProof}; use crate::trace::virtual_bus::{ multilinear::{CompositionPolynomial, MultiLinearPoly}, @@ -10,6 +9,7 @@ use vm_core::FieldElement; use winter_prover::crypto::{ElementHasher, RandomCoin}; mod error; +pub use self::error::Error; /// A struct that contains relevant information for the execution of the multivariate sum-check /// protocol prover. @@ -29,18 +29,17 @@ mod error; /// then engages in an IP to convince the Verifier that the above relation holds for the given /// `f_i` and `v`. More precisely: /// -/// 0. Denote by w(x_0,\cdots, x_{\nu - 1}) := g(f_0((x_0,\cdots, x_{\nu - 1})), -/// \cdots , f_c((x_0,\cdots, x_{\nu - 1}))). +/// 0. Denote by w(x_0,\cdots, x_{\nu - 1}) := g(f_0((x_0,\cdots, x_{\nu - 1})), \cdots , +/// f_c((x_0,\cdots, x_{\nu - 1}))). /// -/// 1. In the first round, the Prover sends the polynomial defined by: -/// s_0(X_0) := \sum_{(x_{1},\cdots, x_{\nu - 1}) w(X_0, x_{1}, \cdots, x_{\nu - 1}) +/// 1. In the first round, the Prover sends the polynomial defined by: s_0(X_0) := +/// \sum_{(x_{1},\cdots, x_{\nu - 1}) w(X_0, x_{1}, \cdots, x_{\nu - 1}) /// /// 2. The Verifier then checks that s_0(0) + s_0(1) = v rejecting if not. /// /// 3. The Verifier samples a random challenge `r_0 ∈ 𝔽` and sends it to the Prover. /// -/// 4. For each i in 1...(\nu - 1): -/// a. The Prover sends the univariate polynomial defined by: +/// 4. For each i in 1...(\nu - 1): a. The Prover sends the univariate polynomial defined by: /// /// s_i(X_i) := \sum_{(x_{i + 1},\cdots, x_{\nu - 1}) /// w(r_0,\cdots, r_{i - 1}, X_i, x_{i + 1}, \cdots, x_{\nu - 1}). @@ -63,9 +62,10 @@ mod error; /// /// 2. The Prover has each `f_i` in its evaluation form over the hyper-cube \{0 , 1\}^{\nu}. /// -/// 3. An optimization is for the Prover to not send `s_i(0)` as it can be recovered from the current -/// reduced claim s_{i - 1}(r_{i - 1}) using the relation s_{i}(0) = s_{i}(1) - s_{i - 1}(r_{i - 1}). -/// This also means that the Verifier can skip point 4.b. +/// 3. An optimization is for the Prover to not send `s_i(0)` as it can be recovered from the +/// current +/// reduced claim s_{i - 1}(r_{i - 1}) using the relation s_{i}(0) = s_{i}(1) - s_{i - 1}(r_{i - +/// 1}). This also means that the Verifier can skip point 4.b. pub struct SumCheckProver where E: FieldElement, @@ -105,7 +105,8 @@ where /// More specifically, executes the sum-check protocol for the following relation /// /// v = \sum_{(x_0,\cdots, x_{\nu - 1}) \in \{0 , 1\}^{\nu}} - /// g(f_0((x_0,\cdots, x_{\nu - 1})), \cdots , f_c((x_0,\cdots, x_{\nu - 1}))) + /// g(f_0((x_0,\cdots, x_{\nu - 1})), \cdots , f_c((x_0,\cdots, x_{\nu - + /// 1}))) /// /// where: /// @@ -201,7 +202,8 @@ where reduce_claim(&round_proofs[i - 1], current_round_claim, round_challenge); // fold each multi-linear using the round challenge - mls.iter_mut().for_each(|ml| ml.bind(round_challenge)); + mls.iter_mut() + .for_each(|ml| ml.bind_least_significant_variable(round_challenge)); // run the i-th round of the protocol using the folded multi-linears for the new reduced // claim. This basically computes the s_i polynomial. @@ -221,7 +223,8 @@ where // generate the last random challenge let round_challenge = coin.draw().map_err(|_| Error::FailedToGenerateChallenge)?; // fold each multi-linear using the last random challenge - mls.iter_mut().for_each(|ml| ml.bind(round_challenge)); + mls.iter_mut() + .for_each(|ml| ml.bind_least_significant_variable(round_challenge)); let round_claim = reduce_claim(&round_proofs[num_rounds - 1], current_round_claim, round_challenge); @@ -253,7 +256,8 @@ where /// we can write /// /// f_i(X_i, x_{i + 1}, \cdots, x_{\nu - 1}) = -/// (1 - X_i) . f_i(0, x_{i + 1}, \cdots, x_{\nu - 1}) + X_i . f_i(1, x_{i + 1}, \cdots, x_{\nu - 1}) +/// (1 - X_i) . f_i(0, x_{i + 1}, \cdots, x_{\nu - 1}) + X_i . f_i(1, x_{i + 1}, \cdots, +/// x_{\nu - 1}) /// /// Note that we omitted writing the folding randomness for readability. /// Since the evaluation domain is {0, 1, ... , d_max}, we can compute the evaluations based on @@ -275,8 +279,11 @@ fn sumcheck_round>( let total_evals = (0..1 << num_rounds).map(|i| { for (j, ml) in mls.iter().enumerate() { - evals_zero[j] = ml.evaluations()[i << 1]; - evals_one[j] = ml.evaluations()[(i << 1) + 1]; + // Holds `f(x_{i+1}, ..., x_v, 0)` + evals_zero[j] = ml.evaluations()[2 * i]; + + // Holds `f(x_{i+1}, ..., x_v, 1)` + evals_one[j] = ml.evaluations()[2 * i + 1]; } let mut total_evals = vec![E::ZERO; composition_poly.max_degree() as usize]; diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index ff3036cc68..f1f5133048 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -1,4 +1,3 @@ -use self::error::Error; use super::{FinalOpeningClaim, Proof, RoundClaim, RoundProof}; use crate::trace::virtual_bus::multilinear::CompositionPolynomial; use alloc::vec::Vec; @@ -7,6 +6,7 @@ use vm_core::FieldElement; use winter_prover::crypto::{ElementHasher, RandomCoin}; mod error; +pub use self::error::Error; /// A struct that contains relevant information for the execution of the multivariate sum-check /// protocol verifier. The protocol is described in [`SumCheckProver`]. diff --git a/processor/src/trace/virtual_bus/univariate.rs b/processor/src/trace/virtual_bus/univariate.rs index dfa6a818d4..0e69cd5c57 100644 --- a/processor/src/trace/virtual_bus/univariate.rs +++ b/processor/src/trace/virtual_bus/univariate.rs @@ -2,7 +2,8 @@ use alloc::vec::Vec; use vm_core::{polynom, FieldElement}; use winter_prover::math::batch_inversion; -/// The evaluations of a univariate polynomial of degree n at 0, 1, ..., n with the evaluation at 0 omitted. +/// The evaluations of a univariate polynomial of degree n at 0, 1, ..., n with the evaluation at 0 +/// omitted. #[derive(Clone, Debug)] pub struct UnivariatePolyEvals { pub(crate) partial_evaluations: Vec, @@ -61,7 +62,8 @@ impl UnivariatePolyEvals { } } -/// The coefficients of a univariate polynomial of degree n with the linear term coefficient omitted. +/// The coefficients of a univariate polynomial of degree n with the linear term coefficient +/// omitted. #[derive(Clone, Debug)] pub struct UnivariatePolyCoef { pub(crate) coefficients: Vec, @@ -102,12 +104,12 @@ impl UnivariatePolyCoef { /// More specifically, we use the representation given in [1], where `V^{-1}` is represented as /// `U * M` where: /// -/// 1. `M` is a lower triangular matrix where its entries are given by -/// M(i, j) = M(i - 1, j) - M(i - 1, j - 1) / (i - 1) +/// 1. `M` is a lower triangular matrix where its entries are given by M(i, j) = M(i - 1, j) - M(i - +/// 1, j - 1) / (i - 1) /// with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. /// -/// 2. `U` is an upper triangular (involutory) matrix where its entries are given by -/// U(i, j) = U(i, j - 1) - U(i - 1, j - 1) +/// 2. `U` is an upper triangular (involutory) matrix where its entries are given by U(i, j) = U(i, +/// j - 1) - U(i - 1, j - 1) /// with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. /// /// Note that the matrix indexing in the formulas above matches the one in the reference and starts From 8b7b5c6739f240281a9abfd077d1ccb55e4f4f67 Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Sun, 16 Jun 2024 17:19:14 -0400 Subject: [PATCH 30/32] remove unused alloc --- air/src/trace/main_trace.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/air/src/trace/main_trace.rs b/air/src/trace/main_trace.rs index f3254e8baa..d3b23b8001 100644 --- a/air/src/trace/main_trace.rs +++ b/air/src/trace/main_trace.rs @@ -19,9 +19,6 @@ use super::{ use core::ops::{Deref, Range}; use vm_core::{utils::range, Felt, Word, ONE, ZERO}; -#[cfg(any(test, feature = "internals"))] -use alloc::vec::Vec; - // CONSTANTS // ================================================================================================ From 0dca8f2dc21596a8e0e8884bc6ce624ee261c8f3 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Sun, 23 Jun 2024 00:37:17 -0700 Subject: [PATCH 31/32] docs: minor comment improvements and typo fixes --- .../src/trace/virtual_bus/circuit/mod.rs | 3 ++ .../src/trace/virtual_bus/circuit/prover.rs | 15 +++++++++ .../src/trace/virtual_bus/circuit/verifier.rs | 20 ++++++++---- processor/src/trace/virtual_bus/mod.rs | 4 +++ .../src/trace/virtual_bus/sum_check/mod.rs | 5 ++- .../virtual_bus/sum_check/verifier/mod.rs | 31 ++++++++++--------- processor/src/trace/virtual_bus/univariate.rs | 12 +++++++ 7 files changed, 68 insertions(+), 22 deletions(-) diff --git a/processor/src/trace/virtual_bus/circuit/mod.rs b/processor/src/trace/virtual_bus/circuit/mod.rs index fa5f0a495c..d4243d01c7 100644 --- a/processor/src/trace/virtual_bus/circuit/mod.rs +++ b/processor/src/trace/virtual_bus/circuit/mod.rs @@ -25,6 +25,9 @@ use super::sum_check::{FinalOpeningClaim, Proof as SumCheckProof}; const NUM_WIRES_PER_TRACE_ROW: usize = 8; const_assert!(NUM_WIRES_PER_TRACE_ROW.is_power_of_two()); +// CIRCUIT WIRE +// ================================================================================================ + /// Represents a fraction `numerator / denominator` as a pair `(numerator, denominator)`. This is /// the type for the gates' inputs in [`prover::EvaluatedCircuit`]. /// diff --git a/processor/src/trace/virtual_bus/circuit/prover.rs b/processor/src/trace/virtual_bus/circuit/prover.rs index 7fadc95d0f..083721da44 100644 --- a/processor/src/trace/virtual_bus/circuit/prover.rs +++ b/processor/src/trace/virtual_bus/circuit/prover.rs @@ -14,6 +14,9 @@ use miden_air::trace::main_trace::MainTrace; use vm_core::{Felt, FieldElement}; use winter_prover::crypto::{ElementHasher, RandomCoin}; +// EVALUATED CIRCUIT +// ================================================================================================ + /// Evaluation of a layered circuit for computing a sum of fractions. /// /// The circuit computes a sum of fractions based on the formula a / c + b / d = (a * d + b * c) / @@ -149,6 +152,9 @@ impl EvaluatedCircuit { } } +// CIRCUIT LAYER +// ================================================================================================ + /// Represents a layer in a [`EvaluatedCircuit`]. /// /// A layer is made up of a set of `n` wires, where `n` is a power of two. This is the natural @@ -182,6 +188,9 @@ impl CircuitLayer { } } +// CIRCUIT LAYER POLYNOMIAL +// ================================================================================================ + /// Holds a layer of an [`EvaluatedCircuit`] in a representation amenable to proving circuit /// evaluation using GKR. #[derive(Clone, Debug)] @@ -216,6 +225,9 @@ where } } +// PROVER +// ================================================================================================ + /// Evaluates and proves a fractional sum circuit given a set of composition polynomials. /// /// For the input layer of the circuit, each individual component of the quadruple @@ -305,6 +317,9 @@ pub fn prove< }) } +// HELPER FUNCTIONS +// ================================================================================================ + /// Proves the final GKR layer which corresponds to the input circuit layer. fn prove_final_circuit_layer< E: FieldElement, diff --git a/processor/src/trace/virtual_bus/circuit/verifier.rs b/processor/src/trace/virtual_bus/circuit/verifier.rs index 8a205f90e5..9369f7067b 100644 --- a/processor/src/trace/virtual_bus/circuit/verifier.rs +++ b/processor/src/trace/virtual_bus/circuit/verifier.rs @@ -13,6 +13,9 @@ use alloc::{borrow::ToOwned, vec::Vec}; use vm_core::{Felt, FieldElement}; use winter_prover::crypto::{ElementHasher, RandomCoin}; +// VERIFIER +// ================================================================================================ + /// Verifies the validity of a GKR proof for the correct evaluation of a fractional sum circuit. pub fn verify< E: FieldElement, @@ -102,9 +105,12 @@ pub fn verify< ) } +// HELPER FUNCTIONS +// ================================================================================================ + /// Verifies sum-check proofs, as part of the GKR proof, for all GKR layers except for the last one /// i.e., the circuit input layer. -pub fn verify_sum_check_proof_before_last< +fn verify_sum_check_proof_before_last< E: FieldElement, C: RandomCoin, H: ElementHasher, @@ -131,7 +137,7 @@ pub fn verify_sum_check_proof_before_last< } /// Verifies the final sum-check proof as part of the GKR proof. -pub fn verify_sum_check_proof_last< +fn verify_sum_check_proof_last< E: FieldElement, C: RandomCoin, H: ElementHasher, @@ -168,15 +174,17 @@ pub fn verify_sum_check_proof_last< GkrCompositionMerge::new(r_sum_check, rand_merge.clone(), log_up_randomness); let verifier = SumCheckVerifier::new( gkr_composition, - GkrMergeQueryBuilder::new(gkr_eval_point.to_owned(), rand_merge), + GkrMergeQueryBuilder::new(gkr_eval_point.to_vec(), rand_merge), ); verifier .verify(claim, after_merge_proof, transcript) .map_err(VerifierError::FailedToVerifySumCheck) } -/// A [`FinalQueryBuilder`] for the sum-check verifier used for all sum-checks but for the final -/// one. +// GKR QUERY BUILDERS +// ================================================================================================ + +/// A [`GkrQueryBuilder`] for the sum-check verifier used for all sum-checks but for the final one. #[derive(Default)] struct GkrQueryBuilder { gkr_eval_point: Vec, @@ -200,7 +208,7 @@ impl CompositionPolyQueryBuilder for GkrQueryBuilder { } } -/// A [`FinalQueryBuilder`] for the sum-check verifier used for the final sum-check. +/// A [`GkrMergeQueryBuilder`] for the sum-check verifier used for the final sum-check. #[derive(Default)] struct GkrMergeQueryBuilder { gkr_eval_point: Vec, diff --git a/processor/src/trace/virtual_bus/mod.rs b/processor/src/trace/virtual_bus/mod.rs index 9ebcfea082..02388d46bb 100644 --- a/processor/src/trace/virtual_bus/mod.rs +++ b/processor/src/trace/virtual_bus/mod.rs @@ -2,16 +2,20 @@ //! //! A global bus is a single bus which encompasses several sub-buses, each representing //! a communication channel between two or more components of the VM. +//! //! A bus represents a client-server relationship between some VM components. The server is //! usually one specific component of the VM, e.g., hasher chiplet, and the client can be one or //! several other components of the VM, e.g., the decoder. The communication between the clients //! and the server is composed of `request` messages made by the clients and corresponding //! `reply` messages by the server. +//! //! The purpose of the sub-bus then, from the verifiable computation point of view, is to ensure //! the consistency between the `request` and `reply` messages exchanged by the clients and //! the server. +//! //! The global bus uses a per-sub-bus address in order to ensure correct routing of the `request` //! messages and their matching `reply` messages. +//! //! Miden VM uses a virtual global bus in the sense that neither the global bus nor the individual //! sub-buses are fully materialized as part of the (auxiliary) trace. This is replaced by a layered //! circuit which computes the global bus relation. The correct evaluation of this circuit is then diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index 8346886203..1d9fa714e4 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -70,8 +70,11 @@ pub struct FinalOpeningClaim { pub openings: Vec, } +// TESTS +// ================================================================================================ + #[cfg(test)] -mod test { +mod tests { use super::{ prover::{FinalClaimBuilder, SumCheckProver}, verifier::{CompositionPolyQueryBuilder, SumCheckVerifier}, diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index f1f5133048..077dd5f86d 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -8,17 +8,20 @@ use winter_prover::crypto::{ElementHasher, RandomCoin}; mod error; pub use self::error::Error; +// SUM-CHECK VERIFIER +// ================================================================================================ + /// A struct that contains relevant information for the execution of the multivariate sum-check -/// protocol verifier. The protocol is described in [`SumCheckProver`]. +/// protocol verifier. The protocol is described in [`super::SumCheckProver`]. +/// /// The sum-check Verifier is composed of two parts: /// /// 1. A multi-round interaction where it sends challenges and receives polynomials. For each -/// polynomial received it uses the sent randomness to reduce the current claim to a new one. -/// +/// polynomial received it uses the sent randomness to reduce the current claim to a new one. /// 2. A final round where the Verifier queries the multi-linear oracles it received at the outset -/// of the protocol (i.e., commitments) for their evaluations at the random point -/// `(r_0, ... , r_{\nu - 1})` where $\nu$ is the number of rounds of the sum-check protocol and -/// `r_i` is the randomness sent by the Verifier at each round. +/// of the protocol (i.e., commitments) for their evaluations at the random point +/// `(r_0, ... , r_{\nu - 1})` where $\nu$ is the number of rounds of the sum-check protocol and +/// `r_i` is the randomness sent by the Verifier at each round. pub struct SumCheckVerifier where E: FieldElement, @@ -51,20 +54,17 @@ where } } - /// Verifies a sum-check proof [Proof] and returns a claim on the openings of the mult-linear + /// Verifies a sum-check proof [Proof] and returns a claim on the openings of the multi-linear /// oracles that are part of the statement being proven. /// /// More precisely, the method: /// /// 1. Generates a `claimed_evaluation` from the round proof polynomials and the round challenge - /// randomness. - /// + /// randomness. /// 2. Computes a query that is built using the [FinalQueryBuilder] from the multi-linear - /// openings and the round challenges. - /// + /// openings and the round challenges. /// 3. Evaluates the composition polynomial at the query and checks that it is equal - /// `claimed_evaluation`. - /// + /// `claimed_evaluation`. /// 4. Outputs a `FinalOpeningClaim` on the multi-linear oracles. /// /// Thus, the proof is correct if the method outputs a [FinalOpeningClaim] and this latter is @@ -133,10 +133,11 @@ where /// Contains the logic for building the final query made to the virtual polynomial. /// /// During the last step of the sum-check protocol, the Verifier must evaluate the composed -/// multilinear polynomials at a random point `(r_0, ... ,r_{\nu - 1})`. To do this, the Verifier -/// asks the Prover for the openings of the mult-linear oracles at `(r_0, ... ,r_{\nu - 1})` i.e., +/// multi-linear polynomials at a random point `(r_0, ... ,r_{\nu - 1})`. To do this, the Verifier +/// asks the Prover for the openings of the multi-linear oracles at `(r_0, ... ,r_{\nu - 1})` i.e., /// `v_i = f_i(r_0, ... ,r_{\nu - 1})`. The Verifier then evaluates `g(v_0, ... , v_{\nu - 1})` and /// compares it to the reduced claim resulting from the round proofs and challenges. +/// /// At this point, for the Verifier to accept the proof, it needs to check that indeed /// `v_i = f_i(r_0, ... ,r_{\nu - 1})`, this is the exact content of [`FinalOpeningClaim`], which /// can be either answered by a direct query to the oracles (i.e., in the compiled protocol this diff --git a/processor/src/trace/virtual_bus/univariate.rs b/processor/src/trace/virtual_bus/univariate.rs index 0e69cd5c57..66a3545a91 100644 --- a/processor/src/trace/virtual_bus/univariate.rs +++ b/processor/src/trace/virtual_bus/univariate.rs @@ -2,6 +2,9 @@ use alloc::vec::Vec; use vm_core::{polynom, FieldElement}; use winter_prover::math::batch_inversion; +// UNIVARIATE POLYNOMIAL (EVALUATION FORM) +// ================================================================================================ + /// The evaluations of a univariate polynomial of degree n at 0, 1, ..., n with the evaluation at 0 /// omitted. #[derive(Clone, Debug)] @@ -62,6 +65,9 @@ impl UnivariatePolyEvals { } } +// UNIVARIATE POLYNOMIAL (COEFFICIENT FORM) +// ================================================================================================ + /// The coefficients of a univariate polynomial of degree n with the linear term coefficient /// omitted. #[derive(Clone, Debug)] @@ -90,6 +96,9 @@ impl UnivariatePolyCoef { } } +// HELPER FUNCTIONS +// ================================================================================================ + /// Given a (row) vector `v`, computes the vector-matrix product `v * V^{-1}` where `V` is /// the Vandermonde matrix over the points `1, ..., n` where `n` is the length of `v`. /// The resulting vector will then be the coefficients of the minimal interpolating polynomial @@ -214,6 +223,9 @@ fn compute_m_entry( value } +// TESTS +// ================================================================================================ + #[test] fn test_poly_partial() { use vm_core::Felt; From ddd7b507bfb3276782982ffbfb6af932efb226d8 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Sun, 23 Jun 2024 00:59:58 -0700 Subject: [PATCH 32/32] refactor: move CompositionPoly from circuit to sumcheck --- processor/src/trace/virtual_bus/circuit/mod.rs | 2 +- .../src/trace/virtual_bus/multilinear/mod.rs | 12 ------------ processor/src/trace/virtual_bus/sum_check/mod.rs | 15 ++++++++++++++- .../src/trace/virtual_bus/sum_check/prover/mod.rs | 7 +++---- .../trace/virtual_bus/sum_check/verifier/mod.rs | 3 +-- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/processor/src/trace/virtual_bus/circuit/mod.rs b/processor/src/trace/virtual_bus/circuit/mod.rs index d4243d01c7..5737693bab 100644 --- a/processor/src/trace/virtual_bus/circuit/mod.rs +++ b/processor/src/trace/virtual_bus/circuit/mod.rs @@ -1,7 +1,7 @@ use core::ops::Add; use crate::trace::virtual_bus::multilinear::EqFunction; -use crate::trace::virtual_bus::{multilinear::CompositionPolynomial, sum_check::RoundProof}; +use crate::trace::virtual_bus::sum_check::{CompositionPolynomial, RoundProof}; use alloc::vec::Vec; use miden_air::trace::chiplets::{MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX}; use miden_air::trace::decoder::{DECODER_OP_BITS_OFFSET, DECODER_USER_OP_HELPERS_OFFSET}; diff --git a/processor/src/trace/virtual_bus/multilinear/mod.rs b/processor/src/trace/virtual_bus/multilinear/mod.rs index 2696b927f2..de892f22d2 100644 --- a/processor/src/trace/virtual_bus/multilinear/mod.rs +++ b/processor/src/trace/virtual_bus/multilinear/mod.rs @@ -106,15 +106,3 @@ impl Index for MultiLinearPoly { &(self.evaluations[index]) } } - -// COMPOSITION POLYNOMIAL -// ================================================================================================ - -/// A multi-variate polynomial for composing individual multi-linear polynomials. -pub trait CompositionPolynomial { - /// Maximum degree in all variables. - fn max_degree(&self) -> u32; - - /// Given a query, of length equal the number of variables, evaluates [Self] at this query. - fn evaluate(&self, query: &[E]) -> E; -} diff --git a/processor/src/trace/virtual_bus/sum_check/mod.rs b/processor/src/trace/virtual_bus/sum_check/mod.rs index 1d9fa714e4..0565bc4c45 100644 --- a/processor/src/trace/virtual_bus/sum_check/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/mod.rs @@ -70,6 +70,18 @@ pub struct FinalOpeningClaim { pub openings: Vec, } +// COMPOSITION POLYNOMIAL +// ================================================================================================ + +/// A multi-variate polynomial for composing individual multi-linear polynomials. +pub trait CompositionPolynomial { + /// Maximum degree in all variables. + fn max_degree(&self) -> u32; + + /// Given a query, of length equal the number of variables, evaluates [Self] at this query. + fn evaluate(&self, query: &[E]) -> E; +} + // TESTS // ================================================================================================ @@ -78,8 +90,9 @@ mod tests { use super::{ prover::{FinalClaimBuilder, SumCheckProver}, verifier::{CompositionPolyQueryBuilder, SumCheckVerifier}, + CompositionPolynomial, }; - use crate::trace::virtual_bus::multilinear::{CompositionPolynomial, MultiLinearPoly}; + use crate::trace::virtual_bus::multilinear::MultiLinearPoly; use alloc::{borrow::ToOwned, vec::Vec}; use test_utils::rand::rand_vector; use vm_core::{crypto::random::RpoRandomCoin, Felt, FieldElement, Word, ONE, ZERO}; diff --git a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs index 7e5ec4c16c..ce211ed686 100644 --- a/processor/src/trace/virtual_bus/sum_check/prover/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/prover/mod.rs @@ -1,8 +1,7 @@ -use super::{reduce_claim, FinalOpeningClaim, Proof, RoundClaim, RoundProof}; -use crate::trace::virtual_bus::{ - multilinear::{CompositionPolynomial, MultiLinearPoly}, - univariate::UnivariatePolyEvals, +use super::{ + reduce_claim, CompositionPolynomial, FinalOpeningClaim, Proof, RoundClaim, RoundProof, }; +use crate::trace::virtual_bus::{multilinear::MultiLinearPoly, univariate::UnivariatePolyEvals}; use alloc::vec::Vec; use core::marker::PhantomData; use vm_core::FieldElement; diff --git a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs index 077dd5f86d..50c045056b 100644 --- a/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs +++ b/processor/src/trace/virtual_bus/sum_check/verifier/mod.rs @@ -1,5 +1,4 @@ -use super::{FinalOpeningClaim, Proof, RoundClaim, RoundProof}; -use crate::trace::virtual_bus::multilinear::CompositionPolynomial; +use super::{CompositionPolynomial, FinalOpeningClaim, Proof, RoundClaim, RoundProof}; use alloc::vec::Vec; use core::marker::PhantomData; use vm_core::FieldElement;