diff --git a/frontend/cs/r1cs/api_assertions.go b/frontend/cs/r1cs/api_assertions.go index 60d66e9a75..acbb602c24 100644 --- a/frontend/cs/r1cs/api_assertions.go +++ b/frontend/cs/r1cs/api_assertions.go @@ -78,9 +78,18 @@ func (builder *builder[E]) AssertIsBoolean(i1 frontend.Variable) { } func (builder *builder[E]) AssertIsCrumb(i1 frontend.Variable) { - i1 = builder.MulAcc(builder.Mul(-3, i1), i1, i1) - i1 = builder.MulAcc(builder.Mul(2, i1), i1, i1) - builder.AssertIsEqual(i1, 0) + if c, ok := builder.constantValue(i1); ok { + if i, ok := builder.cs.Uint64(c); ok && i < 4 { + return + } + panic(fmt.Sprintf("AssertIsCrumb constant input %s is not a crumb", builder.cs.String(c))) + } + + // i1 (i1-1) (i1-2) (i1-3) = (i1² - 3i1) (i1² - 3i1 + 2) + // take X := i1² - 3i1 and we get X (X+2) = 0 + x := builder.MulAcc(builder.Mul(-3, i1), i1, i1) + x = builder.MulAcc(builder.Mul(2, x), x, x) + builder.AssertIsEqual(x, 0) } // AssertIsLessOrEqual adds assertion in constraint builder (v ⩽ bound) diff --git a/frontend/cs/scs/api_assertions.go b/frontend/cs/scs/api_assertions.go index e96554bdf5..a7253e3aaf 100644 --- a/frontend/cs/scs/api_assertions.go +++ b/frontend/cs/scs/api_assertions.go @@ -119,21 +119,24 @@ func (builder *builder[E]) AssertIsBoolean(i1 frontend.Variable) { } func (builder *builder[E]) AssertIsCrumb(i1 frontend.Variable) { - const errorMsg = "AssertIsCrumb: input is not a crumb" if c, ok := builder.constantValue(i1); ok { if i, ok := builder.cs.Uint64(c); ok && i < 4 { return } - panic(errorMsg) + panic(fmt.Sprintf("AssertIsCrumb constant input %s is not a crumb", builder.cs.String(c))) } // i1 (i1-1) (i1-2) (i1-3) = (i1² - 3i1) (i1² - 3i1 + 2) // take X := i1² - 3i1 and we get X (X+2) = 0 + // usually MulAcc is a composition in PLONK, unless we have the condition + // a/c == const. This holds here so this is only a single constraint. x := builder.MulAcc(builder.Mul(-3, i1), i1, i1).(expr.Term[E]) - // TODO @Tabaie Ideally this entire function would live in std/math/bits as it is quite specialized; - // however using two generic MulAccs and an AssertIsEqual results in three constraints rather than two. + // usually bit assertions are defined in [std/math/bits] package, but we + // already have it and want to keep it backwards compatible. By doing it + // directly we can avoid a constraint as we do 2X + X^2 == 0 in a single + // constraint. builder.addPlonkConstraint(sparseR1C[E]{ xa: x.VID, xb: x.VID, diff --git a/internal/stats/latest_stats.csv b/internal/stats/latest_stats.csv index 962a452fe1..9bfb2e9a09 100644 --- a/internal/stats/latest_stats.csv +++ b/internal/stats/latest_stats.csv @@ -1,4 +1,18 @@ circuit,curve,backend,nbConstraints,nbWires +api/AssertIsCrumb,bn254,groth16,3,2 +api/AssertIsCrumb,bls12_377,groth16,3,2 +api/AssertIsCrumb,bls12_381,groth16,3,2 +api/AssertIsCrumb,bls24_315,groth16,3,2 +api/AssertIsCrumb,bls24_317,groth16,3,2 +api/AssertIsCrumb,bw6_761,groth16,3,2 +api/AssertIsCrumb,bw6_633,groth16,3,2 +api/AssertIsCrumb,bn254,plonk,2,1 +api/AssertIsCrumb,bls12_377,plonk,2,1 +api/AssertIsCrumb,bls12_381,plonk,2,1 +api/AssertIsCrumb,bls24_315,plonk,2,1 +api/AssertIsCrumb,bls24_317,plonk,2,1 +api/AssertIsCrumb,bw6_761,plonk,2,1 +api/AssertIsCrumb,bw6_633,plonk,2,1 api/AssertIsLessOrEqual,bn254,groth16,1523,1367 api/AssertIsLessOrEqual,bls12_377,groth16,1517,1349 api/AssertIsLessOrEqual,bls12_381,groth16,1529,1405 diff --git a/internal/stats/snippet.go b/internal/stats/snippet.go index b63e1eb527..c5d1d253b1 100644 --- a/internal/stats/snippet.go +++ b/internal/stats/snippet.go @@ -59,6 +59,10 @@ func initSnippets() { api.AssertIsLessOrEqual(newVariable(), bound) }) + registerSnippet("api/AssertIsCrumb", func(api frontend.API, newVariable func() frontend.Variable) { + api.AssertIsCrumb(newVariable()) + }) + // add std snippets registerSnippet("math/bits.ToBinary", func(api frontend.API, newVariable func() frontend.Variable) { _ = bits.ToBinary(api, newVariable())