github.com/consensys/gnark@v0.11.0/constraint/gkr.go (about) 1 package constraint 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/consensys/gnark-crypto/utils" 8 "github.com/consensys/gnark/constraint/solver" 9 "github.com/consensys/gnark/internal/algo_utils" 10 ) 11 12 type GkrVariable int // Just an alias to hide implementation details. May be more trouble than worth 13 14 type InputDependency struct { 15 OutputWire int 16 OutputInstance int 17 InputInstance int 18 } 19 20 type GkrWire struct { 21 Gate string // TODO: Change to description 22 Inputs []int 23 Dependencies []InputDependency // nil for input wires 24 NbUniqueOutputs int 25 } 26 27 type GkrCircuit []GkrWire 28 29 type GkrInfo struct { 30 Circuit GkrCircuit 31 MaxNIns int 32 NbInstances int 33 HashName string 34 SolveHintID solver.HintID 35 ProveHintID solver.HintID 36 } 37 38 type GkrPermutations struct { 39 SortedInstances []int 40 SortedWires []int 41 InstancesPermutation []int 42 WiresPermutation []int 43 } 44 45 func (w GkrWire) IsInput() bool { 46 return len(w.Inputs) == 0 47 } 48 49 func (w GkrWire) IsOutput() bool { 50 return w.NbUniqueOutputs == 0 51 } 52 53 // AssignmentOffsets returns the index of the first value assigned to a wire TODO: Explain clearly 54 func (d *GkrInfo) AssignmentOffsets() []int { 55 c := d.Circuit 56 res := make([]int, len(c)+1) 57 for i := range c { 58 nbExplicitAssignments := 0 59 if c[i].IsInput() { 60 nbExplicitAssignments = d.NbInstances - len(c[i].Dependencies) 61 } 62 res[i+1] = res[i] + nbExplicitAssignments 63 } 64 return res 65 } 66 67 func (d *GkrInfo) NewInputVariable() GkrVariable { 68 i := len(d.Circuit) 69 d.Circuit = append(d.Circuit, GkrWire{}) 70 return GkrVariable(i) 71 } 72 73 // Compile sorts the circuit wires, their dependencies and the instances 74 func (d *GkrInfo) Compile(nbInstances int) (GkrPermutations, error) { 75 76 var p GkrPermutations 77 d.NbInstances = nbInstances 78 // sort the instances to decide the order in which they are to be solved 79 instanceDeps := make([][]int, nbInstances) 80 for i := range d.Circuit { 81 for _, dep := range d.Circuit[i].Dependencies { 82 instanceDeps[dep.InputInstance] = append(instanceDeps[dep.InputInstance], dep.OutputInstance) 83 } 84 } 85 86 p.SortedInstances, _ = algo_utils.TopologicalSort(instanceDeps) 87 p.InstancesPermutation = algo_utils.InvertPermutation(p.SortedInstances) 88 89 // this whole circuit sorting is a bit of a charade. if things are built using an api, there's no way it could NOT already be topologically sorted 90 // worth keeping for future-proofing? 91 92 inputs := algo_utils.Map(d.Circuit, func(w GkrWire) []int { 93 return w.Inputs 94 }) 95 96 var uniqueOuts [][]int 97 p.SortedWires, uniqueOuts = algo_utils.TopologicalSort(inputs) 98 p.WiresPermutation = algo_utils.InvertPermutation(p.SortedWires) 99 wirePermutationAt := algo_utils.SliceAt(p.WiresPermutation) 100 sorted := make([]GkrWire, len(d.Circuit)) // TODO: Directly manipulate d.Circuit instead 101 for newI, oldI := range p.SortedWires { 102 oldW := d.Circuit[oldI] 103 104 if !oldW.IsInput() { 105 d.MaxNIns = utils.Max(d.MaxNIns, len(oldW.Inputs)) 106 } 107 108 for j := range oldW.Dependencies { 109 dep := &oldW.Dependencies[j] 110 dep.OutputWire = p.WiresPermutation[dep.OutputWire] 111 dep.InputInstance = p.InstancesPermutation[dep.InputInstance] 112 dep.OutputInstance = p.InstancesPermutation[dep.OutputInstance] 113 } 114 sort.Slice(oldW.Dependencies, func(i, j int) bool { 115 return oldW.Dependencies[i].InputInstance < oldW.Dependencies[j].InputInstance 116 }) 117 for i := 1; i < len(oldW.Dependencies); i++ { 118 if oldW.Dependencies[i].InputInstance == oldW.Dependencies[i-1].InputInstance { 119 return p, fmt.Errorf("an input wire can only have one dependency per instance") 120 } 121 } // TODO: Check that dependencies and explicit assignments cover all instances 122 123 sorted[newI] = GkrWire{ 124 Gate: oldW.Gate, 125 Inputs: algo_utils.Map(oldW.Inputs, wirePermutationAt), 126 Dependencies: oldW.Dependencies, 127 NbUniqueOutputs: len(uniqueOuts[oldI]), 128 } 129 } 130 d.Circuit = sorted 131 132 return p, nil 133 } 134 135 func (d *GkrInfo) Is() bool { 136 return d.Circuit != nil 137 } 138 139 // Chunks returns intervals of instances that are independent of each other and can be solved in parallel 140 func (c GkrCircuit) Chunks(nbInstances int) []int { 141 res := make([]int, 0, 1) 142 lastSeenDependencyI := make([]int, len(c)) 143 144 for start, end := 0, 0; start != nbInstances; start = end { 145 end = nbInstances 146 endWireI := -1 147 for wI, w := range c { 148 if wDepI := lastSeenDependencyI[wI]; wDepI < len(w.Dependencies) && w.Dependencies[wDepI].InputInstance < end { 149 end = w.Dependencies[wDepI].InputInstance 150 endWireI = wI 151 } 152 } 153 if endWireI != -1 { 154 lastSeenDependencyI[endWireI]++ 155 } 156 res = append(res, end) 157 } 158 return res 159 }