Skip to content

Commit

Permalink
Merge pull request #164 from ConsenSys/perf-scs-compile
Browse files Browse the repository at this point in the history
fixes #155 slow plonk compile, fixes #163 detects unconstrained inputs
  • Loading branch information
gbotrel authored Nov 3, 2021
2 parents 481475a + 0e64b10 commit 910a18c
Show file tree
Hide file tree
Showing 16 changed files with 448 additions and 228 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,10 @@ jobs:
- name: Test
run: |
go test -v -timeout=30m ./...
go test -v -timeout=30m -tags=noadx -short
- name: Test (race)
if: matrix.os == 'ubuntu-latest'
run: |
go test -v -timeout=30m -race -short ./...
go test -v -timeout=50m -race -short ./...
# - name: Test (32bits)
# if: matrix.os == 'ubuntu-latest'
# run: |
Expand Down
4 changes: 2 additions & 2 deletions debug_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func TestTraceNotEqual(t *testing.T) {
// -------------------------------------------------------------------------------------------------
// Not boolean
type notBooleanTrace struct {
A, B, C frontend.Variable
B, C frontend.Variable
}

func (circuit *notBooleanTrace) Define(curveID ecc.ID, api frontend.API) error {
Expand All @@ -150,7 +150,7 @@ func TestTraceNotBoolean(t *testing.T) {
assert := require.New(t)

var circuit, witness notBooleanTrace
witness.A.Assign(1)
// witness.A.Assign(1)
witness.B.Assign(24)
witness.C.Assign(42)

Expand Down
9 changes: 5 additions & 4 deletions examples/rollup/circuit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func TestCircuitSignature(t *testing.T) {
assert := test.NewAssert(t)

var signatureCircuit circuitSignature
assert.ProverSucceeded(&signatureCircuit, &operator.witnesses, test.WithCurves(ecc.BN254))
assert.ProverSucceeded(&signatureCircuit, &operator.witnesses, test.WithCurves(ecc.BN254), test.WithCompileOpts(frontend.IgnoreUnconstrainedInputs))

}

Expand Down Expand Up @@ -145,7 +145,7 @@ func TestCircuitInclusionProof(t *testing.T) {

var inclusionProofCircuit circuitInclusionProof

assert.ProverSucceeded(&inclusionProofCircuit, &operator.witnesses, test.WithCurves(ecc.BN254))
assert.ProverSucceeded(&inclusionProofCircuit, &operator.witnesses, test.WithCurves(ecc.BN254), test.WithCompileOpts(frontend.IgnoreUnconstrainedInputs))

}

Expand Down Expand Up @@ -202,7 +202,7 @@ func TestCircuitUpdateAccount(t *testing.T) {

var updateAccountCircuit circuitUpdateAccount

assert.ProverSucceeded(&updateAccountCircuit, &operator.witnesses, test.WithCurves(ecc.BN254))
assert.ProverSucceeded(&updateAccountCircuit, &operator.witnesses, test.WithCurves(ecc.BN254), test.WithCompileOpts(frontend.IgnoreUnconstrainedInputs))

}

Expand Down Expand Up @@ -246,6 +246,7 @@ func TestCircuitFull(t *testing.T) {

var rollupCircuit Circuit

assert.ProverSucceeded(&rollupCircuit, &operator.witnesses, test.WithCurves(ecc.BN254))
// TODO full circuit has some unconstrained inputs, that's odd.
assert.ProverSucceeded(&rollupCircuit, &operator.witnesses, test.WithCurves(ecc.BN254), test.WithCompileOpts(frontend.IgnoreUnconstrainedInputs))

}
149 changes: 133 additions & 16 deletions frontend/cs.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ limitations under the License.
package frontend

import (
"errors"
"io"
"math/big"
"sort"
"strconv"
"strings"

"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/backend/hint"
Expand All @@ -36,7 +39,8 @@ type constraintSystem struct {
// Variables (aka wires)
// virtual variables do not result in a new circuit wire
// they may only contain a linear expression
public, secret, internal, virtual variables
public, secret inputs
internal, virtual variables

// list of constraints in the form a * b == c
// a,b and c being linear expressions
Expand All @@ -48,7 +52,8 @@ type constraintSystem struct {
coeffsIDsInt64 map[int64]int // map to check existence of a coefficient (key = int64 value)

// Hints
mHints map[int]compiled.Hint // solver hints
mHints map[int]compiled.Hint // solver hints
mHintsConstrained map[int]bool // marks hints variables constrained status

logs []compiled.LogEntry // list of logs to be printed when solving a circuit. The logs are called with the method Println
debugInfo []compiled.LogEntry // list of logs storing information about R1C
Expand All @@ -63,6 +68,16 @@ type variables struct {
booleans map[int]struct{} // keep track of boolean variables (we constrain them once)
}

type inputs struct {
variables
names []string
}

func (v *inputs) new(cs *constraintSystem, visibility compiled.Visibility, name string) Variable {
v.names = append(v.names, name)
return v.variables.new(cs, visibility)
}

func (v *variables) new(cs *constraintSystem, visibility compiled.Visibility) Variable {
idx := len(v.variables)
variable := Variable{visibility: visibility, id: idx, linExp: cs.LinearExpression(compiled.Pack(idx, compiled.CoeffIdOne, visibility))}
Expand Down Expand Up @@ -96,12 +111,13 @@ func newConstraintSystem(curveID ecc.ID, initialCapacity ...int) constraintSyste
capacity = initialCapacity[0]
}
cs := constraintSystem{
coeffs: make([]big.Int, 4),
coeffsIDsLarge: make(map[string]int),
coeffsIDsInt64: make(map[int64]int, 4),
constraints: make([]compiled.R1C, 0, capacity),
mDebug: make(map[int]int),
mHints: make(map[int]compiled.Hint),
coeffs: make([]big.Int, 4),
coeffsIDsLarge: make(map[string]int),
coeffsIDsInt64: make(map[int64]int, 4),
constraints: make([]compiled.R1C, 0, capacity),
mDebug: make(map[int]int),
mHints: make(map[int]compiled.Hint),
mHintsConstrained: make(map[int]bool),
}

cs.coeffs[compiled.CoeffIdZero].SetInt64(0)
Expand All @@ -114,10 +130,10 @@ func newConstraintSystem(curveID ecc.ID, initialCapacity ...int) constraintSyste
cs.coeffsIDsInt64[2] = compiled.CoeffIdTwo
cs.coeffsIDsInt64[-1] = compiled.CoeffIdMinusOne

cs.public.variables = make([]Variable, 0)
cs.public.variables.variables = make([]Variable, 0)
cs.public.booleans = make(map[int]struct{})

cs.secret.variables = make([]Variable, 0)
cs.secret.variables.variables = make([]Variable, 0)
cs.secret.booleans = make(map[int]struct{})

cs.internal.variables = make([]Variable, 0, capacity)
Expand All @@ -127,7 +143,7 @@ func newConstraintSystem(curveID ecc.ID, initialCapacity ...int) constraintSyste
cs.virtual.booleans = make(map[int]struct{})

// by default the circuit is given on public wire equal to 1
cs.public.variables[0] = cs.newPublicVariable()
cs.public.variables.variables[0] = cs.newPublicVariable("one")

cs.curveID = curveID

Expand All @@ -146,6 +162,9 @@ func (cs *constraintSystem) NewHint(f hint.Function, inputs ...interface{}) Vari
// create resulting wire
r := cs.newInternalVariable()

// mark hint as unconstrained, for now
cs.mHintsConstrained[r.id] = false

// now we need to store the linear expressions of the expected input
// that will be resolved in the solver
hintInputs := make([]compiled.LinearExpression, len(inputs))
Expand All @@ -168,7 +187,7 @@ func (cs *constraintSystem) bitLen() int {
}

func (cs *constraintSystem) one() Variable {
return cs.public.variables[0]
return cs.public.variables.variables[0]
}

// Term packs a variable and a coeff in a compiled.Term and returns it.
Expand Down Expand Up @@ -287,13 +306,13 @@ func (cs *constraintSystem) newInternalVariable() Variable {
}

// newPublicVariable creates a new public variable
func (cs *constraintSystem) newPublicVariable() Variable {
return cs.public.new(cs, compiled.Public)
func (cs *constraintSystem) newPublicVariable(name string) Variable {
return cs.public.new(cs, compiled.Public, name)
}

// newSecretVariable creates a new secret variable
func (cs *constraintSystem) newSecretVariable() Variable {
return cs.secret.new(cs, compiled.Secret)
func (cs *constraintSystem) newSecretVariable(name string) Variable {
return cs.secret.new(cs, compiled.Secret, name)
}

// newVirtualVariable creates a new virtual variable
Expand Down Expand Up @@ -333,3 +352,101 @@ func (cs *constraintSystem) markBoolean(v Variable) bool {
}
return true
}

// checkVariables perform post compilation checks on the variables
//
// 1. checks that all user inputs are referenced in at least one constraint
// 2. checks that all hints are constrained
func (cs *constraintSystem) checkVariables() error {

// TODO @gbotrel add unit test for that.

cptSecret := len(cs.secret.variables.variables)
cptPublic := len(cs.public.variables.variables) - 1
cptHints := len(cs.mHintsConstrained)

secretConstrained := make([]bool, cptSecret)
publicConstrained := make([]bool, cptPublic+1)
publicConstrained[0] = true

// for each constraint, we check the linear expressions and mark our inputs / hints as constrained
processLinearExpression := func(l compiled.LinearExpression) {
for _, t := range l {
if t.CoeffID() == compiled.CoeffIdZero {
// ignore zero coefficient, as it does not constraint the variable
// though, we may want to flag that IF the variable doesn't appear else where
continue
}
visibility := t.VariableVisibility()
vID := t.VariableID()

switch visibility {
case compiled.Public:
if vID != 0 && !publicConstrained[vID] {
publicConstrained[vID] = true
cptPublic--
}
case compiled.Secret:
if !secretConstrained[vID] {
secretConstrained[vID] = true
cptSecret--
}
case compiled.Internal:
if b, ok := cs.mHintsConstrained[vID]; ok && !b {
cs.mHintsConstrained[vID] = true
cptHints--
}
}
}
}
for _, r1c := range cs.constraints {
processLinearExpression(r1c.L)
processLinearExpression(r1c.R)
processLinearExpression(r1c.O)

if cptHints|cptSecret|cptPublic == 0 {
return nil // we can stop.
}

}

// something is a miss, we build the error string
var sbb strings.Builder
if cptSecret != 0 {
sbb.WriteString(strconv.Itoa(cptSecret))
sbb.WriteString(" unconstrained secret input(s):")
sbb.WriteByte('\n')
for i := 0; i < len(secretConstrained) && cptSecret != 0; i++ {
if !secretConstrained[i] {
sbb.WriteString(cs.secret.names[i])
sbb.WriteByte('\n')
cptSecret--
}
}
sbb.WriteByte('\n')
}

if cptPublic != 0 {
sbb.WriteString(strconv.Itoa(cptPublic))
sbb.WriteString(" unconstrained public input(s):")
sbb.WriteByte('\n')
for i := 0; i < len(publicConstrained) && cptPublic != 0; i++ {
if !publicConstrained[i] {
sbb.WriteString(cs.public.names[i])
sbb.WriteByte('\n')
cptPublic--
}
}
sbb.WriteByte('\n')
}

if cptHints != 0 {
sbb.WriteString(strconv.Itoa(cptHints))
sbb.WriteString(" unconstrained hints")
sbb.WriteByte('\n')
// TODO we may add more debug info here --> idea, in NewHint, take the debug stack, and store in the hint map some
// debugInfo to find where a hint was declared (and not constrained)
}
return errors.New(sbb.String())

}
15 changes: 8 additions & 7 deletions frontend/cs_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,13 +441,14 @@ func (cs *constraintSystem) Select(i0, i1, i2 interface{}) Variable {
// ensures that b is boolean
cs.AssertIsBoolean(b)

if b.isConstant() {
c := b.constantValue(cs)
if c.Uint64() == 0 {
return vars[2]
}
return vars[1]
}
// this doesn't work.
// if b.isConstant() {
// c := b.constantValue(cs)
// if c.Uint64() == 0 {
// return vars[2]
// }
// return vars[1]
// }

if vars[1].isConstant() && vars[2].isConstant() {
n1 := vars[1].constantValue(cs)
Expand Down
2 changes: 1 addition & 1 deletion frontend/cs_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
func TestPrintln(t *testing.T) {
// must not panic.
cs := newConstraintSystem(ecc.BN254)
one := cs.newPublicVariable()
one := cs.newPublicVariable("one")

cs.Println(nil)
cs.Println(1)
Expand Down
8 changes: 4 additions & 4 deletions frontend/cs_to_r1cs.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func (cs *constraintSystem) toR1CS(curveID ecc.ID) (CompiledConstraintSystem, er
res := compiled.R1CS{
CS: compiled.CS{
NbInternalVariables: len(cs.internal.variables),
NbPublicVariables: len(cs.public.variables),
NbSecretVariables: len(cs.secret.variables),
NbPublicVariables: len(cs.public.variables.variables),
NbSecretVariables: len(cs.secret.variables.variables),
DebugInfo: make([]compiled.LogEntry, len(cs.debugInfo)),
Logs: make([]compiled.LogEntry, len(cs.logs)),
MHints: make(map[int]compiled.Hint, len(cs.mHints)),
Expand Down Expand Up @@ -68,11 +68,11 @@ func (cs *constraintSystem) toR1CS(curveID ecc.ID) (CompiledConstraintSystem, er
shiftVID := func(oldID int, visibility compiled.Visibility) int {
switch visibility {
case compiled.Internal:
return oldID + len(cs.public.variables) + len(cs.secret.variables)
return oldID + len(cs.public.variables.variables) + len(cs.secret.variables.variables)
case compiled.Public:
return oldID
case compiled.Secret:
return oldID + len(cs.public.variables)
return oldID + len(cs.public.variables.variables)
}
return oldID
}
Expand Down
Loading

0 comments on commit 910a18c

Please sign in to comment.