Skip to content

Commit

Permalink
refactor: remove StaticHint wrapper, log duplicate hints (#289)
Browse files Browse the repository at this point in the history
* refactor: remove statichint

* feat: log.Warn when duplicate hint registration.

* style: code cleaning
  • Loading branch information
gbotrel authored Mar 23, 2022
1 parent 8770c18 commit bad1f2e
Show file tree
Hide file tree
Showing 22 changed files with 127 additions and 184 deletions.
21 changes: 16 additions & 5 deletions backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,19 @@ type ProverOption func(*ProverConfig) error

// ProverConfig is the configuration for the prover with the options applied.
type ProverConfig struct {
Force bool // defaults to false
HintFunctions []hint.Function // defaults to all built-in hint functions
CircuitLogger zerolog.Logger // defaults to gnark.Logger
Force bool // defaults to false
HintFunctions map[hint.ID]hint.Function // defaults to all built-in hint functions
CircuitLogger zerolog.Logger // defaults to gnark.Logger
}

// NewProverConfig returns a default ProverConfig with given prover options opts
// applied.
func NewProverConfig(opts ...ProverOption) (ProverConfig, error) {
log := logger.Logger()
opt := ProverConfig{CircuitLogger: log, HintFunctions: hint.GetAll()}
opt := ProverConfig{CircuitLogger: log, HintFunctions: make(map[hint.ID]hint.Function)}
for _, v := range hint.GetRegistered() {
opt.HintFunctions[hint.UUID(v)] = v
}
for _, option := range opts {
if err := option(&opt); err != nil {
return ProverConfig{}, err
Expand All @@ -86,10 +89,18 @@ func IgnoreSolverError() ProverOption {
// WithHints is a prover option that specifies additional hint functions to be used
// by the constraint solver.
func WithHints(hintFunctions ...hint.Function) ProverOption {
log := logger.Logger()
return func(opt *ProverConfig) error {
// it is an error to register hint function several times, but as the
// prover already checks it then omit here.
opt.HintFunctions = append(opt.HintFunctions, hintFunctions...)
for _, h := range hintFunctions {
uuid := hint.UUID(h)
if _, ok := opt.HintFunctions[uuid]; ok {
log.Warn().Int("hintID", int(uuid)).Str("name", hint.Name(h)).Msg("duplicate hint function")
} else {
opt.HintFunctions[uuid] = h
}
}
return nil
}
}
Expand Down
12 changes: 4 additions & 8 deletions backend/hint/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,14 @@ import (
"github.com/consensys/gnark-crypto/ecc"
)

var (
// IsZero computes the value 1 - a^(modulus-1) for the single input a. This
// corresponds to checking if a == 0 (for which the function returns 1) or a
// != 0 (for which the function returns 0).
IsZero = NewStaticHint(isZero)
)

func init() {
Register(IsZero)
}

func isZero(curveID ecc.ID, inputs []*big.Int, results []*big.Int) error {
// IsZero computes the value 1 - a^(modulus-1) for the single input a. This
// corresponds to checking if a == 0 (for which the function returns 1) or a
// != 0 (for which the function returns 0).
func IsZero(curveID ecc.ID, inputs []*big.Int, results []*big.Int) error {
result := results[0]

// get fr modulus
Expand Down
50 changes: 12 additions & 38 deletions backend/hint/hint.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,37 +76,19 @@ import (
// ID is a unique identifier for a hint function used for lookup.
type ID uint32

// StaticFunction is a function which takes a constant number of inputs and
// returns a constant number of outputs. Use NewStaticHint() to construct an
// instance compatible with Function interface.
type StaticFunction func(curveID ecc.ID, inputs []*big.Int, outputs []*big.Int) error

// Function defines an annotated hint function. To initialize a hint function
// with static number of inputs and outputs, use NewStaticHint().
type Function interface {
// UUID returns an unique identifier for the hint function. UUID is used for
// lookup of the hint function.
UUID() ID

// Call is invoked by the framework to obtain the result from inputs.
// Elements in outputs are not guaranteed to be initialized to 0
Call(curveID ecc.ID, inputs []*big.Int, outputs []*big.Int) error

// String returns a human-readable description of the function used in logs
// and debug messages.
String() string
}

func NewStaticHint(fn StaticFunction) Function {
return fn
}
// Function defines an annotated hint function; the number of inputs and outputs injected at solving
// time is defined in the circuit (compile time).
//
// For example:
// b := api.NewHint(hint, 2, a)
// --> at solving time, hint is going to be invoked with 1 input (a) and is expected to return 2 outputs
// b[0] and b[1].
type Function func(curveID ecc.ID, inputs []*big.Int, outputs []*big.Int) error

// UUID is a reference function for computing the hint ID based on a function name
func UUID(fn StaticFunction) ID {
func UUID(fn Function) ID {
hf := fnv.New32a()
name := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
// using a name for identifying different hints should be enough as we get a
// solve-time error when there are duplicate hints with the same signature.
name := Name(fn)

// TODO relying on name to derive UUID is risky; if fn is an anonymous func, wil be package.glob..funcN
// and if new anonymous functions are added in the package, N may change, so will UUID.
Expand All @@ -115,15 +97,7 @@ func UUID(fn StaticFunction) ID {
return ID(hf.Sum32())
}

func (h StaticFunction) Call(curveID ecc.ID, inputs []*big.Int, res []*big.Int) error {
return h(curveID, inputs, res)
}

func (h StaticFunction) UUID() ID {
return UUID(h)
}

func (h StaticFunction) String() string {
fnptr := reflect.ValueOf(h).Pointer()
func Name(fn Function) string {
fnptr := reflect.ValueOf(fn).Pointer()
return runtime.FuncForPC(fnptr).Name()
}
18 changes: 10 additions & 8 deletions backend/hint/registry.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
package hint

import (
"fmt"
"sync"

"github.com/consensys/gnark/logger"
)

var registry = make(map[ID]Function)
var registryM sync.RWMutex

// Register registers an annotated hint function in the global registry. All
// registered hint functions can be retrieved with a call to GetAll(). It is an
// error to register a single function twice and results in a panic.
// Register registers an hint function in the global registry.
func Register(hintFn Function) {
registryM.Lock()
defer registryM.Unlock()
key := hintFn.UUID()
key := UUID(hintFn)
name := Name(hintFn)
if _, ok := registry[key]; ok {
panic(fmt.Sprintf("function %s registered twice", hintFn))
log := logger.Logger()
log.Warn().Str("name", name).Msg("function registered multiple times")
return
}
registry[key] = hintFn
}

// GetAll returns all registered hint functions.
func GetAll() []Function {
// GetRegistered returns all registered hint functions.
func GetRegistered() []Function {
registryM.RLock()
defer registryM.RUnlock()
ret := make([]Function, 0, len(registry))
Expand Down
4 changes: 2 additions & 2 deletions frontend/cs/r1cs/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ func (system *r1cs) NewHint(f hint.Function, nbOutputs int, inputs ...frontend.V
}

// register the hint as dependency
hintUUID, hintID := f.UUID(), f.String()
hintUUID, hintID := hint.UUID(f), hint.Name(f)
if id, ok := system.MHintsDependencies[hintUUID]; ok {
// hint already registered, let's ensure string id matches
if id != hintID {
Expand Down Expand Up @@ -636,7 +636,7 @@ func (system *r1cs) NewHint(f hint.Function, nbOutputs int, inputs ...frontend.V
res[i] = r
}

ch := &compiled.Hint{ID: f.UUID(), Inputs: hintInputs, Wires: varIDs}
ch := &compiled.Hint{ID: hintUUID, Inputs: hintInputs, Wires: varIDs}
for _, vID := range varIDs {
system.MHints[vID] = ch
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/cs/scs/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ func (system *scs) NewHint(f hint.Function, nbOutputs int, inputs ...frontend.Va
}

// register the hint as dependency
hintUUID, hintID := f.UUID(), f.String()
hintUUID, hintID := hint.UUID(f), hint.Name(f)
if id, ok := system.MHintsDependencies[hintUUID]; ok {
// hint already registered, let's ensure string id matches
if id != hintID {
Expand Down Expand Up @@ -541,7 +541,7 @@ func (system *scs) NewHint(f hint.Function, nbOutputs int, inputs ...frontend.Va
res[i] = r
}

ch := &compiled.Hint{ID: f.UUID(), Inputs: hintInputs, Wires: varIDs}
ch := &compiled.Hint{ID: hintUUID, Inputs: hintInputs, Wires: varIDs}
for _, vID := range varIDs {
system.MHints[vID] = ch
}
Expand Down
13 changes: 3 additions & 10 deletions internal/backend/bls12-377/cs/solution.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 3 additions & 10 deletions internal/backend/bls12-381/cs/solution.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 3 additions & 10 deletions internal/backend/bls24-315/cs/solution.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 3 additions & 10 deletions internal/backend/bn254/cs/solution.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 3 additions & 10 deletions internal/backend/bw6-633/cs/solution.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 3 additions & 10 deletions internal/backend/bw6-761/cs/solution.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit bad1f2e

Please sign in to comment.