-
Notifications
You must be signed in to change notification settings - Fork 406
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into feat/plonk_update
- Loading branch information
Showing
17 changed files
with
647 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,31 @@ | ||
package evmprecompiles | ||
|
||
import ( | ||
"fmt" | ||
|
||
"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. | ||
// | ||
// Internally, uses 4k elements for representing the base, exponent and modulus, | ||
// upper bounding the sizes of the inputs. The runtime is constant regardless of | ||
// 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] { | ||
// x^0 = 1 | ||
// x mod 0 = 0 | ||
f, err := emulated.NewField[emparams.Mod1e4096](api) | ||
if err != nil { | ||
panic(fmt.Sprintf("new field: %v", err)) | ||
} | ||
// in case modulus is zero, then need to compute with dummy values and return zero as a result | ||
isZeroMod := f.IsZero(modulus) | ||
modulus = f.Select(isZeroMod, f.One(), modulus) | ||
res := f.ModExp(base, exp, modulus) | ||
res = f.Select(isZeroMod, f.Zero(), res) | ||
return res | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package evmprecompiles | ||
|
||
import ( | ||
"crypto/rand" | ||
"fmt" | ||
"math/big" | ||
"testing" | ||
|
||
"github.com/consensys/gnark-crypto/ecc" | ||
"github.com/consensys/gnark/frontend" | ||
"github.com/consensys/gnark/std/math/emulated" | ||
"github.com/consensys/gnark/std/math/emulated/emparams" | ||
"github.com/consensys/gnark/test" | ||
) | ||
|
||
type expmodCircuit struct { | ||
Base emulated.Element[emparams.Mod1e4096] | ||
Exp emulated.Element[emparams.Mod1e4096] | ||
Mod emulated.Element[emparams.Mod1e4096] | ||
Result emulated.Element[emparams.Mod1e4096] | ||
edgeCases bool | ||
} | ||
|
||
func (c *expmodCircuit) Define(api frontend.API) error { | ||
res := Expmod(api, &c.Base, &c.Exp, &c.Mod) | ||
f, err := emulated.NewField[emparams.Mod1e4096](api) | ||
if err != nil { | ||
return fmt.Errorf("new field: %w", err) | ||
} | ||
if c.edgeCases { | ||
// cannot use ModAssertIsEqual for edge cases. But the output is either | ||
// 0 or 1 so can use AssertIsEqual | ||
f.AssertIsEqual(res, &c.Result) | ||
} else { | ||
// for random case need to use ModAssertIsEqual | ||
f.ModAssertIsEqual(&c.Result, res, &c.Mod) | ||
} | ||
return nil | ||
} | ||
|
||
func testInstance(edgeCases bool, base, exp, modulus, result *big.Int) error { | ||
circuit := &expmodCircuit{edgeCases: edgeCases} | ||
assignment := &expmodCircuit{ | ||
Base: emulated.ValueOf[emparams.Mod1e4096](base), | ||
Exp: emulated.ValueOf[emparams.Mod1e4096](exp), | ||
Mod: emulated.ValueOf[emparams.Mod1e4096](modulus), | ||
Result: emulated.ValueOf[emparams.Mod1e4096](result), | ||
} | ||
return test.IsSolved(circuit, assignment, ecc.BLS12_377.ScalarField()) | ||
} | ||
|
||
func TestRandomInstance(t *testing.T) { | ||
assert := test.NewAssert(t) | ||
for _, bits := range []int{256, 512, 1024, 2048, 4096} { | ||
assert.Run(func(assert *test.Assert) { | ||
modulus := new(big.Int).Lsh(big.NewInt(1), uint(bits)) | ||
base, _ := rand.Int(rand.Reader, modulus) | ||
exp, _ := rand.Int(rand.Reader, modulus) | ||
res := new(big.Int).Exp(base, exp, modulus) | ||
err := testInstance(false, base, exp, modulus, res) | ||
assert.NoError(err) | ||
}, fmt.Sprintf("random-%d", bits)) | ||
} | ||
} | ||
|
||
func TestEdgeCases(t *testing.T) { | ||
assert := test.NewAssert(t) | ||
testCases := []struct { | ||
base, exp, modulus, result *big.Int | ||
}{ | ||
{big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)}, // 0^0 = 0 mod 0 | ||
{big.NewInt(0), big.NewInt(0), big.NewInt(1), big.NewInt(1)}, // 0^0 = 1 mod 1 | ||
{big.NewInt(0), big.NewInt(0), big.NewInt(123), big.NewInt(1)}, // 0^0 = 1 mod 123 | ||
{big.NewInt(123), big.NewInt(123), big.NewInt(0), big.NewInt(0)}, // 123^123 = 0 mod 0 | ||
{big.NewInt(123), big.NewInt(123), big.NewInt(0), big.NewInt(0)}, // 123^123 = 0 mod 1 | ||
{big.NewInt(0), big.NewInt(123), big.NewInt(123), big.NewInt(0)}, // 0^123 = 0 mod 123 | ||
{big.NewInt(123), big.NewInt(0), big.NewInt(123), big.NewInt(1)}, // 123^0 = 1 mod 123 | ||
|
||
} | ||
for i, tc := range testCases { | ||
assert.Run(func(assert *test.Assert) { | ||
err := testInstance(true, tc.base, tc.exp, tc.modulus, tc.result) | ||
assert.NoError(err) | ||
}, fmt.Sprintf("edge-%d", i)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package emulated | ||
|
||
import ( | ||
"errors" | ||
|
||
"github.com/consensys/gnark/frontend" | ||
) | ||
|
||
// ModMul computes a*b mod modulus. Instead of taking modulus as a constant | ||
// parametrized by T, it is passed as an argument. This allows to use a variable | ||
// modulus in the circuit. Type parameter T should be sufficiently big to fit a, | ||
// b and modulus. Recommended to use [emparams.Mod1e512] or | ||
// [emparams.Mod1e4096]. | ||
// | ||
// NB! circuit complexity depends on T rather on the actual length of the modulus. | ||
func (f *Field[T]) ModMul(a, b *Element[T], modulus *Element[T]) *Element[T] { | ||
res := f.mulMod(a, b, 0, modulus) | ||
return res | ||
} | ||
|
||
// ModAdd computes a+b mod modulus. Instead of taking modulus as a constant | ||
// parametrized by T, it is passed as an argument. This allows to use a variable | ||
// modulus in the circuit. Type parameter T should be sufficiently big to fit a, | ||
// b and modulus. Recommended to use [emparams.Mod1e512] or | ||
// [emparams.Mod1e4096]. | ||
// | ||
// NB! circuit complexity depends on T rather on the actual length of the modulus. | ||
func (f *Field[T]) ModAdd(a, b *Element[T], modulus *Element[T]) *Element[T] { | ||
// inlined version of [Field.reduceAndOp] which uses variable-modulus reduction | ||
var nextOverflow uint | ||
var err error | ||
var target overflowError | ||
for nextOverflow, err = f.addPreCond(a, b); errors.As(err, &target); nextOverflow, err = f.addPreCond(a, b) { | ||
if errors.As(err, &target) { | ||
if !target.reduceRight { | ||
a = f.mulMod(a, f.shortOne(), 0, modulus) | ||
} else { | ||
b = f.mulMod(b, f.shortOne(), 0, modulus) | ||
} | ||
} | ||
} | ||
res := f.add(a, b, nextOverflow) | ||
return res | ||
} | ||
|
||
func (f *Field[T]) modSub(a, b *Element[T], modulus *Element[T]) *Element[T] { | ||
// like fixed modulus subtraction, but for sub padding need to use hint | ||
// instead of assuming T as a constant. And when doing as a hint, then need | ||
// to assert that the padding is a multiple of the modulus (done inside callSubPaddingHint) | ||
nextOverflow := max(b.overflow+1, a.overflow) + 1 | ||
nbLimbs := max(len(a.Limbs), len(b.Limbs)) | ||
limbs := make([]frontend.Variable, nbLimbs) | ||
padding := f.computeSubPaddingHint(b.overflow, uint(nbLimbs), modulus) | ||
for i := range limbs { | ||
limbs[i] = padding.Limbs[i] | ||
if i < len(a.Limbs) { | ||
limbs[i] = f.api.Add(limbs[i], a.Limbs[i]) | ||
} | ||
if i < len(b.Limbs) { | ||
limbs[i] = f.api.Sub(limbs[i], b.Limbs[i]) | ||
} | ||
} | ||
res := f.newInternalElement(limbs, nextOverflow) | ||
return res | ||
} | ||
|
||
// ModAssertIsEqual asserts equality of a and b mod modulus. Instead of taking | ||
// modulus as a constant parametrized by T, it is passed as an argument. This | ||
// allows to use a variable modulus in the circuit. Type parameter T should be | ||
// sufficiently big to fit a, b and modulus. Recommended to use | ||
// [emparams.Mod1e512] or [emparams.Mod1e4096]. | ||
// | ||
// NB! circuit complexity depends on T rather on the actual length of the modulus. | ||
func (f *Field[T]) ModAssertIsEqual(a, b *Element[T], modulus *Element[T]) { | ||
// like fixed modulus AssertIsEqual, but uses current Sub implementation for | ||
// computing the diff | ||
diff := f.modSub(b, a, modulus) | ||
f.checkZero(diff, modulus) | ||
} | ||
|
||
// ModExp computes base^exp mod modulus. Instead of taking modulus as a constant | ||
// parametrized by T, it is passed as an argument. This allows to use a variable | ||
// modulus in the circuit. Type parameter T should be sufficiently big to fit | ||
// base, exp and modulus. Recommended to use [emparams.Mod1e512] or | ||
// [emparams.Mod1e4096]. | ||
// | ||
// NB! circuit complexity depends on T rather on the actual length of the modulus. | ||
func (f *Field[T]) ModExp(base, exp, modulus *Element[T]) *Element[T] { | ||
expBts := f.ToBits(exp) | ||
n := len(expBts) | ||
res := f.Select(expBts[0], base, f.One()) | ||
base = f.ModMul(base, base, modulus) | ||
for i := 1; i < n-1; i++ { | ||
res = f.Select(expBts[i], f.ModMul(base, res, modulus), res) | ||
base = f.ModMul(base, base, modulus) | ||
} | ||
res = f.Select(expBts[n-1], f.ModMul(base, res, modulus), res) | ||
return res | ||
} |
Oops, something went wrong.