Skip to content

Commit

Permalink
Implement Nondetbigint3v1 hint (#331)
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

* Use FeltZero/FeltOne in tests

* Use FeltZero in tests

* Add secp util functions to clean up hint

* Make function private

* Moving comments around

* Comment test values

* Simpler context init in tests

* Add GetVariableValueAsBigInt method to scope manager

* Simpler tests

* Remove duplicate test check
  • Loading branch information
har777 committed Apr 8, 2024
1 parent 3f7673d commit 87bff04
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 26 deletions.
15 changes: 15 additions & 0 deletions pkg/hintrunner/hinter/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package hinter

import (
"fmt"
"math/big"
)

// ScopeManager handles all operations regarding scopes:
Expand Down Expand Up @@ -67,6 +68,20 @@ func (sm *ScopeManager) GetVariableValue(name string) (any, error) {
return nil, fmt.Errorf("variable %s not found in current scope", name)
}

func (sm *ScopeManager) GetVariableValueAsBigInt(name string) (*big.Int, error) {
value, err := sm.GetVariableValue(name)
if err != nil {
return nil, err
}

valueBig, ok := value.(*big.Int)
if !ok {
return nil, fmt.Errorf("value: %s is not a *big.Int", value)
}

return valueBig, nil
}

func (sm *ScopeManager) getCurrentScope() (*map[string]any, error) {
if len(sm.scopes) == 0 {
return nil, fmt.Errorf("expected at least one existing scope")
Expand Down
23 changes: 23 additions & 0 deletions pkg/hintrunner/utils/secp_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,26 @@ func SecPPacked(limbs [3]*fp.Element) (*big.Int, error) {

return packedBig, nil
}

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)

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

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

if num.Cmp(big.NewInt(0)) != 0 {
return nil, fmt.Errorf("num != 0")
}

return split, nil
}
3 changes: 2 additions & 1 deletion pkg/hintrunner/zero/hintcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ const (
// ------ Usort hints related code ------

// ------ Elliptic Curve hints related code ------
ecNegateCode string = "from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack\n\ny = pack(ids.point.y, PRIME) % SECP_P\n# The modulo operation in python always returns a nonnegative number.\nvalue = (-y) % SECP_P"
ecNegateCode string = "from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack\n\ny = pack(ids.point.y, PRIME) % SECP_P\n# The modulo operation in python always returns a nonnegative number.\nvalue = (-y) % SECP_P"
nondetBigint3V1Code string = "from starkware.cairo.common.cairo_secp.secp_utils import split\n\nsegments.write_arg(ids.res.address_, split(value))"

// ------ Signature hints related code ------

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 @@ -110,6 +110,8 @@ func GetHintFromCode(program *zero.ZeroProgram, rawHint zero.Hint, hintPC uint64
// EC hints
case ecNegateCode:
return createEcNegateHinter(resolver)
case nondetBigint3V1Code:
return createNondetBigint3V1Hinter(resolver)
// Blake hints
case blake2sAddUint256BigendCode:
return createBlake2sAddUint256Hinter(resolver, true)
Expand Down
55 changes: 55 additions & 0 deletions pkg/hintrunner/zero/zerohint_ec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package zero

import (
"fmt"

"github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/hinter"
secp_utils "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/utils"
VM "github.com/NethermindEth/cairo-vm-go/pkg/vm"
mem "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory"
"github.com/consensys/gnark-crypto/ecc/stark-curve/fp"
)

Expand Down Expand Up @@ -67,3 +69,56 @@ func createEcNegateHinter(resolver hintReferenceResolver) (hinter.Hinter, error)

return newEcNegateHint(point), nil
}

func newNondetBigint3V1Hint(res hinter.ResOperander) hinter.Hinter {
return &GenericZeroHinter{
Name: "NondetBigint3V1",
Op: func(vm *VM.VirtualMachine, ctx *hinter.HintRunnerContext) error {
//> from starkware.cairo.common.cairo_secp.secp_utils import split
//> segments.write_arg(ids.res.address_, split(value))

address, err := res.GetAddress(vm)
if err != nil {
return err
}

valueBig, err := ctx.ScopeManager.GetVariableValueAsBigInt("value")
if err != nil {
return err
}

//> split(value)
values, err := secp_utils.SecPSplit(valueBig)
if err != nil {
return err
}

//> segments.write_arg(ids.res.address_, values)
for i := 0; i < 3; i++ {
valueAddr, err := address.AddOffset(int16(i))
if err != nil {
return err
}

valueFelt := new(fp.Element).SetBigInt(values[i])
valueMv := mem.MemoryValueFromFieldElement(valueFelt)

err = vm.Memory.WriteToAddress(&valueAddr, &valueMv)
if err != nil {
return err
}
}

return nil
},
}
}

func createNondetBigint3V1Hinter(resolver hintReferenceResolver) (hinter.Hinter, error) {
res, err := resolver.GetResOperander("res")
if err != nil {
return nil, err
}

return newNondetBigint3V1Hint(res), nil
}
154 changes: 153 additions & 1 deletion pkg/hintrunner/zero/zerohint_ec_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package zero

import (
"github.com/NethermindEth/cairo-vm-go/pkg/utils"
"math/big"
"testing"

"github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/hinter"
"github.com/NethermindEth/cairo-vm-go/pkg/utils"
"github.com/consensys/gnark-crypto/ecc/stark-curve/fp"
)

func TestZeroHintEc(t *testing.T) {
Expand Down Expand Up @@ -189,5 +191,155 @@ func TestZeroHintEc(t *testing.T) {
check: varValueInScopeEquals("value", bigIntString("115792089237316195423511115915312127562362008772591693155831694873530722155557", 10)),
},
},
"NondetBigint3V1": {
{
operanders: []*hintOperander{
{Name: "res", Kind: uninitialized},
},
ctxInit: func(ctx *hinter.HintRunnerContext) {
// GetSecPBig() % fp.Modulus() but with first digit 3 replaced with 7
value := bigIntString("7618502788666127798953978732740734578953660990361066340291730267696802036752", 10)
ctx.ScopeManager.EnterScope(map[string]any{"value": value})
},
makeHinter: func(ctx *hintTestContext) hinter.Hinter {
return newNondetBigint3V1Hint(ctx.operanders["res"])
},
check: consecutiveVarValueEquals("res", []*fp.Element{feltString("72082201994522260246887440"), feltString("9036023809832564006525928"), feltString("1272654087330362946288670")}),
},
{
operanders: []*hintOperander{
{Name: "res", Kind: uninitialized},
},
ctxInit: func(ctx *hinter.HintRunnerContext) {
// GetSecPBig() % fp.Modulus()
value := bigIntString("3618502788666127798953978732740734578953660990361066340291730267696802036752", 10)
ctx.ScopeManager.EnterScope(map[string]any{"value": value})
},
makeHinter: func(ctx *hintTestContext) hinter.Hinter {
return newNondetBigint3V1Hint(ctx.operanders["res"])
},
check: consecutiveVarValueEquals("res", []*fp.Element{feltString("77371252455336262886226960"), feltString("77371252455336267181195263"), feltString("604462909807314034753535")}),
},
{
operanders: []*hintOperander{
{Name: "res", Kind: uninitialized},
},
ctxInit: func(ctx *hinter.HintRunnerContext) {
ctx.ScopeManager.EnterScope(map[string]any{"value": big.NewInt(123456)})
},
makeHinter: func(ctx *hintTestContext) hinter.Hinter {
return newNondetBigint3V1Hint(ctx.operanders["res"])
},
check: consecutiveVarValueEquals("res", []*fp.Element{feltString("123456"), &utils.FeltZero, &utils.FeltZero}),
},
{
operanders: []*hintOperander{
{Name: "res", Kind: uninitialized},
},
ctxInit: func(ctx *hinter.HintRunnerContext) {
ctx.ScopeManager.EnterScope(map[string]any{"value": big.NewInt(-123456)})
},
makeHinter: func(ctx *hintTestContext) hinter.Hinter {
return newNondetBigint3V1Hint(ctx.operanders["res"])
},
errCheck: errorTextContains("num != 0"),
},
{
operanders: []*hintOperander{
{Name: "res", Kind: uninitialized},
},
ctxInit: func(ctx *hinter.HintRunnerContext) {
// 2**86 - 1
value := bigIntString("77371252455336267181195263", 10)
ctx.ScopeManager.EnterScope(map[string]any{"value": value})
},
makeHinter: func(ctx *hintTestContext) hinter.Hinter {
return newNondetBigint3V1Hint(ctx.operanders["res"])
},
check: consecutiveVarValueEquals("res", []*fp.Element{feltString("77371252455336267181195263"), &utils.FeltZero, &utils.FeltZero}),
},
{
operanders: []*hintOperander{
{Name: "res", Kind: uninitialized},
},
ctxInit: func(ctx *hinter.HintRunnerContext) {
// 2**86
value := bigIntString("77371252455336267181195264", 10)
ctx.ScopeManager.EnterScope(map[string]any{"value": value})
},
makeHinter: func(ctx *hintTestContext) hinter.Hinter {
return newNondetBigint3V1Hint(ctx.operanders["res"])
},
check: consecutiveVarValueEquals("res", []*fp.Element{&utils.FeltZero, &utils.FeltOne, &utils.FeltZero}),
},
{
operanders: []*hintOperander{
{Name: "res", Kind: uninitialized},
},
ctxInit: func(ctx *hinter.HintRunnerContext) {
// 2**86 + 1
value := bigIntString("77371252455336267181195265", 10)
ctx.ScopeManager.EnterScope(map[string]any{"value": value})
},
makeHinter: func(ctx *hintTestContext) hinter.Hinter {
return newNondetBigint3V1Hint(ctx.operanders["res"])
},
check: consecutiveVarValueEquals("res", []*fp.Element{&utils.FeltOne, &utils.FeltOne, &utils.FeltZero}),
},
{
operanders: []*hintOperander{
{Name: "res", Kind: uninitialized},
},
ctxInit: func(ctx *hinter.HintRunnerContext) {
ctx.ScopeManager.EnterScope(map[string]any{"value": big.NewInt(0)})
},
makeHinter: func(ctx *hintTestContext) hinter.Hinter {
return newNondetBigint3V1Hint(ctx.operanders["res"])
},
check: consecutiveVarValueEquals("res", []*fp.Element{&utils.FeltZero, &utils.FeltZero, &utils.FeltZero}),
},
{
operanders: []*hintOperander{
{Name: "res", Kind: uninitialized},
},
ctxInit: func(ctx *hinter.HintRunnerContext) {
// (2**86 - 1) * 2
value := bigIntString("154742504910672534362390526", 10)
ctx.ScopeManager.EnterScope(map[string]any{"value": value})
},
makeHinter: func(ctx *hintTestContext) hinter.Hinter {
return newNondetBigint3V1Hint(ctx.operanders["res"])
},
check: consecutiveVarValueEquals("res", []*fp.Element{feltString("77371252455336267181195262"), &utils.FeltOne, &utils.FeltZero}),
},
{
operanders: []*hintOperander{
{Name: "res", Kind: uninitialized},
},
ctxInit: func(ctx *hinter.HintRunnerContext) {
// 2**86 * 2
value := bigIntString("154742504910672534362390528", 10)
ctx.ScopeManager.EnterScope(map[string]any{"value": value})
},
makeHinter: func(ctx *hintTestContext) hinter.Hinter {
return newNondetBigint3V1Hint(ctx.operanders["res"])
},
check: consecutiveVarValueEquals("res", []*fp.Element{&utils.FeltZero, feltString("2"), &utils.FeltZero}),
},
{
operanders: []*hintOperander{
{Name: "res", Kind: uninitialized},
},
ctxInit: func(ctx *hinter.HintRunnerContext) {
// (2**86 + 1) * 2
value := bigIntString("154742504910672534362390530", 10)
ctx.ScopeManager.EnterScope(map[string]any{"value": value})
},
makeHinter: func(ctx *hintTestContext) hinter.Hinter {
return newNondetBigint3V1Hint(ctx.operanders["res"])
},
check: consecutiveVarValueEquals("res", []*fp.Element{feltString("2"), feltString("2"), &utils.FeltZero}),
},
},
})
}
24 changes: 0 additions & 24 deletions pkg/hintrunner/zero/zerohint_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package zero

import (
"encoding/json"
"fmt"
"testing"

Expand Down Expand Up @@ -263,29 +262,6 @@ func runHinterTests(t *testing.T, tests map[string][]hintTestCase) {
}

for testGroup, cases := range tests {
{
// A sanity check: test that there are no duplicated test cases inside a group.
type testCaseKey struct {
Operanders []*hintOperander
IsErrorCheck bool
}
set := map[string]struct{}{}
for i, tc := range cases {
key := testCaseKey{
Operanders: tc.operanders,
IsErrorCheck: tc.errCheck != nil,
}
stringKey, err := json.Marshal(key)
if err != nil {
t.Fatal(err)
}
if _, ok := set[string(stringKey)]; ok {
t.Fatalf("%s: duplicated test case case (i=%d) found: %s", testGroup, i, stringKey)
}
set[string(stringKey)] = struct{}{}
}
}

for i, tc := range cases {
t.Run(fmt.Sprintf("%s_%d", testGroup, i), func(t *testing.T) {
runTest(t, tc)
Expand Down
28 changes: 28 additions & 0 deletions pkg/hintrunner/zero/zerohint_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,34 @@ func allVarValueEquals(expectedValues map[string]*fp.Element) func(t *testing.T,
}
}

func consecutiveVarValueEquals(varName string, expectedValues []*fp.Element) func(t *testing.T, ctx *hintTestContext) {
return func(t *testing.T, ctx *hintTestContext) {
o := ctx.operanders[varName]
addr, err := o.GetAddress(ctx.vm)
if err != nil {
t.Fatal(err)
}

for idx := 0; idx < len(expectedValues); idx++ {
offsetAddress, err := addr.AddOffset(int16(idx))
if err != nil {
t.Fatal(err)
}

actualFelt, err := ctx.vm.Memory.ReadFromAddressAsElement(&offsetAddress)
if err != nil {
t.Fatal(err)
}

expectedFelt := expectedValues[idx]

if !actualFelt.Equal(expectedFelt) {
t.Fatalf("%s value mismatch at %s:\nhave: %v\nwant: %v", varName, offsetAddress, &actualFelt, expectedFelt)
}
}
}
}

func varValueInScopeEquals(varName string, expected any) func(t *testing.T, ctx *hintTestContext) {
return func(t *testing.T, ctx *hintTestContext) {
value, err := ctx.runnerContext.ScopeManager.GetVariableValue(varName)
Expand Down

0 comments on commit 87bff04

Please sign in to comment.