Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes #155 slow plonk compile, fixes #163 detects unconstrained inputs #164

Merged
merged 10 commits into from
Nov 3, 2021
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(hintID hint.ID, inputs ...interface{}) Varia
// 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