From 378dd33a9b5c7c05e59bdb9b2d2aaf73b56fdbbc Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Fri, 12 Jul 2024 13:29:55 +0200 Subject: [PATCH] feat: use blake2 for variable hashcode (#1197) --- frontend/cs/r1cs/builder.go | 4 ++-- frontend/internal/expr/linear_expression.go | 19 ++++++++++++------- frontend/internal/expr/term.go | 21 ++++++++++++++++++--- std/math/emulated/field.go | 6 +++--- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/frontend/cs/r1cs/builder.go b/frontend/cs/r1cs/builder.go index a070a6c7ad..3102addbb5 100644 --- a/frontend/cs/r1cs/builder.go +++ b/frontend/cs/r1cs/builder.go @@ -58,7 +58,7 @@ type builder struct { kvstore.Store // map for recording boolean constrained variables (to not constrain them twice) - mtBooleans map[uint64][]expr.LinearExpression + mtBooleans map[[16]byte][]expr.LinearExpression tOne constraint.Element eZero, eOne expr.LinearExpression @@ -82,7 +82,7 @@ func newBuilder(field *big.Int, config frontend.CompileConfig) *builder { macCapacity = config.CompressThreshold } builder := builder{ - mtBooleans: make(map[uint64][]expr.LinearExpression, config.Capacity/10), + mtBooleans: make(map[[16]byte][]expr.LinearExpression, config.Capacity/10), config: config, heap: make(minHeap, 0, 100), mbuf1: make(expr.LinearExpression, 0, macCapacity), diff --git a/frontend/internal/expr/linear_expression.go b/frontend/internal/expr/linear_expression.go index 682bf0985d..8aabe7ed95 100644 --- a/frontend/internal/expr/linear_expression.go +++ b/frontend/internal/expr/linear_expression.go @@ -2,6 +2,7 @@ package expr import ( "github.com/consensys/gnark/constraint" + "golang.org/x/crypto/blake2b" ) type LinearExpression []Term @@ -52,12 +53,16 @@ func (l LinearExpression) Less(i, j int) bool { return iID < jID } -// HashCode returns a fast-to-compute but NOT collision resistant hash code identifier for the linear -// expression -func (l LinearExpression) HashCode() uint64 { - h := uint64(17) - for _, val := range l { - h = h*23 + val.HashCode() // TODO @gbotrel revisit +// HashCode returns a collision-resistant identifier of the linear expression. It is constructed from the hash codes of the terms. +func (l LinearExpression) HashCode() [16]byte { + h, err := blake2b.New256(nil) + if err != nil { + panic(err) } - return h + for i := range l { + termHash := l[i].HashCode() + h.Write(termHash[:]) + } + crc := h.Sum(nil) + return [16]byte(crc[:16]) } diff --git a/frontend/internal/expr/term.go b/frontend/internal/expr/term.go index 5c9ba5877c..ff803dd57f 100644 --- a/frontend/internal/expr/term.go +++ b/frontend/internal/expr/term.go @@ -1,6 +1,11 @@ package expr -import "github.com/consensys/gnark/constraint" +import ( + "encoding/binary" + + "github.com/consensys/gnark/constraint" + "golang.org/x/crypto/blake2b" +) type Term struct { VID int @@ -20,6 +25,16 @@ func (t Term) WireID() int { return t.VID } -func (t Term) HashCode() uint64 { - return t.Coeff[0]*29 + uint64(t.VID<<12) +// HashCode returns a collision resistant hash code identifier for the term. +func (t Term) HashCode() [16]byte { + h, err := blake2b.New256(nil) + if err != nil { + panic(err) + } + h.Write(binary.BigEndian.AppendUint64(nil, uint64(t.VID))) + for i := range t.Coeff { + h.Write(binary.BigEndian.AppendUint64(nil, uint64(t.Coeff[i]))) + } + crc := h.Sum(nil) + return [16]byte(crc[:16]) } diff --git a/std/math/emulated/field.go b/std/math/emulated/field.go index 6c1f19b04d..96bca2fd79 100644 --- a/std/math/emulated/field.go +++ b/std/math/emulated/field.go @@ -43,7 +43,7 @@ type Field[T FieldParams] struct { log zerolog.Logger - constrainedLimbs map[uint64]struct{} + constrainedLimbs map[[16]byte]struct{} checker frontend.Rangechecker mulChecks []mulCheck[T] @@ -69,7 +69,7 @@ func NewField[T FieldParams](native frontend.API) (*Field[T], error) { f := &Field[T]{ api: native, log: logger.Logger(), - constrainedLimbs: make(map[uint64]struct{}), + constrainedLimbs: make(map[[16]byte]struct{}), checker: rangecheck.New(native), } @@ -216,7 +216,7 @@ func (f *Field[T]) enforceWidthConditional(a *Element[T]) (didConstrain bool) { } continue } - if vv, ok := a.Limbs[i].(interface{ HashCode() uint64 }); ok { + if vv, ok := a.Limbs[i].(interface{ HashCode() [16]byte }); ok { // okay, this is a canonical variable and it has a hashcode. We use // it to see if the limb is already constrained. h := vv.HashCode()