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

feat: parse range constraints from binfile #375

Merged
merged 4 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/air/gadgets/bits.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func ApplyBitwidthGadget(col uint, nbits uint, schema *air.Schema) {
// Create Column + Constraint
es[i] = air.NewColumnAccess(index+i, 0).Mul(air.NewConst(coefficient))

schema.AddRangeConstraint(index+i, &fr256)
schema.AddRangeConstraint(index+i, fr256)
// Update coefficient
coefficient.Mul(&coefficient, &fr256)
}
Expand Down
10 changes: 8 additions & 2 deletions pkg/air/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ type LookupConstraint = *constraint.LookupConstraint[*ColumnAccess]
// VanishingConstraint captures the essence of a vanishing constraint at the AIR level.
type VanishingConstraint = *constraint.VanishingConstraint[constraint.ZeroTest[Expr]]

// RangeConstraint captures the essence of a range constraints at the AIR level.
type RangeConstraint = *constraint.RangeConstraint[*ColumnAccess]

// PermutationConstraint captures the essence of a permutation constraint at the AIR level.
// Specifically, it represents a constraint that one (or more) columns are a permutation of another.
type PermutationConstraint = *constraint.PermutationConstraint
Expand Down Expand Up @@ -150,8 +153,11 @@ func (p *Schema) AddVanishingConstraint(handle string, context trace.Context, do
}

// AddRangeConstraint appends a new range constraint.
func (p *Schema) AddRangeConstraint(column uint, bound *fr.Element) {
p.constraints = append(p.constraints, constraint.NewRangeConstraint(column, bound))
func (p *Schema) AddRangeConstraint(column uint, bound fr.Element) {
col := p.Columns().Nth(column)
handle := col.QualifiedName(p)
tc := constraint.NewRangeConstraint[*ColumnAccess](handle, col.Context(), NewColumnAccess(column, 0), bound)
p.constraints = append(p.constraints, tc)
}

// ============================================================================
Expand Down
17 changes: 17 additions & 0 deletions pkg/binfile/constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type jsonConstraint struct {
Vanishes *jsonVanishingConstraint
Permutation *jsonPermutationConstraint
Lookup *jsonLookupConstraint
InRange *jsonRangeConstraint
}

type jsonDomain struct {
Expand All @@ -38,6 +39,12 @@ type jsonLookupConstraint struct {
To []jsonTypedExpr `json:"including"`
}

type jsonRangeConstraint struct {
Handle string `json:"handle"`
Expr jsonTypedExpr `json:"exp"`
Max jsonExprConst `json:"max"`
}

// =============================================================================
// Translation
// =============================================================================
Expand Down Expand Up @@ -70,6 +77,16 @@ func (e jsonConstraint) addToSchema(colmap map[uint]uint, schema *hir.Schema) {
}
// Add constraint
schema.AddLookupConstraint(e.Lookup.Handle, sourceCtx, targetCtx, sources, targets)
} else if e.InRange != nil {
// Translate the vanishing expression
expr := e.InRange.Expr.ToHir(colmap, schema)
// Determine enclosing module
ctx := expr.Context(schema)
// Convert bound into max
bound := e.InRange.Max.ToField()
handle := expr.Lisp(schema).String(true)
// Construct the vanishing constraint
schema.AddRangeConstraint(handle, ctx, expr, bound)
} else if e.Permutation == nil {
// Catch all
panic("Unknown JSON constraint encountered")
Expand Down
5 changes: 3 additions & 2 deletions pkg/binfile/constraint_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,9 @@ func allocateRegisters(cs *constraintSet, schema *hir.Schema) map[uint]uint {
// Add column for this
cid := schema.AddDataColumn(ctx, handle.column, col_type)
// Check whether a type constraint required or not.
if c.MustProve {
schema.AddTypeConstraint(cid, col_type)
if c.MustProve && col_type.AsUint() != nil {
bound := col_type.AsUint().Bound()
schema.AddRangeConstraint(c.Handle, ctx, &hir.ColumnAccess{Column: cid, Shift: 0}, *bound)
}
}
}
Expand Down
22 changes: 16 additions & 6 deletions pkg/binfile/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,20 @@ func (e *jsonTypedExpr) ToHir(colmap map[uint]uint, schema *hir.Schema) hir.Expr
// ToHir converts a big integer represented as a sequence of unsigned 32bit
// words into HIR constant expression.
func (e *jsonExprConst) ToHir(schema *hir.Schema) hir.Expr {
return &hir.Constant{Val: e.ToField()}
}

func (e *jsonExprConst) ToField() fr.Element {
var num fr.Element
//
val := e.ToBigInt()
// Construct Field Value
num.SetBigInt(val)
//
return num
}

func (e *jsonExprConst) ToBigInt() *big.Int {
sign := int(e.BigInt[0].(float64))
words := e.BigInt[1].([]any)
// Begin
Expand All @@ -100,12 +114,8 @@ func (e *jsonExprConst) ToHir(schema *hir.Schema) hir.Expr {
} else {
panic(fmt.Sprintf("Unknown BigInt sign: %d", sign))
}
// Construct Field Value
var num fr.Element

num.SetBigInt(val)
// Done!
return &hir.Constant{Val: num}
// Done
return val
}

func (e *jsonExprColumn) ToHir(colmap map[uint]uint, schema *hir.Schema) hir.Expr {
Expand Down
9 changes: 6 additions & 3 deletions pkg/hir/lower.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"github.com/consensys/gnark-crypto/ecc/bls12-377/fr"
"github.com/consensys/go-corset/pkg/mir"
sc "github.com/consensys/go-corset/pkg/schema"
"github.com/consensys/go-corset/pkg/schema/constraint"
)

// LowerToMir lowers (or refines) an HIR table into an MIR schema. That means
Expand Down Expand Up @@ -53,8 +52,12 @@ func lowerConstraintToMir(c sc.Constraint, schema *mir.Schema) {
for _, mir_expr := range mir_exprs {
schema.AddVanishingConstraint(v.Handle(), v.Context(), v.Domain(), mir_expr)
}
} else if v, ok := c.(*constraint.TypeConstraint); ok {
schema.AddTypeConstraint(v.Target(), v.Type())
} else if v, ok := c.(RangeConstraint); ok {
mir_exprs := v.Target().LowerTo(schema)
// Add individual constraints arising
for _, mir_expr := range mir_exprs {
schema.AddRangeConstraint(v.Handle(), v.Context(), mir_expr, v.Bound())
}
} else {
// Should be unreachable as no other constraint types can be added to a
// schema.
Expand Down
39 changes: 38 additions & 1 deletion pkg/hir/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ func (p *hirParser) parseDeclaration(s sexp.SExp) error {
return p.parseLookupDeclaration(e)
} else if e.Len() == 3 && e.MatchSymbols(1, "definterleaved") {
return p.parseInterleavingDeclaration(e)
} else if e.Len() == 3 && e.MatchSymbols(1, "definrange") {
return p.parseRangeDeclaration(e)
}
}
// Error
Expand Down Expand Up @@ -176,7 +178,11 @@ func (p *hirParser) parseColumnDeclaration(e sexp.SExp) error {
}
// Register column
cid := p.env.AddDataColumn(p.module, columnName, columnType)
p.env.schema.AddTypeConstraint(cid, columnType)
// Apply type constraint (if applicable)
if columnType.AsUint() != nil {
bound := columnType.AsUint().Bound()
p.env.schema.AddRangeConstraint(columnName, p.module, &ColumnAccess{cid, 0}, *bound)
}
//
return nil
}
Expand Down Expand Up @@ -409,6 +415,37 @@ func (p *hirParser) parseInterleavingDeclaration(l *sexp.List) error {
return nil
}

// Parse a range constraint
func (p *hirParser) parseRangeDeclaration(l *sexp.List) error {
var bound fr.Element
// Check bound
if l.Get(2).AsSymbol() == nil {
return p.translator.SyntaxError(l.Get(2), "malformed bound")
}
// Parse bound
if _, err := bound.SetString(l.Get(2).AsSymbol().Value); err != nil {
return p.translator.SyntaxError(l.Get(2), "malformed bound")
}
// Parse expression
expr, err := p.translator.Translate(l.Get(1))
if err != nil {
return err
}
// Determine evaluation context of expression.
ctx := expr.Context(p.env.schema)
// Sanity check we have a sensible context here.
if ctx.IsConflicted() {
return p.translator.SyntaxError(l.Get(1), "conflicting evaluation context")
} else if ctx.IsVoid() {
return p.translator.SyntaxError(l.Get(1), "empty evaluation context")
}
//
handle := l.Get(1).String(true)
p.env.schema.AddRangeConstraint(handle, ctx, expr, bound)
//
return nil
}

// Parse a property assertion
func (p *hirParser) parseAssertionDeclaration(elements []sexp.SExp) error {
handle := elements[1].AsSymbol().Value
Expand Down
13 changes: 8 additions & 5 deletions pkg/hir/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package hir
import (
"fmt"

"github.com/consensys/gnark-crypto/ecc/bls12-377/fr"
"github.com/consensys/go-corset/pkg/schema"
sc "github.com/consensys/go-corset/pkg/schema"
"github.com/consensys/go-corset/pkg/schema/assignment"
Expand All @@ -24,6 +25,9 @@ type VanishingConstraint = *constraint.VanishingConstraint[ZeroArrayTest]
// certain expression forms cannot be permitted (e.g. the use of lists).
type LookupConstraint = *constraint.LookupConstraint[UnitExpr]

// RangeConstraint captures the essence of a range constraints at the HIR level.
type RangeConstraint = *constraint.RangeConstraint[MaxExpr]

// PropertyAssertion captures the notion of an arbitrary property which should
// hold for all acceptable traces. However, such a property is not enforced by
// the prover.
Expand Down Expand Up @@ -127,12 +131,11 @@ func (p *Schema) AddVanishingConstraint(handle string, context trace.Context, do
constraint.NewVanishingConstraint(handle, context, domain, ZeroArrayTest{expr}))
}

// AddTypeConstraint appends a new range constraint.
func (p *Schema) AddTypeConstraint(target uint, t sc.Type) {
// AddRangeConstraint appends a new range constraint with a raw bound.
func (p *Schema) AddRangeConstraint(handle string, context trace.Context, expr Expr, bound fr.Element) {
// Check whether is a field type, as these can actually be ignored.
if t.AsField() == nil {
p.constraints = append(p.constraints, constraint.NewTypeConstraint(target, t))
}
maxExpr := MaxExpr{expr}
p.constraints = append(p.constraints, constraint.NewRangeConstraint[MaxExpr](handle, context, maxExpr, bound))
}

// AddPropertyAssertion appends a new property assertion.
Expand Down
78 changes: 78 additions & 0 deletions pkg/hir/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package hir

import (
"github.com/consensys/gnark-crypto/ecc/bls12-377/fr"
"github.com/consensys/go-corset/pkg/mir"
sc "github.com/consensys/go-corset/pkg/schema"
"github.com/consensys/go-corset/pkg/sexp"
tr "github.com/consensys/go-corset/pkg/trace"
Expand Down Expand Up @@ -133,3 +134,80 @@ func (e UnitExpr) RequiredCells(row int, trace tr.Trace) *util.AnySortedSet[tr.C
func (e UnitExpr) Lisp(schema sc.Schema) sexp.SExp {
return e.expr.Lisp(schema)
}

// ============================================================================
// MaxExpr
// ============================================================================

// MaxExpr is an adaptor for a general expression which can be used in
// situations where an Evaluable expression is required. This performs a
// similar function to the ZeroArrayTest, but actually produces a value.
// Specifically, the value produced is always the maximum of all values
// produced. This is only useful in specific situations (e.g. checking range
// constraints).
type MaxExpr struct {
//
expr Expr
}

// NewMaxExpr constructs a unit wrapper around an HIR expression. In essence,
// this introduces a runtime check that the given expression only every reduces
// to a single value. Evaluation of this expression will panic if that
// condition does not hold. The intention is that this error is checked for
// upstream (e.g. as part of the compiler front end).
func NewMaxExpr(expr Expr) MaxExpr {
return MaxExpr{expr}
}

// EvalAt evaluates a column access at a given row in a trace, which returns the
// value at that row of the column in question or nil is that row is
// out-of-bounds.
func (e MaxExpr) EvalAt(k int, trace tr.Trace) fr.Element {
vals := e.expr.EvalAllAt(k, trace)
//
max := fr.NewElement(0)
//
for _, v := range vals {
if max.Cmp(&v) < 0 {
max = v
}
}
//
return max
}

// Bounds returns max shift in either the negative (left) or positive
// direction (right).
func (e MaxExpr) Bounds() util.Bounds {
return e.expr.Bounds()
}

// Context determines the evaluation context (i.e. enclosing module) for this
// expression.
func (e MaxExpr) Context(schema sc.Schema) tr.Context {
return e.expr.Context(schema)
}

// RequiredColumns returns the set of columns on which this term depends.
// That is, columns whose values may be accessed when evaluating this term
// on a given trace.
func (e MaxExpr) RequiredColumns() *util.SortedSet[uint] {
return e.expr.RequiredColumns()
}

// RequiredCells returns the set of trace cells on which this term depends.
// In this case, that is the empty set.
func (e MaxExpr) RequiredCells(row int, trace tr.Trace) *util.AnySortedSet[tr.CellRef] {
return e.expr.RequiredCells(row, trace)
}

// LowerTo lowers a max expressions down to one or more expressions at the MIR level.
func (e MaxExpr) LowerTo(schema *mir.Schema) []mir.Expr {
return e.expr.LowerTo(schema)
}

// Lisp converts this schema element into a simple S-Expression, for example
// so it can be printed.
func (e MaxExpr) Lisp(schema sc.Schema) sexp.SExp {
return e.expr.Lisp(schema)
}
Loading