From 980a7b28381798ad61638f2e5e544b253b935179 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 12 Jun 2024 13:32:19 +0000 Subject: [PATCH 1/5] fix: exact width for upper part --- std/math/bitslice/partition.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/math/bitslice/partition.go b/std/math/bitslice/partition.go index f20c6d3513..ed7d1170a1 100644 --- a/std/math/bitslice/partition.go +++ b/std/math/bitslice/partition.go @@ -58,7 +58,7 @@ func Partition(api frontend.API, v frontend.Variable, split uint, opts ...Option if opt.digits > 0 { upperBound = opt.digits } - rh.Check(upper, upperBound) + rh.Check(upper, upperBound-int(split)) rh.Check(lower, int(split)) m := big.NewInt(1) From f187f0e9f38c7dffeb3ff19bf2852215f157df95 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 12 Jun 2024 14:26:56 +0000 Subject: [PATCH 2/5] docs: add documentation --- std/math/bitslice/opts.go | 8 ++++++++ std/math/bitslice/partition.go | 1 + 2 files changed, 9 insertions(+) diff --git a/std/math/bitslice/opts.go b/std/math/bitslice/opts.go index fa333efb1a..fa55cfae79 100644 --- a/std/math/bitslice/opts.go +++ b/std/math/bitslice/opts.go @@ -17,8 +17,14 @@ func parseOpts(opts ...Option) (*opt, error) { return o, nil } +// Option allows to customize the behavior of functions in this package. See +// [WithNbDigits] and [WithUnconstrainedOutputs] for examples. type Option func(*opt) error +// WithNbDigits sets the bound on the number of digits the input can have. If +// this is not set, then we use standard binary decomposition of the input. If +// it is set and it is less than the width of the native field, then we use +// lookup table based method for bounding the inputs which is more efficient. func WithNbDigits(nbDigits int) Option { return func(o *opt) error { if nbDigits < 1 { @@ -29,6 +35,8 @@ func WithNbDigits(nbDigits int) Option { } } +// WithUnconstrainedOutputs allows to skip the output decomposition and outputs +// width checks. Can be used when these are performed by the caller. func WithUnconstrainedOutputs() Option { return func(o *opt) error { o.nocheck = true diff --git a/std/math/bitslice/partition.go b/std/math/bitslice/partition.go index ed7d1170a1..677e6b81a4 100644 --- a/std/math/bitslice/partition.go +++ b/std/math/bitslice/partition.go @@ -1,3 +1,4 @@ +// Package bitslice allows partitioning variables. package bitslice import ( From c57b531dd9a3bfbf9e899f696fc3f8066a35aaeb Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 12 Jun 2024 14:27:17 +0000 Subject: [PATCH 3/5] fix: defer to math.bits when nbdigits big or not set --- std/math/bitslice/partition.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/std/math/bitslice/partition.go b/std/math/bitslice/partition.go index 677e6b81a4..7b07c3f983 100644 --- a/std/math/bitslice/partition.go +++ b/std/math/bitslice/partition.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/bits" "github.com/consensys/gnark/std/rangecheck" ) @@ -41,6 +42,19 @@ func Partition(api frontend.API, v frontend.Variable, split uint, opts ...Option } return 0, v } + // when nbDigits is not set, then we assume the bound is the field size. + // However, in that case the decomposition check is more involved as we need + // to avoid the recomposed value to overflow the field. We do not have good + // methods for avoiding it when using range checker gadget, so we defer it + // to `bits.ToBinary`. + if opt.digits == 0 || opt.digits >= api.Compiler().FieldBitLen() { + bts := bits.ToBinary(api, v) + lowerBts := bts[:split] + upperBts := bts[split:] + lower = bits.FromBinary(api, lowerBts) + upper = bits.FromBinary(api, upperBts) + return lower, upper + } ret, err := api.Compiler().NewHint(partitionHint, 2, split, v) if err != nil { panic(err) From 91ca00ae6afd810c9945f2de4c6a52091eacf899 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 12 Jun 2024 14:39:04 +0000 Subject: [PATCH 4/5] test: add issue 1153 repro --- std/math/bitslice/partition_test.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/std/math/bitslice/partition_test.go b/std/math/bitslice/partition_test.go index 0f92740a93..a098e824b7 100644 --- a/std/math/bitslice/partition_test.go +++ b/std/math/bitslice/partition_test.go @@ -7,13 +7,21 @@ import ( "github.com/consensys/gnark/test" ) +// TODO: add option for choosing nbDigits + type partitionCircuit struct { Split uint In, ExpLower, ExpUpper frontend.Variable + + nbDigitsOpt int } func (c *partitionCircuit) Define(api frontend.API) error { - lower, upper := Partition(api, c.In, c.Split) + var opts []Option + if c.nbDigitsOpt > 0 { + opts = append(opts, WithNbDigits(c.nbDigitsOpt)) + } + lower, upper := Partition(api, c.In, c.Split, opts...) api.AssertIsEqual(lower, c.ExpLower) api.AssertIsEqual(upper, c.ExpUpper) return nil @@ -26,3 +34,8 @@ func TestPartition(t *testing.T) { assert.CheckCircuit(&partitionCircuit{Split: 16}, test.WithValidAssignment(&partitionCircuit{Split: 16, ExpUpper: 0xffff, ExpLower: 0x1234, In: 0xffff1234})) assert.CheckCircuit(&partitionCircuit{Split: 32}, test.WithValidAssignment(&partitionCircuit{Split: 32, ExpUpper: 0, ExpLower: 0xffff1234, In: 0xffff1234})) } + +func TestIssue1153(t *testing.T) { + assert := test.NewAssert(t) + assert.CheckCircuit(&partitionCircuit{Split: 8, nbDigitsOpt: 16}, test.WithInvalidAssignment(&partitionCircuit{ExpUpper: 0xff1, ExpLower: 0x21, In: 0xff121})) +} From 9c41095908d3b9445a281465372f7c54297e2b26 Mon Sep 17 00:00:00 2001 From: AlexandreBelling Date: Fri, 28 Jun 2024 12:32:47 +0200 Subject: [PATCH 5/5] make exp_mod work with parametric fields (#1182) --- std/evmprecompiles/05-expmod.go | 5 ++--- std/math/emulated/emparams/emparams.go | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/std/evmprecompiles/05-expmod.go b/std/evmprecompiles/05-expmod.go index 442ba88095..f1fa11357f 100644 --- a/std/evmprecompiles/05-expmod.go +++ b/std/evmprecompiles/05-expmod.go @@ -5,7 +5,6 @@ import ( "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/math/emulated" - "github.com/consensys/gnark/std/math/emulated/emparams" ) // Expmod implements [MODEXP] precompile contract at address 0x05. @@ -15,10 +14,10 @@ import ( // the actual length of the inputs. // // [MODEXP]: https://ethereum.github.io/execution-specs/autoapi/ethereum/paris/vm/precompiled_contracts/expmod/index.html -func Expmod(api frontend.API, base, exp, modulus *emulated.Element[emparams.Mod1e4096]) *emulated.Element[emparams.Mod1e4096] { +func Expmod[P emulated.FieldParams](api frontend.API, base, exp, modulus *emulated.Element[P]) *emulated.Element[P] { // x^0 = 1 // x mod 0 = 0 - f, err := emulated.NewField[emparams.Mod1e4096](api) + f, err := emulated.NewField[P](api) if err != nil { panic(fmt.Sprintf("new field: %v", err)) } diff --git a/std/math/emulated/emparams/emparams.go b/std/math/emulated/emparams/emparams.go index bbee1afabf..22e7872203 100644 --- a/std/math/emulated/emparams/emparams.go +++ b/std/math/emulated/emparams/emparams.go @@ -319,3 +319,22 @@ func (Mod1e512) Modulus() *big.Int { val, _ := new(big.Int).SetString("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16) return val } + +// Mod1e256 provides type parametrization for emulated aritmetic: +// - limbs: 4 +// - limb width: 64 bits +// +// The modulus for type parametrisation is 2^256-1. +// +// This is non-prime modulus. It is mainly targeted for using variable-modulus +// operations (ModAdd, ModMul, ModExp, ModAssertIsEqual) for variable modulus +// arithmetic. +type Mod1e256 struct{} + +func (Mod1e256) NbLimbs() uint { return 4 } +func (Mod1e256) BitsPerLimb() uint { return 64 } +func (Mod1e256) IsPrime() bool { return false } +func (Mod1e256) Modulus() *big.Int { + val, _ := new(big.Int).SetString("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16) + return val +}