diff --git a/prover/protocol/column/column.go b/prover/protocol/column/column.go index d44d4cc16aa..906bf9102ad 100644 --- a/prover/protocol/column/column.go +++ b/prover/protocol/column/column.go @@ -96,6 +96,14 @@ func NbLeaves(h ifaces.Column) int { panic("unreachable") } +// EvalExprColumn resolves an expression to a column assignment. The expression +// must be converted to a board prior to evaluating the expression. +// +// - If the expression does not uses ifaces.Column as metadata, the function +// will panic. +// +// - If the expression contains several columns and they don't contain all +// have the same size. func EvalExprColumn(run ifaces.Runtime, board symbolic.ExpressionBoard) smartvectors.SmartVector { var ( diff --git a/prover/protocol/distributed/compiler/permutation/permutation.go b/prover/protocol/distributed/compiler/permutation/permutation.go index c4165ce59d9..7e822489db9 100644 --- a/prover/protocol/distributed/compiler/permutation/permutation.go +++ b/prover/protocol/distributed/compiler/permutation/permutation.go @@ -1,18 +1,247 @@ -package permutation +package dist_permutation import ( + "github.com/consensys/linea-monorepo/prover/maths/field" "github.com/consensys/linea-monorepo/prover/protocol/coin" + "github.com/consensys/linea-monorepo/prover/protocol/column" + modulediscoverer "github.com/consensys/linea-monorepo/prover/protocol/distributed/module_discoverer" + "github.com/consensys/linea-monorepo/prover/protocol/ifaces" + "github.com/consensys/linea-monorepo/prover/protocol/query" "github.com/consensys/linea-monorepo/prover/protocol/wizard" + "github.com/consensys/linea-monorepo/prover/protocol/wizardutils" + "github.com/consensys/linea-monorepo/prover/symbolic" ) -// CompileDist compiles permutation queries distributedly. -// It receives a compiledIOP object relevant to a segment. -// The seed is a random coin from randomness beacon (FS of all LPP commitments). -// It scans compiledIOP to find the permutation queries. -// All the compilation steps are similar to the permutation compilation apart from: -// - random coins \alpha and \gamma are generated from the seed (and the tableName). -// - no verifierAction is needed over the ZOpening. -// - ZOpenings are declared as public input. -func CompileDist(comp *wizard.CompiledIOP, seed coin.Info) { +// Used for deriving names of queries and coins +const grandProductStr = "GRAND_PRODUCT" +/* +The below function aims to process all the permutation queries specific to a target module +into a grand product query. We store the randomised symbolic products of A and B of permuation +queries combinedly into the Numerators and the Denominators of the GrandProduct query +*/ +type PermutationIntoGrandProductCtx struct { + // stores the expressions Ai + \beta_i for the ith permutation query, Ai is a linear combination with alpha_i + // in case of multi-column + Numerators []*symbolic.Expression + // stores the expressions Bi + \beta_i for the ith permutation query, Bi is a linear combination with alpha_i + // in case of multi-column + Denominators []*symbolic.Expression + // stores the field element obtained by collapsing the expression + // \prod(Numerators)/\prod(Denominators) + ParamY field.Element + // The query id specific to the target module + QueryId ifaces.QueryID + // The module name for which we are processing the grand product query + TargetModuleName string +} + +// Returns a new PermutationIntoGrandProductCtx +func NewPermutationIntoGrandProductCtx(s Settings) *PermutationIntoGrandProductCtx { + permCtx := PermutationIntoGrandProductCtx{} + permCtx.Numerators = make([]*symbolic.Expression, 0, s.MaxNumOfQueryPerModule) + permCtx.Denominators = make([]*symbolic.Expression, 0, s.MaxNumOfQueryPerModule) + return &permCtx +} + +// AddGdProductQuery processes all permutation queries specific to a target module +// into a grand product query. It stores the randomised symbolic products of A and B +// of permutation queries combinedly into the Numerators and the Denominators of the +// GrandProduct query. +// +// Parameters: +// - initialComp: The initial compiledIOP +// - moduleComp: The compiledIOP for the target module +// - targetModuleName: The name of the module for which we are processing the grand product query +// - run: The prover runtime +// +// Returns: +// - An instance of the grand product query (ifaces.Query) +func (p *PermutationIntoGrandProductCtx) AddGdProductQuery(initialComp, moduleComp *wizard.CompiledIOP, + targetModuleName modulediscoverer.ModuleName, run *wizard.ProverRuntime) ifaces.Query { + numRounds := initialComp.NumRounds() + // Initialise the period separating module discoverer + disc := modulediscoverer.PeriodSeperatingModuleDiscoverer{} + disc.Analyze(initialComp) + qId := deriveName[ifaces.QueryID](ifaces.QueryID(targetModuleName)) + p.QueryId = qId + p.TargetModuleName = string(targetModuleName) + /* + Handles the lookups and permutations checks + */ + for i := 0; i < numRounds; i++ { + queries := initialComp.QueriesNoParams.AllKeysAt(i) + for j, qName := range queries { + // Skip if it was already compiled + if initialComp.QueriesNoParams.IsIgnored(qName) { + continue + } + + switch q_ := initialComp.QueriesNoParams.Data(qName).(type) { + case query.Permutation: + { + moduleNameA := disc.FindModule(q_.A[0][0]) + moduleNameB := disc.FindModule(q_.B[0][0]) + if moduleNameA == targetModuleName && moduleNameB != targetModuleName { + p.push(moduleComp, &q_, i, j, true, false) + } else if moduleNameA != targetModuleName && moduleNameB == targetModuleName { + p.push(moduleComp, &q_, i, j, false, false) + } else if moduleNameA == targetModuleName && moduleNameB == targetModuleName { + p.push(moduleComp, &q_, i, j, true, true) + } else { + continue + } + } + default: + continue + } + } + } + // We register the grand product query in round one because + // alphas, betas, and the query param are assigned in round one + G := moduleComp.InsertGrandProduct(1, qId, p.Numerators, p.Denominators) + // The below prover action is responsible for computing the query parameter + // and assign it in round one + moduleComp.RegisterProverAction(1, p.AssignParam(run, qId)) + return G +} + +// push processes a permutation query and adds its symbolic factors to the Numerators or Denominators +// based on the provided flags. It also inserts coins for alpha and beta for each permutation query. +// +// Parameters: +// - comp: The compiled IOP for the target module +// - q: The permutation query to be processed +// - round: The round number of the permutation query +// - queryInRound: The index of the permutation query within the round +// - isNumerator: A flag indicating whether to add the symbolic factor to the Numerators +// - isBoth: A flag indicating whether we need to add the symbolic factor to both Numerators and Denominators +func (p *PermutationIntoGrandProductCtx) push(comp *wizard.CompiledIOP, q *query.Permutation, round, queryInRound int, isNumerator, isBoth bool) { + var ( + isMultiColumn = len(q.A[0]) > 1 + alpha coin.Info + beta coin.Info + ) + if isMultiColumn { + // alpha has to be different for different queries for a particular round for the soundness of z-packing + alpha = comp.InsertCoin(1, deriveName[coin.Name](ifaces.QueryID(p.TargetModuleName), "ALPHA", round, queryInRound), coin.Field) + } + // beta has to be different for different queries for a particular round for the soundness of z-packing + beta = comp.InsertCoin(1, deriveName[coin.Name](ifaces.QueryID(p.TargetModuleName), "BETA", round, queryInRound), coin.Field) + if isNumerator && !isBoth { + // Take only the numerator + factor := computeFactor(q.A, isMultiColumn, &alpha, &beta) + p.Numerators = append(p.Numerators, factor) + } else if !isNumerator && !isBoth { + // Take only the denominator + factor := computeFactor(q.B, isMultiColumn, &alpha, &beta) + p.Denominators = append(p.Denominators, factor) + } else if isNumerator && isBoth { + // Take both the numerator and the denominator + numFactor := computeFactor(q.A, isMultiColumn, &alpha, &beta) + denFactor := computeFactor(q.B, isMultiColumn, &alpha, &beta) + p.Numerators = append(p.Numerators, numFactor) + p.Denominators = append(p.Denominators, denFactor) + } else if !isNumerator && isBoth { + panic("Invalid case") + } +} + +// computeFactor computes the symbolic factor for a permutation query based on the given parameters. +// It iterates through the fragments of the query, computes the linear combination of columns with alpha +// (if multi-column) or directly uses the column as a variable, adds the beta value, and multiplies it with +// the current factor. The final computed factor is returned. +// +// Parameters: +// - aOrB: A 2D slice of Column interfaces representing the fragments of the permutation query. +// - isMultiColumn: A boolean indicating whether the permutation query is multi-column. +// - alpha: A pointer to a CoinInfo struct representing the alpha coin for the permutation query. +// - beta: A pointer to a CoinInfo struct representing the beta coin for the permutation query. +// +// Returns: +// - A pointer to a symbolic.Expression representing the computed factor for the permutation query. +func computeFactor(aOrB [][]ifaces.Column, isMultiColumn bool, alpha, beta *coin.Info) *symbolic.Expression { + var ( + numFrag = len(aOrB) + factor = symbolic.NewConstant(1) + fragFactor *symbolic.Expression + ) + + for frag := range numFrag { + if isMultiColumn { + fragFactor = wizardutils.RandLinCombColSymbolic(*alpha, aOrB[frag]) + } else { + fragFactor = ifaces.ColumnAsVariable(aOrB[frag][0]) + } + fragFactor = symbolic.Add(fragFactor, *beta) + factor = symbolic.Mul(factor, fragFactor) + } + return factor +} + +// AssignParam computes the query parameter for the grand product query and assigns it in round one. +// It multiplies the products of the Numerators and Denominators, evaluates the resulting symbolic expressions, +// and assigns the result to the field element ParamY. +// +// Parameters: +// - run: The prover runtime. +// - name: The query ID specific to the target module. +// +// Returns: +// - A pointer to the PermutationIntoGrandProductCtx instance with the updated ParamY field. +func (p *PermutationIntoGrandProductCtx) AssignParam(run *wizard.ProverRuntime, name ifaces.QueryID) *PermutationIntoGrandProductCtx { + var ( + numNumerators = len(p.Numerators) + numDenominators = len(p.Denominators) + numProd = symbolic.NewConstant(1) + denProd = symbolic.NewConstant(1) + ) + // Multiply all Numerators + for i := 0; i < numNumerators; i++ { + numProd = symbolic.Mul(numProd, p.Numerators[i]) + } + // Multiply all Denominators + for j := 0; j < numDenominators; j++ { + denProd = symbolic.Mul(denProd, p.Denominators[j]) + } + // Evaluate the symbolic expressions for Numerator and Denominator products + numProdFrVec := column.EvalExprColumn(run, numProd.Board()).IntoRegVecSaveAlloc() + denProdFrVec := column.EvalExprColumn(run, denProd.Board()).IntoRegVecSaveAlloc() + numProdFr := numProdFrVec[0] + denProdFr := denProdFrVec[0] + // Multiply all field elements in the Numerator product vector + if len(numProdFrVec) > 1 { + for i := 1; i < len(numProdFrVec); i++ { + numProdFr.Mul(&numProdFr, &numProdFrVec[i]) + } + } + // Multiply all field elements in the Denominator product vector + if len(denProdFrVec) > 1 { + for j := 1; j < len(denProdFrVec); j++ { + denProdFr.Mul(&denProdFr, &denProdFrVec[j]) + } + } + // Invert the Denominator product field element + denProdFr.Inverse(&denProdFr) + // Compute the final query parameter Y + Y := numProdFr.Mul(&numProdFr, &denProdFr) + p.ParamY = *Y + return p +} + +// Run executes the grand product query by assigning the computed parameter Y to the prover runtime. +// +// Parameters: +// - run: The prover runtime where the grand product query will be executed. +// +// The function does not return any value. It directly assigns the computed parameter Y to the prover runtime +// using the AssignGrandProduct method of the runtime. +func (p *PermutationIntoGrandProductCtx) Run(run *wizard.ProverRuntime) { + run.AssignGrandProduct(p.QueryId, p.ParamY) +} + +// deriveName constructs a name for the PermutationIntoGrandProduct context +func deriveName[R ~string](q ifaces.QueryID, ss ...any) R { + ss = append([]any{grandProductStr, q}, ss...) + return wizardutils.DeriveName[R](ss...) } diff --git a/prover/protocol/distributed/compiler/permutation/permutation_test.go b/prover/protocol/distributed/compiler/permutation/permutation_test.go new file mode 100644 index 00000000000..74a23932b7d --- /dev/null +++ b/prover/protocol/distributed/compiler/permutation/permutation_test.go @@ -0,0 +1,171 @@ +package dist_permutation_test + +import ( + "testing" + + "github.com/consensys/linea-monorepo/prover/maths/common/smartvectors" + dist_permutation "github.com/consensys/linea-monorepo/prover/protocol/distributed/compiler/permutation" + "github.com/consensys/linea-monorepo/prover/protocol/ifaces" + "github.com/consensys/linea-monorepo/prover/protocol/wizard" +) + +func TestDistPermutationNoMultiColumnNoFragment(t *testing.T) { + var ( + runS *wizard.ProverRuntime + G ifaces.Query + permCtx *dist_permutation.PermutationIntoGrandProductCtx + ) + permCtx = dist_permutation.NewPermutationIntoGrandProductCtx(dist_permutation.Settings{MaxNumOfQueryPerModule: 4}) + initialDefine := func(builder *wizard.Builder) { + A := []ifaces.Column{ + builder.RegisterCommit("MODULE_A.A0", 4), + } + B := []ifaces.Column{ + builder.RegisterCommit("MODULE_B.B0", 4), + } + C := []ifaces.Column{ + builder.RegisterCommit("MODULE_C.C0", 4), + } + _ = builder.CompiledIOP.InsertPermutation(0, "P_MOD_A_MOD_B", A, B) + _ = builder.CompiledIOP.InsertPermutation(0, "P_MOD_C_MOD_A", C, A) + _ = builder.CompiledIOP.InsertPermutation(0, "P_MOD_B_MOD_C", B, C) + } + + moduleADefine := func(builder *wizard.Builder) { + builder.RegisterCommit("MODULE_A.A0", 4) + } + + initialComp := wizard.Compile(initialDefine) + moduleAComp := wizard.Compile(moduleADefine) + + moduleAProve := func(run *wizard.ProverRuntime) { + runS = run + run.AssignColumn("MODULE_A.A0", smartvectors.ForTest(1, 2, 3, 4)) + G = permCtx.AddGdProductQuery(initialComp, moduleAComp, "MODULE_A", run) + } + _ = wizard.Prove(moduleAComp, moduleAProve) + errG := G.Check(runS) + + if errG != nil { + t.Fatalf("error verifying the grand product: %v", errG.Error()) + } +} + +func TestDistPermutationNoFragment(t *testing.T) { + var ( + runS *wizard.ProverRuntime + G ifaces.Query + permCtx *dist_permutation.PermutationIntoGrandProductCtx + ) + permCtx = dist_permutation.NewPermutationIntoGrandProductCtx(dist_permutation.Settings{MaxNumOfQueryPerModule: 4}) + initialDefine := func(builder *wizard.Builder) { + A := []ifaces.Column{ + builder.RegisterCommit("MODULE_A.A0", 4), + builder.RegisterCommit("MODULE_A.A1", 4), + builder.RegisterCommit("MODULE_A.A2", 4), + } + B := []ifaces.Column{ + builder.RegisterCommit("MODULE_B.B0", 4), + builder.RegisterCommit("MODULE_B.B1", 4), + builder.RegisterCommit("MODULE_B.B2", 4), + } + C := []ifaces.Column{ + builder.RegisterCommit("MODULE_C.C0", 4), + builder.RegisterCommit("MODULE_C.C1", 4), + builder.RegisterCommit("MODULE_C.C2", 4), + } + _ = builder.CompiledIOP.InsertPermutation(0, "P_MOD_A_MOD_B", A, B) + _ = builder.CompiledIOP.InsertPermutation(0, "P_MOD_C_MOD_A", C, A) + _ = builder.CompiledIOP.InsertPermutation(0, "P_MOD_B_MOD_C", B, C) + } + + moduleADefine := func(builder *wizard.Builder) { + builder.RegisterCommit("MODULE_A.A0", 4) + builder.RegisterCommit("MODULE_A.A1", 4) + builder.RegisterCommit("MODULE_A.A2", 4) + } + + initialComp := wizard.Compile(initialDefine) + moduleAComp := wizard.Compile(moduleADefine) + + moduleAProve := func(run *wizard.ProverRuntime) { + runS = run + run.AssignColumn("MODULE_A.A0", smartvectors.ForTest(1, 2, 3, 4)) + run.AssignColumn("MODULE_A.A1", smartvectors.ForTest(1, 2, 3, 4)) + run.AssignColumn("MODULE_A.A2", smartvectors.ForTest(1, 2, 3, 4)) + G = permCtx.AddGdProductQuery(initialComp, moduleAComp, "MODULE_A", run) + } + _ = wizard.Prove(moduleAComp, moduleAProve) + errG := G.Check(runS) + + if errG != nil { + t.Fatalf("error verifying the grand product: %v", errG.Error()) + } +} + +func TestDistPermutationFragment(t *testing.T) { + var ( + runS *wizard.ProverRuntime + G ifaces.Query + permCtx *dist_permutation.PermutationIntoGrandProductCtx + ) + permCtx = dist_permutation.NewPermutationIntoGrandProductCtx(dist_permutation.Settings{MaxNumOfQueryPerModule: 4}) + initialDefine := func(builder *wizard.Builder) { + A := [][]ifaces.Column{ + {builder.RegisterCommit("MODULE_A.A00", 4), + builder.RegisterCommit("MODULE_A.A10", 4), + builder.RegisterCommit("MODULE_A.A20", 4)}, + {builder.RegisterCommit("MODULE_A.A01", 4), + builder.RegisterCommit("MODULE_A.A11", 4), + builder.RegisterCommit("MODULE_A.A21", 4)}, + } + B := [][]ifaces.Column{ + {builder.RegisterCommit("MODULE_B.B00", 4), + builder.RegisterCommit("MODULE_B.B10", 4), + builder.RegisterCommit("MODULE_B.B20", 4)}, + {builder.RegisterCommit("MODULE_B.B01", 4), + builder.RegisterCommit("MODULE_B.B11", 4), + builder.RegisterCommit("MODULE_B.B21", 4)}, + } + C := [][]ifaces.Column{ + {builder.RegisterCommit("MODULE_C.C00", 4), + builder.RegisterCommit("MODULE_C.C10", 4), + builder.RegisterCommit("MODULE_C.C20", 4)}, + {builder.RegisterCommit("MODULE_C.C01", 4), + builder.RegisterCommit("MODULE_C.C11", 4), + builder.RegisterCommit("MODULE_C.C21", 4)}, + } + _ = builder.CompiledIOP.InsertFragmentedPermutation(0, "P_MOD_A_MOD_B", A, B) + _ = builder.CompiledIOP.InsertFragmentedPermutation(0, "P_MOD_C_MOD_A", C, A) + _ = builder.CompiledIOP.InsertFragmentedPermutation(0, "P_MOD_B_MOD_C", B, C) + } + + moduleADefine := func(builder *wizard.Builder) { + builder.RegisterCommit("MODULE_A.A00", 4) + builder.RegisterCommit("MODULE_A.A10", 4) + builder.RegisterCommit("MODULE_A.A20", 4) + builder.RegisterCommit("MODULE_A.A01", 4) + builder.RegisterCommit("MODULE_A.A11", 4) + builder.RegisterCommit("MODULE_A.A21", 4) + } + + initialComp := wizard.Compile(initialDefine) + moduleAComp := wizard.Compile(moduleADefine) + + moduleAProve := func(run *wizard.ProverRuntime) { + runS = run + run.AssignColumn("MODULE_A.A00", smartvectors.ForTest(1, 2, 3, 4)) + run.AssignColumn("MODULE_A.A10", smartvectors.ForTest(1, 2, 3, 4)) + run.AssignColumn("MODULE_A.A20", smartvectors.ForTest(1, 2, 3, 4)) + run.AssignColumn("MODULE_A.A01", smartvectors.ForTest(1, 2, 3, 4)) + run.AssignColumn("MODULE_A.A11", smartvectors.ForTest(1, 2, 3, 4)) + run.AssignColumn("MODULE_A.A21", smartvectors.ForTest(1, 2, 3, 4)) + G = permCtx.AddGdProductQuery(initialComp, moduleAComp, "MODULE_A", run) + } + _ = wizard.Prove(moduleAComp, moduleAProve) + errG := G.Check(runS) + + if errG != nil { + t.Fatalf("error verifying the grand product: %v", errG.Error()) + } +} diff --git a/prover/protocol/distributed/compiler/permutation/settings.go b/prover/protocol/distributed/compiler/permutation/settings.go new file mode 100644 index 00000000000..a19a6b3afb4 --- /dev/null +++ b/prover/protocol/distributed/compiler/permutation/settings.go @@ -0,0 +1,6 @@ +package dist_permutation + +type Settings struct { + // Maximum number of permutation queries per module + MaxNumOfQueryPerModule int +} diff --git a/prover/protocol/distributed/distributed.go b/prover/protocol/distributed/distributed.go index 893dd006de7..daed7e542d1 100644 --- a/prover/protocol/distributed/distributed.go +++ b/prover/protocol/distributed/distributed.go @@ -9,7 +9,7 @@ import ( "github.com/consensys/linea-monorepo/prover/protocol/wizard" ) -type moduleName = string +type ModuleName = string type DistributedWizard struct { // initializedWizard @@ -32,10 +32,11 @@ type ModuleDiscoverer interface { // Analyze is responsible for letting the module discoverer compute how to // group best the columns into modules. Analyze(comp *wizard.CompiledIOP) - ModuleList(comp *wizard.CompiledIOP) []moduleName - FindModule(col ifaces.Column) moduleName + NbModules() int + ModuleList(comp *wizard.CompiledIOP) []ModuleName + FindModule(col ifaces.Column) ModuleName // given a query and a module name it checks if the query is inside the module - QueryIsInModule(ifaces.Query, moduleName) bool + QueryIsInModule(ifaces.Query, ModuleName) bool } // This transforms the initial wizard. So it is not really the initial @@ -73,7 +74,7 @@ func Distribute(initialWizard *wizard.CompiledIOP, disc ModuleDiscoverer, maxSeg // it should scan comp and based on module name build compiledIOP for LPP and for GL. func extractDistModule( comp *wizard.CompiledIOP, disc ModuleDiscoverer, - moduleName moduleName, + moduleName ModuleName, maxSegmentSize, maxNumSegment int, ) DistributedModule { // initialize two compiledIOPs, for LPP and GL. diff --git a/prover/protocol/distributed/module_discover.go b/prover/protocol/distributed/module_discover.go index 41393e0d22c..b2271d6e368 100644 --- a/prover/protocol/distributed/module_discover.go +++ b/prover/protocol/distributed/module_discover.go @@ -19,7 +19,7 @@ func (split HorizontalSplitting) Split(comp *wizard.CompiledIOP) { } // QueryIsInModule checks if the given query is inside the given module -func (split HorizontalSplitting) QueryIsInModule(ifaces.Query, moduleName) bool { +func (split HorizontalSplitting) QueryIsInModule(ifaces.Query, ModuleName) bool { panic("unimplemented") } diff --git a/prover/protocol/distributed/module_discoverer/period_separating_module_discoverer.go b/prover/protocol/distributed/module_discoverer/period_separating_module_discoverer.go new file mode 100644 index 00000000000..1fce3f67910 --- /dev/null +++ b/prover/protocol/distributed/module_discoverer/period_separating_module_discoverer.go @@ -0,0 +1,75 @@ +package distributed + +import ( + "strings" + + "github.com/consensys/linea-monorepo/prover/protocol/ifaces" + "github.com/consensys/linea-monorepo/prover/protocol/wizard" +) + +type ModuleName string + +// Example struct implementing ModuleDiscoverer +type PeriodSeperatingModuleDiscoverer struct { + modules map[ModuleName][]ifaces.Column +} + +// Analyze groups columns into modules +func (p *PeriodSeperatingModuleDiscoverer) Analyze(comp *wizard.CompiledIOP) { + p.modules = make(map[ModuleName][]ifaces.Column) + numRounds := comp.NumRounds() + for i := range numRounds { + for _, col := range comp.Columns.AllHandlesAtRound(i) { + module := periodLogicToDetermineModule(col) + p.modules[module] = append(p.modules[module], col) + } + } +} + +func periodLogicToDetermineModule(col ifaces.Column) ModuleName { + colName := col.GetColID() + return ModuleName(periodSeparator(string(colName))) +} + +func periodSeparator(name string) string { + // Find the index of the first occurrence of a period + index := strings.Index(name, ".") + if index == -1 { + // If no period is found, return the original string + return name + } + // Return the substring before the first period + return name[:index] +} + +// NbModules returns the number of modules +func (p *PeriodSeperatingModuleDiscoverer) NbModules() int { + return len(p.modules) +} + +// ModuleList returns the list of module names +func (p *PeriodSeperatingModuleDiscoverer) ModuleList(comp *wizard.CompiledIOP) []ModuleName { + moduleNames := make([]ModuleName, 0, len(p.modules)) + for moduleName := range p.modules { + moduleNames = append(moduleNames, moduleName) + } + return moduleNames +} + +// FindModule finds the module name for a given column +func (p *PeriodSeperatingModuleDiscoverer) FindModule(col ifaces.Column) ModuleName { + for moduleName, columns := range p.modules { + for _, c := range columns { + if c == col { + return moduleName + } + } + } + return "no column found" // Return a default or error value +} + +// QueryIsInModule checks if the given query is inside the given module +func (p *PeriodSeperatingModuleDiscoverer) QueryIsInModule(ifaces.Query, ModuleName) bool { + panic("unimplemented") + +} diff --git a/prover/protocol/query/grand_product.go b/prover/protocol/query/grand_product.go new file mode 100644 index 00000000000..9ad6e885c66 --- /dev/null +++ b/prover/protocol/query/grand_product.go @@ -0,0 +1,113 @@ +package query + +import ( + "fmt" + + "github.com/consensys/gnark/frontend" + "github.com/consensys/linea-monorepo/prover/crypto/fiatshamir" + "github.com/consensys/linea-monorepo/prover/maths/field" + "github.com/consensys/linea-monorepo/prover/protocol/column" + "github.com/consensys/linea-monorepo/prover/protocol/ifaces" + "github.com/consensys/linea-monorepo/prover/symbolic" + "github.com/consensys/linea-monorepo/prover/utils" +) + +// The GrandProduct query is obtained by process all the permuation queries specific to a target module. +// We store the randomised symbolic products of A and B of permuation queries combinedly +// into the Numerators and the Denominators of the GrandProduct query +type GrandProduct struct { + ID ifaces.QueryID + Numerators []*symbolic.Expression // stores A as multi-column + Denominators []*symbolic.Expression // stores B as multi-column + Round int +} + +type GrandProductParams struct { + Y field.Element +} + +// NewGrandProduct creates a new instance of a GrandProduct query. +// The GrandProduct query is obtained by processing all permutation queries specific to a target module. +// We store the randomized symbolic products of A and B of permutation queries combinedly +// into the Numerators and the Denominators of the GrandProduct query. +// +// Parameters: +// - round: The round number of the query. +// - id: The unique identifier for the query. +// - numerators: A slice of symbolic expressions representing the numerators of the permutation queries. +// - denominators: A slice of symbolic expressions representing the denominators of the permutation queries. +// +// Returns: +// - A pointer to a new instance of GrandProduct. +func NewGrandProduct(round int, id ifaces.QueryID, numerators, denominators []*symbolic.Expression) *GrandProduct { + return &GrandProduct{ + ID: id, + Numerators: numerators, + Denominators: denominators, + Round: round, + } +} + +// Constructor for grand product query parameters +func NewGrandProductParams(y field.Element) GrandProductParams { + return GrandProductParams{Y: y} +} + +// Name returns the unique identifier of the GrandProduct query. +func (g GrandProduct) Name() ifaces.QueryID { + return g.ID +} + +// Updates a Fiat-Shamir state +func (gp GrandProductParams) UpdateFS(fs *fiatshamir.State) { + fs.Update(gp.Y) +} + +// Check verifies the satisfaction of the GrandProduct query using the provided runtime. +// It calculates the product of numerators and denominators, and checks +// if prod(Numerators) == Prod(Denominators)*ParamY, and returns an error if the condition is not satisfied. +// +// Parameters: +// - run: The runtime interface providing access to the query parameter for query verification. +// +// Returns: +// - An error if the grand product query is not satisfied, or nil if it is satisfied. +func (g *GrandProduct) Check(run ifaces.Runtime) error { + var ( + numNumerators = len(g.Numerators) + numDenominators = len(g.Denominators) + numProd = symbolic.NewConstant(1) + denProd = symbolic.NewConstant(1) + ) + + for i := 0; i < numNumerators; i++ { + numProd = symbolic.Mul(numProd, g.Numerators[i]) + } + for j := 0; j < numDenominators; j++ { + denProd = symbolic.Mul(denProd, g.Denominators[j]) + } + params := run.GetParams(g.ID).(GrandProductParams) + numProdFrVec := column.EvalExprColumn(run, numProd.Board()).IntoRegVecSaveAlloc() + denProdFrVec := column.EvalExprColumn(run, denProd.Board()).IntoRegVecSaveAlloc() + numProdFr := numProdFrVec[0] + denProdFr := denProdFrVec[0] + if len(numProdFrVec) > 1 { + for i := 1; i < len(numProdFrVec); i++ { + numProdFr.Mul(&numProdFr, &numProdFrVec[i]) + } + } + if len(numProdFrVec) > 1 { + for j := 1; j < len(denProdFrVec); j++ { + denProdFr.Mul(&denProdFr, &denProdFrVec[j]) + } + } + if numProdFr != *denProdFr.Mul(&denProdFr, ¶ms.Y) { + return fmt.Errorf("the grand product query %v is not satisfied, numProd = %v, denProd = %v, param.Y = %v", g.ID, numProdFr, denProdFr, params.Y) + } + + return nil +} + +func (g GrandProduct) CheckGnark(api frontend.API, run ifaces.GnarkRuntime) { + utils.Panic("Unimplemented") +} diff --git a/prover/protocol/query/grand_product_test.go b/prover/protocol/query/grand_product_test.go new file mode 100644 index 00000000000..adbfcb2a0dd --- /dev/null +++ b/prover/protocol/query/grand_product_test.go @@ -0,0 +1,56 @@ +package query_test + +import ( + "testing" + + "github.com/consensys/linea-monorepo/prover/maths/common/smartvectors" + "github.com/consensys/linea-monorepo/prover/maths/field" + "github.com/consensys/linea-monorepo/prover/protocol/coin" + "github.com/consensys/linea-monorepo/prover/protocol/ifaces" + "github.com/consensys/linea-monorepo/prover/protocol/wizard" + "github.com/consensys/linea-monorepo/prover/symbolic" +) + +func TestGrandProduct(t *testing.T) { + + var ( + runS *wizard.ProverRuntime + G ifaces.Query + ) + + define := func(builder *wizard.Builder) { + A0 := builder.RegisterCommit("A0", 4) + B0 := builder.RegisterCommit("B0", 4) + C0 := builder.RegisterCommit("C0", 4) + alpha := builder.RegisterRandomCoin("ALPHA", coin.Field) + A := []*symbolic.Expression{ + symbolic.Add(A0, symbolic.Mul(B0, alpha)), + symbolic.Add(B0, symbolic.Mul(C0, alpha)), + symbolic.Add(C0, symbolic.Mul(A0, alpha)), + } + B := []*symbolic.Expression{ + symbolic.Add(C0, symbolic.Mul(A0, alpha)), + symbolic.Add(B0, symbolic.Mul(C0, alpha)), + symbolic.Add(A0, symbolic.Mul(B0, alpha)), + } + G = builder.CompiledIOP.InsertGrandProduct(0, "G", A, B) + } + + prove := func(run *wizard.ProverRuntime) { + runS = run + run.AssignColumn("A0", smartvectors.ForTest(1, 2, 3, 4)) + run.AssignColumn("B0", smartvectors.ForTest(1, 2, 3, 4)) + run.AssignColumn("C0", smartvectors.ForTest(1, 2, 3, 4)) + runS.AssignGrandProduct("G", field.One()) + } + + var ( + comp = wizard.Compile(define) + _ = wizard.Prove(comp, prove) + errG = G.Check(runS) + ) + + if errG != nil { + t.Fatalf("error verifying the grand product: %v", errG.Error()) + } +} diff --git a/prover/protocol/wizard/compiled.go b/prover/protocol/wizard/compiled.go index 6cf5c37a3cb..ff65bd675ba 100644 --- a/prover/protocol/wizard/compiled.go +++ b/prover/protocol/wizard/compiled.go @@ -629,3 +629,12 @@ func (c *CompiledIOP) RegisterVerifierAction(round int, action VerifierAction) { // switch. c.InsertVerifier(round, action.Run, action.RunGnark) } + +// Register a GrandProduct query +func (c *CompiledIOP) InsertGrandProduct(round int, id ifaces.QueryID, numerators, denominators []*symbolic.Expression) *query.GrandProduct { + c.assertConsistentRound(round) + q := query.NewGrandProduct(round, id, numerators, denominators) + // Finally registers the query + c.QueriesParams.AddToRound(round, q.Name(), q) + return q +} diff --git a/prover/protocol/wizard/prover.go b/prover/protocol/wizard/prover.go index caa4df4aa12..dbbbeddc3b9 100644 --- a/prover/protocol/wizard/prover.go +++ b/prover/protocol/wizard/prover.go @@ -696,3 +696,22 @@ func (run *ProverRuntime) GetLocalPointEvalParams(name ifaces.QueryID) query.Loc func (run *ProverRuntime) GetParams(name ifaces.QueryID) ifaces.QueryParams { return run.QueriesParams.MustGet(name) } + +// AssignGrandProduct assigns the value \prod(num)/\prod(den) +// The function will panic if: +// - the parameters were already assigned +// - the specified query is not registered +// - the assignment round is incorrect +func (run *ProverRuntime) AssignGrandProduct(name ifaces.QueryID, y field.Element) { + + // Global prover locks for accessing the maps + run.lock.Lock() + defer run.lock.Unlock() + + // Make sure, it is done at the right round + run.Spec.QueriesParams.MustBeInRound(run.currRound, name) + + // Adds it to the assignments + params := query.NewGrandProductParams(y) + run.QueriesParams.InsertNew(name, params) +}