diff --git a/debug_test.go b/debug_test.go index 06e2b6ae0c..3569e3f493 100644 --- a/debug_test.go +++ b/debug_test.go @@ -83,7 +83,7 @@ func TestTraceDivBy0(t *testing.T) { { _, err := getGroth16Trace(&circuit, &witness) assert.Error(err) - assert.Contains(err.Error(), "constraint is not satisfied: [div] 2/(-2 + 2) == ") + assert.Contains(err.Error(), "constraint #0 is not satisfied: [div] 2/(-2 + 2) == ") assert.Contains(err.Error(), "(*divBy0Trace).Define") assert.Contains(err.Error(), "debug_test.go:") } @@ -91,7 +91,7 @@ func TestTraceDivBy0(t *testing.T) { { _, err := getPlonkTrace(&circuit, &witness) assert.Error(err) - assert.Contains(err.Error(), "constraint is not satisfied: [inverse] 1/0 < ∞") + assert.Contains(err.Error(), "constraint #1 is not satisfied: [inverse] 1/0 < ∞") assert.Contains(err.Error(), "(*divBy0Trace).Define") assert.Contains(err.Error(), "debug_test.go:") } @@ -120,7 +120,7 @@ func TestTraceNotEqual(t *testing.T) { { _, err := getGroth16Trace(&circuit, &witness) assert.Error(err) - assert.Contains(err.Error(), "constraint is not satisfied: [assertIsEqual] 1 == (24 + 42)") + assert.Contains(err.Error(), "constraint #0 is not satisfied: [assertIsEqual] 1 == (24 + 42)") assert.Contains(err.Error(), "(*notEqualTrace).Define") assert.Contains(err.Error(), "debug_test.go:") } @@ -128,7 +128,7 @@ func TestTraceNotEqual(t *testing.T) { { _, err := getPlonkTrace(&circuit, &witness) assert.Error(err) - assert.Contains(err.Error(), "constraint is not satisfied: [assertIsEqual] 1 + -66 == 0") + assert.Contains(err.Error(), "constraint #1 is not satisfied: [assertIsEqual] 1 + -66 == 0") assert.Contains(err.Error(), "(*notEqualTrace).Define") assert.Contains(err.Error(), "debug_test.go:") } @@ -157,7 +157,7 @@ func TestTraceNotBoolean(t *testing.T) { { _, err := getGroth16Trace(&circuit, &witness) assert.Error(err) - assert.Contains(err.Error(), "constraint is not satisfied: [assertIsBoolean] (24 + 42) == (0|1)") + assert.Contains(err.Error(), "constraint #0 is not satisfied: [assertIsBoolean] (24 + 42) == (0|1)") assert.Contains(err.Error(), "(*notBooleanTrace).Define") assert.Contains(err.Error(), "debug_test.go:") } @@ -165,7 +165,7 @@ func TestTraceNotBoolean(t *testing.T) { { _, err := getPlonkTrace(&circuit, &witness) assert.Error(err) - assert.Contains(err.Error(), "constraint is not satisfied: [assertIsBoolean] 66 == (0|1)") + assert.Contains(err.Error(), "constraint #1 is not satisfied: [assertIsBoolean] 66 == (0|1)") assert.Contains(err.Error(), "(*notBooleanTrace).Define") assert.Contains(err.Error(), "debug_test.go:") } diff --git a/frontend/ccs.go b/frontend/ccs.go index e867502a3e..d3659cc5c5 100644 --- a/frontend/ccs.go +++ b/frontend/ccs.go @@ -41,9 +41,6 @@ type CompiledConstraintSystem interface { CurveID() ecc.ID FrSize() int - // ToHTML generates a human readable representation of the constraint system - ToHTML(w io.Writer) error - // GetCounters return the collected constraint counters, if any GetCounters() []compiled.Counter diff --git a/internal/backend/bls12-377/cs/r1cs.go b/internal/backend/bls12-377/cs/r1cs.go index 00fe977d7d..b2c0f0ee04 100644 --- a/internal/backend/bls12-377/cs/r1cs.go +++ b/internal/backend/bls12-377/cs/r1cs.go @@ -32,7 +32,6 @@ import ( "github.com/consensys/gnark/internal/backend/ioutils" "github.com/consensys/gnark-crypto/ecc" - "text/template" "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" @@ -119,11 +118,11 @@ func (cs *R1CS) Solve(witness, a, b, c []fr.Element, opt backend.ProverConfig) ( // ensure a[i] * b[i] == c[i] check.Mul(&a[i], &b[i]) if !check.Equal(&c[i]) { + errMsg := fmt.Sprintf("%s ⋅ %s != %s", a[i].String(), b[i].String(), c[i].String()) if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -289,32 +288,10 @@ func (cs *R1CS) solveConstraint(r compiled.R1C, solution *solution) error { return nil } -// TODO @gbotrel clean logs and html see https://github.com/ConsenSys/gnark/issues/140 - -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *R1CS) ToHTML(w io.Writer) error { - t, err := template.New("cs.html").Funcs(template.FuncMap{ - "toHTML": toHTML, - "add": add, - "sub": sub, - }).Parse(compiled.R1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - -func add(a, b int) int { - return a + b -} - -func sub(a, b int) int { - return a - b -} - +// GetConstraints return a list of constraint formatted as L⋅R == O +// such that [0] -> L, [1] -> R, [2] -> O func (cs *R1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string, len(cs.Constraints)) for _, c := range cs.Constraints { // for each constraint, we build a string representation of it's L, R and O part // if we are worried about perf for large cs, we could do a string builder + csv format. @@ -350,7 +327,7 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { return } else { sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.WriteString("⋅") } vID := t.WireID() visibility := t.VariableVisibility() @@ -375,59 +352,6 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { } } -func toHTML(l compiled.Variable, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - for i := 0; i < len(l.LinExp); i++ { - termToHTML(l.LinExp[i], &sbb, coeffs, MHints, false) - if i+1 < len(l.LinExp) { - sbb.WriteString(" + ") - } - } - return sbb.String() -} - -func termToHTML(t compiled.Term, sbb *strings.Builder, coeffs []fr.Element, MHints map[int]compiled.Hint, offset bool) { - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteString("-") - } else if tID == compiled.CoeffIdZero { - sbb.WriteString("0") - return - } else { - sbb.WriteString("") - sbb.WriteString(coeffs[tID].String()) - sbb.WriteString("*") - } - - vID := t.WireID() - class := "" - switch t.VariableVisibility() { - case schema.Internal: - class = "internal" - if _, ok := MHints[vID]; ok { - class = "hint" - } - case schema.Public: - class = "public" - case schema.Secret: - class = "secret" - case schema.Virtual: - class = "virtual" - case schema.Unset: - class = "unset" - default: - panic("not implemented") - } - if offset { - vID++ // for sparse R1CS, we offset to have same variable numbers as in R1CS - } - sbb.WriteString(fmt.Sprintf("v%d", class, vID)) - -} - // GetNbCoefficients return the number of unique coefficients needed in the R1CS func (cs *R1CS) GetNbCoefficients() int { return len(cs.Coefficients) diff --git a/internal/backend/bls12-377/cs/r1cs_sparse.go b/internal/backend/bls12-377/cs/r1cs_sparse.go index c8b89de4bb..57b0a41588 100644 --- a/internal/backend/bls12-377/cs/r1cs_sparse.go +++ b/internal/backend/bls12-377/cs/r1cs_sparse.go @@ -24,7 +24,6 @@ import ( "math/big" "os" "strings" - "text/template" "github.com/consensys/gnark/backend" "github.com/consensys/gnark/backend/witness" @@ -110,11 +109,11 @@ func (cs *SparseR1CS) Solve(witness []fr.Element, opt backend.ProverConfig) ([]f return solution.values, fmt.Errorf("constraint %d: %w", i, err) } if err := cs.checkConstraint(cs.Constraints[i], &solution); err != nil { + errMsg := err.Error() if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -255,37 +254,81 @@ func (cs *SparseR1CS) IsSolved(witness *witness.Witness, opts ...backend.ProverO return err } +// GetConstraints return a list of constraint formatted as in the paper +// https://eprint.iacr.org/2019/953.pdf section 6 such that +// qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC == 0 +// each constraint is thus decomposed in [5]string with +// [0] = qL⋅xa +// [1] = qR⋅xb +// [2] = qO⋅xc +// [3] = qM⋅(xaxb) +// [4] = qC func (cs *SparseR1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string, 0, len(cs.Constraints)) for _, c := range cs.Constraints { - // if we are worried about perf for large cs, we could do a string builder + csv format. - var line [6]string - line[0] = cs.termToString(c.L) - line[1] = cs.termToString(c.R) - line[2] = cs.termToString(c.M[0]) - line[3] = cs.termToString(c.M[1]) - line[4] = cs.termToString(c.O) - line[5] = cs.Coefficients[c.K].String() - r = append(r, line[:]) + fc := cs.formatConstraint(c) + r = append(r, fc[:]) } return r } -func (cs *SparseR1CS) termToString(t compiled.Term) string { +// r[0] = qL⋅xa +// r[1] = qR⋅xb +// r[2] = qO⋅xc +// r[3] = qM⋅(xaxb) +// r[4] = qC +func (cs *SparseR1CS) formatConstraint(c compiled.SparseR1C) (r [5]string) { + isZeroM := (c.M[0].CoeffID() == compiled.CoeffIdZero) && (c.M[1].CoeffID() == compiled.CoeffIdZero) + var sbb strings.Builder - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteByte('-') - } else if tID == compiled.CoeffIdZero { - sbb.WriteByte('0') - return sbb.String() + cs.termToString(c.L, &sbb, false) + r[0] = sbb.String() + + sbb.Reset() + cs.termToString(c.R, &sbb, false) + r[1] = sbb.String() + + sbb.Reset() + cs.termToString(c.O, &sbb, false) + r[2] = sbb.String() + + if isZeroM { + r[3] = "0" } else { - sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.Reset() + sbb.WriteString(cs.Coefficients[c.M[0].CoeffID()].String()) + sbb.WriteString("⋅") + sbb.WriteByte('(') + cs.termToString(c.M[0], &sbb, true) + sbb.WriteString(" × ") + cs.termToString(c.M[1], &sbb, true) + sbb.WriteByte(')') + r[3] = sbb.String() + } + + r[4] = cs.Coefficients[c.K].String() + + return +} + +func (cs *SparseR1CS) termToString(t compiled.Term, sbb *strings.Builder, vOnly bool) { + if !vOnly { + tID := t.CoeffID() + if tID == compiled.CoeffIdOne { + // do nothing, just print the variable + sbb.WriteString("1") + } else if tID == compiled.CoeffIdMinusOne { + // print neg sign + sbb.WriteString("-1") + } else if tID == compiled.CoeffIdZero { + sbb.WriteByte('0') + return + } else { + sbb.WriteString(cs.Coefficients[tID].String()) + } + sbb.WriteString("⋅") } + vID := t.WireID() visibility := t.VariableVisibility() @@ -303,7 +346,6 @@ func (cs *SparseR1CS) termToString(t compiled.Term) string { default: sbb.WriteString("") } - return sbb.String() } // checkConstraint verifies that the constraint holds @@ -318,12 +360,12 @@ func (cs *SparseR1CS) checkConstraint(c compiled.SparseR1C, solution *solution) var t fr.Element t.Mul(&m0, &m1).Add(&t, &l).Add(&t, &r).Add(&t, &o).Add(&t, &cs.Coefficients[c.K]) if !t.IsZero() { - return fmt.Errorf("%w\n%s + %s + (%s * %s) + %s + %s != 0", ErrUnsatisfiedConstraint, + return fmt.Errorf("qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC != 0 → %s + %s + %s + (%s × %s) + %s != 0", l.String(), r.String(), + o.String(), m0.String(), m1.String(), - o.String(), cs.Coefficients[c.K].String(), ) } @@ -331,39 +373,6 @@ func (cs *SparseR1CS) checkConstraint(c compiled.SparseR1C, solution *solution) } -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *SparseR1CS) ToHTML(w io.Writer) error { - t, err := template.New("scs.html").Funcs(template.FuncMap{ - "toHTML": toHTMLTerm, - "toHTMLCoeff": toHTMLCoeff, - "add": add, - "sub": sub, - }).Parse(compiled.SparseR1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - -func toHTMLTerm(t compiled.Term, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - termToHTML(t, &sbb, coeffs, MHints, true) - return sbb.String() -} - -func toHTMLCoeff(cID int, coeffs []fr.Element) string { - if cID == compiled.CoeffIdMinusOne { - // print neg sign - return "-1" - } - var sbb strings.Builder - sbb.WriteString("") - sbb.WriteString(coeffs[cID].String()) - sbb.WriteString("") - return sbb.String() -} - // FrSize return fr.Limbs * 8, size in byte of a fr element func (cs *SparseR1CS) FrSize() int { return fr.Limbs * 8 diff --git a/internal/backend/bls12-377/cs/solution.go b/internal/backend/bls12-377/cs/solution.go index cdef436358..99ddb85aaf 100644 --- a/internal/backend/bls12-377/cs/solution.go +++ b/internal/backend/bls12-377/cs/solution.go @@ -33,9 +33,6 @@ import ( curve "github.com/consensys/gnark-crypto/ecc/bls12-377" ) -// ErrUnsatisfiedConstraint can be generated when solving a R1CS -var ErrUnsatisfiedConstraint = errors.New("constraint is not satisfied") - // solution represents elements needed to compute // a solution to a R1CS or SparseR1CS type solution struct { diff --git a/internal/backend/bls12-381/cs/r1cs.go b/internal/backend/bls12-381/cs/r1cs.go index 66df1f795f..fbecb56a02 100644 --- a/internal/backend/bls12-381/cs/r1cs.go +++ b/internal/backend/bls12-381/cs/r1cs.go @@ -32,7 +32,6 @@ import ( "github.com/consensys/gnark/internal/backend/ioutils" "github.com/consensys/gnark-crypto/ecc" - "text/template" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" @@ -119,11 +118,11 @@ func (cs *R1CS) Solve(witness, a, b, c []fr.Element, opt backend.ProverConfig) ( // ensure a[i] * b[i] == c[i] check.Mul(&a[i], &b[i]) if !check.Equal(&c[i]) { + errMsg := fmt.Sprintf("%s ⋅ %s != %s", a[i].String(), b[i].String(), c[i].String()) if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -289,32 +288,10 @@ func (cs *R1CS) solveConstraint(r compiled.R1C, solution *solution) error { return nil } -// TODO @gbotrel clean logs and html see https://github.com/ConsenSys/gnark/issues/140 - -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *R1CS) ToHTML(w io.Writer) error { - t, err := template.New("cs.html").Funcs(template.FuncMap{ - "toHTML": toHTML, - "add": add, - "sub": sub, - }).Parse(compiled.R1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - -func add(a, b int) int { - return a + b -} - -func sub(a, b int) int { - return a - b -} - +// GetConstraints return a list of constraint formatted as L⋅R == O +// such that [0] -> L, [1] -> R, [2] -> O func (cs *R1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string, len(cs.Constraints)) for _, c := range cs.Constraints { // for each constraint, we build a string representation of it's L, R and O part // if we are worried about perf for large cs, we could do a string builder + csv format. @@ -350,7 +327,7 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { return } else { sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.WriteString("⋅") } vID := t.WireID() visibility := t.VariableVisibility() @@ -375,59 +352,6 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { } } -func toHTML(l compiled.Variable, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - for i := 0; i < len(l.LinExp); i++ { - termToHTML(l.LinExp[i], &sbb, coeffs, MHints, false) - if i+1 < len(l.LinExp) { - sbb.WriteString(" + ") - } - } - return sbb.String() -} - -func termToHTML(t compiled.Term, sbb *strings.Builder, coeffs []fr.Element, MHints map[int]compiled.Hint, offset bool) { - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteString("-") - } else if tID == compiled.CoeffIdZero { - sbb.WriteString("0") - return - } else { - sbb.WriteString("") - sbb.WriteString(coeffs[tID].String()) - sbb.WriteString("*") - } - - vID := t.WireID() - class := "" - switch t.VariableVisibility() { - case schema.Internal: - class = "internal" - if _, ok := MHints[vID]; ok { - class = "hint" - } - case schema.Public: - class = "public" - case schema.Secret: - class = "secret" - case schema.Virtual: - class = "virtual" - case schema.Unset: - class = "unset" - default: - panic("not implemented") - } - if offset { - vID++ // for sparse R1CS, we offset to have same variable numbers as in R1CS - } - sbb.WriteString(fmt.Sprintf("v%d", class, vID)) - -} - // GetNbCoefficients return the number of unique coefficients needed in the R1CS func (cs *R1CS) GetNbCoefficients() int { return len(cs.Coefficients) diff --git a/internal/backend/bls12-381/cs/r1cs_sparse.go b/internal/backend/bls12-381/cs/r1cs_sparse.go index b528dc3a21..850c85b0fb 100644 --- a/internal/backend/bls12-381/cs/r1cs_sparse.go +++ b/internal/backend/bls12-381/cs/r1cs_sparse.go @@ -24,7 +24,6 @@ import ( "math/big" "os" "strings" - "text/template" "github.com/consensys/gnark/backend" "github.com/consensys/gnark/backend/witness" @@ -110,11 +109,11 @@ func (cs *SparseR1CS) Solve(witness []fr.Element, opt backend.ProverConfig) ([]f return solution.values, fmt.Errorf("constraint %d: %w", i, err) } if err := cs.checkConstraint(cs.Constraints[i], &solution); err != nil { + errMsg := err.Error() if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -255,37 +254,81 @@ func (cs *SparseR1CS) IsSolved(witness *witness.Witness, opts ...backend.ProverO return err } +// GetConstraints return a list of constraint formatted as in the paper +// https://eprint.iacr.org/2019/953.pdf section 6 such that +// qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC == 0 +// each constraint is thus decomposed in [5]string with +// [0] = qL⋅xa +// [1] = qR⋅xb +// [2] = qO⋅xc +// [3] = qM⋅(xaxb) +// [4] = qC func (cs *SparseR1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string, 0, len(cs.Constraints)) for _, c := range cs.Constraints { - // if we are worried about perf for large cs, we could do a string builder + csv format. - var line [6]string - line[0] = cs.termToString(c.L) - line[1] = cs.termToString(c.R) - line[2] = cs.termToString(c.M[0]) - line[3] = cs.termToString(c.M[1]) - line[4] = cs.termToString(c.O) - line[5] = cs.Coefficients[c.K].String() - r = append(r, line[:]) + fc := cs.formatConstraint(c) + r = append(r, fc[:]) } return r } -func (cs *SparseR1CS) termToString(t compiled.Term) string { +// r[0] = qL⋅xa +// r[1] = qR⋅xb +// r[2] = qO⋅xc +// r[3] = qM⋅(xaxb) +// r[4] = qC +func (cs *SparseR1CS) formatConstraint(c compiled.SparseR1C) (r [5]string) { + isZeroM := (c.M[0].CoeffID() == compiled.CoeffIdZero) && (c.M[1].CoeffID() == compiled.CoeffIdZero) + var sbb strings.Builder - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteByte('-') - } else if tID == compiled.CoeffIdZero { - sbb.WriteByte('0') - return sbb.String() + cs.termToString(c.L, &sbb, false) + r[0] = sbb.String() + + sbb.Reset() + cs.termToString(c.R, &sbb, false) + r[1] = sbb.String() + + sbb.Reset() + cs.termToString(c.O, &sbb, false) + r[2] = sbb.String() + + if isZeroM { + r[3] = "0" } else { - sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.Reset() + sbb.WriteString(cs.Coefficients[c.M[0].CoeffID()].String()) + sbb.WriteString("⋅") + sbb.WriteByte('(') + cs.termToString(c.M[0], &sbb, true) + sbb.WriteString(" × ") + cs.termToString(c.M[1], &sbb, true) + sbb.WriteByte(')') + r[3] = sbb.String() + } + + r[4] = cs.Coefficients[c.K].String() + + return +} + +func (cs *SparseR1CS) termToString(t compiled.Term, sbb *strings.Builder, vOnly bool) { + if !vOnly { + tID := t.CoeffID() + if tID == compiled.CoeffIdOne { + // do nothing, just print the variable + sbb.WriteString("1") + } else if tID == compiled.CoeffIdMinusOne { + // print neg sign + sbb.WriteString("-1") + } else if tID == compiled.CoeffIdZero { + sbb.WriteByte('0') + return + } else { + sbb.WriteString(cs.Coefficients[tID].String()) + } + sbb.WriteString("⋅") } + vID := t.WireID() visibility := t.VariableVisibility() @@ -303,7 +346,6 @@ func (cs *SparseR1CS) termToString(t compiled.Term) string { default: sbb.WriteString("") } - return sbb.String() } // checkConstraint verifies that the constraint holds @@ -318,12 +360,12 @@ func (cs *SparseR1CS) checkConstraint(c compiled.SparseR1C, solution *solution) var t fr.Element t.Mul(&m0, &m1).Add(&t, &l).Add(&t, &r).Add(&t, &o).Add(&t, &cs.Coefficients[c.K]) if !t.IsZero() { - return fmt.Errorf("%w\n%s + %s + (%s * %s) + %s + %s != 0", ErrUnsatisfiedConstraint, + return fmt.Errorf("qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC != 0 → %s + %s + %s + (%s × %s) + %s != 0", l.String(), r.String(), + o.String(), m0.String(), m1.String(), - o.String(), cs.Coefficients[c.K].String(), ) } @@ -331,39 +373,6 @@ func (cs *SparseR1CS) checkConstraint(c compiled.SparseR1C, solution *solution) } -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *SparseR1CS) ToHTML(w io.Writer) error { - t, err := template.New("scs.html").Funcs(template.FuncMap{ - "toHTML": toHTMLTerm, - "toHTMLCoeff": toHTMLCoeff, - "add": add, - "sub": sub, - }).Parse(compiled.SparseR1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - -func toHTMLTerm(t compiled.Term, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - termToHTML(t, &sbb, coeffs, MHints, true) - return sbb.String() -} - -func toHTMLCoeff(cID int, coeffs []fr.Element) string { - if cID == compiled.CoeffIdMinusOne { - // print neg sign - return "-1" - } - var sbb strings.Builder - sbb.WriteString("") - sbb.WriteString(coeffs[cID].String()) - sbb.WriteString("") - return sbb.String() -} - // FrSize return fr.Limbs * 8, size in byte of a fr element func (cs *SparseR1CS) FrSize() int { return fr.Limbs * 8 diff --git a/internal/backend/bls12-381/cs/solution.go b/internal/backend/bls12-381/cs/solution.go index 72c803dae3..255c7721d9 100644 --- a/internal/backend/bls12-381/cs/solution.go +++ b/internal/backend/bls12-381/cs/solution.go @@ -33,9 +33,6 @@ import ( curve "github.com/consensys/gnark-crypto/ecc/bls12-381" ) -// ErrUnsatisfiedConstraint can be generated when solving a R1CS -var ErrUnsatisfiedConstraint = errors.New("constraint is not satisfied") - // solution represents elements needed to compute // a solution to a R1CS or SparseR1CS type solution struct { diff --git a/internal/backend/bls24-315/cs/r1cs.go b/internal/backend/bls24-315/cs/r1cs.go index 7bc054c6ce..aa32a40d8f 100644 --- a/internal/backend/bls24-315/cs/r1cs.go +++ b/internal/backend/bls24-315/cs/r1cs.go @@ -32,7 +32,6 @@ import ( "github.com/consensys/gnark/internal/backend/ioutils" "github.com/consensys/gnark-crypto/ecc" - "text/template" "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" @@ -119,11 +118,11 @@ func (cs *R1CS) Solve(witness, a, b, c []fr.Element, opt backend.ProverConfig) ( // ensure a[i] * b[i] == c[i] check.Mul(&a[i], &b[i]) if !check.Equal(&c[i]) { + errMsg := fmt.Sprintf("%s ⋅ %s != %s", a[i].String(), b[i].String(), c[i].String()) if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -289,32 +288,10 @@ func (cs *R1CS) solveConstraint(r compiled.R1C, solution *solution) error { return nil } -// TODO @gbotrel clean logs and html see https://github.com/ConsenSys/gnark/issues/140 - -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *R1CS) ToHTML(w io.Writer) error { - t, err := template.New("cs.html").Funcs(template.FuncMap{ - "toHTML": toHTML, - "add": add, - "sub": sub, - }).Parse(compiled.R1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - -func add(a, b int) int { - return a + b -} - -func sub(a, b int) int { - return a - b -} - +// GetConstraints return a list of constraint formatted as L⋅R == O +// such that [0] -> L, [1] -> R, [2] -> O func (cs *R1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string, len(cs.Constraints)) for _, c := range cs.Constraints { // for each constraint, we build a string representation of it's L, R and O part // if we are worried about perf for large cs, we could do a string builder + csv format. @@ -350,7 +327,7 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { return } else { sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.WriteString("⋅") } vID := t.WireID() visibility := t.VariableVisibility() @@ -375,59 +352,6 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { } } -func toHTML(l compiled.Variable, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - for i := 0; i < len(l.LinExp); i++ { - termToHTML(l.LinExp[i], &sbb, coeffs, MHints, false) - if i+1 < len(l.LinExp) { - sbb.WriteString(" + ") - } - } - return sbb.String() -} - -func termToHTML(t compiled.Term, sbb *strings.Builder, coeffs []fr.Element, MHints map[int]compiled.Hint, offset bool) { - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteString("-") - } else if tID == compiled.CoeffIdZero { - sbb.WriteString("0") - return - } else { - sbb.WriteString("") - sbb.WriteString(coeffs[tID].String()) - sbb.WriteString("*") - } - - vID := t.WireID() - class := "" - switch t.VariableVisibility() { - case schema.Internal: - class = "internal" - if _, ok := MHints[vID]; ok { - class = "hint" - } - case schema.Public: - class = "public" - case schema.Secret: - class = "secret" - case schema.Virtual: - class = "virtual" - case schema.Unset: - class = "unset" - default: - panic("not implemented") - } - if offset { - vID++ // for sparse R1CS, we offset to have same variable numbers as in R1CS - } - sbb.WriteString(fmt.Sprintf("v%d", class, vID)) - -} - // GetNbCoefficients return the number of unique coefficients needed in the R1CS func (cs *R1CS) GetNbCoefficients() int { return len(cs.Coefficients) diff --git a/internal/backend/bls24-315/cs/r1cs_sparse.go b/internal/backend/bls24-315/cs/r1cs_sparse.go index 759b55c41f..b1bdda70b2 100644 --- a/internal/backend/bls24-315/cs/r1cs_sparse.go +++ b/internal/backend/bls24-315/cs/r1cs_sparse.go @@ -24,7 +24,6 @@ import ( "math/big" "os" "strings" - "text/template" "github.com/consensys/gnark/backend" "github.com/consensys/gnark/backend/witness" @@ -110,11 +109,11 @@ func (cs *SparseR1CS) Solve(witness []fr.Element, opt backend.ProverConfig) ([]f return solution.values, fmt.Errorf("constraint %d: %w", i, err) } if err := cs.checkConstraint(cs.Constraints[i], &solution); err != nil { + errMsg := err.Error() if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -255,37 +254,81 @@ func (cs *SparseR1CS) IsSolved(witness *witness.Witness, opts ...backend.ProverO return err } +// GetConstraints return a list of constraint formatted as in the paper +// https://eprint.iacr.org/2019/953.pdf section 6 such that +// qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC == 0 +// each constraint is thus decomposed in [5]string with +// [0] = qL⋅xa +// [1] = qR⋅xb +// [2] = qO⋅xc +// [3] = qM⋅(xaxb) +// [4] = qC func (cs *SparseR1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string, 0, len(cs.Constraints)) for _, c := range cs.Constraints { - // if we are worried about perf for large cs, we could do a string builder + csv format. - var line [6]string - line[0] = cs.termToString(c.L) - line[1] = cs.termToString(c.R) - line[2] = cs.termToString(c.M[0]) - line[3] = cs.termToString(c.M[1]) - line[4] = cs.termToString(c.O) - line[5] = cs.Coefficients[c.K].String() - r = append(r, line[:]) + fc := cs.formatConstraint(c) + r = append(r, fc[:]) } return r } -func (cs *SparseR1CS) termToString(t compiled.Term) string { +// r[0] = qL⋅xa +// r[1] = qR⋅xb +// r[2] = qO⋅xc +// r[3] = qM⋅(xaxb) +// r[4] = qC +func (cs *SparseR1CS) formatConstraint(c compiled.SparseR1C) (r [5]string) { + isZeroM := (c.M[0].CoeffID() == compiled.CoeffIdZero) && (c.M[1].CoeffID() == compiled.CoeffIdZero) + var sbb strings.Builder - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteByte('-') - } else if tID == compiled.CoeffIdZero { - sbb.WriteByte('0') - return sbb.String() + cs.termToString(c.L, &sbb, false) + r[0] = sbb.String() + + sbb.Reset() + cs.termToString(c.R, &sbb, false) + r[1] = sbb.String() + + sbb.Reset() + cs.termToString(c.O, &sbb, false) + r[2] = sbb.String() + + if isZeroM { + r[3] = "0" } else { - sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.Reset() + sbb.WriteString(cs.Coefficients[c.M[0].CoeffID()].String()) + sbb.WriteString("⋅") + sbb.WriteByte('(') + cs.termToString(c.M[0], &sbb, true) + sbb.WriteString(" × ") + cs.termToString(c.M[1], &sbb, true) + sbb.WriteByte(')') + r[3] = sbb.String() + } + + r[4] = cs.Coefficients[c.K].String() + + return +} + +func (cs *SparseR1CS) termToString(t compiled.Term, sbb *strings.Builder, vOnly bool) { + if !vOnly { + tID := t.CoeffID() + if tID == compiled.CoeffIdOne { + // do nothing, just print the variable + sbb.WriteString("1") + } else if tID == compiled.CoeffIdMinusOne { + // print neg sign + sbb.WriteString("-1") + } else if tID == compiled.CoeffIdZero { + sbb.WriteByte('0') + return + } else { + sbb.WriteString(cs.Coefficients[tID].String()) + } + sbb.WriteString("⋅") } + vID := t.WireID() visibility := t.VariableVisibility() @@ -303,7 +346,6 @@ func (cs *SparseR1CS) termToString(t compiled.Term) string { default: sbb.WriteString("") } - return sbb.String() } // checkConstraint verifies that the constraint holds @@ -318,12 +360,12 @@ func (cs *SparseR1CS) checkConstraint(c compiled.SparseR1C, solution *solution) var t fr.Element t.Mul(&m0, &m1).Add(&t, &l).Add(&t, &r).Add(&t, &o).Add(&t, &cs.Coefficients[c.K]) if !t.IsZero() { - return fmt.Errorf("%w\n%s + %s + (%s * %s) + %s + %s != 0", ErrUnsatisfiedConstraint, + return fmt.Errorf("qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC != 0 → %s + %s + %s + (%s × %s) + %s != 0", l.String(), r.String(), + o.String(), m0.String(), m1.String(), - o.String(), cs.Coefficients[c.K].String(), ) } @@ -331,39 +373,6 @@ func (cs *SparseR1CS) checkConstraint(c compiled.SparseR1C, solution *solution) } -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *SparseR1CS) ToHTML(w io.Writer) error { - t, err := template.New("scs.html").Funcs(template.FuncMap{ - "toHTML": toHTMLTerm, - "toHTMLCoeff": toHTMLCoeff, - "add": add, - "sub": sub, - }).Parse(compiled.SparseR1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - -func toHTMLTerm(t compiled.Term, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - termToHTML(t, &sbb, coeffs, MHints, true) - return sbb.String() -} - -func toHTMLCoeff(cID int, coeffs []fr.Element) string { - if cID == compiled.CoeffIdMinusOne { - // print neg sign - return "-1" - } - var sbb strings.Builder - sbb.WriteString("") - sbb.WriteString(coeffs[cID].String()) - sbb.WriteString("") - return sbb.String() -} - // FrSize return fr.Limbs * 8, size in byte of a fr element func (cs *SparseR1CS) FrSize() int { return fr.Limbs * 8 diff --git a/internal/backend/bls24-315/cs/solution.go b/internal/backend/bls24-315/cs/solution.go index a457362c51..b69f343696 100644 --- a/internal/backend/bls24-315/cs/solution.go +++ b/internal/backend/bls24-315/cs/solution.go @@ -33,9 +33,6 @@ import ( curve "github.com/consensys/gnark-crypto/ecc/bls24-315" ) -// ErrUnsatisfiedConstraint can be generated when solving a R1CS -var ErrUnsatisfiedConstraint = errors.New("constraint is not satisfied") - // solution represents elements needed to compute // a solution to a R1CS or SparseR1CS type solution struct { diff --git a/internal/backend/bn254/cs/r1cs.go b/internal/backend/bn254/cs/r1cs.go index 554a461f4e..e6e45b2116 100644 --- a/internal/backend/bn254/cs/r1cs.go +++ b/internal/backend/bn254/cs/r1cs.go @@ -32,7 +32,6 @@ import ( "github.com/consensys/gnark/internal/backend/ioutils" "github.com/consensys/gnark-crypto/ecc" - "text/template" "github.com/consensys/gnark-crypto/ecc/bn254/fr" @@ -119,11 +118,11 @@ func (cs *R1CS) Solve(witness, a, b, c []fr.Element, opt backend.ProverConfig) ( // ensure a[i] * b[i] == c[i] check.Mul(&a[i], &b[i]) if !check.Equal(&c[i]) { + errMsg := fmt.Sprintf("%s ⋅ %s != %s", a[i].String(), b[i].String(), c[i].String()) if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -289,32 +288,10 @@ func (cs *R1CS) solveConstraint(r compiled.R1C, solution *solution) error { return nil } -// TODO @gbotrel clean logs and html see https://github.com/ConsenSys/gnark/issues/140 - -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *R1CS) ToHTML(w io.Writer) error { - t, err := template.New("cs.html").Funcs(template.FuncMap{ - "toHTML": toHTML, - "add": add, - "sub": sub, - }).Parse(compiled.R1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - -func add(a, b int) int { - return a + b -} - -func sub(a, b int) int { - return a - b -} - +// GetConstraints return a list of constraint formatted as L⋅R == O +// such that [0] -> L, [1] -> R, [2] -> O func (cs *R1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string, len(cs.Constraints)) for _, c := range cs.Constraints { // for each constraint, we build a string representation of it's L, R and O part // if we are worried about perf for large cs, we could do a string builder + csv format. @@ -350,7 +327,7 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { return } else { sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.WriteString("⋅") } vID := t.WireID() visibility := t.VariableVisibility() @@ -375,59 +352,6 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { } } -func toHTML(l compiled.Variable, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - for i := 0; i < len(l.LinExp); i++ { - termToHTML(l.LinExp[i], &sbb, coeffs, MHints, false) - if i+1 < len(l.LinExp) { - sbb.WriteString(" + ") - } - } - return sbb.String() -} - -func termToHTML(t compiled.Term, sbb *strings.Builder, coeffs []fr.Element, MHints map[int]compiled.Hint, offset bool) { - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteString("-") - } else if tID == compiled.CoeffIdZero { - sbb.WriteString("0") - return - } else { - sbb.WriteString("") - sbb.WriteString(coeffs[tID].String()) - sbb.WriteString("*") - } - - vID := t.WireID() - class := "" - switch t.VariableVisibility() { - case schema.Internal: - class = "internal" - if _, ok := MHints[vID]; ok { - class = "hint" - } - case schema.Public: - class = "public" - case schema.Secret: - class = "secret" - case schema.Virtual: - class = "virtual" - case schema.Unset: - class = "unset" - default: - panic("not implemented") - } - if offset { - vID++ // for sparse R1CS, we offset to have same variable numbers as in R1CS - } - sbb.WriteString(fmt.Sprintf("v%d", class, vID)) - -} - // GetNbCoefficients return the number of unique coefficients needed in the R1CS func (cs *R1CS) GetNbCoefficients() int { return len(cs.Coefficients) diff --git a/internal/backend/bn254/cs/r1cs_sparse.go b/internal/backend/bn254/cs/r1cs_sparse.go index cc4cbfd833..fa3a146d1e 100644 --- a/internal/backend/bn254/cs/r1cs_sparse.go +++ b/internal/backend/bn254/cs/r1cs_sparse.go @@ -24,7 +24,6 @@ import ( "math/big" "os" "strings" - "text/template" "github.com/consensys/gnark/backend" "github.com/consensys/gnark/backend/witness" @@ -110,11 +109,11 @@ func (cs *SparseR1CS) Solve(witness []fr.Element, opt backend.ProverConfig) ([]f return solution.values, fmt.Errorf("constraint %d: %w", i, err) } if err := cs.checkConstraint(cs.Constraints[i], &solution); err != nil { + errMsg := err.Error() if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -255,37 +254,81 @@ func (cs *SparseR1CS) IsSolved(witness *witness.Witness, opts ...backend.ProverO return err } +// GetConstraints return a list of constraint formatted as in the paper +// https://eprint.iacr.org/2019/953.pdf section 6 such that +// qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC == 0 +// each constraint is thus decomposed in [5]string with +// [0] = qL⋅xa +// [1] = qR⋅xb +// [2] = qO⋅xc +// [3] = qM⋅(xaxb) +// [4] = qC func (cs *SparseR1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string, 0, len(cs.Constraints)) for _, c := range cs.Constraints { - // if we are worried about perf for large cs, we could do a string builder + csv format. - var line [6]string - line[0] = cs.termToString(c.L) - line[1] = cs.termToString(c.R) - line[2] = cs.termToString(c.M[0]) - line[3] = cs.termToString(c.M[1]) - line[4] = cs.termToString(c.O) - line[5] = cs.Coefficients[c.K].String() - r = append(r, line[:]) + fc := cs.formatConstraint(c) + r = append(r, fc[:]) } return r } -func (cs *SparseR1CS) termToString(t compiled.Term) string { +// r[0] = qL⋅xa +// r[1] = qR⋅xb +// r[2] = qO⋅xc +// r[3] = qM⋅(xaxb) +// r[4] = qC +func (cs *SparseR1CS) formatConstraint(c compiled.SparseR1C) (r [5]string) { + isZeroM := (c.M[0].CoeffID() == compiled.CoeffIdZero) && (c.M[1].CoeffID() == compiled.CoeffIdZero) + var sbb strings.Builder - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteByte('-') - } else if tID == compiled.CoeffIdZero { - sbb.WriteByte('0') - return sbb.String() + cs.termToString(c.L, &sbb, false) + r[0] = sbb.String() + + sbb.Reset() + cs.termToString(c.R, &sbb, false) + r[1] = sbb.String() + + sbb.Reset() + cs.termToString(c.O, &sbb, false) + r[2] = sbb.String() + + if isZeroM { + r[3] = "0" } else { - sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.Reset() + sbb.WriteString(cs.Coefficients[c.M[0].CoeffID()].String()) + sbb.WriteString("⋅") + sbb.WriteByte('(') + cs.termToString(c.M[0], &sbb, true) + sbb.WriteString(" × ") + cs.termToString(c.M[1], &sbb, true) + sbb.WriteByte(')') + r[3] = sbb.String() + } + + r[4] = cs.Coefficients[c.K].String() + + return +} + +func (cs *SparseR1CS) termToString(t compiled.Term, sbb *strings.Builder, vOnly bool) { + if !vOnly { + tID := t.CoeffID() + if tID == compiled.CoeffIdOne { + // do nothing, just print the variable + sbb.WriteString("1") + } else if tID == compiled.CoeffIdMinusOne { + // print neg sign + sbb.WriteString("-1") + } else if tID == compiled.CoeffIdZero { + sbb.WriteByte('0') + return + } else { + sbb.WriteString(cs.Coefficients[tID].String()) + } + sbb.WriteString("⋅") } + vID := t.WireID() visibility := t.VariableVisibility() @@ -303,7 +346,6 @@ func (cs *SparseR1CS) termToString(t compiled.Term) string { default: sbb.WriteString("") } - return sbb.String() } // checkConstraint verifies that the constraint holds @@ -318,12 +360,12 @@ func (cs *SparseR1CS) checkConstraint(c compiled.SparseR1C, solution *solution) var t fr.Element t.Mul(&m0, &m1).Add(&t, &l).Add(&t, &r).Add(&t, &o).Add(&t, &cs.Coefficients[c.K]) if !t.IsZero() { - return fmt.Errorf("%w\n%s + %s + (%s * %s) + %s + %s != 0", ErrUnsatisfiedConstraint, + return fmt.Errorf("qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC != 0 → %s + %s + %s + (%s × %s) + %s != 0", l.String(), r.String(), + o.String(), m0.String(), m1.String(), - o.String(), cs.Coefficients[c.K].String(), ) } @@ -331,39 +373,6 @@ func (cs *SparseR1CS) checkConstraint(c compiled.SparseR1C, solution *solution) } -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *SparseR1CS) ToHTML(w io.Writer) error { - t, err := template.New("scs.html").Funcs(template.FuncMap{ - "toHTML": toHTMLTerm, - "toHTMLCoeff": toHTMLCoeff, - "add": add, - "sub": sub, - }).Parse(compiled.SparseR1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - -func toHTMLTerm(t compiled.Term, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - termToHTML(t, &sbb, coeffs, MHints, true) - return sbb.String() -} - -func toHTMLCoeff(cID int, coeffs []fr.Element) string { - if cID == compiled.CoeffIdMinusOne { - // print neg sign - return "-1" - } - var sbb strings.Builder - sbb.WriteString("") - sbb.WriteString(coeffs[cID].String()) - sbb.WriteString("") - return sbb.String() -} - // FrSize return fr.Limbs * 8, size in byte of a fr element func (cs *SparseR1CS) FrSize() int { return fr.Limbs * 8 diff --git a/internal/backend/bn254/cs/solution.go b/internal/backend/bn254/cs/solution.go index 7061493101..f4dff4d854 100644 --- a/internal/backend/bn254/cs/solution.go +++ b/internal/backend/bn254/cs/solution.go @@ -33,9 +33,6 @@ import ( curve "github.com/consensys/gnark-crypto/ecc/bn254" ) -// ErrUnsatisfiedConstraint can be generated when solving a R1CS -var ErrUnsatisfiedConstraint = errors.New("constraint is not satisfied") - // solution represents elements needed to compute // a solution to a R1CS or SparseR1CS type solution struct { diff --git a/internal/backend/bw6-633/cs/r1cs.go b/internal/backend/bw6-633/cs/r1cs.go index 20e19fb5bd..441905ff6e 100644 --- a/internal/backend/bw6-633/cs/r1cs.go +++ b/internal/backend/bw6-633/cs/r1cs.go @@ -32,7 +32,6 @@ import ( "github.com/consensys/gnark/internal/backend/ioutils" "github.com/consensys/gnark-crypto/ecc" - "text/template" "github.com/consensys/gnark-crypto/ecc/bw6-633/fr" @@ -119,11 +118,11 @@ func (cs *R1CS) Solve(witness, a, b, c []fr.Element, opt backend.ProverConfig) ( // ensure a[i] * b[i] == c[i] check.Mul(&a[i], &b[i]) if !check.Equal(&c[i]) { + errMsg := fmt.Sprintf("%s ⋅ %s != %s", a[i].String(), b[i].String(), c[i].String()) if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -289,32 +288,10 @@ func (cs *R1CS) solveConstraint(r compiled.R1C, solution *solution) error { return nil } -// TODO @gbotrel clean logs and html see https://github.com/ConsenSys/gnark/issues/140 - -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *R1CS) ToHTML(w io.Writer) error { - t, err := template.New("cs.html").Funcs(template.FuncMap{ - "toHTML": toHTML, - "add": add, - "sub": sub, - }).Parse(compiled.R1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - -func add(a, b int) int { - return a + b -} - -func sub(a, b int) int { - return a - b -} - +// GetConstraints return a list of constraint formatted as L⋅R == O +// such that [0] -> L, [1] -> R, [2] -> O func (cs *R1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string, len(cs.Constraints)) for _, c := range cs.Constraints { // for each constraint, we build a string representation of it's L, R and O part // if we are worried about perf for large cs, we could do a string builder + csv format. @@ -350,7 +327,7 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { return } else { sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.WriteString("⋅") } vID := t.WireID() visibility := t.VariableVisibility() @@ -375,59 +352,6 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { } } -func toHTML(l compiled.Variable, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - for i := 0; i < len(l.LinExp); i++ { - termToHTML(l.LinExp[i], &sbb, coeffs, MHints, false) - if i+1 < len(l.LinExp) { - sbb.WriteString(" + ") - } - } - return sbb.String() -} - -func termToHTML(t compiled.Term, sbb *strings.Builder, coeffs []fr.Element, MHints map[int]compiled.Hint, offset bool) { - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteString("-") - } else if tID == compiled.CoeffIdZero { - sbb.WriteString("0") - return - } else { - sbb.WriteString("") - sbb.WriteString(coeffs[tID].String()) - sbb.WriteString("*") - } - - vID := t.WireID() - class := "" - switch t.VariableVisibility() { - case schema.Internal: - class = "internal" - if _, ok := MHints[vID]; ok { - class = "hint" - } - case schema.Public: - class = "public" - case schema.Secret: - class = "secret" - case schema.Virtual: - class = "virtual" - case schema.Unset: - class = "unset" - default: - panic("not implemented") - } - if offset { - vID++ // for sparse R1CS, we offset to have same variable numbers as in R1CS - } - sbb.WriteString(fmt.Sprintf("v%d", class, vID)) - -} - // GetNbCoefficients return the number of unique coefficients needed in the R1CS func (cs *R1CS) GetNbCoefficients() int { return len(cs.Coefficients) diff --git a/internal/backend/bw6-633/cs/r1cs_sparse.go b/internal/backend/bw6-633/cs/r1cs_sparse.go index a63dc66b11..b014842de1 100644 --- a/internal/backend/bw6-633/cs/r1cs_sparse.go +++ b/internal/backend/bw6-633/cs/r1cs_sparse.go @@ -24,7 +24,6 @@ import ( "math/big" "os" "strings" - "text/template" "github.com/consensys/gnark/backend" "github.com/consensys/gnark/backend/witness" @@ -110,11 +109,11 @@ func (cs *SparseR1CS) Solve(witness []fr.Element, opt backend.ProverConfig) ([]f return solution.values, fmt.Errorf("constraint %d: %w", i, err) } if err := cs.checkConstraint(cs.Constraints[i], &solution); err != nil { + errMsg := err.Error() if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -255,37 +254,81 @@ func (cs *SparseR1CS) IsSolved(witness *witness.Witness, opts ...backend.ProverO return err } +// GetConstraints return a list of constraint formatted as in the paper +// https://eprint.iacr.org/2019/953.pdf section 6 such that +// qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC == 0 +// each constraint is thus decomposed in [5]string with +// [0] = qL⋅xa +// [1] = qR⋅xb +// [2] = qO⋅xc +// [3] = qM⋅(xaxb) +// [4] = qC func (cs *SparseR1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string, 0, len(cs.Constraints)) for _, c := range cs.Constraints { - // if we are worried about perf for large cs, we could do a string builder + csv format. - var line [6]string - line[0] = cs.termToString(c.L) - line[1] = cs.termToString(c.R) - line[2] = cs.termToString(c.M[0]) - line[3] = cs.termToString(c.M[1]) - line[4] = cs.termToString(c.O) - line[5] = cs.Coefficients[c.K].String() - r = append(r, line[:]) + fc := cs.formatConstraint(c) + r = append(r, fc[:]) } return r } -func (cs *SparseR1CS) termToString(t compiled.Term) string { +// r[0] = qL⋅xa +// r[1] = qR⋅xb +// r[2] = qO⋅xc +// r[3] = qM⋅(xaxb) +// r[4] = qC +func (cs *SparseR1CS) formatConstraint(c compiled.SparseR1C) (r [5]string) { + isZeroM := (c.M[0].CoeffID() == compiled.CoeffIdZero) && (c.M[1].CoeffID() == compiled.CoeffIdZero) + var sbb strings.Builder - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteByte('-') - } else if tID == compiled.CoeffIdZero { - sbb.WriteByte('0') - return sbb.String() + cs.termToString(c.L, &sbb, false) + r[0] = sbb.String() + + sbb.Reset() + cs.termToString(c.R, &sbb, false) + r[1] = sbb.String() + + sbb.Reset() + cs.termToString(c.O, &sbb, false) + r[2] = sbb.String() + + if isZeroM { + r[3] = "0" } else { - sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.Reset() + sbb.WriteString(cs.Coefficients[c.M[0].CoeffID()].String()) + sbb.WriteString("⋅") + sbb.WriteByte('(') + cs.termToString(c.M[0], &sbb, true) + sbb.WriteString(" × ") + cs.termToString(c.M[1], &sbb, true) + sbb.WriteByte(')') + r[3] = sbb.String() + } + + r[4] = cs.Coefficients[c.K].String() + + return +} + +func (cs *SparseR1CS) termToString(t compiled.Term, sbb *strings.Builder, vOnly bool) { + if !vOnly { + tID := t.CoeffID() + if tID == compiled.CoeffIdOne { + // do nothing, just print the variable + sbb.WriteString("1") + } else if tID == compiled.CoeffIdMinusOne { + // print neg sign + sbb.WriteString("-1") + } else if tID == compiled.CoeffIdZero { + sbb.WriteByte('0') + return + } else { + sbb.WriteString(cs.Coefficients[tID].String()) + } + sbb.WriteString("⋅") } + vID := t.WireID() visibility := t.VariableVisibility() @@ -303,7 +346,6 @@ func (cs *SparseR1CS) termToString(t compiled.Term) string { default: sbb.WriteString("") } - return sbb.String() } // checkConstraint verifies that the constraint holds @@ -318,12 +360,12 @@ func (cs *SparseR1CS) checkConstraint(c compiled.SparseR1C, solution *solution) var t fr.Element t.Mul(&m0, &m1).Add(&t, &l).Add(&t, &r).Add(&t, &o).Add(&t, &cs.Coefficients[c.K]) if !t.IsZero() { - return fmt.Errorf("%w\n%s + %s + (%s * %s) + %s + %s != 0", ErrUnsatisfiedConstraint, + return fmt.Errorf("qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC != 0 → %s + %s + %s + (%s × %s) + %s != 0", l.String(), r.String(), + o.String(), m0.String(), m1.String(), - o.String(), cs.Coefficients[c.K].String(), ) } @@ -331,39 +373,6 @@ func (cs *SparseR1CS) checkConstraint(c compiled.SparseR1C, solution *solution) } -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *SparseR1CS) ToHTML(w io.Writer) error { - t, err := template.New("scs.html").Funcs(template.FuncMap{ - "toHTML": toHTMLTerm, - "toHTMLCoeff": toHTMLCoeff, - "add": add, - "sub": sub, - }).Parse(compiled.SparseR1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - -func toHTMLTerm(t compiled.Term, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - termToHTML(t, &sbb, coeffs, MHints, true) - return sbb.String() -} - -func toHTMLCoeff(cID int, coeffs []fr.Element) string { - if cID == compiled.CoeffIdMinusOne { - // print neg sign - return "-1" - } - var sbb strings.Builder - sbb.WriteString("") - sbb.WriteString(coeffs[cID].String()) - sbb.WriteString("") - return sbb.String() -} - // FrSize return fr.Limbs * 8, size in byte of a fr element func (cs *SparseR1CS) FrSize() int { return fr.Limbs * 8 diff --git a/internal/backend/bw6-633/cs/solution.go b/internal/backend/bw6-633/cs/solution.go index a3cca08dd0..d8c2e5c824 100644 --- a/internal/backend/bw6-633/cs/solution.go +++ b/internal/backend/bw6-633/cs/solution.go @@ -33,9 +33,6 @@ import ( curve "github.com/consensys/gnark-crypto/ecc/bw6-633" ) -// ErrUnsatisfiedConstraint can be generated when solving a R1CS -var ErrUnsatisfiedConstraint = errors.New("constraint is not satisfied") - // solution represents elements needed to compute // a solution to a R1CS or SparseR1CS type solution struct { diff --git a/internal/backend/bw6-761/cs/r1cs.go b/internal/backend/bw6-761/cs/r1cs.go index 7a857ab11e..81b561f198 100644 --- a/internal/backend/bw6-761/cs/r1cs.go +++ b/internal/backend/bw6-761/cs/r1cs.go @@ -32,7 +32,6 @@ import ( "github.com/consensys/gnark/internal/backend/ioutils" "github.com/consensys/gnark-crypto/ecc" - "text/template" "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" @@ -119,11 +118,11 @@ func (cs *R1CS) Solve(witness, a, b, c []fr.Element, opt backend.ProverConfig) ( // ensure a[i] * b[i] == c[i] check.Mul(&a[i], &b[i]) if !check.Equal(&c[i]) { + errMsg := fmt.Sprintf("%s ⋅ %s != %s", a[i].String(), b[i].String(), c[i].String()) if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -289,32 +288,10 @@ func (cs *R1CS) solveConstraint(r compiled.R1C, solution *solution) error { return nil } -// TODO @gbotrel clean logs and html see https://github.com/ConsenSys/gnark/issues/140 - -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *R1CS) ToHTML(w io.Writer) error { - t, err := template.New("cs.html").Funcs(template.FuncMap{ - "toHTML": toHTML, - "add": add, - "sub": sub, - }).Parse(compiled.R1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - -func add(a, b int) int { - return a + b -} - -func sub(a, b int) int { - return a - b -} - +// GetConstraints return a list of constraint formatted as L⋅R == O +// such that [0] -> L, [1] -> R, [2] -> O func (cs *R1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string, len(cs.Constraints)) for _, c := range cs.Constraints { // for each constraint, we build a string representation of it's L, R and O part // if we are worried about perf for large cs, we could do a string builder + csv format. @@ -350,7 +327,7 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { return } else { sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.WriteString("⋅") } vID := t.WireID() visibility := t.VariableVisibility() @@ -375,59 +352,6 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { } } -func toHTML(l compiled.Variable, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - for i := 0; i < len(l.LinExp); i++ { - termToHTML(l.LinExp[i], &sbb, coeffs, MHints, false) - if i+1 < len(l.LinExp) { - sbb.WriteString(" + ") - } - } - return sbb.String() -} - -func termToHTML(t compiled.Term, sbb *strings.Builder, coeffs []fr.Element, MHints map[int]compiled.Hint, offset bool) { - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteString("-") - } else if tID == compiled.CoeffIdZero { - sbb.WriteString("0") - return - } else { - sbb.WriteString("") - sbb.WriteString(coeffs[tID].String()) - sbb.WriteString("*") - } - - vID := t.WireID() - class := "" - switch t.VariableVisibility() { - case schema.Internal: - class = "internal" - if _, ok := MHints[vID]; ok { - class = "hint" - } - case schema.Public: - class = "public" - case schema.Secret: - class = "secret" - case schema.Virtual: - class = "virtual" - case schema.Unset: - class = "unset" - default: - panic("not implemented") - } - if offset { - vID++ // for sparse R1CS, we offset to have same variable numbers as in R1CS - } - sbb.WriteString(fmt.Sprintf("v%d", class, vID)) - -} - // GetNbCoefficients return the number of unique coefficients needed in the R1CS func (cs *R1CS) GetNbCoefficients() int { return len(cs.Coefficients) diff --git a/internal/backend/bw6-761/cs/r1cs_sparse.go b/internal/backend/bw6-761/cs/r1cs_sparse.go index 4ede062b48..aa551a2e38 100644 --- a/internal/backend/bw6-761/cs/r1cs_sparse.go +++ b/internal/backend/bw6-761/cs/r1cs_sparse.go @@ -24,7 +24,6 @@ import ( "math/big" "os" "strings" - "text/template" "github.com/consensys/gnark/backend" "github.com/consensys/gnark/backend/witness" @@ -110,11 +109,11 @@ func (cs *SparseR1CS) Solve(witness []fr.Element, opt backend.ProverConfig) ([]f return solution.values, fmt.Errorf("constraint %d: %w", i, err) } if err := cs.checkConstraint(cs.Constraints[i], &solution); err != nil { + errMsg := err.Error() if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -255,37 +254,81 @@ func (cs *SparseR1CS) IsSolved(witness *witness.Witness, opts ...backend.ProverO return err } +// GetConstraints return a list of constraint formatted as in the paper +// https://eprint.iacr.org/2019/953.pdf section 6 such that +// qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC == 0 +// each constraint is thus decomposed in [5]string with +// [0] = qL⋅xa +// [1] = qR⋅xb +// [2] = qO⋅xc +// [3] = qM⋅(xaxb) +// [4] = qC func (cs *SparseR1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string, 0, len(cs.Constraints)) for _, c := range cs.Constraints { - // if we are worried about perf for large cs, we could do a string builder + csv format. - var line [6]string - line[0] = cs.termToString(c.L) - line[1] = cs.termToString(c.R) - line[2] = cs.termToString(c.M[0]) - line[3] = cs.termToString(c.M[1]) - line[4] = cs.termToString(c.O) - line[5] = cs.Coefficients[c.K].String() - r = append(r, line[:]) + fc := cs.formatConstraint(c) + r = append(r, fc[:]) } return r } -func (cs *SparseR1CS) termToString(t compiled.Term) string { +// r[0] = qL⋅xa +// r[1] = qR⋅xb +// r[2] = qO⋅xc +// r[3] = qM⋅(xaxb) +// r[4] = qC +func (cs *SparseR1CS) formatConstraint(c compiled.SparseR1C) (r [5]string) { + isZeroM := (c.M[0].CoeffID() == compiled.CoeffIdZero) && (c.M[1].CoeffID() == compiled.CoeffIdZero) + var sbb strings.Builder - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteByte('-') - } else if tID == compiled.CoeffIdZero { - sbb.WriteByte('0') - return sbb.String() + cs.termToString(c.L, &sbb, false) + r[0] = sbb.String() + + sbb.Reset() + cs.termToString(c.R, &sbb, false) + r[1] = sbb.String() + + sbb.Reset() + cs.termToString(c.O, &sbb, false) + r[2] = sbb.String() + + if isZeroM { + r[3] = "0" } else { - sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.Reset() + sbb.WriteString(cs.Coefficients[c.M[0].CoeffID()].String()) + sbb.WriteString("⋅") + sbb.WriteByte('(') + cs.termToString(c.M[0], &sbb, true) + sbb.WriteString(" × ") + cs.termToString(c.M[1], &sbb, true) + sbb.WriteByte(')') + r[3] = sbb.String() + } + + r[4] = cs.Coefficients[c.K].String() + + return +} + +func (cs *SparseR1CS) termToString(t compiled.Term, sbb *strings.Builder, vOnly bool) { + if !vOnly { + tID := t.CoeffID() + if tID == compiled.CoeffIdOne { + // do nothing, just print the variable + sbb.WriteString("1") + } else if tID == compiled.CoeffIdMinusOne { + // print neg sign + sbb.WriteString("-1") + } else if tID == compiled.CoeffIdZero { + sbb.WriteByte('0') + return + } else { + sbb.WriteString(cs.Coefficients[tID].String()) + } + sbb.WriteString("⋅") } + vID := t.WireID() visibility := t.VariableVisibility() @@ -303,7 +346,6 @@ func (cs *SparseR1CS) termToString(t compiled.Term) string { default: sbb.WriteString("") } - return sbb.String() } // checkConstraint verifies that the constraint holds @@ -318,12 +360,12 @@ func (cs *SparseR1CS) checkConstraint(c compiled.SparseR1C, solution *solution) var t fr.Element t.Mul(&m0, &m1).Add(&t, &l).Add(&t, &r).Add(&t, &o).Add(&t, &cs.Coefficients[c.K]) if !t.IsZero() { - return fmt.Errorf("%w\n%s + %s + (%s * %s) + %s + %s != 0", ErrUnsatisfiedConstraint, + return fmt.Errorf("qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC != 0 → %s + %s + %s + (%s × %s) + %s != 0", l.String(), r.String(), + o.String(), m0.String(), m1.String(), - o.String(), cs.Coefficients[c.K].String(), ) } @@ -331,39 +373,6 @@ func (cs *SparseR1CS) checkConstraint(c compiled.SparseR1C, solution *solution) } -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *SparseR1CS) ToHTML(w io.Writer) error { - t, err := template.New("scs.html").Funcs(template.FuncMap{ - "toHTML": toHTMLTerm, - "toHTMLCoeff": toHTMLCoeff, - "add": add, - "sub": sub, - }).Parse(compiled.SparseR1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - -func toHTMLTerm(t compiled.Term, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - termToHTML(t, &sbb, coeffs, MHints, true) - return sbb.String() -} - -func toHTMLCoeff(cID int, coeffs []fr.Element) string { - if cID == compiled.CoeffIdMinusOne { - // print neg sign - return "-1" - } - var sbb strings.Builder - sbb.WriteString("") - sbb.WriteString(coeffs[cID].String()) - sbb.WriteString("") - return sbb.String() -} - // FrSize return fr.Limbs * 8, size in byte of a fr element func (cs *SparseR1CS) FrSize() int { return fr.Limbs * 8 diff --git a/internal/backend/bw6-761/cs/solution.go b/internal/backend/bw6-761/cs/solution.go index 7532956d8e..ba1a4b0688 100644 --- a/internal/backend/bw6-761/cs/solution.go +++ b/internal/backend/bw6-761/cs/solution.go @@ -33,9 +33,6 @@ import ( curve "github.com/consensys/gnark-crypto/ecc/bw6-761" ) -// ErrUnsatisfiedConstraint can be generated when solving a R1CS -var ErrUnsatisfiedConstraint = errors.New("constraint is not satisfied") - // solution represents elements needed to compute // a solution to a R1CS or SparseR1CS type solution struct { diff --git a/internal/backend/circuits/cmp.go b/internal/backend/circuits/cmp.go index 59f19419a7..1aecdde18c 100644 --- a/internal/backend/circuits/cmp.go +++ b/internal/backend/circuits/cmp.go @@ -55,5 +55,5 @@ func init() { }, } - addNewEntry("cmp", &cmpCircuit{}, good, bad, []ecc.ID{ecc.BW6_761}) + addNewEntry("cmp", &cmpCircuit{}, good, bad, ecc.Implemented()) } diff --git a/internal/generator/backend/template/representations/r1cs.go.tmpl b/internal/generator/backend/template/representations/r1cs.go.tmpl index 88ce8c351a..e8577d81ab 100644 --- a/internal/generator/backend/template/representations/r1cs.go.tmpl +++ b/internal/generator/backend/template/representations/r1cs.go.tmpl @@ -14,7 +14,6 @@ import ( "github.com/consensys/gnark/backend/witness" "github.com/consensys/gnark-crypto/ecc" - "text/template" {{ template "import_fr" . }} {{ template "import_witness" . }} @@ -101,11 +100,11 @@ func (cs *R1CS) Solve(witness, a, b, c []fr.Element, opt backend.ProverConfig) ( // ensure a[i] * b[i] == c[i] check.Mul(&a[i], &b[i]) if !check.Equal(&c[i]) { + errMsg := fmt.Sprintf("%s ⋅ %s != %s", a[i].String(), b[i].String(), c[i].String()) if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -271,32 +270,10 @@ func (cs *R1CS) solveConstraint(r compiled.R1C, solution *solution) error { return nil } -// TODO @gbotrel clean logs and html see https://github.com/ConsenSys/gnark/issues/140 - -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *R1CS) ToHTML(w io.Writer) error { - t, err := template.New("cs.html").Funcs(template.FuncMap{ - "toHTML": toHTML, - "add": add, - "sub": sub, - }).Parse(compiled.R1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - -func add(a, b int) int { - return a + b -} - -func sub(a, b int) int { - return a - b -} - +// GetConstraints return a list of constraint formatted as L⋅R == O +// such that [0] -> L, [1] -> R, [2] -> O func (cs *R1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string , len(cs.Constraints)) for _, c := range cs.Constraints { // for each constraint, we build a string representation of it's L, R and O part // if we are worried about perf for large cs, we could do a string builder + csv format. @@ -332,7 +309,7 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { return } else { sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.WriteString("⋅") } vID := t.WireID() visibility := t.VariableVisibility() @@ -358,59 +335,6 @@ func (cs *R1CS) termToString(t compiled.Term, sbb *strings.Builder) { } -func toHTML(l compiled.Variable, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - for i := 0; i < len(l.LinExp); i++ { - termToHTML(l.LinExp[i], &sbb, coeffs, MHints, false) - if i+1 < len(l.LinExp) { - sbb.WriteString(" + ") - } - } - return sbb.String() -} - -func termToHTML(t compiled.Term, sbb *strings.Builder, coeffs []fr.Element, MHints map[int]compiled.Hint, offset bool) { - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteString("-") - } else if tID == compiled.CoeffIdZero { - sbb.WriteString("0") - return - } else { - sbb.WriteString("") - sbb.WriteString(coeffs[tID].String()) - sbb.WriteString("*") - } - - vID := t.WireID() - class := "" - switch t.VariableVisibility() { - case schema.Internal: - class = "internal" - if _, ok := MHints[vID]; ok { - class = "hint" - } - case schema.Public: - class = "public" - case schema.Secret: - class = "secret" - case schema.Virtual: - class = "virtual" - case schema.Unset: - class = "unset" - default: - panic("not implemented") - } - if offset { - vID++ // for sparse R1CS, we offset to have same variable numbers as in R1CS - } - sbb.WriteString(fmt.Sprintf("v%d", class, vID)) - -} - // GetNbCoefficients return the number of unique coefficients needed in the R1CS func (cs *R1CS) GetNbCoefficients() int { return len(cs.Coefficients) diff --git a/internal/generator/backend/template/representations/r1cs.sparse.go.tmpl b/internal/generator/backend/template/representations/r1cs.sparse.go.tmpl index 50d5e488c4..e423b6e8e8 100644 --- a/internal/generator/backend/template/representations/r1cs.sparse.go.tmpl +++ b/internal/generator/backend/template/representations/r1cs.sparse.go.tmpl @@ -5,7 +5,6 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/consensys/gnark-crypto/ecc" "strings" - "text/template" "os" "github.com/consensys/gnark/internal/backend/ioutils" @@ -97,11 +96,11 @@ func (cs *SparseR1CS) Solve(witness []fr.Element, opt backend.ProverConfig) ([]f return solution.values, fmt.Errorf("constraint %d: %w", i, err) } if err := cs.checkConstraint(cs.Constraints[i], &solution); err != nil { + errMsg := err.Error() if dID, ok := cs.MDebug[i]; ok { - debugInfoStr := solution.logValue(cs.DebugInfo[dID]) - return solution.values, fmt.Errorf("%w: %s", ErrUnsatisfiedConstraint, debugInfoStr) + errMsg = solution.logValue(cs.DebugInfo[dID]) } - return solution.values, ErrUnsatisfiedConstraint + return solution.values, fmt.Errorf("constraint #%d is not satisfied: %s", i, errMsg) } } @@ -245,38 +244,82 @@ func (cs *SparseR1CS) IsSolved(witness *witness.Witness, opts ...backend.ProverO return err } +// GetConstraints return a list of constraint formatted as in the paper +// https://eprint.iacr.org/2019/953.pdf section 6 such that +// qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC == 0 +// each constraint is thus decomposed in [5]string with +// [0] = qL⋅xa +// [1] = qR⋅xb +// [2] = qO⋅xc +// [3] = qM⋅(xaxb) +// [4] = qC func (cs *SparseR1CS) GetConstraints() [][]string { - var r [][]string + r := make([][]string , 0, len(cs.Constraints)) for _, c := range cs.Constraints { - // if we are worried about perf for large cs, we could do a string builder + csv format. - var line [6]string - line[0] = cs.termToString(c.L) - line[1] = cs.termToString(c.R) - line[2] = cs.termToString(c.M[0]) - line[3] = cs.termToString(c.M[1]) - line[4] = cs.termToString(c.O) - line[5] = cs.Coefficients[c.K].String() - r = append(r, line[:]) + fc := cs.formatConstraint(c) + r = append(r, fc[:]) } return r } +// r[0] = qL⋅xa +// r[1] = qR⋅xb +// r[2] = qO⋅xc +// r[3] = qM⋅(xaxb) +// r[4] = qC +func (cs *SparseR1CS) formatConstraint(c compiled.SparseR1C) (r [5]string) { + isZeroM := (c.M[0].CoeffID() == compiled.CoeffIdZero) && (c.M[1].CoeffID() == compiled.CoeffIdZero) -func (cs *SparseR1CS) termToString(t compiled.Term) string { var sbb strings.Builder - tID := t.CoeffID() - if tID == compiled.CoeffIdOne { - // do nothing, just print the variable - } else if tID == compiled.CoeffIdMinusOne { - // print neg sign - sbb.WriteByte('-') - } else if tID == compiled.CoeffIdZero { - sbb.WriteByte('0') - return sbb.String() + cs.termToString(c.L, &sbb, false) + r[0] = sbb.String() + + sbb.Reset() + cs.termToString(c.R, &sbb, false) + r[1] = sbb.String() + + sbb.Reset() + cs.termToString(c.O, &sbb, false) + r[2] = sbb.String() + + if isZeroM { + r[3] = "0" } else { - sbb.WriteString(cs.Coefficients[tID].String()) - sbb.WriteByte('*') + sbb.Reset() + sbb.WriteString(cs.Coefficients[c.M[0].CoeffID()].String()) + sbb.WriteString("⋅") + sbb.WriteByte('(') + cs.termToString(c.M[0], &sbb, true) + sbb.WriteString(" × ") + cs.termToString(c.M[1], &sbb, true) + sbb.WriteByte(')') + r[3] = sbb.String() } + + r[4] = cs.Coefficients[c.K].String() + + return +} + + +func (cs *SparseR1CS) termToString(t compiled.Term, sbb *strings.Builder, vOnly bool) { + if !vOnly { + tID := t.CoeffID() + if tID == compiled.CoeffIdOne { + // do nothing, just print the variable + sbb.WriteString("1") + } else if tID == compiled.CoeffIdMinusOne { + // print neg sign + sbb.WriteString("-1") + } else if tID == compiled.CoeffIdZero { + sbb.WriteByte('0') + return + } else { + sbb.WriteString(cs.Coefficients[tID].String()) + } + sbb.WriteString("⋅") + } + vID := t.WireID() visibility := t.VariableVisibility() @@ -294,7 +337,6 @@ func (cs *SparseR1CS) termToString(t compiled.Term) string { default: sbb.WriteString("") } - return sbb.String() } @@ -311,54 +353,19 @@ func (cs *SparseR1CS) checkConstraint(c compiled.SparseR1C, solution *solution) var t fr.Element t.Mul(&m0, &m1).Add(&t, &l).Add(&t, &r).Add(&t, &o).Add(&t, &cs.Coefficients[c.K]) if !t.IsZero() { - return fmt.Errorf("%w\n%s + %s + (%s * %s) + %s + %s != 0",ErrUnsatisfiedConstraint, - l.String(), - r.String(), - m0.String(), - m1.String(), - o.String(), - cs.Coefficients[c.K].String(), + return fmt.Errorf("qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC != 0 → %s + %s + %s + (%s × %s) + %s != 0", + l.String(), + r.String(), + o.String(), + m0.String(), + m1.String(), + cs.Coefficients[c.K].String(), ) } return nil } - -// ToHTML returns an HTML human-readable representation of the constraint system -func (cs *SparseR1CS) ToHTML(w io.Writer) error { - t, err := template.New("scs.html").Funcs(template.FuncMap{ - "toHTML": toHTMLTerm, - "toHTMLCoeff": toHTMLCoeff, - "add": add, - "sub": sub, - }).Parse(compiled.SparseR1CSTemplate) - if err != nil { - return err - } - - return t.Execute(w, cs) -} - - -func toHTMLTerm(t compiled.Term, coeffs []fr.Element, MHints map[int]compiled.Hint) string { - var sbb strings.Builder - termToHTML(t, &sbb, coeffs, MHints, true) - return sbb.String() -} - -func toHTMLCoeff(cID int, coeffs []fr.Element) string { - if cID == compiled.CoeffIdMinusOne { - // print neg sign - return "-1" - } - var sbb strings.Builder - sbb.WriteString("") - sbb.WriteString(coeffs[cID].String()) - sbb.WriteString("") - return sbb.String() -} - // FrSize return fr.Limbs * 8, size in byte of a fr element func (cs *SparseR1CS) FrSize() int { return fr.Limbs * 8 diff --git a/internal/generator/backend/template/representations/solution.go.tmpl b/internal/generator/backend/template/representations/solution.go.tmpl index 88ba32f0c5..ab64434800 100644 --- a/internal/generator/backend/template/representations/solution.go.tmpl +++ b/internal/generator/backend/template/representations/solution.go.tmpl @@ -14,9 +14,6 @@ import ( {{ template "import_curve" . }} ) -// ErrUnsatisfiedConstraint can be generated when solving a R1CS -var ErrUnsatisfiedConstraint = errors.New("constraint is not satisfied") - // solution represents elements needed to compute // a solution to a R1CS or SparseR1CS type solution struct {