Skip to content

Commit

Permalink
Implement Ecdoubleslopev1 hint (#341)
Browse files Browse the repository at this point in the history
* Implement EcNegate hint

* Implement NondetBigint3V1 hint

* Fix context usage + add some functions to return constants

* Add scope check test utils + usage

* More comments and tests

* More tests

* Clean up some code

* Implement FastEcAddAssignNewY hint

* Implement FastEcAddAssignNewX hint

* Make hint code less verbose

* Add more tests

* Fix comment formatting

* Clean up EcNegate using new util functions

* Implement EcDoubleSlopeV1 hint

* Use FeltZero/FeltOne in tests

* Use FeltZero in tests

* Add secp util functions to clean up hint

* Make function private

* Moving comments around

* Fix import

* Change zero compare

* Make return simpler

* Cleaner return

* Add more tests

* Comment test values

* Comment test values

* Comment test values

* Comment test values

* Update comment

* Simpler context init in tests

* Add GetVariableValueAsBigInt method to scope manager

* Simpler tests

* Remove duplicate test check

* Clean up scope usage

* Use existing constant

* Simpler loop

* Add AssignVariables method to scope manager + use it

* Use AssignVariables in FastEcAddAssignNewY

* Use util methods to make code simpler

* Use feltUint64 when applicable

* Change argument to accept slice

* Add tests for div_mod and igcdex

* Fetch SECP_P from scope in FastEcAddAssignNewY

* Use InitializeScopeManager

* Return big.Int instead of *big.Int

* More return big.Int instead of *big.Int

* Rename to divmod

* Change signature of EcDoubleSlope

* More return big.Int instead of *big.Int

* Remove redundant tests

* Fix some nits

* Return a new big.Int for sign function

---------

Co-authored-by: Carmen Cabrera <kr1000a@gmail.com>
  • Loading branch information
har777 and cicr99 committed Apr 19, 2024
1 parent 7d292fa commit fd3723a
Show file tree
Hide file tree
Showing 11 changed files with 748 additions and 399 deletions.
86 changes: 86 additions & 0 deletions pkg/hintrunner/utils/math_utils.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
package utils

import (
"errors"
"fmt"
"math/big"

"github.com/consensys/gnark-crypto/ecc/stark-curve/fp"
)

func EcDoubleSlope(pointX, pointY, alpha, prime *big.Int) (big.Int, error) {
// https://github.com/starkware-libs/cairo-lang/blob/efa9648f57568aad8f8a13fbf027d2de7c63c2c0/src/starkware/python/math_utils.py#L151

if new(big.Int).Mod(pointY, prime).Cmp(big.NewInt(0)) == 0 {
return *big.NewInt(0), errors.New("point[1] % p == 0")
}

n := big.NewInt(3)
n.Mul(n, pointX)
n.Mul(n, pointX)
n.Add(n, alpha)

m := big.NewInt(2)
m.Mul(m, pointY)

return divmod(n, m, prime)
}

func AsInt(valueFelt *fp.Element) big.Int {
// https://github.com/starkware-libs/cairo-lang/blob/efa9648f57568aad8f8a13fbf027d2de7c63c2c0/src/starkware/cairo/common/math_utils.py#L8

var valueBig big.Int
valueFelt.BigInt(&valueBig)
return AsIntBig(&valueBig)
Expand All @@ -23,6 +44,71 @@ func AsIntBig(value *big.Int) big.Int {
return *new(big.Int).Sub(value, fp.Modulus())
}

func divmod(n, m, p *big.Int) (big.Int, error) {
// https://github.com/starkware-libs/cairo-lang/blob/efa9648f57568aad8f8a13fbf027d2de7c63c2c0/src/starkware/python/math_utils.py#L26

a, _, c := igcdex(m, p)
if c.Cmp(big.NewInt(1)) != 0 {
return *big.NewInt(0), errors.New("no solution exists (gcd(m, p) != 1)")
}
res := new(big.Int)
res.Mul(n, &a)
res.Mod(res, p)
return *res, nil
}

func igcdex(a, b *big.Int) (big.Int, big.Int, big.Int) {
// https://github.com/sympy/sympy/blob/d91b8ad6d36a59a879cc70e5f4b379da5fdd46ce/sympy/core/intfunc.py#L362

if a.Cmp(big.NewInt(0)) == 0 && b.Cmp(big.NewInt(0)) == 0 {
return *big.NewInt(0), *big.NewInt(1), *big.NewInt(0)
}
g, x, y := gcdext(a, b)
return x, y, g
}

func gcdext(a, b *big.Int) (big.Int, big.Int, big.Int) {
// https://github.com/sympy/sympy/blob/d91b8ad6d36a59a879cc70e5f4b379da5fdd46ce/sympy/external/ntheory.py#L125

if a.Cmp(big.NewInt(0)) == 0 || b.Cmp(big.NewInt(0)) == 0 {
g := new(big.Int)
if a.Cmp(big.NewInt(0)) == 0 {
g.Abs(b)
} else {
g.Abs(a)
}

if g.Cmp(big.NewInt(0)) == 0 {
return *big.NewInt(0), *big.NewInt(0), *big.NewInt(0)
}
return *g, *new(big.Int).Div(a, g), *new(big.Int).Div(b, g)
}

xSign, aSigned := sign(a)
ySign, bSigned := sign(b)
x, r := big.NewInt(1), big.NewInt(0)
y, s := big.NewInt(0), big.NewInt(1)

for bSigned.Sign() != 0 {
q, c := new(big.Int).DivMod(&aSigned, &bSigned, new(big.Int))
aSigned = bSigned
bSigned = *c
x, r = r, new(big.Int).Sub(x, new(big.Int).Mul(q, r))
y, s = s, new(big.Int).Sub(y, new(big.Int).Mul(q, s))
}

return aSigned, *new(big.Int).Mul(x, big.NewInt(int64(xSign))), *new(big.Int).Mul(y, big.NewInt(int64(ySign)))
}

func sign(n *big.Int) (int, big.Int) {
// https://github.com/sympy/sympy/blob/d91b8ad6d36a59a879cc70e5f4b379da5fdd46ce/sympy/external/ntheory.py#L119

if n.Sign() < 0 {
return -1, *new(big.Int).Abs(n)
}
return 1, *new(big.Int).Set(n)
}

func SafeDiv(x, y *big.Int) (big.Int, error) {
if y.Cmp(big.NewInt(0)) == 0 {
return *big.NewInt(0), fmt.Errorf("Division by zero.")
Expand Down
114 changes: 114 additions & 0 deletions pkg/hintrunner/utils/math_utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package utils

import (
"math/big"
"testing"
)

func TestDivMod(t *testing.T) {
// https://github.com/starkware-libs/cairo-lang/blob/efa9648f57568aad8f8a13fbf027d2de7c63c2c0/src/starkware/python/math_utils_test.py#L108
tests := []struct {
name string
n, m, p *big.Int
expected *big.Int
expectedErrMsg string
}{
{
name: "Basic case",
n: big.NewInt(2),
m: big.NewInt(3),
p: big.NewInt(5),
expected: big.NewInt(4),
},
{
name: "Error case",
n: big.NewInt(8),
m: big.NewInt(10),
p: big.NewInt(5),
expectedErrMsg: "no solution exists (gcd(m, p) != 1)",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual, err := divmod(tt.n, tt.m, tt.p)

if err != nil {
if err.Error() != tt.expectedErrMsg {
t.Errorf("got error: %v, want: %v", err, tt.expectedErrMsg)
}
return
}

if actual.Cmp(tt.expected) != 0 {
t.Errorf("got quotient: %v, want: %v", actual, tt.expected)
}
})
}
}

func TestIgcdex(t *testing.T) {
// https://github.com/sympy/sympy/blob/e7fb2714f17b30b83e424448aad0da9e94a4b577/sympy/core/tests/test_numbers.py#L278
tests := []struct {
name string
a, b *big.Int
expectedX, expectedY, expectedG *big.Int
}{
{
name: "Case 1",
a: big.NewInt(2),
b: big.NewInt(3),
expectedX: big.NewInt(-1),
expectedY: big.NewInt(1),
expectedG: big.NewInt(1),
},
{
name: "Case 2",
a: big.NewInt(10),
b: big.NewInt(12),
expectedX: big.NewInt(-1),
expectedY: big.NewInt(1),
expectedG: big.NewInt(2),
},
{
name: "Case 3",
a: big.NewInt(100),
b: big.NewInt(2004),
expectedX: big.NewInt(-20),
expectedY: big.NewInt(1),
expectedG: big.NewInt(4),
},
{
name: "Case 4",
a: big.NewInt(0),
b: big.NewInt(0),
expectedX: big.NewInt(0),
expectedY: big.NewInt(1),
expectedG: big.NewInt(0),
},
{
name: "Case 5",
a: big.NewInt(1),
b: big.NewInt(0),
expectedX: big.NewInt(1),
expectedY: big.NewInt(0),
expectedG: big.NewInt(1),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actualX, actualY, actualG := igcdex(tt.a, tt.b)

if actualX.Cmp(tt.expectedX) != 0 {
t.Errorf("got x: %v, want: %v", actualX, tt.expectedX)
}
if actualY.Cmp(tt.expectedY) != 0 {
t.Errorf("got x: %v, want: %v", actualY, tt.expectedY)
}
if actualG.Cmp(tt.expectedG) != 0 {
t.Errorf("got x: %v, want: %v", actualG, tt.expectedG)
}
})
}
}
31 changes: 17 additions & 14 deletions pkg/hintrunner/utils/secp_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,35 @@ import (
"github.com/consensys/gnark-crypto/ecc/stark-curve/fp"
)

func getBaseBig() (*big.Int, bool) {
func getBaseBig() (big.Int, bool) {
// 2**86
return new(big.Int).SetString("77371252455336267181195264", 10)
base, ok := new(big.Int).SetString("77371252455336267181195264", 10)
return *base, ok
}

func GetSecPBig() (*big.Int, bool) {
func GetSecPBig() (big.Int, bool) {
// 2**256 - 2**32 - 2**9 - 2**8 - 2**7 - 2**6 - 2**4 - 1
return new(big.Int).SetString("115792089237316195423570985008687907853269984665640564039457584007908834671663", 10)
secP, ok := new(big.Int).SetString("115792089237316195423570985008687907853269984665640564039457584007908834671663", 10)
return *secP, ok
}

func SecPPacked(limbs [3]*fp.Element) (*big.Int, error) {
func SecPPacked(limbs [3]*fp.Element) (big.Int, error) {
// https://github.com/starkware-libs/cairo-lang/blob/efa9648f57568aad8f8a13fbf027d2de7c63c2c0/src/starkware/cairo/common/cairo_secp/secp_utils.py#L28

baseBig, ok := getBaseBig()
if !ok {
return nil, fmt.Errorf("getBaseBig failed")
return *big.NewInt(0), fmt.Errorf("getBaseBig failed")
}

packedBig := new(big.Int)
for idx, limb := range limbs {
limbBig := AsInt(limb)
valueToAddBig := new(big.Int).Exp(baseBig, big.NewInt(int64(idx)), nil)
valueToAddBig := new(big.Int).Exp(&baseBig, big.NewInt(int64(idx)), nil)
valueToAddBig.Mul(valueToAddBig, &limbBig)
packedBig.Add(packedBig, valueToAddBig)
}

return packedBig, nil
return *packedBig, nil
}

func GetBetaBig() big.Int {
Expand All @@ -48,10 +50,10 @@ func GetNBig() big.Int {
return *NBig
}

func SecPSplit(num *big.Int) ([]*big.Int, error) {
func SecPSplit(num *big.Int) ([]big.Int, error) {
// https://github.com/starkware-libs/cairo-lang/blob/efa9648f57568aad8f8a13fbf027d2de7c63c2c0/src/starkware/cairo/common/cairo_secp/secp_utils.py#L14

split := make([]*big.Int, 3)
split := make([]big.Int, 3)

baseBig, ok := getBaseBig()
if !ok {
Expand All @@ -60,8 +62,9 @@ func SecPSplit(num *big.Int) ([]*big.Int, error) {

var residue big.Int
for i := 0; i < 3; i++ {
num.DivMod(num, baseBig, &residue)
split[i] = new(big.Int).Set(&residue)
num.DivMod(num, &baseBig, &residue)
splitVal := new(big.Int).Set(&residue)
split[i] = *splitVal
}

if num.Cmp(big.NewInt(0)) != 0 {
Expand All @@ -73,6 +76,6 @@ func SecPSplit(num *big.Int) ([]*big.Int, error) {

func GetSecp256R1_P() (big.Int, bool) {
// 2**256 - 2**224 + 2**192 + 2**96 - 1
secp256r1_p, ok := new(big.Int).SetString("115792089210356248762697446949407573530086143415290314195533631308867097853951", 10)
return *secp256r1_p, ok
secp256R1_P, ok := new(big.Int).SetString("115792089210356248762697446949407573530086143415290314195533631308867097853951", 10)
return *secp256R1_P, ok
}
1 change: 1 addition & 0 deletions pkg/hintrunner/zero/hintcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const (
nondetBigint3V1Code string = "from starkware.cairo.common.cairo_secp.secp_utils import split\n\nsegments.write_arg(ids.res.address_, split(value))"
fastEcAddAssignNewYCode string = "value = new_y = (slope * (x0 - new_x) - y0) % SECP_P"
fastEcAddAssignNewXCode string = "from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack\n\nslope = pack(ids.slope, PRIME)\nx0 = pack(ids.point0.x, PRIME)\nx1 = pack(ids.point1.x, PRIME)\ny0 = pack(ids.point0.y, PRIME)\n\nvalue = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P"
ecDoubleSlopeV1Code string = "from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack\nfrom starkware.python.math_utils import ec_double_slope\n\n# Compute the slope.\nx = pack(ids.point.x, PRIME)\ny = pack(ids.point.y, PRIME)\nvalue = slope = ec_double_slope(point=(x, y), alpha=0, p=SECP_P)"

// ------ Signature hints related code ------
verifyECDSASignatureCode string = "ecdsa_builtin.add_signature(ids.ecdsa_ptr.address_, (ids.signature_r, ids.signature_s))"
Expand Down
2 changes: 2 additions & 0 deletions pkg/hintrunner/zero/zerohint.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ func GetHintFromCode(program *zero.ZeroProgram, rawHint zero.Hint, hintPC uint64
return createFastEcAddAssignNewYHinter()
case fastEcAddAssignNewXCode:
return createFastEcAddAssignNewXHinter(resolver)
case ecDoubleSlopeV1Code:
return createEcDoubleSlopeV1Hinter(resolver)
// Blake hints
case blake2sAddUint256BigendCode:
return createBlake2sAddUint256Hinter(resolver, true)
Expand Down
Loading

0 comments on commit fd3723a

Please sign in to comment.