From 9120e21f260e9186820fdaacbf786b3b7143a03d Mon Sep 17 00:00:00 2001 From: "Mariano A. Nicolini" Date: Tue, 26 Sep 2023 16:09:36 -0300 Subject: [PATCH 1/4] Add fast ec add assign hints (#288) * Finish impl of fastEcAddAssignNewX hint * Start hint testing * Test for FastEcAddAssignNewX hint passing * Add test for TestFastEcAddAssignNewY * Fix test, all test passing * Remove unused code --- pkg/hints/ec_hint.go | 105 ++++++++++ pkg/hints/ec_hint_test.go | 295 +++++++++++++++++++++++++++ pkg/hints/hint_codes/ec_op_hints.go | 21 ++ pkg/hints/hint_processor.go | 8 + pkg/hints/hint_utils/bigint_utils.go | 14 ++ pkg/hints/hint_utils/secp_utils.go | 5 + 6 files changed, 448 insertions(+) diff --git a/pkg/hints/ec_hint.go b/pkg/hints/ec_hint.go index 6cbaba9e..698011d6 100644 --- a/pkg/hints/ec_hint.go +++ b/pkg/hints/ec_hint.go @@ -181,3 +181,108 @@ func computeSlope(vm *VirtualMachine, execScopes ExecutionScopes, idsData IdsMan return nil } + +/* +Implements hint: + + %{ + from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack + + slope = pack(ids.slope, PRIME) + x0 = pack(ids.point0.x, PRIME) + x1 = pack(ids.point1.x, PRIME) + y0 = pack(ids.point0.y, PRIME) + + value = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P" + %} +*/ +func fastEcAddAssignNewX(ids IdsManager, vm *VirtualMachine, execScopes *ExecutionScopes, point0Alias string, point1Alias string, secpP big.Int) error { + execScopes.AssignOrUpdateVariable("SECP_P", secpP) + + point0, err := EcPointFromVarName(point0Alias, vm, ids) + if err != nil { + return err + } + + point1, err := EcPointFromVarName(point1Alias, vm, ids) + if err != nil { + return err + } + + slopeUnpacked, err := BigInt3FromVarName("slope", ids, vm) + if err != nil { + return err + } + + slope := slopeUnpacked.Pack86() + slope = *new(big.Int).Mod(&slope, &secpP) + + x0 := point0.X.Pack86() + x0 = *new(big.Int).Mod(&x0, &secpP) + + x1 := point1.X.Pack86() + x1 = *new(big.Int).Mod(&x1, &secpP) + + y0 := point0.Y.Pack86() + y0 = *new(big.Int).Mod(&y0, &secpP) + + slopeSquared := new(big.Int).Mul(&slope, &slope) + x0PlusX1 := new(big.Int).Add(&x0, &x1) + + value := *new(big.Int).Sub(slopeSquared, x0PlusX1) + value = *new(big.Int).Mod(&value, &secpP) + + execScopes.AssignOrUpdateVariable("slope", slope) + execScopes.AssignOrUpdateVariable("x0", x0) + execScopes.AssignOrUpdateVariable("y0", y0) + execScopes.AssignOrUpdateVariable("value", value) + execScopes.AssignOrUpdateVariable("new_x", value) + + return nil +} + +/* +Implements hint: + + %{ value = new_y = (slope * (x0 - new_x) - y0) % SECP_P %} +*/ +func fastEcAddAssignNewY(execScopes *ExecutionScopes) error { + slope, err := execScopes.Get("slope") + if err != nil { + return err + } + slopeBigInt := slope.(big.Int) + x0, err := execScopes.Get("x0") + if err != nil { + return err + } + x0BigInt := x0.(big.Int) + + newX, err := execScopes.Get("new_x") + if err != nil { + return err + } + newXBigInt := newX.(big.Int) + + y0, err := execScopes.Get("y0") + if err != nil { + return err + } + y0BigInt := y0.(big.Int) + + secpP, err := execScopes.Get("SECP_P") + if err != nil { + return err + } + secpBigInt := secpP.(big.Int) + + x0MinusNewX := *new(big.Int).Sub(&x0BigInt, &newXBigInt) + x0MinusNewXMinusY0 := *new(big.Int).Sub(&x0MinusNewX, &y0BigInt) + valueBeforeMod := *new(big.Int).Mul(&slopeBigInt, &x0MinusNewXMinusY0) + value := *new(big.Int).Mod(&valueBeforeMod, &secpBigInt) + + execScopes.AssignOrUpdateVariable("value", value) + execScopes.AssignOrUpdateVariable("new_y", value) + + return nil +} diff --git a/pkg/hints/ec_hint_test.go b/pkg/hints/ec_hint_test.go index 4e5e0fbc..5e74cef9 100644 --- a/pkg/hints/ec_hint_test.go +++ b/pkg/hints/ec_hint_test.go @@ -234,3 +234,298 @@ func TestRunComputeSlopeOk(t *testing.T) { } } } + +func TestFastEcAddAssignNewXHint(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Fp = NewRelocatable(1, 14) + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "point0": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(2)), + NewMaybeRelocatableFelt(FeltFromUint64(3)), + NewMaybeRelocatableFelt(FeltFromUint64(4)), + NewMaybeRelocatableFelt(FeltFromUint64(5)), + NewMaybeRelocatableFelt(FeltFromUint64(6)), + }, + "point1": { + NewMaybeRelocatableFelt(FeltFromUint64(7)), + NewMaybeRelocatableFelt(FeltFromUint64(8)), + NewMaybeRelocatableFelt(FeltFromUint64(9)), + NewMaybeRelocatableFelt(FeltFromUint64(10)), + NewMaybeRelocatableFelt(FeltFromUint64(11)), + NewMaybeRelocatableFelt(FeltFromUint64(12)), + }, + "slope": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + }, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: FAST_EC_ADD_ASSIGN_NEW_X, + }) + + execScopes := types.NewExecutionScopes() + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("FAST_EC_ADD_ASSIGN_NEW_X hint test failed with error %s", err) + } + + slope, _ := execScopes.Get("slope") + slopeRes := slope.(big.Int) + + x0, _ := execScopes.Get("x0") + x0Res := x0.(big.Int) + + y0, _ := execScopes.Get("y0") + y0Res := y0.(big.Int) + + value, _ := execScopes.Get("value") + valueRes := value.(big.Int) + + // expected values + expectedSlope, _ := new(big.Int).SetString("1", 10) + expectedX0, _ := new(big.Int).SetString("17958932119522135058886879379160190656204633450479617", 10) + expectedY0, _ := new(big.Int).SetString("35917864239044270117773758835691633767745534082154500", 10) + expectedValue, _ := new(big.Int).SetString("115792089237316195423570913172959429764729749118122892656190048516840670362664", 10) + + if expectedValue.Cmp(&valueRes) != 0 { + t.Errorf("expected value=%v, got: value=%v", expectedValue, valueRes) + } + + if expectedSlope.Cmp(&slopeRes) != 0 { + t.Errorf("expected slope=%v, got: slope=%v", expectedSlope, slopeRes) + } + + if expectedX0.Cmp(&x0Res) != 0 { + t.Errorf("expected x0=%v, got: x0=%v", expectedX0, x0Res) + } + + if expectedY0.Cmp(&y0Res) != 0 { + t.Errorf("expected y0=%v, got: y0=%v", expectedY0, y0Res) + } +} + +func TestFastEcAddAssignNewXV2Hint(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Fp = NewRelocatable(1, 14) + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "point0": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(2)), + NewMaybeRelocatableFelt(FeltFromUint64(3)), + NewMaybeRelocatableFelt(FeltFromUint64(4)), + NewMaybeRelocatableFelt(FeltFromUint64(5)), + NewMaybeRelocatableFelt(FeltFromUint64(6)), + }, + "point1": { + NewMaybeRelocatableFelt(FeltFromUint64(7)), + NewMaybeRelocatableFelt(FeltFromUint64(8)), + NewMaybeRelocatableFelt(FeltFromUint64(9)), + NewMaybeRelocatableFelt(FeltFromUint64(10)), + NewMaybeRelocatableFelt(FeltFromUint64(11)), + NewMaybeRelocatableFelt(FeltFromUint64(12)), + }, + "slope": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + }, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: FAST_EC_ADD_ASSIGN_NEW_X_V2, + }) + + execScopes := types.NewExecutionScopes() + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("FAST_EC_ADD_ASSIGN_NEW_X_V2 hint test failed with error %s", err) + } + + slope, _ := execScopes.Get("slope") + slopeRes := slope.(big.Int) + + x0, _ := execScopes.Get("x0") + x0Res := x0.(big.Int) + + y0, _ := execScopes.Get("y0") + y0Res := y0.(big.Int) + + value, _ := execScopes.Get("value") + valueRes := value.(big.Int) + + // expected values + expectedSlope, _ := new(big.Int).SetString("1", 10) + expectedX0, _ := new(big.Int).SetString("17958932119522135058886879379160190656204633450479617", 10) + expectedY0, _ := new(big.Int).SetString("35917864239044270117773758835691633767745534082154500", 10) + expectedValue, _ := new(big.Int).SetString("57896044618658097711785420668615475838094756785302610636461256512888400510950", 10) + + if expectedValue.Cmp(&valueRes) != 0 { + t.Errorf("expected value=%v, got: value=%v", expectedValue, valueRes) + } + + if expectedSlope.Cmp(&slopeRes) != 0 { + t.Errorf("expected slope=%v, got: slope=%v", expectedValue, valueRes) + } + + if expectedX0.Cmp(&x0Res) != 0 { + t.Errorf("expected x0=%v, got: x0=%v", expectedX0, x0Res) + } + + if expectedY0.Cmp(&y0Res) != 0 { + t.Errorf("expected y0 to be %v, got: y0=%v", expectedY0, y0Res) + } +} + +func TestFastEcAddAssignNewXV3Hint(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Fp = NewRelocatable(1, 14) + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "pt0": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(2)), + NewMaybeRelocatableFelt(FeltFromUint64(3)), + NewMaybeRelocatableFelt(FeltFromUint64(4)), + NewMaybeRelocatableFelt(FeltFromUint64(5)), + NewMaybeRelocatableFelt(FeltFromUint64(6)), + }, + "pt1": { + NewMaybeRelocatableFelt(FeltFromUint64(7)), + NewMaybeRelocatableFelt(FeltFromUint64(8)), + NewMaybeRelocatableFelt(FeltFromUint64(9)), + NewMaybeRelocatableFelt(FeltFromUint64(10)), + NewMaybeRelocatableFelt(FeltFromUint64(11)), + NewMaybeRelocatableFelt(FeltFromUint64(12)), + }, + "slope": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + }, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: FAST_EC_ADD_ASSIGN_NEW_X_V3, + }) + + execScopes := types.NewExecutionScopes() + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("FAST_EC_ADD_ASSIGN_NEW_X_V3 hint test failed with error %s", err) + } + + slope, _ := execScopes.Get("slope") + slopeRes := slope.(big.Int) + + x0, _ := execScopes.Get("x0") + x0Res := x0.(big.Int) + + y0, _ := execScopes.Get("y0") + y0Res := y0.(big.Int) + + value, _ := execScopes.Get("value") + valueRes := value.(big.Int) + + // expected values + expectedSlope, _ := new(big.Int).SetString("1", 10) + expectedX0, _ := new(big.Int).SetString("17958932119522135058886879379160190656204633450479617", 10) + expectedY0, _ := new(big.Int).SetString("35917864239044270117773758835691633767745534082154500", 10) + expectedValue, _ := new(big.Int).SetString("115792089237316195423570913172959429764729749118122892656190048516840670362664", 10) + + if expectedValue.Cmp(&valueRes) != 0 { + t.Errorf("expected value=%v, got: value=%v", expectedValue, valueRes) + } + + if expectedSlope.Cmp(&slopeRes) != 0 { + t.Errorf("expected slope=%v, got: slope=%v", expectedValue, valueRes) + } + + if expectedX0.Cmp(&x0Res) != 0 { + t.Errorf("expected x0=%v, got: x0=%v", expectedX0, x0Res) + } + + if expectedY0.Cmp(&y0Res) != 0 { + t.Errorf("expected y0 to be %v, got: y0=%v", expectedY0, y0Res) + } +} + +func TestFastEcAddAssignNewY(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Fp = NewRelocatable(1, 14) + + idsManager := IdsManager{} + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: FAST_EC_ADD_ASSIGN_NEW_Y, + }) + + x0, _ := new(big.Int).SetString("17958932119522135058886879379160190656204633450479617", 10) + y0, _ := new(big.Int).SetString("35917864239044270117773758835691633767745534082154500", 10) + slope, _ := new(big.Int).SetString("1", 10) + newX, _ := new(big.Int).SetString("115792089237316195423570913172959429764729749118122892656190048516840670362664", 10) + secpP, _ := new(big.Int).SetString("115792089237316195423570985008687907853269984665640564039457584007908834671663", 10) + + execScopes := types.NewExecutionScopes() + execScopes.AssignOrUpdateVariable("x0", *x0) + execScopes.AssignOrUpdateVariable("y0", *y0) + execScopes.AssignOrUpdateVariable("slope", *slope) + execScopes.AssignOrUpdateVariable("new_x", *newX) + execScopes.AssignOrUpdateVariable("SECP_P", *secpP) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("FAST_EC_ADD_ASSIGN_NEW_Y hint test failed with error %s", err) + } + + // Result values + newY, _ := execScopes.Get("new_y") + newYRes := newY.(big.Int) + + value, _ := execScopes.Get("value") + valueRes := value.(big.Int) + + // Expected values + expectedNewY, _ := new(big.Int).SetString("53876796358566405176660638214851824423950167532634116", 10) + expectedValue, _ := new(big.Int).SetString("53876796358566405176660638214851824423950167532634116", 10) + + if expectedValue.Cmp(&valueRes) != 0 { + t.Errorf("expected value=%v, got: value=%v", expectedValue, valueRes) + } + + if expectedNewY.Cmp(&newYRes) != 0 { + t.Errorf("expected new_y=%v, got: new_y=%v", expectedValue, valueRes) + } +} diff --git a/pkg/hints/hint_codes/ec_op_hints.go b/pkg/hints/hint_codes/ec_op_hints.go index 254720c5..005b055d 100644 --- a/pkg/hints/hint_codes/ec_op_hints.go +++ b/pkg/hints/hint_codes/ec_op_hints.go @@ -4,3 +4,24 @@ const EC_NEGATE = "from starkware.cairo.common.cairo_secp.secp_utils import SECP const EC_NEGATE_EMBEDDED_SECP = "from starkware.cairo.common.cairo_secp.secp_utils import pack\nSECP_P = 2**255-19\n\ny = pack(ids.point.y, PRIME) % SECP_P\n# The modulo operation in python always returns a nonnegative number.\nvalue = (-y) % SECP_P" const EC_DOUBLE_SLOPE_V1 = "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)" const COMPUTE_SLOPE_V1 = "from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack\nfrom starkware.python.math_utils import line_slope\n\n# Compute the slope.\nx0 = pack(ids.point0.x, PRIME)\ny0 = pack(ids.point0.y, PRIME)\nx1 = pack(ids.point1.x, PRIME)\ny1 = pack(ids.point1.y, PRIME)\nvalue = slope = line_slope(point1=(x0, y0), point2=(x1, y1), p=SECP_P)" +const FAST_EC_ADD_ASSIGN_NEW_X = `"from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack + +slope = pack(ids.slope, PRIME) +x0 = pack(ids.point0.x, PRIME) +x1 = pack(ids.point1.x, PRIME) +y0 = pack(ids.point0.y, PRIME) + +value = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P` + +const FAST_EC_ADD_ASSIGN_NEW_X_V2 = "from starkware.cairo.common.cairo_secp.secp_utils import pack\nSECP_P = 2**255-19\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" + +const FAST_EC_ADD_ASSIGN_NEW_X_V3 = `"from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack + +slope = pack(ids.slope, PRIME) +x0 = pack(ids.pt0.x, PRIME) +x1 = pack(ids.pt1.x, PRIME) +y0 = pack(ids.pt0.y, PRIME) + +value = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P"` + +const FAST_EC_ADD_ASSIGN_NEW_Y = "value = new_y = (slope * (x0 - new_x) - y0) % SECP_P" diff --git a/pkg/hints/hint_processor.go b/pkg/hints/hint_processor.go index 8718a624..fb05f4b4 100644 --- a/pkg/hints/hint_processor.go +++ b/pkg/hints/hint_processor.go @@ -148,6 +148,14 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any, return splitInt(data.Ids, vm) case SPLIT_INT_ASSERT_RANGE: return splitIntAssertRange(data.Ids, vm) + case FAST_EC_ADD_ASSIGN_NEW_X: + return fastEcAddAssignNewX(data.Ids, vm, execScopes, "point0", "point1", SECP_P()) + case FAST_EC_ADD_ASSIGN_NEW_X_V2: + return fastEcAddAssignNewX(data.Ids, vm, execScopes, "point0", "point1", SECP_P_V2()) + case FAST_EC_ADD_ASSIGN_NEW_X_V3: + return fastEcAddAssignNewX(data.Ids, vm, execScopes, "pt0", "pt1", SECP_P()) + case FAST_EC_ADD_ASSIGN_NEW_Y: + return fastEcAddAssignNewY(execScopes) default: return errors.Errorf("Unknown Hint: %s", data.Code) } diff --git a/pkg/hints/hint_utils/bigint_utils.go b/pkg/hints/hint_utils/bigint_utils.go index acbed672..10cff28c 100644 --- a/pkg/hints/hint_utils/bigint_utils.go +++ b/pkg/hints/hint_utils/bigint_utils.go @@ -93,3 +93,17 @@ func BigInt3FromBaseAddr(addr Relocatable, name string, vm *VirtualMachine) (Big limbs, err := limbsFromBaseAddress(3, name, addr, vm) return BigInt3{Limbs: limbs}, err } + +func BigInt3FromVarName(name string, ids IdsManager, vm *VirtualMachine) (BigInt3, error) { + bigIntAddr, err := ids.GetAddr(name, vm) + if err != nil { + return BigInt3{}, err + } + + bigInt, err := BigInt3FromBaseAddr(bigIntAddr, name, vm) + if err != nil { + return BigInt3{}, err + } + + return bigInt, err +} diff --git a/pkg/hints/hint_utils/secp_utils.go b/pkg/hints/hint_utils/secp_utils.go index d7c9955b..b673e87c 100644 --- a/pkg/hints/hint_utils/secp_utils.go +++ b/pkg/hints/hint_utils/secp_utils.go @@ -7,6 +7,11 @@ func SECP_P() big.Int { return *secpP } +func SECP_P_V2() big.Int { + secpP, _ := new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10) + return *secpP +} + func ALPHA() big.Int { alpha := big.NewInt(0) return *alpha From 64e07f9d049bc43c63f4847ed5fe5f1c40d7c313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Rodr=C3=ADguez=20Chatruc?= <49622509+jrchatruc@users.noreply.github.com> Date: Tue, 26 Sep 2023 16:11:02 -0300 Subject: [PATCH 2/4] Fix ldl warning (#289) --- pkg/lambdaworks/lambdaworks.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/lambdaworks/lambdaworks.go b/pkg/lambdaworks/lambdaworks.go index 59f3a477..d2627b93 100644 --- a/pkg/lambdaworks/lambdaworks.go +++ b/pkg/lambdaworks/lambdaworks.go @@ -1,7 +1,7 @@ package lambdaworks /* -#cgo LDFLAGS: pkg/lambdaworks/lib/liblambdaworks.a -ldl +#cgo LDFLAGS: pkg/lambdaworks/lib/liblambdaworks.a #include "lib/lambdaworks.h" #include */ From d638fec71da24fe17822dcc24612e06731aa04e3 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 26 Sep 2023 23:17:05 +0300 Subject: [PATCH 3/4] Implement remaining hints in `keccak` module (#276) * Add hint codes * Move divrem hint codes to math hint codes * Finish implementation of unsignedDivRem hint * Add some fields to range check builtin runner * WIP implementation of signedDivRem hint * Dummy commit * Save work in progress * Add FeltFromBigInt function * Add unsigned div rem integration test * Implement UnsafeKeccak * Update dependencies * Add unit test * Fix division bug and make integration test pass * Fix hash * Save work in progress * Add unit tests * Add integration test * Finished unit test for divrem hints * Remove unused commented code * Add missing file * Add constant + GetStructFieldRelocatable + start hint * Add test file * Add MemorySegmentManager.GetFeltRange * Progress * Finish hint * Add unit test * Add integration test * Remove nParts and bound from range check runner fields, add method to calculate bound from constant and add test to assert bound is never zero * Remove unused input parameter to NewRangeCheckBuiltinRunner * Change number to constant in Bound impl * Add hint + test * Add integration tests * Add hint + tests * Add hint * Fix test * Add test * Implement CAIRO_KECCAK_FINALIZE * Extract aliased constants * Fix index out of range * Fix var name * Make error more expressive * Fix hint * Add test, fix test * Add test files * Implement keccakWriteArgs * Extend hint parsing * Update program.go * Fix DivCeil * Add integration test * Fix DivCeil * v2 * Fix bug * Fix bug but better * Update keccak_add_uint256.cairo * Update cairo_run_test.go --------- Co-authored-by: Mariano Nicolini --- cairo_programs/cairo_keccak.cairo | 29 +++ cairo_programs/keccak_add_uint256.cairo | 31 +++ cairo_programs/keccak_integration_tests.cairo | 107 +++++++++ pkg/builtins/keccak.go | 4 +- pkg/hints/hint_codes/keccak_hint_codes.go | 33 +++ pkg/hints/hint_processor.go | 12 + pkg/hints/hint_utils/hint_reference.go | 12 + pkg/hints/hint_utils/hint_reference_test.go | 14 ++ pkg/hints/keccak_hints.go | 138 +++++++++++ pkg/hints/keccak_hints_test.go | 223 ++++++++++++++++++ pkg/vm/cairo_run/cairo_run_test.go | 12 + pkg/vm/vm_core.go | 2 +- pkg/vm/vm_test.go | 4 +- 13 files changed, 616 insertions(+), 5 deletions(-) create mode 100644 cairo_programs/cairo_keccak.cairo create mode 100644 cairo_programs/keccak_add_uint256.cairo create mode 100644 cairo_programs/keccak_integration_tests.cairo diff --git a/cairo_programs/cairo_keccak.cairo b/cairo_programs/cairo_keccak.cairo new file mode 100644 index 00000000..8adcd515 --- /dev/null +++ b/cairo_programs/cairo_keccak.cairo @@ -0,0 +1,29 @@ +%builtins range_check bitwise + +from starkware.cairo.common.cairo_keccak.keccak import cairo_keccak, finalize_keccak +from starkware.cairo.common.uint256 import Uint256 +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin +from starkware.cairo.common.alloc import alloc + +func main{range_check_ptr: felt, bitwise_ptr: BitwiseBuiltin*}() { + alloc_locals; + + let (keccak_ptr: felt*) = alloc(); + let keccak_ptr_start = keccak_ptr; + + let (inputs: felt*) = alloc(); + + assert inputs[0] = 8031924123371070792; + assert inputs[1] = 560229490; + + let n_bytes = 16; + + let (res: Uint256) = cairo_keccak{keccak_ptr=keccak_ptr}(inputs=inputs, n_bytes=n_bytes); + + assert res.low = 293431514620200399776069983710520819074; + assert res.high = 317109767021952548743448767588473366791; + + finalize_keccak(keccak_ptr_start=keccak_ptr_start, keccak_ptr_end=keccak_ptr); + + return (); +} diff --git a/cairo_programs/keccak_add_uint256.cairo b/cairo_programs/keccak_add_uint256.cairo new file mode 100644 index 00000000..34ef99a9 --- /dev/null +++ b/cairo_programs/keccak_add_uint256.cairo @@ -0,0 +1,31 @@ +%builtins output range_check bitwise + +from starkware.cairo.common.keccak_utils.keccak_utils import keccak_add_uint256 +from starkware.cairo.common.uint256 import Uint256 +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.serialize import serialize_word + +func main{output_ptr: felt*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*}() { + alloc_locals; + + let (inputs) = alloc(); + let inputs_start = inputs; + + let num = Uint256(34623634663146736, 598249824422424658356); + + keccak_add_uint256{inputs=inputs_start}(num=num, bigend=0); + + assert inputs[0] = 34623634663146736; + assert inputs[1] = 0; + assert inputs[2] = 7954014063719006644; + assert inputs[3] = 32; + + serialize_word(inputs[0]); + serialize_word(inputs[1]); + serialize_word(inputs[2]); + serialize_word(inputs[3]); + + return (); +} + diff --git a/cairo_programs/keccak_integration_tests.cairo b/cairo_programs/keccak_integration_tests.cairo new file mode 100644 index 00000000..7f476245 --- /dev/null +++ b/cairo_programs/keccak_integration_tests.cairo @@ -0,0 +1,107 @@ +%builtins range_check bitwise + +from starkware.cairo.common.keccak import unsafe_keccak, unsafe_keccak_finalize, KeccakState +from starkware.cairo.common.cairo_keccak.keccak import cairo_keccak, finalize_keccak +from starkware.cairo.common.keccak_utils.keccak_utils import keccak_add_uint256 +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.uint256 import Uint256 +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin +from starkware.cairo.common.math import unsigned_div_rem + +func fill_array(array: felt*, base: felt, step: felt, array_length: felt, iter: felt) { + if (iter == array_length) { + return (); + } + assert array[iter] = base + step * iter; + return fill_array(array, base, step, array_length, iter + 1); +} + +func test_integration{range_check_ptr: felt, bitwise_ptr: BitwiseBuiltin*}(iter: felt, last: felt) { + alloc_locals; + if (iter == last) { + return (); + } + + let (data_1: felt*) = alloc(); + let data_len: felt = 15; + let chunk_len: felt = 5; + + fill_array(data_1, iter, iter + 1, data_len, 0); + + let (low_1: felt, high_1: felt) = unsafe_keccak(data_1, chunk_len); + let (low_2: felt, high_2: felt) = unsafe_keccak(data_1 + chunk_len, chunk_len); + let (low_3: felt, high_3: felt) = unsafe_keccak(data_1 + 2 * chunk_len, chunk_len); + + // With the results of unsafe_keccak, create an array to pass to unsafe_keccak_finalize + // through a KeccakState + let (data_2: felt*) = alloc(); + assert data_2[0] = low_1; + assert data_2[1] = high_1; + assert data_2[2] = low_2; + assert data_2[3] = high_2; + assert data_2[4] = low_3; + assert data_2[5] = high_3; + + let keccak_state: KeccakState = KeccakState(start_ptr=data_2, end_ptr=data_2 + 6); + let res_1: Uint256 = unsafe_keccak_finalize(keccak_state); + + let (data_3: felt*) = alloc(); + + // This is done to make sure that the numbers inserted in data_3 + // fit in a u64 + let (q, r) = unsigned_div_rem(res_1.low, 18446744073709551615); + assert data_3[0] = q; + let (q, r) = unsigned_div_rem(res_1.high, 18446744073709551615); + assert data_3[1] = q; + + let (keccak_ptr: felt*) = alloc(); + let keccak_ptr_start = keccak_ptr; + + let res_2: Uint256 = cairo_keccak{keccak_ptr=keccak_ptr}(data_3, 16); + + finalize_keccak(keccak_ptr_start=keccak_ptr_start, keccak_ptr_end=keccak_ptr); + + let (inputs) = alloc(); + let inputs_start = inputs; + keccak_add_uint256{inputs=inputs_start}(num=res_2, bigend=0); + + // These values are hardcoded for last = 10 + // Since we are dealing with hash functions and using the output of one of them + // as the input of the other, asserting only the last results of the iteration + // should be enough + if (iter == last - 1 and last == 10) { + assert res_2.low = 3896836249413878817054429671793519200; + assert res_2.high = 253424239110447628170109510737834198489; + + assert inputs[0] = 16681956707691293280; + assert inputs[1] = 211247916371739620; + assert inputs[2] = 6796127878994642393; + assert inputs[3] = 13738155530201662906; + } + + // These values are hardcoded for last = 100 + // This should be used for benchmarking. + if (iter == last - 1 and last == 100) { + assert res_2.low = 52798800345724801884797411011515944813; + assert res_2.high = 159010026777930121161844734347918361509; + + assert inputs[0] = 14656556134934286189; + assert inputs[1] = 2862228701973161639; + assert inputs[2] = 206697371206337445; + assert inputs[3] = 8619950823980503604; + } + + return test_integration{range_check_ptr=range_check_ptr, bitwise_ptr=bitwise_ptr}( + iter + 1, last + ); +} + +func run_test{range_check_ptr: felt, bitwise_ptr: BitwiseBuiltin*}(last: felt) { + test_integration(0, last); + return (); +} + +func main{range_check_ptr: felt, bitwise_ptr: BitwiseBuiltin*}() { + run_test(10); + return (); +} diff --git a/pkg/builtins/keccak.go b/pkg/builtins/keccak.go index 46b53809..a5f9638e 100644 --- a/pkg/builtins/keccak.go +++ b/pkg/builtins/keccak.go @@ -101,7 +101,7 @@ func (k *KeccakBuiltinRunner) DeduceMemoryCell(address Relocatable, mem *Memory) for i := 0; i < 25; i++ { output_message_u64[i] = binary.LittleEndian.Uint64(output_message_bytes[8*i : 8*i+8]) } - keccakF1600(&output_message_u64) + KeccakF1600(&output_message_u64) // Convert back to bytes output_message := make([]byte, 0, 200) @@ -152,7 +152,7 @@ var rc = [24]uint64{ // keccakF1600 applies the Keccak permutation to a 1600b-wide // state represented as a slice of 25 uint64s. -func keccakF1600(a *[25]uint64) { +func KeccakF1600(a *[25]uint64) { // Implementation translated from Keccak-inplace.c // in the keccak reference code. var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64 diff --git a/pkg/hints/hint_codes/keccak_hint_codes.go b/pkg/hints/hint_codes/keccak_hint_codes.go index c9cc1ea1..9927b27b 100644 --- a/pkg/hints/hint_codes/keccak_hint_codes.go +++ b/pkg/hints/hint_codes/keccak_hint_codes.go @@ -3,3 +3,36 @@ package hint_codes const UNSAFE_KECCAK = "from eth_hash.auto import keccak\n\ndata, length = ids.data, ids.length\n\nif '__keccak_max_size' in globals():\n assert length <= __keccak_max_size, \\\n f'unsafe_keccak() can only be used with length<={__keccak_max_size}. ' \\\n f'Got: length={length}.'\n\nkeccak_input = bytearray()\nfor word_i, byte_i in enumerate(range(0, length, 16)):\n word = memory[data + word_i]\n n_bytes = min(16, length - byte_i)\n assert 0 <= word < 2 ** (8 * n_bytes)\n keccak_input += word.to_bytes(n_bytes, 'big')\n\nhashed = keccak(keccak_input)\nids.high = int.from_bytes(hashed[:16], 'big')\nids.low = int.from_bytes(hashed[16:32], 'big')" const UNSAFE_KECCAK_FINALIZE = "from eth_hash.auto import keccak\nkeccak_input = bytearray()\nn_elms = ids.keccak_state.end_ptr - ids.keccak_state.start_ptr\nfor word in memory.get_range(ids.keccak_state.start_ptr, n_elms):\n keccak_input += word.to_bytes(16, 'big')\nhashed = keccak(keccak_input)\nids.high = int.from_bytes(hashed[:16], 'big')\nids.low = int.from_bytes(hashed[16:32], 'big')" + +const COMPARE_BYTES_IN_WORD_NONDET = "memory[ap] = to_felt_or_relocatable(ids.n_bytes < ids.BYTES_IN_WORD)" + +const COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET = "memory[ap] = to_felt_or_relocatable(ids.n_bytes >= ids.KECCAK_FULL_RATE_IN_BYTES)" + +const BLOCK_PERMUTATION = `from starkware.cairo.common.keccak_utils.keccak_utils import keccak_func +_keccak_state_size_felts = int(ids.KECCAK_STATE_SIZE_FELTS) +assert 0 <= _keccak_state_size_felts < 100 + +output_values = keccak_func(memory.get_range( + ids.keccak_ptr - _keccak_state_size_felts, _keccak_state_size_felts)) +segments.write_arg(ids.keccak_ptr, output_values)` + +const CAIRO_KECCAK_FINALIZE_V1 = `# Add dummy pairs of input and output. +_keccak_state_size_felts = int(ids.KECCAK_STATE_SIZE_FELTS) +_block_size = int(ids.BLOCK_SIZE) +assert 0 <= _keccak_state_size_felts < 100 +assert 0 <= _block_size < 10 +inp = [0] * _keccak_state_size_felts +padding = (inp + keccak_func(inp)) * _block_size +segments.write_arg(ids.keccak_ptr_end, padding)` + +const CAIRO_KECCAK_FINALIZE_V2 = `# Add dummy pairs of input and output. +_keccak_state_size_felts = int(ids.KECCAK_STATE_SIZE_FELTS) +_block_size = int(ids.BLOCK_SIZE) +assert 0 <= _keccak_state_size_felts < 100 +assert 0 <= _block_size < 1000 +inp = [0] * _keccak_state_size_felts +padding = (inp + keccak_func(inp)) * _block_size +segments.write_arg(ids.keccak_ptr_end, padding)` + +const KECCAK_WRITE_ARGS = `segments.write_arg(ids.inputs, [ids.low % 2 ** 64, ids.low // 2 ** 64]) +segments.write_arg(ids.inputs + 2, [ids.high % 2 ** 64, ids.high // 2 ** 64])` diff --git a/pkg/hints/hint_processor.go b/pkg/hints/hint_processor.go index fb05f4b4..9c5947cd 100644 --- a/pkg/hints/hint_processor.go +++ b/pkg/hints/hint_processor.go @@ -120,6 +120,18 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any, return unsafeKeccak(data.Ids, vm, *execScopes) case UNSAFE_KECCAK_FINALIZE: return unsafeKeccakFinalize(data.Ids, vm) + case COMPARE_BYTES_IN_WORD_NONDET: + return compareBytesInWordNondet(data.Ids, vm, constants) + case COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET: + return compareKeccakFullRateInBytesNondet(data.Ids, vm, constants) + case BLOCK_PERMUTATION: + return blockPermutation(data.Ids, vm, constants) + case CAIRO_KECCAK_FINALIZE_V1: + return cairoKeccakFinalize(data.Ids, vm, constants, 10) + case CAIRO_KECCAK_FINALIZE_V2: + return cairoKeccakFinalize(data.Ids, vm, constants, 1000) + case KECCAK_WRITE_ARGS: + return keccakWriteArgs(data.Ids, vm) case UNSIGNED_DIV_REM: return unsignedDivRem(data.Ids, vm) case SIGNED_DIV_REM: diff --git a/pkg/hints/hint_utils/hint_reference.go b/pkg/hints/hint_utils/hint_reference.go index 27149cd6..4954cb91 100644 --- a/pkg/hints/hint_utils/hint_reference.go +++ b/pkg/hints/hint_utils/hint_reference.go @@ -204,6 +204,18 @@ func ParseHintReference(reference parser.Reference) HintReference { ValueType: valueType, } } + // Reference no dereference 2 offsets - + : cast(reg - off1 + off2, type) + _, err = fmt.Sscanf(valueString, "cast%c%c - %d + %d, %s", &off1Reg0, &off1Reg1, &off1, &off2, &valueType) + if err == nil { + off1Reg := getRegister(off1Reg0, off1Reg1) + return HintReference{ + ApTrackingData: reference.ApTrackingData, + Offset1: OffsetValue{ValueType: Reference, Register: off1Reg, Value: -off1}, + Offset2: OffsetValue{Value: off2}, + Dereference: dereference, + ValueType: valueType, + } + } // No matches (aka wrong format) return HintReference{ApTrackingData: reference.ApTrackingData} } diff --git a/pkg/hints/hint_utils/hint_reference_test.go b/pkg/hints/hint_utils/hint_reference_test.go index 868274dd..98a5b983 100644 --- a/pkg/hints/hint_utils/hint_reference_test.go +++ b/pkg/hints/hint_utils/hint_reference_test.go @@ -326,3 +326,17 @@ func TestParseHintDereferenceReferenceDoubleDerefBothOffOmitted(t *testing.T) { t.Errorf("Wrong parsed reference, %+v", ParseHintReference(reference)) } } + +func TestParseHintDereferenceValueMinusValPlusVal(t *testing.T) { + reference := parser.Reference{Value: "[cast(ap - 0 + (-1), felt*)]"} + expected := HintReference{ + Offset1: OffsetValue{ValueType: Reference, Value: 0, Dereference: false}, + Offset2: OffsetValue{ValueType: Value, Value: -1, Dereference: false}, + ValueType: "felt*", + Dereference: true, + } + + if ParseHintReference(reference) != expected { + t.Errorf("Wrong parsed reference, %+v", ParseHintReference(reference)) + } +} diff --git a/pkg/hints/keccak_hints.go b/pkg/hints/keccak_hints.go index 969ccdba..e3b4382b 100644 --- a/pkg/hints/keccak_hints.go +++ b/pkg/hints/keccak_hints.go @@ -1,7 +1,10 @@ package hints import ( + "math" + "github.com/ebfe/keccak" + "github.com/lambdaclass/cairo-vm.go/pkg/builtins" . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" . "github.com/lambdaclass/cairo-vm.go/pkg/types" @@ -117,3 +120,138 @@ func unsafeKeccakFinalize(ids IdsManager, vm *VirtualMachine) error { } return ids.Insert("low", NewMaybeRelocatableFelt(low), vm) } + +func compareBytesInWordNondet(ids IdsManager, vm *VirtualMachine, constants *map[string]Felt) error { + nBytes, err := ids.GetFelt("n_bytes", vm) + if err != nil { + return err + } + bytesInWord, err := ids.GetConst("BYTES_IN_WORD", constants) + if nBytes.Cmp(bytesInWord) == -1 { + return vm.Segments.Memory.Insert(vm.RunContext.Ap, NewMaybeRelocatableFelt(FeltOne())) + } + return vm.Segments.Memory.Insert(vm.RunContext.Ap, NewMaybeRelocatableFelt(FeltZero())) +} + +func compareKeccakFullRateInBytesNondet(ids IdsManager, vm *VirtualMachine, constants *map[string]Felt) error { + nBytes, err := ids.GetFelt("n_bytes", vm) + if err != nil { + return err + } + bytesInWord, err := ids.GetConst("KECCAK_FULL_RATE_IN_BYTES", constants) + if nBytes.Cmp(bytesInWord) != -1 { + return vm.Segments.Memory.Insert(vm.RunContext.Ap, NewMaybeRelocatableFelt(FeltOne())) + } + return vm.Segments.Memory.Insert(vm.RunContext.Ap, NewMaybeRelocatableFelt(FeltZero())) +} + +func blockPermutation(ids IdsManager, vm *VirtualMachine, constants *map[string]Felt) error { + const KECCAK_SIZE = 25 + keccakStateSizeFeltsFelt, err := ids.GetConst("KECCAK_STATE_SIZE_FELTS", constants) + if err != nil { + return err + } + if keccakStateSizeFeltsFelt.Cmp(FeltFromUint64(KECCAK_SIZE)) != 0 { + return errors.New("Assertion failed: _keccak_state_size_felts == 25") + } + + keccakPtr, err := ids.GetRelocatable("keccak_ptr", vm) + if err != nil { + return err + } + startPtr, err := keccakPtr.SubUint(KECCAK_SIZE) + if err != nil { + return err + } + inputFelt, err := vm.Segments.GetFeltRange(startPtr, KECCAK_SIZE) + if err != nil { + return err + } + + var inputU64 [KECCAK_SIZE]uint64 + for i := 0; i < KECCAK_SIZE; i++ { + val, err := inputFelt[i].ToU64() + if err != nil { + return err + } + inputU64[i] = val + } + + builtins.KeccakF1600(&inputU64) + + output := make([]MaybeRelocatable, 0, KECCAK_SIZE) + for i := 0; i < KECCAK_SIZE; i++ { + output = append(output, *NewMaybeRelocatableFelt(FeltFromUint64(inputU64[i]))) + } + + _, err = vm.Segments.LoadData(keccakPtr, &output) + return err +} + +func cairoKeccakFinalize(ids IdsManager, vm *VirtualMachine, constants *map[string]Felt, blockSizeLimit uint64) error { + const KECCAK_SIZE = 25 + keccakStateSizeFeltsFelt, err := ids.GetConst("KECCAK_STATE_SIZE_FELTS", constants) + if err != nil { + return err + } + if keccakStateSizeFeltsFelt.Cmp(FeltFromUint64(KECCAK_SIZE)) != 0 { + return errors.New("Assertion failed: _keccak_state_size_felts == 25") + } + + blockSizeFelt, err := ids.GetConst("BLOCK_SIZE", constants) + if err != nil { + return err + } + if blockSizeFelt.Cmp(FeltFromUint64(blockSizeLimit)) != -1 { + return errors.Errorf("assert 0 <= _block_size < %d", blockSizeLimit) + } + blockSize, _ := blockSizeFelt.ToU64() + var input [KECCAK_SIZE]uint64 + builtins.KeccakF1600(&input) + padding := make([]MaybeRelocatable, 0, KECCAK_SIZE*2*blockSize) + for i := 0; i < KECCAK_SIZE; i++ { + padding = append(padding, *NewMaybeRelocatableFelt(FeltZero())) + } + for i := 0; i < KECCAK_SIZE; i++ { + padding = append(padding, *NewMaybeRelocatableFelt(FeltFromUint64(input[i]))) + } + for i := 1; i < int(blockSize); i++ { + padding = append(padding, padding[:50]...) + } + keccakEndPtr, err := ids.GetRelocatable("keccak_ptr_end", vm) + if err != nil { + return err + } + _, err = vm.Segments.LoadData(keccakEndPtr, &padding) + return err +} + +func keccakWriteArgs(ids IdsManager, vm *VirtualMachine) error { + inputs, err := ids.GetRelocatable("inputs", vm) + if err != nil { + return err + } + low, err := ids.GetFelt("low", vm) + if err != nil { + return err + } + high, err := ids.GetFelt("high", vm) + if err != nil { + return err + } + low_args := []MaybeRelocatable{ + *NewMaybeRelocatableFelt(low.And(FeltFromUint64(math.MaxUint64))), + *NewMaybeRelocatableFelt(low.Shr(64)), + } + high_args := []MaybeRelocatable{ + *NewMaybeRelocatableFelt(high.And(FeltFromUint64(math.MaxUint64))), + *NewMaybeRelocatableFelt(high.Shr(64)), + } + + inputs, err = vm.Segments.LoadData(inputs, &low_args) + if err != nil { + return err + } + _, err = vm.Segments.LoadData(inputs, &high_args) + return err +} diff --git a/pkg/hints/keccak_hints_test.go b/pkg/hints/keccak_hints_test.go index 6f20d8e4..3fe61996 100644 --- a/pkg/hints/keccak_hints_test.go +++ b/pkg/hints/keccak_hints_test.go @@ -166,3 +166,226 @@ func TestUnsafeKeccakFinalizeOk(t *testing.T) { t.Errorf("Wrong/No ids.low\n Expected %s, got %s.", expectedLow.ToHexString(), low.ToHexString()) } } + +func TestCompareBytesInWordHintEq(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + // Advance fp to avoid clashes with values inserted into ap + vm.RunContext.Fp.Offset += 1 + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n_bytes": {NewMaybeRelocatableFelt(FeltFromUint64(17))}, + }, + vm, + ) + constants := SetupConstantsForTest( + map[string]Felt{ + "BYTES_IN_WORD": FeltFromUint64(17), + }, + &idsManager) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: COMPARE_BYTES_IN_WORD_NONDET, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("COMPARE_BYTES_IN_WORD_NONDET hint test failed with error %s", err) + } + // Check the value of memory[ap] + val, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil || !val.IsZero() { + t.Error("Wrong/No value inserted into ap") + } +} + +func TestCompareBytesInWordHintGt(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + // Advance fp to avoid clashes with values inserted into ap + vm.RunContext.Fp.Offset += 1 + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n_bytes": {NewMaybeRelocatableFelt(FeltFromUint64(18))}, + }, + vm, + ) + constants := SetupConstantsForTest( + map[string]Felt{ + "BYTES_IN_WORD": FeltFromUint64(17), + }, + &idsManager) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: COMPARE_BYTES_IN_WORD_NONDET, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("COMPARE_BYTES_IN_WORD_NONDET hint test failed with error %s", err) + } + // Check the value of memory[ap] + val, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil || !val.IsZero() { + t.Error("Wrong/No value inserted into ap") + } +} + +func TestCompareBytesInWordHintLt(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + // Advance fp to avoid clashes with values inserted into ap + vm.RunContext.Fp.Offset += 1 + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n_bytes": {NewMaybeRelocatableFelt(FeltFromUint64(16))}, + }, + vm, + ) + constants := SetupConstantsForTest( + map[string]Felt{ + "BYTES_IN_WORD": FeltFromUint64(17), + }, + &idsManager) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: COMPARE_BYTES_IN_WORD_NONDET, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("COMPARE_BYTES_IN_WORD_NONDET hint test failed with error %s", err) + } + // Check the value of memory[ap] + val, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil || val != FeltOne() { + t.Error("Wrong/No value inserted into ap") + } +} + +func TestCompareKeccakFullRateInBytesHintEq(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + // Advance fp to avoid clashes with values inserted into ap + vm.RunContext.Fp.Offset += 1 + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n_bytes": {NewMaybeRelocatableFelt(FeltFromUint64(17))}, + }, + vm, + ) + constants := SetupConstantsForTest( + map[string]Felt{ + "KECCAK_FULL_RATE_IN_BYTES": FeltFromUint64(17), + }, + &idsManager) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET hint test failed with error %s", err) + } + // Check the value of memory[ap] + val, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil || val != FeltOne() { + t.Error("Wrong/No value inserted into ap") + } +} + +func TestCompareKeccakFullRateInBytesHintGt(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + // Advance fp to avoid clashes with values inserted into ap + vm.RunContext.Fp.Offset += 1 + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n_bytes": {NewMaybeRelocatableFelt(FeltFromUint64(18))}, + }, + vm, + ) + constants := SetupConstantsForTest( + map[string]Felt{ + "KECCAK_FULL_RATE_IN_BYTES": FeltFromUint64(17), + }, + &idsManager) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET hint test failed with error %s", err) + } + // Check the value of memory[ap] + val, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil || val != FeltOne() { + t.Error("Wrong/No value inserted into ap") + } +} + +func TestCompareKeccakFullRateInBytesHintLt(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + // Advance fp to avoid clashes with values inserted into ap + vm.RunContext.Fp.Offset += 1 + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n_bytes": {NewMaybeRelocatableFelt(FeltFromUint64(16))}, + }, + vm, + ) + constants := SetupConstantsForTest( + map[string]Felt{ + "KECCAK_FULL_RATE_IN_BYTES": FeltFromUint64(17), + }, + &idsManager) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET hint test failed with error %s", err) + } + // Check the value of memory[ap] + val, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil || !val.IsZero() { + t.Error("Wrong/No value inserted into ap") + } +} + +func TestBlockPermutationOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + keccak_ptr := vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "keccak_ptr": {NewMaybeRelocatableRelocatable(keccak_ptr.AddUint(25))}, + }, + vm, + ) + data := make([]MaybeRelocatable, 0, 25) + for i := 0; i < 25; i++ { + data = append(data, *NewMaybeRelocatableFelt(FeltZero())) + } + vm.Segments.LoadData(keccak_ptr, &data) + hintProcessor := CairoVmHintProcessor{} + constants := SetupConstantsForTest( + map[string]Felt{ + "KECCAK_STATE_SIZE_FELTS": FeltFromUint64(25), + }, + &idsManager) + hintData := any(HintData{ + Ids: idsManager, + Code: BLOCK_PERMUTATION, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("BLOCK_PERMUTATION hint test failed with error %s", err) + } +} diff --git a/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index 34ad51dd..3046cb5c 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -320,3 +320,15 @@ func TestSplitIntHint(t *testing.T) { func TestSplitIntHintProofMode(t *testing.T) { testProgramProof("split_int", t) } + +func TestKeccakIntegrationTests(t *testing.T) { + testProgram("keccak_integration_tests", t) +} + +func TestCairoKeccak(t *testing.T) { + testProgram("cairo_keccak", t) +} + +func TestKeccakAddUint256(t *testing.T) { + testProgram("keccak_add_uint256", t) +} diff --git a/pkg/vm/vm_core.go b/pkg/vm/vm_core.go index 7f5aec40..b8aa1e2f 100644 --- a/pkg/vm/vm_core.go +++ b/pkg/vm/vm_core.go @@ -247,7 +247,7 @@ func (vm *VirtualMachine) OpcodeAssertions(instruction Instruction, operands Ope return &VirtualMachineError{"UnconstrainedResAssertEq"} } if !operands.Res.IsEqual(&operands.Dst) { - return &VirtualMachineError{"DiffAssertValues"} + return &VirtualMachineError{fmt.Sprintf("An ASSERT_EQ instruction failed: %s != %s.", operands.Res.ToString(), operands.Dst.ToString())} } case Call: new_rel := vm.RunContext.Pc.AddUint(instruction.Size()) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index a0163e1f..8f3ab08c 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -623,7 +623,7 @@ func TestOpcodeAssertionsInstructionFailed(t *testing.T) { testVm := vm.NewVirtualMachine() err := testVm.OpcodeAssertions(instruction, operands) - if err.Error() != "DiffAssertValues" { + if err == nil { t.Error("Assertion should error out with DiffAssertValues") } @@ -653,7 +653,7 @@ func TestOpcodeAssertionsInstructionFailedRelocatables(t *testing.T) { testVm := vm.NewVirtualMachine() err := testVm.OpcodeAssertions(instruction, operands) - if err.Error() != "DiffAssertValues" { + if err == nil { t.Error("Assertion should error out with DiffAssertValues") } } From 8a6c9494874919312e42c247826de785cb48e3f2 Mon Sep 17 00:00:00 2001 From: mmsc2 <88055861+mmsc2@users.noreply.github.com> Date: Tue, 26 Sep 2023 19:51:27 -0300 Subject: [PATCH 4/4] Secp p hints (#275) * Add ec hints * Implement hints * Add the hints to the processor * Test pack86 function * Test hint * Delete debug info, Test ec negative op * Second hint test * Test embedded hint * Change to Camel case * Implement slope hints * Fix format * Delete github conflict string * Tests hints * Tests hints slopes * Rename misleading name function * Fix function name * Fix error in function call * Delete debug info * Delete unused import * Secp hints * Secpr21 * Add it to the hint processor * Hints secp * bigint3 nondet * Zero verify * Merge main * Add hint to hint processor * Debug info * Prints * Test verify with unit test * Debug unit test * Test verify zero with debug * Non det big 3 test * Modify test to use ids manager * debug info * Fix broken test * Move file from hints_utils and rename * Delete debug * Move integration test to cairo_run_test.go * Return error of IdsData.Insert * Change to camel case * Delete merge characters --------- Co-authored-by: Mariano A. Nicolini --- cairo_programs/ec_double_slope.cairo | 212 +++++++++++++++++++++++++++ pkg/hints/bigint_hint.go | 56 +++++++ pkg/hints/bigint_hint_test.go | 75 ++++++++++ pkg/hints/ec_hint.go | 86 ++++++++++- pkg/hints/field_utils.go | 55 +++++++ pkg/hints/field_utils_test.go | 56 +++++++ pkg/hints/hint_codes/ec_op_hints.go | 2 + pkg/hints/hint_codes/secp_p_hint.go | 6 + pkg/hints/hint_processor.go | 12 ++ pkg/hints/hint_utils/bigint_utils.go | 1 + pkg/hints/hint_utils/secp_utils.go | 40 ++++- pkg/lambdaworks/lambdaworks.go | 7 +- pkg/vm/cairo_run/cairo_run_test.go | 3 + 13 files changed, 600 insertions(+), 11 deletions(-) create mode 100644 cairo_programs/ec_double_slope.cairo create mode 100644 pkg/hints/bigint_hint.go create mode 100644 pkg/hints/bigint_hint_test.go create mode 100644 pkg/hints/field_utils.go create mode 100644 pkg/hints/field_utils_test.go create mode 100644 pkg/hints/hint_codes/secp_p_hint.go diff --git a/cairo_programs/ec_double_slope.cairo b/cairo_programs/ec_double_slope.cairo new file mode 100644 index 00000000..10177482 --- /dev/null +++ b/cairo_programs/ec_double_slope.cairo @@ -0,0 +1,212 @@ +%builtins range_check + +// Source: https://github.com/rdubois-crypto/efficient-secp256r1/blob/4b74807c5e91f1ed4cb00a1c973be05c63986e61/src/secp256r1/ec.cairo +from starkware.cairo.common.cairo_secp.bigint import BigInt3, UnreducedBigInt3, nondet_bigint3 +from starkware.cairo.common.cairo_secp.ec import EcPoint + +// src.secp256r1.constants +// SECP_REM is defined by the equation: +// secp256r1_prime = 2 ** 256 - SECP_REM. +const SECP_REM = 2 ** 224 - 2 ** 192 - 2 ** 96 + 1; + +const BASE = 2 ** 86; + +// A = 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc +const A0 = 0x3ffffffffffffffffffffc; +const A1 = 0x3ff; +const A2 = 0xffffffff0000000100000; + +// Constants for unreduced_mul/sqr +const s2 = (-(2 ** 76)) - 2 ** 12; +const s1 = (-(2 ** 66)) + 4; +const s0 = 2 ** 56; + +const r2 = 2 ** 54 - 2 ** 22; +const r1 = -(2 ** 12); +const r0 = 4; + +// src.secp256r1.field +// Adapt from starkware.cairo.common.math's assert_250_bit +func assert_165_bit{range_check_ptr}(value) { + const UPPER_BOUND = 2 ** 165; + const SHIFT = 2 ** 128; + const HIGH_BOUND = UPPER_BOUND / SHIFT; + + let low = [range_check_ptr]; + let high = [range_check_ptr + 1]; + + %{ + from starkware.cairo.common.math_utils import as_int + + # Correctness check. + value = as_int(ids.value, PRIME) % PRIME + assert value < ids.UPPER_BOUND, f'{value} is outside of the range [0, 2**250).' + + # Calculation for the assertion. + ids.high, ids.low = divmod(ids.value, ids.SHIFT) + %} + + assert [range_check_ptr + 2] = HIGH_BOUND - 1 - high; + + assert value = high * SHIFT + low; + + let range_check_ptr = range_check_ptr + 3; + return (); +} + +// src.secp256r1.field +// Computes the multiplication of two big integers, given in BigInt3 representation, modulo the +// secp256r1 prime. +// +// Arguments: +// x, y - the two BigInt3 to operate on. +// +// Returns: +// x * y in an UnreducedBigInt3 representation (the returned limbs may be above 3 * BASE). +// +// This means that if unreduced_mul is called on the result of nondet_bigint3, or the difference +// between two such results, we have: +// Soundness guarantee: the limbs are in the range (). +// Completeness guarantee: the limbs are in the range (). +func unreduced_mul(a: BigInt3, b: BigInt3) -> (res_low: UnreducedBigInt3) { + tempvar twice_d2 = a.d2 * b.d2; + tempvar d1d2 = a.d2 * b.d1 + a.d1 * b.d2; + return ( + UnreducedBigInt3( + d0=a.d0 * b.d0 + s0 * twice_d2 + r0 * d1d2, + d1=a.d1 * b.d0 + a.d0 * b.d1 + s1 * twice_d2 + r1 * d1d2, + d2=a.d2 * b.d0 + a.d1 * b.d1 + a.d0 * b.d2 + s2 * twice_d2 + r2 * d1d2, + ), + ); +} + +// src.secp256r1.field +// Computes the square of a big integer, given in BigInt3 representation, modulo the +// secp256r1 prime. +// +// Has the same guarantees as in unreduced_mul(a, a). +func unreduced_sqr(a: BigInt3) -> (res_low: UnreducedBigInt3) { + tempvar twice_d2 = a.d2 * a.d2; + tempvar twice_d1d2 = a.d2 * a.d1 + a.d1 * a.d2; + tempvar d1d0 = a.d1 * a.d0; + return ( + UnreducedBigInt3( + d0=a.d0 * a.d0 + s0 * twice_d2 + r0 * twice_d1d2, + d1=d1d0 + d1d0 + s1 * twice_d2 + r1 * twice_d1d2, + d2=a.d2 * a.d0 + a.d1 * a.d1 + a.d0 * a.d2 + s2 * twice_d2 + r2 * twice_d1d2, + ), + ); +} + +// src.secp256r1.field +// Verifies that the given unreduced value is equal to zero modulo the secp256r1 prime. +// +// Completeness assumption: val's limbs are in the range (-2**210.99, 2**210.99). +// Soundness assumption: val's limbs are in the range (-2**250, 2**250). +func verify_zero{range_check_ptr}(val: UnreducedBigInt3) { + alloc_locals; + local q; + // local q_sign; + let q_sign = 1; + // original: + // %{ from starkware.cairo.common.cairo_secp.secp_utils import SECP256R1_P as SECP_P %} + // %{ + // from starkware.cairo.common.cairo_secp.secp_utils import pack + + // q, r = divmod(pack(ids.val, PRIME), SECP_P) + // assert r == 0, f"verify_zero: Invalid input {ids.val.d0, ids.val.d1, ids.val.d2}." + // if q >= 0: + // ids.q = q % PRIME + // ids.q_sign = 1 + // else: + // ids.q = (0-q) % PRIME + // ids.q_sign = -1 % PRIME + // %} + %{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P %} + %{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + + q, r = divmod(pack(ids.val, PRIME), SECP_P) + assert r == 0, f"verify_zero: Invalid input {ids.val.d0, ids.val.d1, ids.val.d2}." + ids.q = q % PRIME + %} + // assert_250_bit(q); // 256K steps + // assert_le_felt(q, 2**165); // 275K steps + assert_165_bit(q); + assert q_sign * (val.d2 + val.d1 / BASE + val.d0 / BASE ** 2) = q * ( + (BASE / 4) - SECP_REM / BASE ** 2 + ); + // Multiply by BASE**2 both sides: + // (q_sign) * val = q * (BASE**3 / 4 - SECP_REM) + // = q * (2**256 - SECP_REM) = q * secp256r1_prime = 0 mod secp256r1_prime + return (); +} + +// Computes the slope of the elliptic curve at a given point. +// The slope is used to compute point + point. +// +// Arguments: +// point - the point to operate on. +// +// Returns: +// slope - the slope of the curve at point, in BigInt3 representation. +// +// Assumption: point != 0. +func compute_doubling_slope{range_check_ptr}(point: EcPoint) -> (slope: BigInt3) { + // Note that y cannot be zero: assume that it is, then point = -point, so 2 * point = 0, which + // contradicts the fact that the size of the curve is odd. + // originals: + // %{ from starkware.cairo.common.cairo_secp.secp_utils import SECP256R1_P as SECP_P %} + // %{ from starkware.cairo.common.cairo_secp.secp_utils import SECP256R1_ALPHA as ALPHA %} + %{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P %} + %{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_ALPHA as ALPHA %} + %{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + from starkware.python.math_utils import ec_double_slope + + # Compute the slope. + x = pack(ids.point.x, PRIME) + y = pack(ids.point.y, PRIME) + value = slope = ec_double_slope(point=(x, y), alpha=ALPHA, p=SECP_P) + %} + let (slope: BigInt3) = nondet_bigint3(); + + let (x_sqr: UnreducedBigInt3) = unreduced_sqr(point.x); + let (slope_y: UnreducedBigInt3) = unreduced_mul(slope, point.y); + verify_zero( + UnreducedBigInt3( + d0=3 * x_sqr.d0 + A0 - 2 * slope_y.d0, + d1=3 * x_sqr.d1 + A1 - 2 * slope_y.d1, + d2=3 * x_sqr.d2 + A2 - 2 * slope_y.d2, + ), + ); + + return (slope=slope); +} + +func test_doubling_slope{range_check_ptr}() { + let point = EcPoint(BigInt3(614323, 5456867, 101208), BigInt3(773712524, 77371252, 5298795)); + + let (slope) = compute_doubling_slope(point); + + assert slope = BigInt3( + 64081873649130491683833713, 34843994309543177837008178, 16548672716077616016846383 + ); + + let point = EcPoint( + BigInt3(51215, 36848548548458, 634734734), BigInt3(26362, 263724839599, 901297012) + ); + + let (slope) = compute_doubling_slope(point); + + assert slope = BigInt3( + 71848883893335852660776740, 75644451964360469099209675, 547087410329256463669633 + ); + + return (); +} + +func main{range_check_ptr}() { + test_doubling_slope(); + return (); +} diff --git a/pkg/hints/bigint_hint.go b/pkg/hints/bigint_hint.go new file mode 100644 index 00000000..a13fc098 --- /dev/null +++ b/pkg/hints/bigint_hint.go @@ -0,0 +1,56 @@ +package hints + +import ( + "errors" + "math/big" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +/* +Implements hint: +%{ + from starkware.cairo.common.cairo_secp.secp_utils import split + + segments.write_arg(ids.res.address_, split(value)) +%} +*/ + +func NondetBigInt3(virtual_machine VirtualMachine, execScopes ExecutionScopes, idsData IdsManager) error { + resRelloc, err := idsData.GetAddr("res", &virtual_machine) + if err != nil { + return err + } + + valueUncast, err := execScopes.Get("value") + if err != nil { + return err + } + value, ok := valueUncast.(big.Int) + if !ok { + return errors.New("Could not cast value into big int") + } + + bigint3Split, err := Bigint3Split(value) + if err != nil { + return err + } + + arg := make([]memory.MaybeRelocatable, 0) + + for i := 0; i < 3; i++ { + m := memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromBigInt(&bigint3Split[i])) + arg = append(arg, *m) + } + + _, loadErr := virtual_machine.Segments.LoadData(resRelloc, &arg) + if loadErr != nil { + return loadErr + } + + return nil +} diff --git a/pkg/hints/bigint_hint_test.go b/pkg/hints/bigint_hint_test.go new file mode 100644 index 00000000..27bc55b4 --- /dev/null +++ b/pkg/hints/bigint_hint_test.go @@ -0,0 +1,75 @@ +package hints_test + +import ( + "math/big" + "testing" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints" + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_codes" + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestNonDetBigInt3Ok(t *testing.T) { + vm := NewVirtualMachine() + + vm.Segments.AddSegment() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Pc = NewRelocatable(0, 0) + vm.RunContext.Ap = NewRelocatable(1, 6) + vm.RunContext.Fp = NewRelocatable(1, 6) + + value, _ := new(big.Int).SetString("7737125245533626718119526477371252455336267181195264773712524553362", 10) + execScopes := NewExecutionScopes() + + execScopes.AssignOrUpdateVariable("value", *value) + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "res": {nil}, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: NONDET_BIGINT3_V1, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("Non Det Big Int 3 hint test failed with error: %s", err) + } else { + valueInStruct0, err := idsManager.GetStructFieldFelt("res", 0, vm) + expected0 := lambdaworks.FeltFromDecString("773712524553362") + if err != nil { + t.Errorf("error fetching from ids manager : %s", err) + } + if valueInStruct0 != expected0 { + t.Errorf(" Incorrect field value %s, expected %s", valueInStruct0.ToBigInt().Text(10), expected0.ToBigInt().Text(10)) + } + + valueInStruct1, err := idsManager.GetStructFieldFelt("res", 1, vm) + expected1 := lambdaworks.FeltFromDecString("57408430697461422066401280") + if err != nil { + t.Errorf("error fetching from ids manager : %s", err) + } + if valueInStruct1 != expected1 { + t.Errorf(" Incorrect field value %s, expected %s", valueInStruct1.ToBigInt().Text(10), expected1.ToBigInt().Text(10)) + } + + valueInStruct2, err := idsManager.GetStructFieldFelt("res", 2, vm) + expected2 := lambdaworks.FeltFromDecString("1292469707114105") + if err != nil { + t.Errorf("error fetching from ids manager : %s", err) + } + if valueInStruct2 != expected2 { + t.Errorf(" Incorrect field value %s, expected %s", valueInStruct2.ToBigInt().Text(10), expected2.ToBigInt().Text(10)) + } + } +} diff --git a/pkg/hints/ec_hint.go b/pkg/hints/ec_hint.go index 698011d6..4b0a8ce2 100644 --- a/pkg/hints/ec_hint.go +++ b/pkg/hints/ec_hint.go @@ -1,6 +1,7 @@ package hints import ( + "errors" "math/big" "github.com/lambdaclass/cairo-vm.go/pkg/builtins" @@ -39,6 +40,7 @@ func EcPointFromVarName(name string, vm *VirtualMachine, idsData IdsManager) (Ec /* Implements main logic for `EC_NEGATE` and `EC_NEGATE_EMBEDDED_SECP` hints */ + func ecNegate(vm *vm.VirtualMachine, execScopes types.ExecutionScopes, ids hint_utils.IdsManager, secpP big.Int) error { point, err := ids.GetRelocatable("point", vm) if err != nil { @@ -182,9 +184,85 @@ func computeSlope(vm *VirtualMachine, execScopes ExecutionScopes, idsData IdsMan return nil } +/* +Implements hint: +%{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_ALPHA as ALPHA %} +*/ + +func importSecp256r1Alpha(execScopes ExecutionScopes) error { + execScopes.AssignOrUpdateVariable("ALPHA", SECP256R1_ALPHA()) + return nil +} + +/* +Implements hint: +%{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_N as N %} +*/ +func importSECP256R1N(execScopes ExecutionScopes) error { + execScopes.AssignOrUpdateVariable("N", SECP256R1_N()) + return nil +} + +/* +Implements hint: +%{ +from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P +%} +*/ + +func importSECP256R1P(execScopes ExecutionScopes) error { + execScopes.AssignOrUpdateVariable("SECP_P", SECP256R1_P()) + return nil +} + /* Implements hint: + %{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + from starkware.python.math_utils import ec_double_slope + # Compute the slope. + x = pack(ids.point.x, PRIME) + y = pack(ids.point.y, PRIME) + value = slope = ec_double_slope(point=(x, y), alpha=ALPHA, p=SECP_P) + +%} +*/ +func computeDoublingSlopeExternalConsts(vm VirtualMachine, execScopes ExecutionScopes, ids_data IdsManager) error { + // ids.point + point, err := EcPointFromVarName("point", &vm, ids_data) + if err != nil { + return err + } + + secpPuncast, err := execScopes.Get("SECP_P") + if err != nil { + return err + } + secpP, ok := secpPuncast.(big.Int) + if !ok { + return errors.New("Could not cast secp into big int") + } + + alphaUncast, err := execScopes.Get("ALPHA") + if err != nil { + return nil + } + + alpha, ok := alphaUncast.(big.Int) + if !ok { + return errors.New("Could not cast alpha into big int") + } + + doublePoint_b := builtins.DoublePointB{X: point.X.Pack86(), Y: point.Y.Pack86()} + + value, err := builtins.EcDoubleSlope(doublePoint_b, alpha, secpP) + execScopes.AssignOrUpdateVariable("value", value) + execScopes.AssignOrUpdateVariable("slope", value) + return nil +} + +/* %{ from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack @@ -194,21 +272,17 @@ Implements hint: y0 = pack(ids.point0.y, PRIME) value = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P" - %} + +%} */ func fastEcAddAssignNewX(ids IdsManager, vm *VirtualMachine, execScopes *ExecutionScopes, point0Alias string, point1Alias string, secpP big.Int) error { execScopes.AssignOrUpdateVariable("SECP_P", secpP) point0, err := EcPointFromVarName(point0Alias, vm, ids) - if err != nil { - return err - } - point1, err := EcPointFromVarName(point1Alias, vm, ids) if err != nil { return err } - slopeUnpacked, err := BigInt3FromVarName("slope", ids, vm) if err != nil { return err diff --git a/pkg/hints/field_utils.go b/pkg/hints/field_utils.go new file mode 100644 index 00000000..bb81d791 --- /dev/null +++ b/pkg/hints/field_utils.go @@ -0,0 +1,55 @@ +package hints + +import ( + "errors" + "math/big" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" + + . "github.com/lambdaclass/cairo-vm.go/pkg/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" +) + +/* +Implements hint: +%{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + + q, r = divmod(pack(ids.val, PRIME), SECP_P) + assert r == 0, f"verify_zero: Invalid input {ids.val.d0, ids.val.d1, ids.val.d2}." + ids.q = q % PRIME +%} +*/ + +func verifyZeroWithExternalConst(vm VirtualMachine, execScopes ExecutionScopes, idsData IdsManager) error { + secpPuncast, err := execScopes.Get("SECP_P") + if err != nil { + return err + } + secpP, ok := secpPuncast.(big.Int) + if !ok { + return errors.New("Could not cast secpP into big int") + } + + addr, err := idsData.GetAddr("val", &vm) + if err != nil { + return err + } + + val, err := BigInt3FromBaseAddr(addr, "val", &vm) + if err != nil { + return err + } + + v := val.Pack86() + q, r := v.DivMod(&v, &secpP, new(big.Int)) + + if r.Cmp(big.NewInt(0)) != 0 { + return errors.New("verify remainder is not zero: Invalid input") + } + + quotient := memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromBigInt(q)) + return idsData.Insert("q", quotient, &vm) +} diff --git a/pkg/hints/field_utils_test.go b/pkg/hints/field_utils_test.go new file mode 100644 index 00000000..534f8eff --- /dev/null +++ b/pkg/hints/field_utils_test.go @@ -0,0 +1,56 @@ +package hints_test + +import ( + "math/big" + "testing" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints" + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_codes" + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestVerifyZeroWithExternalConst(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Pc = NewRelocatable(0, 0) + vm.RunContext.Ap = NewRelocatable(1, 9) + vm.RunContext.Fp = NewRelocatable(1, 9) + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "val": {NewMaybeRelocatableFelt(lambdaworks.FeltFromUint64(55)), NewMaybeRelocatableFelt(lambdaworks.FeltFromUint64(0)), NewMaybeRelocatableFelt(lambdaworks.FeltFromUint64(0))}, + "q": {nil}, + }, + vm, + ) + + newScepP := big.NewInt(55) + execScopes := NewExecutionScopes() + + execScopes.AssignOrUpdateVariable("SECP_P", *newScepP) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: VERIFY_ZERO_EXTERNAL_SECP, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("verifyZeroWithExternalConst hint test failed with error: %s", err) + } else { + valueInMemory, err := idsManager.GetFelt("q", vm) + if err != nil { + t.Errorf("could not fetch value with error: %s", err) + } + if valueInMemory != FeltFromUint64(1) { + t.Errorf("value in memory is not the expected") + } + } +} diff --git a/pkg/hints/hint_codes/ec_op_hints.go b/pkg/hints/hint_codes/ec_op_hints.go index 005b055d..fabd0531 100644 --- a/pkg/hints/hint_codes/ec_op_hints.go +++ b/pkg/hints/hint_codes/ec_op_hints.go @@ -4,6 +4,8 @@ const EC_NEGATE = "from starkware.cairo.common.cairo_secp.secp_utils import SECP const EC_NEGATE_EMBEDDED_SECP = "from starkware.cairo.common.cairo_secp.secp_utils import pack\nSECP_P = 2**255-19\n\ny = pack(ids.point.y, PRIME) % SECP_P\n# The modulo operation in python always returns a nonnegative number.\nvalue = (-y) % SECP_P" const EC_DOUBLE_SLOPE_V1 = "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)" const COMPUTE_SLOPE_V1 = "from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack\nfrom starkware.python.math_utils import line_slope\n\n# Compute the slope.\nx0 = pack(ids.point0.x, PRIME)\ny0 = pack(ids.point0.y, PRIME)\nx1 = pack(ids.point1.x, PRIME)\ny1 = pack(ids.point1.y, PRIME)\nvalue = slope = line_slope(point1=(x0, y0), point2=(x1, y1), p=SECP_P)" +const EC_DOUBLE_SLOPE_EXTERNAL_CONSTS = "from starkware.cairo.common.cairo_secp.secp_utils import 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=ALPHA, p=SECP_P)" +const NONDET_BIGINT3_V1 = "from starkware.cairo.common.cairo_secp.secp_utils import split\n\nsegments.write_arg(ids.res.address_, split(value))" const FAST_EC_ADD_ASSIGN_NEW_X = `"from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack slope = pack(ids.slope, PRIME) diff --git a/pkg/hints/hint_codes/secp_p_hint.go b/pkg/hints/hint_codes/secp_p_hint.go new file mode 100644 index 00000000..2d6dac14 --- /dev/null +++ b/pkg/hints/hint_codes/secp_p_hint.go @@ -0,0 +1,6 @@ +package hint_codes + +const IMPORT_SECP256R1_ALPHA = "from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_ALPHA as ALPHA" +const IMPORT_SECP256R1_N = "from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_N as N" +const IMPORT_SECP256R1_P = "from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P" +const VERIFY_ZERO_EXTERNAL_SECP = "from starkware.cairo.common.cairo_secp.secp_utils import pack\n\nq, r = divmod(pack(ids.val, PRIME), SECP_P)\nassert r == 0, f\"verify_zero: Invalid input {ids.val.d0, ids.val.d1, ids.val.d2}.\"\nids.q = q % PRIME" diff --git a/pkg/hints/hint_processor.go b/pkg/hints/hint_processor.go index 9c5947cd..32cab3b2 100644 --- a/pkg/hints/hint_processor.go +++ b/pkg/hints/hint_processor.go @@ -156,10 +156,22 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any, return Assert250Bit(data.Ids, vm, constants) case SPLIT_FELT: return SplitFelt(data.Ids, vm, constants) + case IMPORT_SECP256R1_ALPHA: + return importSecp256r1Alpha(*execScopes) + case IMPORT_SECP256R1_N: + return importSECP256R1N(*execScopes) + case IMPORT_SECP256R1_P: + return importSECP256R1P(*execScopes) + case EC_DOUBLE_SLOPE_EXTERNAL_CONSTS: + return computeDoublingSlopeExternalConsts(*vm, *execScopes, data.Ids) + case NONDET_BIGINT3_V1: + return NondetBigInt3(*vm, *execScopes, data.Ids) case SPLIT_INT: return splitInt(data.Ids, vm) case SPLIT_INT_ASSERT_RANGE: return splitIntAssertRange(data.Ids, vm) + case VERIFY_ZERO_EXTERNAL_SECP: + return verifyZeroWithExternalConst(*vm, *execScopes, data.Ids) case FAST_EC_ADD_ASSIGN_NEW_X: return fastEcAddAssignNewX(data.Ids, vm, execScopes, "point0", "point1", SECP_P()) case FAST_EC_ADD_ASSIGN_NEW_X_V2: diff --git a/pkg/hints/hint_utils/bigint_utils.go b/pkg/hints/hint_utils/bigint_utils.go index 10cff28c..9a900443 100644 --- a/pkg/hints/hint_utils/bigint_utils.go +++ b/pkg/hints/hint_utils/bigint_utils.go @@ -6,6 +6,7 @@ import ( . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" . "github.com/lambdaclass/cairo-vm.go/pkg/vm" . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" + "github.com/pkg/errors" ) diff --git a/pkg/hints/hint_utils/secp_utils.go b/pkg/hints/hint_utils/secp_utils.go index b673e87c..a125823a 100644 --- a/pkg/hints/hint_utils/secp_utils.go +++ b/pkg/hints/hint_utils/secp_utils.go @@ -1,6 +1,9 @@ package hint_utils -import "math/big" +import ( + "errors" + "math/big" +) func SECP_P() big.Int { secpP, _ := new(big.Int).SetString("115792089237316195423570985008687907853269984665640564039457584007908834671663", 10) @@ -16,3 +19,38 @@ func ALPHA() big.Int { alpha := big.NewInt(0) return *alpha } + +func SECP256R1_ALPHA() big.Int { + secpPalpha, _ := new(big.Int).SetString("115792089210356248762697446949407573530086143415290314195533631308867097853948", 10) + return *secpPalpha +} + +func SECP256R1_N() big.Int { + secp256, _ := new(big.Int).SetString("115792089210356248762697446949407573529996955224135760342422259061068512044369", 10) + return *secp256 +} + +func SECP256R1_P() big.Int { + secp256r1, _ := new(big.Int).SetString("115792089210356248762697446949407573530086143415290314195533631308867097853951", 10) + return *secp256r1 +} + +func BASE_MINUS_ONE() *big.Int { + res, _ := new(big.Int).SetString("77371252455336267181195263", 10) + return res +} + +func Bigint3Split(integer big.Int) ([]big.Int, error) { + canonicalRepr := make([]big.Int, 3) + num := integer + + for i := 0; i < 3; i++ { + canonicalRepr[i] = *new(big.Int).And(&num, BASE_MINUS_ONE()) + num.Rsh(&num, 86) + } + if num.Cmp(big.NewInt(0)) != 0 { + return nil, errors.New("HintError SecpSplitOutOfRange") + } + + return canonicalRepr, nil +} diff --git a/pkg/lambdaworks/lambdaworks.go b/pkg/lambdaworks/lambdaworks.go index d2627b93..3cd29d21 100644 --- a/pkg/lambdaworks/lambdaworks.go +++ b/pkg/lambdaworks/lambdaworks.go @@ -309,10 +309,9 @@ func FeltFromBigInt(n *big.Int) Felt { if n.Cmp(prime) != -1 { n = new(big.Int).Mod(n, prime) } - bytes := n.Bytes() - var bytes32 [32]byte - copy(bytes32[:], bytes) - return FeltFromLeBytes(&bytes32) + + value := n.Text(10) + return FeltFromDecString(value) } const CAIRO_PRIME_HEX = "0x800000000000011000000000000000000000000000000000000000000000001" diff --git a/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index 3046cb5c..47fb4e24 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -321,6 +321,9 @@ func TestSplitIntHintProofMode(t *testing.T) { testProgramProof("split_int", t) } +func TestIntegrationEcDoubleSlope(t *testing.T) { + testProgram("ec_double_slope", t) +} func TestKeccakIntegrationTests(t *testing.T) { testProgram("keccak_integration_tests", t) }