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  }