diff --git a/frontend/cs/scs/builder.go b/frontend/cs/scs/builder.go index b789289b1b..3a2195afc2 100644 --- a/frontend/cs/scs/builder.go +++ b/frontend/cs/scs/builder.go @@ -701,12 +701,14 @@ func (builder *builder) ToCanonicalVariable(v frontend.Variable) frontend.Canoni func (builder *builder) GetWireConstraints(wires []frontend.Variable, addMissing bool) ([][2]int, error) { // construct a lookup table table for later quick access when iterating over instructions lookup := make(map[int]struct{}) - for _, w := range wires { + wireTerms := make([]expr.Term, len(wires)) // stores the term of each wire. + for i, w := range wires { ww, ok := w.(expr.Term) if !ok { panic("input wire is not a Term") } lookup[ww.WireID()] = struct{}{} + wireTerms[i] = ww } nbPub := builder.cs.GetNbPublicVariables() res := make([][2]int, 0, len(wires)) @@ -731,7 +733,15 @@ func (builder *builder) GetWireConstraints(wires []frontend.Variable, addMissing } if addMissing { nbWitnessWires := builder.cs.GetNbPublicVariables() + builder.cs.GetNbSecretVariables() - for k := range lookup { + // It is important to iterate over wireTerms here as doing it over [lookup] + // would result in a non-deterministic order of constraints. + for _, ww := range wireTerms { + + if _, ok := lookup[ww.WireID()]; !ok { + continue + } + + k := ww.WireID() if k >= nbWitnessWires { return nil, fmt.Errorf("addMissing is true, but wire %d is not a witness", k) } @@ -741,6 +751,7 @@ func (builder *builder) GetWireConstraints(wires []frontend.Variable, addMissing QL: constraint.CoeffIdOne, QO: constraint.CoeffIdMinusOne, }, builder.genericGate) + res = append(res, [2]int{nbPub + constraintIdx, 0}) delete(lookup, k) } @@ -777,7 +788,7 @@ func (builder *builder) GetWireConstraints(wires []frontend.Variable, addMissing // // ``` // -// GetWiresConstraintsNoDuplicate([]frontend.Variable{a, a, b, a, c}) => wa, wb, wc +// GetWiresConstraints([]frontend.Variable{a, a, b, a, c}) => wa, wb, wc // // ``` func (builder *builder) GetWiresConstraintExact(wires []frontend.Variable, addMissing bool) ([][2]int, error) { @@ -787,6 +798,9 @@ func (builder *builder) GetWiresConstraintExact(wires []frontend.Variable, addMi // canonical variables: therefore not to constants and not too terms // with a coeff different from 1. This may add constraints but has the // benefit of making it simpler to read the LRO values. + // + // wireIDsSetOrdered stores the same values as wireIDsSet but in order + // of insertion. This is necessary to ensure var ( wireIDsSet = make(map[int]struct{}) wireTerms = make([]expr.Term, len(wires)) @@ -854,15 +868,24 @@ func (builder *builder) GetWiresConstraintExact(wires []frontend.Variable, addMi } if addMissing { - for k := range wireIDsSet { + for _, ww := range wireTerms { + + // The above loop removes the wireIDs from the set when they are + // found. This means that a wireID is missing if and only if it + // is still in [wireIDsSet]. + if _, isIndeedMissing := wireIDsSet[ww.VID]; !isIndeedMissing { + continue + } + constraintIdx := builder.cs.AddSparseR1C(constraint.SparseR1C{ - XA: uint32(k), - XC: uint32(k), + XA: uint32(ww.VID), + XC: uint32(ww.VID), QL: constraint.CoeffIdOne, QO: constraint.CoeffIdMinusOne, }, builder.genericGate) - foundWireIDPosition[k] = [2]int{nbPub + constraintIdx, 0} - delete(wireIDsSet, k) + + foundWireIDPosition[ww.VID] = [2]int{nbPub + constraintIdx, 0} + delete(wireIDsSet, ww.VID) } }