From 9cde79b36064d67d802e7f8d4df08d3fd9ff3a70 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 3 Oct 2023 01:44:55 +0300 Subject: [PATCH 1/9] Add testing util CheckScopeVar` (#308) --- pkg/hints/hint_utils/testing_utils.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/hints/hint_utils/testing_utils.go b/pkg/hints/hint_utils/testing_utils.go index 46403f07..a52ddb0d 100644 --- a/pkg/hints/hint_utils/testing_utils.go +++ b/pkg/hints/hint_utils/testing_utils.go @@ -1,8 +1,12 @@ package hint_utils import ( + "reflect" + "testing" + "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" "github.com/lambdaclass/cairo-vm.go/pkg/parser" + "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" ) @@ -56,3 +60,13 @@ func SetupConstantsForTest(new_constants map[string]lambdaworks.Felt, ids *IdsMa } return constants } + +func CheckScopeVar[T any](name string, expectedVal T, scopes *types.ExecutionScopes, t *testing.T) { + val, err := types.FetchScopeVar[T](name, scopes) + if err != nil { + t.Error(err.Error()) + } + if !reflect.DeepEqual(val, expectedVal) { + t.Errorf("Wrong scope var %s.\n Expected: %v, got: %v", name, expectedVal, val) + } +} From 76d65f699b7dc9579a40f364b5fce136e9a49bda Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 3 Oct 2023 17:48:16 +0300 Subject: [PATCH 2/9] Implement most signature hints (#291) * 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 * Add hint codes * Implement base hint * Add hints * Add hints to ExecuteHint * debug info * Fix broken test * Move integration test to cairo_run_test.go * Move file from hints_utils and rename * Delete debug * Return error of IdsData.Insert * Change to camel case * Add unit test * Add unit test * Add hint codes * Implement hint * Add SafeDivBig * Add generic way to fetch scope variables * Add generic fetch * Add generic way to fetch scope variables * Use more specific error * Add hints to ExecuteHint * Add extra hint * Fix logic, add unit test * Add unit test * use boolean flag instead or arg * Fix scope var name * Fix scope var name in tests * Make FetchScopeVar work despite references * Revert "Make FetchScopeVar work despite references" This reverts commit 69993be48a9a8fea8d241450463e4dd240091056. * Handle scope variables as big.Int instead of *big.Int * Fix merge cnflicts * Fix tests * Implement Igcdex + add tests * Implement DivMod * Use DivMod instead of Div + Mod * Dont modify the original value in bigint3_split function * Push test file * Remove redundant check * Merge math_utils/utils & utils/math_utils --------- Co-authored-by: Milton Co-authored-by: mmsc2 <88055861+mmsc2@users.noreply.github.com> Co-authored-by: Mariano A. Nicolini Co-authored-by: Pedro Fontana --- cairo_programs/div_mod_n.cairo | 129 +++++++++++++++ pkg/builtins/ec_op.go | 5 +- pkg/hints/hint_codes/signature_hint_codes.go | 21 +++ pkg/hints/hint_processor.go | 10 ++ pkg/hints/hint_utils/bigint_utils.go | 17 +- pkg/hints/hint_utils/secp_utils.go | 2 +- pkg/hints/math_hints.go | 2 +- pkg/hints/signature_hints.go | 84 ++++++++++ pkg/hints/signature_hints_test.go | 159 ++++++++++++++++++ pkg/math_utils/utils.go | 27 ---- pkg/math_utils/utils_test.go | 129 --------------- pkg/utils/math_utils.go | 46 ++++++ pkg/utils/math_utils_test.go | 161 +++++++++++++++++++ pkg/vm/cairo_run/cairo_run_test.go | 4 + 14 files changed, 626 insertions(+), 170 deletions(-) create mode 100644 cairo_programs/div_mod_n.cairo create mode 100644 pkg/hints/hint_codes/signature_hint_codes.go create mode 100644 pkg/hints/signature_hints.go create mode 100644 pkg/hints/signature_hints_test.go delete mode 100644 pkg/math_utils/utils.go delete mode 100644 pkg/math_utils/utils_test.go diff --git a/cairo_programs/div_mod_n.cairo b/cairo_programs/div_mod_n.cairo new file mode 100644 index 00000000..4dbe3c82 --- /dev/null +++ b/cairo_programs/div_mod_n.cairo @@ -0,0 +1,129 @@ +%builtins range_check + +from starkware.cairo.common.cairo_secp.bigint import BigInt3, nondet_bigint3, BASE, bigint_mul +from starkware.cairo.common.cairo_secp.constants import BETA, N0, N1, N2 + +// Source: https://github.com/myBraavos/efficient-secp256r1/blob/73cca4d53730cb8b2dcf34e36c7b8f34b96b3230/src/secp256r1/signature.cairo + +// Computes a * b^(-1) modulo the size of the elliptic curve (N). +// +// Prover assumptions: +// * All the limbs of a are in the range (-2 ** 210.99, 2 ** 210.99). +// * All the limbs of b are in the range (-2 ** 124.99, 2 ** 124.99). +// * b is in the range [0, 2 ** 256). +// +// Soundness assumptions: +// * The limbs of a are in the range (-2 ** 249, 2 ** 249). +// * The limbs of b are in the range (-2 ** 159.83, 2 ** 159.83). +func div_mod_n{range_check_ptr}(a: BigInt3, b: BigInt3) -> (res: BigInt3) { + %{ + from starkware.cairo.common.cairo_secp.secp_utils import N, pack + from starkware.python.math_utils import div_mod, safe_div + + a = pack(ids.a, PRIME) + b = pack(ids.b, PRIME) + value = res = div_mod(a, b, N) + %} + let (res) = nondet_bigint3(); + + %{ value = k_plus_one = safe_div(res * b - a, N) + 1 %} + let (k_plus_one) = nondet_bigint3(); + let k = BigInt3(d0=k_plus_one.d0 - 1, d1=k_plus_one.d1, d2=k_plus_one.d2); + + let (res_b) = bigint_mul(res, b); + let n = BigInt3(N0, N1, N2); + let (k_n) = bigint_mul(k, n); + + // We should now have res_b = k_n + a. Since the numbers are in unreduced form, + // we should handle the carry. + + tempvar carry1 = (res_b.d0 - k_n.d0 - a.d0) / BASE; + assert [range_check_ptr + 0] = carry1 + 2 ** 127; + + tempvar carry2 = (res_b.d1 - k_n.d1 - a.d1 + carry1) / BASE; + assert [range_check_ptr + 1] = carry2 + 2 ** 127; + + tempvar carry3 = (res_b.d2 - k_n.d2 - a.d2 + carry2) / BASE; + assert [range_check_ptr + 2] = carry3 + 2 ** 127; + + tempvar carry4 = (res_b.d3 - k_n.d3 + carry3) / BASE; + assert [range_check_ptr + 3] = carry4 + 2 ** 127; + + assert res_b.d4 - k_n.d4 + carry4 = 0; + + let range_check_ptr = range_check_ptr + 4; + + return (res=res); +} + +func div_mod_n_alt{range_check_ptr}(a: BigInt3, b: BigInt3) -> (res: BigInt3) { + // just used to import N + %{ + from starkware.cairo.common.cairo_secp.secp_utils import N, pack + from starkware.python.math_utils import div_mod, safe_div + + a = pack(ids.a, PRIME) + b = pack(ids.b, PRIME) + value = res = div_mod(a, b, N) + %} + + %{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + from starkware.python.math_utils import div_mod, safe_div + + a = pack(ids.a, PRIME) + b = pack(ids.b, PRIME) + value = res = div_mod(a, b, N) + %} + let (res) = nondet_bigint3(); + + %{ value = k_plus_one = safe_div(res * b - a, N) + 1 %} + let (k_plus_one) = nondet_bigint3(); + let k = BigInt3(d0=k_plus_one.d0 - 1, d1=k_plus_one.d1, d2=k_plus_one.d2); + + let (res_b) = bigint_mul(res, b); + let n = BigInt3(N0, N1, N2); + let (k_n) = bigint_mul(k, n); + + tempvar carry1 = (res_b.d0 - k_n.d0 - a.d0) / BASE; + assert [range_check_ptr + 0] = carry1 + 2 ** 127; + + tempvar carry2 = (res_b.d1 - k_n.d1 - a.d1 + carry1) / BASE; + assert [range_check_ptr + 1] = carry2 + 2 ** 127; + + tempvar carry3 = (res_b.d2 - k_n.d2 - a.d2 + carry2) / BASE; + assert [range_check_ptr + 2] = carry3 + 2 ** 127; + + tempvar carry4 = (res_b.d3 - k_n.d3 + carry3) / BASE; + assert [range_check_ptr + 3] = carry4 + 2 ** 127; + + assert res_b.d4 - k_n.d4 + carry4 = 0; + + let range_check_ptr = range_check_ptr + 4; + + return (res=res); +} + +func test_div_mod_n{range_check_ptr: felt}() { + let a: BigInt3 = BigInt3(100, 99, 98); + let b: BigInt3 = BigInt3(10, 9, 8); + + let (res) = div_mod_n(a, b); + + assert res = BigInt3( + 3413472211745629263979533, 17305268010345238170172332, 11991751872105858217578135 + ); + + // test alternative hint + let (res_alt) = div_mod_n_alt(a, b); + + assert res_alt = res; + + return (); +} + +func main{range_check_ptr: felt}() { + test_div_mod_n(); + + return (); +} diff --git a/pkg/builtins/ec_op.go b/pkg/builtins/ec_op.go index 0e973a0a..9053c25c 100644 --- a/pkg/builtins/ec_op.go +++ b/pkg/builtins/ec_op.go @@ -5,7 +5,6 @@ import ( "math/big" "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" - "github.com/lambdaclass/cairo-vm.go/pkg/math_utils" "github.com/lambdaclass/cairo-vm.go/pkg/utils" "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" "github.com/pkg/errors" @@ -263,7 +262,7 @@ func LineSlope(point_a PartialSumB, point_b DoublePointB, prime big.Int) (big.In n := new(big.Int).Sub(&point_a.Y, &point_b.Y) m := new(big.Int).Sub(&point_a.X, &point_b.X) - z, err := math_utils.DivMod(n, m, &prime) + z, err := utils.DivMod(n, m, &prime) if err != nil { return big.Int{}, err } @@ -299,7 +298,7 @@ func EcDoubleSlope(point DoublePointB, alpha big.Int, prime big.Int) (big.Int, e n.Add(n, &alpha) m := new(big.Int).Mul(&point.Y, big.NewInt(2)) - z, err := math_utils.DivMod(n, m, &prime) + z, err := utils.DivMod(n, m, &prime) if err != nil { return big.Int{}, err diff --git a/pkg/hints/hint_codes/signature_hint_codes.go b/pkg/hints/hint_codes/signature_hint_codes.go new file mode 100644 index 00000000..37b37051 --- /dev/null +++ b/pkg/hints/hint_codes/signature_hint_codes.go @@ -0,0 +1,21 @@ +package hint_codes + +const DIV_MOD_N_PACKED_DIVMOD_V1 = `from starkware.cairo.common.cairo_secp.secp_utils import N, pack +from starkware.python.math_utils import div_mod, safe_div + +a = pack(ids.a, PRIME) +b = pack(ids.b, PRIME) +value = res = div_mod(a, b, N)` + +const DIV_MOD_N_PACKED_DIVMOD_EXTERNAL_N = `from starkware.cairo.common.cairo_secp.secp_utils import pack +from starkware.python.math_utils import div_mod, safe_div + +a = pack(ids.a, PRIME) +b = pack(ids.b, PRIME) +value = res = div_mod(a, b, N)` + +const DIV_MOD_N_SAFE_DIV = "value = k = safe_div(res * b - a, N)" + +const DIV_MOD_N_SAFE_DIV_PLUS_ONE = "value = k_plus_one = safe_div(res * b - a, N) + 1" + +const XS_SAFE_DIV = "value = k = safe_div(res * s - x, N)" diff --git a/pkg/hints/hint_processor.go b/pkg/hints/hint_processor.go index ccac7bd2..9685de6e 100644 --- a/pkg/hints/hint_processor.go +++ b/pkg/hints/hint_processor.go @@ -188,6 +188,16 @@ 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 DIV_MOD_N_PACKED_DIVMOD_V1: + return divModNPackedDivMod(data.Ids, vm, execScopes) + case DIV_MOD_N_PACKED_DIVMOD_EXTERNAL_N: + return divModNPackedDivModExternalN(data.Ids, vm, execScopes) + case XS_SAFE_DIV: + return divModNSafeDiv(data.Ids, execScopes, "x", "s", false) + case DIV_MOD_N_SAFE_DIV: + return divModNSafeDiv(data.Ids, execScopes, "a", "b", false) + case DIV_MOD_N_SAFE_DIV_PLUS_ONE: + return divModNSafeDiv(data.Ids, execScopes, "a", "b", true) case VERIFY_ZERO_EXTERNAL_SECP: return verifyZeroWithExternalConst(*vm, *execScopes, data.Ids) case FAST_EC_ADD_ASSIGN_NEW_X: diff --git a/pkg/hints/hint_utils/bigint_utils.go b/pkg/hints/hint_utils/bigint_utils.go index 9a900443..2fac8054 100644 --- a/pkg/hints/hint_utils/bigint_utils.go +++ b/pkg/hints/hint_utils/bigint_utils.go @@ -96,15 +96,14 @@ func BigInt3FromBaseAddr(addr Relocatable, name string, vm *VirtualMachine) (Big } func BigInt3FromVarName(name string, ids IdsManager, vm *VirtualMachine) (BigInt3, error) { - bigIntAddr, err := ids.GetAddr(name, vm) - if err != nil { - return BigInt3{}, err - } + limbs, err := limbsFromVarName(3, name, ids, vm) + return BigInt3{Limbs: limbs}, err +} - bigInt, err := BigInt3FromBaseAddr(bigIntAddr, name, vm) - if err != nil { - return BigInt3{}, err - } +// Uint384 + +type Uint384 = BigInt3 - return bigInt, err +func Uint384FromVarName(name string, ids IdsManager, vm *VirtualMachine) (Uint384, error) { + return BigInt3FromVarName(name, ids, vm) } diff --git a/pkg/hints/hint_utils/secp_utils.go b/pkg/hints/hint_utils/secp_utils.go index a125823a..bec3b650 100644 --- a/pkg/hints/hint_utils/secp_utils.go +++ b/pkg/hints/hint_utils/secp_utils.go @@ -46,7 +46,7 @@ func Bigint3Split(integer big.Int) ([]big.Int, error) { for i := 0; i < 3; i++ { canonicalRepr[i] = *new(big.Int).And(&num, BASE_MINUS_ONE()) - num.Rsh(&num, 86) + num = *new(big.Int).Rsh(&num, 86) } if num.Cmp(big.NewInt(0)) != 0 { return nil, errors.New("HintError SecpSplitOutOfRange") diff --git a/pkg/hints/math_hints.go b/pkg/hints/math_hints.go index 03b239c3..a577131d 100644 --- a/pkg/hints/math_hints.go +++ b/pkg/hints/math_hints.go @@ -7,8 +7,8 @@ import ( . "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/math_utils" . "github.com/lambdaclass/cairo-vm.go/pkg/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/utils" . "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/signature_hints.go b/pkg/hints/signature_hints.go new file mode 100644 index 00000000..37b57f7f --- /dev/null +++ b/pkg/hints/signature_hints.go @@ -0,0 +1,84 @@ +package hints + +import ( + "math/big" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + . "github.com/lambdaclass/cairo-vm.go/pkg/types" + "github.com/lambdaclass/cairo-vm.go/pkg/utils" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" +) + +func divModNPacked(ids IdsManager, vm *VirtualMachine, scopes *ExecutionScopes, n *big.Int) error { + a, err := Uint384FromVarName("a", ids, vm) + if err != nil { + return err + } + b, err := Uint384FromVarName("b", ids, vm) + if err != nil { + return err + } + packedA := a.Pack86() + packedB := b.Pack86() + + val, err := utils.DivMod(&packedA, &packedB, n) + if err != nil { + return err + } + + scopes.AssignOrUpdateVariable("a", packedA) + scopes.AssignOrUpdateVariable("b", packedB) + scopes.AssignOrUpdateVariable("value", *val) + scopes.AssignOrUpdateVariable("res", *val) + + return nil +} + +func divModNPackedDivMod(ids IdsManager, vm *VirtualMachine, scopes *ExecutionScopes) error { + n, _ := new(big.Int).SetString("115792089237316195423570985008687907852837564279074904382605163141518161494337", 10) + scopes.AssignOrUpdateVariable("N", *n) + return divModNPacked(ids, vm, scopes, n) +} + +func divModNPackedDivModExternalN(ids IdsManager, vm *VirtualMachine, scopes *ExecutionScopes) error { + n, err := FetchScopeVar[big.Int]("N", scopes) + if err != nil { + return err + } + return divModNPacked(ids, vm, scopes, &n) +} + +func divModNSafeDiv(ids IdsManager, scopes *ExecutionScopes, aAlias string, bAlias string, addOne bool) error { + // Fetch scope variables + a, err := FetchScopeVar[big.Int](aAlias, scopes) + if err != nil { + return err + } + + b, err := FetchScopeVar[big.Int](bAlias, scopes) + if err != nil { + return err + } + + res, err := FetchScopeVar[big.Int]("res", scopes) + if err != nil { + return err + } + + n, err := FetchScopeVar[big.Int]("N", scopes) + if err != nil { + return err + } + + // Hint logic + value, err := utils.SafeDivBig(new(big.Int).Sub(new(big.Int).Mul(&res, &b), &a), &n) + if err != nil { + return err + } + if addOne { + value = new(big.Int).Add(value, big.NewInt(1)) + } + // Update scope + scopes.AssignOrUpdateVariable("value", *value) + return nil +} diff --git a/pkg/hints/signature_hints_test.go b/pkg/hints/signature_hints_test.go new file mode 100644 index 00000000..4c3de522 --- /dev/null +++ b/pkg/hints/signature_hints_test.go @@ -0,0 +1,159 @@ +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 TestDivModNPackedDivMod(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFelt(FeltFromUint64(10)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + }, + "b": { + NewMaybeRelocatableFelt(FeltFromUint64(2)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + }, + }, + vm, + ) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: DIV_MOD_N_PACKED_DIVMOD_V1, + }) + scopes := NewExecutionScopes() + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err != nil { + t.Errorf("DIV_MOD_N_PACKED_DIVMOD_V1 hint test failed with error %s", err) + } + // Check result in scope + expectedRes := big.NewInt(5) + + res, err := FetchScopeVar[big.Int]("res", scopes) + if err != nil || res.Cmp(expectedRes) != 0 { + t.Error("Wrong/No scope value res") + } + + val, err := FetchScopeVar[big.Int]("value", scopes) + if err != nil || val.Cmp(expectedRes) != 0 { + t.Error("Wrong/No scope var value") + } +} + +func TestDivModNPackedDivModExternalN(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFelt(FeltFromUint64(20)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + }, + "b": { + NewMaybeRelocatableFelt(FeltFromUint64(2)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + }, + }, + vm, + ) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: DIV_MOD_N_PACKED_DIVMOD_EXTERNAL_N, + }) + scopes := NewExecutionScopes() + scopes.AssignOrUpdateVariable("N", *big.NewInt(7)) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err != nil { + t.Errorf("DIV_MOD_N_PACKED_DIVMOD_EXTERNAL_N hint test failed with error %s", err) + } + // Check result in scope + expectedRes := big.NewInt(3) + + res, err := FetchScopeVar[big.Int]("res", scopes) + if err != nil || res.Cmp(expectedRes) != 0 { + t.Error("Wrong/No scope value res") + } + + val, err := FetchScopeVar[big.Int]("value", scopes) + if err != nil || val.Cmp(expectedRes) != 0 { + t.Error("Wrong/No scope var value") + } +} + +func TestDivModSafeDivOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{}, + vm, + ) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: DIV_MOD_N_SAFE_DIV, + }) + scopes := NewExecutionScopes() + scopes.AssignOrUpdateVariable("N", *big.NewInt(5)) + scopes.AssignOrUpdateVariable("a", *big.NewInt(10)) + scopes.AssignOrUpdateVariable("b", *big.NewInt(30)) + scopes.AssignOrUpdateVariable("res", *big.NewInt(2)) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err != nil { + t.Errorf("DIV_MOD_N_SAFE_DIV hint test failed with error %s", err) + } + // Check result in scope + expectedValue := big.NewInt(10) // (2 * 30 - 10) / 5 = 10 + + val, err := FetchScopeVar[big.Int]("value", scopes) + if err != nil || val.Cmp(expectedValue) != 0 { + t.Error("Wrong/No scope value val") + } +} + +func TestDivModSafeDivPlusOneOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{}, + vm, + ) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: DIV_MOD_N_SAFE_DIV_PLUS_ONE, + }) + scopes := NewExecutionScopes() + scopes.AssignOrUpdateVariable("N", *big.NewInt(5)) + scopes.AssignOrUpdateVariable("a", *big.NewInt(10)) + scopes.AssignOrUpdateVariable("b", *big.NewInt(30)) + scopes.AssignOrUpdateVariable("res", *big.NewInt(2)) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err != nil { + t.Errorf("DIV_MOD_N_SAFE_DIV_PLUS_ONE hint test failed with error %s", err) + } + // Check result in scope + expectedValue := big.NewInt(11) // (2 * 30 - 10) / 5 + 1 = 11 + + val, err := FetchScopeVar[big.Int]("value", scopes) + if err != nil || val.Cmp(expectedValue) != 0 { + t.Error("Wrong/No scope value val") + } +} diff --git a/pkg/math_utils/utils.go b/pkg/math_utils/utils.go deleted file mode 100644 index 282ba4a3..00000000 --- a/pkg/math_utils/utils.go +++ /dev/null @@ -1,27 +0,0 @@ -package math_utils - -import ( - "github.com/pkg/errors" - "math/big" -) - -// Finds a nonnegative integer x < p such that (m * x) % p == n. -func DivMod(n *big.Int, m *big.Int, p *big.Int) (*big.Int, error) { - a := new(big.Int) - gcd := new(big.Int) - gcd.GCD(a, nil, m, p) - - if gcd.Cmp(big.NewInt(1)) != 0 { - return nil, errors.Errorf("gcd(%s, %s) != 1", m, p) - } - - return n.Mul(n, a).Mod(n, p), nil -} - -func ISqrt(x *big.Int) (*big.Int, error) { - if x.Sign() == -1 { - return nil, errors.Errorf("Expected x: %s to be non-negative", x) - } - res := new(big.Int) - return res.Sqrt(x), nil -} diff --git a/pkg/math_utils/utils_test.go b/pkg/math_utils/utils_test.go deleted file mode 100644 index c4eee853..00000000 --- a/pkg/math_utils/utils_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package math_utils_test - -import ( - "math/big" - "testing" - - . "github.com/lambdaclass/cairo-vm.go/pkg/math_utils" -) - -func TestDivModOk(t *testing.T) { - a := new(big.Int) - b := new(big.Int) - prime := new(big.Int) - expected := new(big.Int) - - a.SetString("11260647941622813594563746375280766662237311019551239924981511729608487775604310196863705127454617186486639011517352066501847110680463498585797912894788", 10) - b.SetString("4020711254448367604954374443741161860304516084891705811279711044808359405970", 10) - prime.SetString("800000000000011000000000000000000000000000000000000000000000001", 16) - expected.SetString("2904750555256547440469454488220756360634457312540595732507835416669695939476", 10) - - num, err := DivMod(a, b, prime) - if err != nil { - t.Errorf("DivMod failed with error: %s", err) - } - if num.Cmp(expected) != 0 { - t.Errorf("Expected result: %s to be equal to %s", num, expected) - } -} - -func TestDivModMZeroFail(t *testing.T) { - a := new(big.Int) - b := new(big.Int) - prime := new(big.Int) - - a.SetString("11260647941622813594563746375280766662237311019551239924981511729608487775604310196863705127454617186486639011517352066501847110680463498585797912894788", 10) - prime.SetString("800000000000011000000000000000000000000000000000000000000000001", 16) - - _, err := DivMod(a, b, prime) - if err == nil { - t.Errorf("DivMod expected to failed with gcd != 1") - } -} - -func TestDivModMEqPFail(t *testing.T) { - a := new(big.Int) - b := new(big.Int) - prime := new(big.Int) - - a.SetString("11260647941622813594563746375280766662237311019551239924981511729608487775604310196863705127454617186486639011517352066501847110680463498585797912894788", 10) - b.SetString("800000000000011000000000000000000000000000000000000000000000001", 16) - prime.SetString("800000000000011000000000000000000000000000000000000000000000001", 16) - - _, err := DivMod(a, b, prime) - if err == nil { - t.Errorf("DivMod expected to failed with gcd != 1") - } -} - -func TestIsSqrtOk(t *testing.T) { - x := new(big.Int) - y := new(big.Int) - x.SetString("4573659632505831259480", 10) - y.Mul(x, x) - - sqr_y, err := ISqrt(y) - if err != nil { - t.Errorf("ISqrt failed with error: %s", err) - } - if x.Cmp(sqr_y) != 0 { - t.Errorf("Failed to get square root of x^2, x: %s", x) - } -} - -func TestCalculateIsqrtA(t *testing.T) { - x := new(big.Int) - x.SetString("81", 10) - sqrt, err := ISqrt(x) - if err != nil { - t.Error("ISqrt failed") - } - - expected := new(big.Int) - expected.SetString("9", 10) - - if sqrt.Cmp(expected) != 0 { - t.Errorf("ISqrt failed, expected %d, got %d", expected, sqrt) - } -} - -func TestCalculateIsqrtB(t *testing.T) { - x := new(big.Int) - x.SetString("4573659632505831259480", 10) - square := new(big.Int) - square = square.Mul(x, x) - - sqrt, err := ISqrt(square) - if err != nil { - t.Error("ISqrt failed") - } - - if sqrt.Cmp(x) != 0 { - t.Errorf("ISqrt failed, expected %d, got %d", x, sqrt) - } -} - -func TestCalculateIsqrtC(t *testing.T) { - x := new(big.Int) - x.SetString("3618502788666131213697322783095070105623107215331596699973092056135872020481", 10) - square := new(big.Int) - square = square.Mul(x, x) - - sqrt, err := ISqrt(square) - if err != nil { - t.Error("ISqrt failed") - } - - if sqrt.Cmp(x) != 0 { - t.Errorf("ISqrt failed, expected %d, got %d", x, sqrt) - } -} - -func TestIsSqrtFail(t *testing.T) { - x := big.NewInt(-1) - - _, err := ISqrt(x) - if err == nil { - t.Errorf("expected ISqrt to fail") - } -} diff --git a/pkg/utils/math_utils.go b/pkg/utils/math_utils.go index b1054f70..7f011a6c 100644 --- a/pkg/utils/math_utils.go +++ b/pkg/utils/math_utils.go @@ -63,3 +63,49 @@ func SafeDivBig(x *big.Int, y *big.Int) (*big.Int, error) { } return q, nil } + +// Finds a nonnegative integer x < p such that (m * x) % p == n. +func DivMod(n *big.Int, m *big.Int, p *big.Int) (*big.Int, error) { + a, _, c := Igcdex(m, p) + if c.Cmp(big.NewInt(1)) != 0 { + return nil, errors.Errorf("Operation failed: divmod(%s, %s, %s), igcdex(%s, %s) != 1 ", n.Text(10), m.Text(10), p.Text(10), m.Text(10), p.Text(10)) + } + return new(big.Int).Mod(new(big.Int).Mul(n, a), p), nil +} + +func Igcdex(a *big.Int, b *big.Int) (*big.Int, *big.Int, *big.Int) { + zero := big.NewInt(0) + one := big.NewInt(1) + switch true { + case a.Cmp(zero) == 0 && b.Cmp(zero) == 0: + return zero, one, zero + case a.Cmp(zero) == 0: + return zero, big.NewInt(int64(a.Sign())), new(big.Int).Abs(b) + case b.Cmp(zero) == 0: + return big.NewInt(int64(a.Sign())), zero, new(big.Int).Abs(a) + default: + xSign := big.NewInt(int64(a.Sign())) + ySign := big.NewInt(int64(b.Sign())) + a = new(big.Int).Abs(a) + b = new(big.Int).Abs(b) + x, y, r, s := big.NewInt(1), big.NewInt(0), big.NewInt(0), big.NewInt(1) + for b.Cmp(zero) != 0 { + q, c := new(big.Int).DivMod(a, b, new(big.Int)) + x = new(big.Int).Sub(x, new(big.Int).Mul(q, r)) + y = new(big.Int).Sub(y, new(big.Int).Mul(q, s)) + + a, b, r, s, x, y = b, c, x, y, r, s + } + + return new(big.Int).Mul(x, xSign), new(big.Int).Mul(y, ySign), a + + } +} + +func ISqrt(x *big.Int) (*big.Int, error) { + if x.Sign() == -1 { + return nil, errors.Errorf("Expected x: %s to be non-negative", x) + } + res := new(big.Int) + return res.Sqrt(x), nil +} diff --git a/pkg/utils/math_utils_test.go b/pkg/utils/math_utils_test.go index e3b2a152..3b308322 100644 --- a/pkg/utils/math_utils_test.go +++ b/pkg/utils/math_utils_test.go @@ -44,3 +44,164 @@ func TestSafeDivBigErrZeroDivison(t *testing.T) { t.Error("SafeDivBig should have failed") } } + +func TestIgcdex11(t *testing.T) { + a := big.NewInt(1) + b := big.NewInt(1) + expectedX, expectedY, expectedZ := big.NewInt(0), big.NewInt(1), big.NewInt(1) + x, y, z := Igcdex(a, b) + if x.Cmp(expectedX) != 0 || y.Cmp(expectedY) != 0 || z.Cmp(expectedZ) != 0 { + t.Error("Wrong values returned by Igcdex") + } +} + +func TestIgcdex00(t *testing.T) { + a := big.NewInt(0) + b := big.NewInt(0) + expectedX, expectedY, expectedZ := big.NewInt(0), big.NewInt(1), big.NewInt(0) + x, y, z := Igcdex(a, b) + if x.Cmp(expectedX) != 0 || y.Cmp(expectedY) != 0 || z.Cmp(expectedZ) != 0 { + t.Error("Wrong values returned by Igcdex") + } +} + +func TestIgcdex10(t *testing.T) { + a := big.NewInt(1) + b := big.NewInt(0) + expectedX, expectedY, expectedZ := big.NewInt(1), big.NewInt(0), big.NewInt(1) + x, y, z := Igcdex(a, b) + if x.Cmp(expectedX) != 0 || y.Cmp(expectedY) != 0 || z.Cmp(expectedZ) != 0 { + t.Error("Wrong values returned by Igcdex") + } +} + +func TestIgcdex46(t *testing.T) { + a := big.NewInt(4) + b := big.NewInt(6) + expectedX, expectedY, expectedZ := big.NewInt(-1), big.NewInt(1), big.NewInt(2) + x, y, z := Igcdex(a, b) + if x.Cmp(expectedX) != 0 || y.Cmp(expectedY) != 0 || z.Cmp(expectedZ) != 0 { + t.Error("Wrong values returned by Igcdex") + } +} + +func TestDivModOk(t *testing.T) { + a := new(big.Int) + b := new(big.Int) + prime := new(big.Int) + expected := new(big.Int) + + a.SetString("11260647941622813594563746375280766662237311019551239924981511729608487775604310196863705127454617186486639011517352066501847110680463498585797912894788", 10) + b.SetString("4020711254448367604954374443741161860304516084891705811279711044808359405970", 10) + prime.SetString("800000000000011000000000000000000000000000000000000000000000001", 16) + expected.SetString("2904750555256547440469454488220756360634457312540595732507835416669695939476", 10) + + num, err := DivMod(a, b, prime) + if err != nil { + t.Errorf("DivMod failed with error: %s", err) + } + if num.Cmp(expected) != 0 { + t.Errorf("Expected result: %s to be equal to %s", num, expected) + } +} + +func TestDivModMZeroFail(t *testing.T) { + a := new(big.Int) + b := new(big.Int) + prime := new(big.Int) + + a.SetString("11260647941622813594563746375280766662237311019551239924981511729608487775604310196863705127454617186486639011517352066501847110680463498585797912894788", 10) + prime.SetString("800000000000011000000000000000000000000000000000000000000000001", 16) + + _, err := DivMod(a, b, prime) + if err == nil { + t.Errorf("DivMod expected to failed with gcd != 1") + } +} + +func TestDivModMEqPFail(t *testing.T) { + a := new(big.Int) + b := new(big.Int) + prime := new(big.Int) + + a.SetString("11260647941622813594563746375280766662237311019551239924981511729608487775604310196863705127454617186486639011517352066501847110680463498585797912894788", 10) + b.SetString("800000000000011000000000000000000000000000000000000000000000001", 16) + prime.SetString("800000000000011000000000000000000000000000000000000000000000001", 16) + + _, err := DivMod(a, b, prime) + if err == nil { + t.Errorf("DivMod expected to failed with gcd != 1") + } +} + +func TestIsSqrtOk(t *testing.T) { + x := new(big.Int) + y := new(big.Int) + x.SetString("4573659632505831259480", 10) + y.Mul(x, x) + + sqr_y, err := ISqrt(y) + if err != nil { + t.Errorf("ISqrt failed with error: %s", err) + } + if x.Cmp(sqr_y) != 0 { + t.Errorf("Failed to get square root of x^2, x: %s", x) + } +} + +func TestCalculateIsqrtA(t *testing.T) { + x := new(big.Int) + x.SetString("81", 10) + sqrt, err := ISqrt(x) + if err != nil { + t.Error("ISqrt failed") + } + + expected := new(big.Int) + expected.SetString("9", 10) + + if sqrt.Cmp(expected) != 0 { + t.Errorf("ISqrt failed, expected %d, got %d", expected, sqrt) + } +} + +func TestCalculateIsqrtB(t *testing.T) { + x := new(big.Int) + x.SetString("4573659632505831259480", 10) + square := new(big.Int) + square = square.Mul(x, x) + + sqrt, err := ISqrt(square) + if err != nil { + t.Error("ISqrt failed") + } + + if sqrt.Cmp(x) != 0 { + t.Errorf("ISqrt failed, expected %d, got %d", x, sqrt) + } +} + +func TestCalculateIsqrtC(t *testing.T) { + x := new(big.Int) + x.SetString("3618502788666131213697322783095070105623107215331596699973092056135872020481", 10) + square := new(big.Int) + square = square.Mul(x, x) + + sqrt, err := ISqrt(square) + if err != nil { + t.Error("ISqrt failed") + } + + if sqrt.Cmp(x) != 0 { + t.Errorf("ISqrt failed, expected %d, got %d", x, sqrt) + } +} + +func TestIsSqrtFail(t *testing.T) { + x := big.NewInt(-1) + + _, err := ISqrt(x) + if err == nil { + t.Errorf("expected ISqrt to fail") + } +} diff --git a/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index 845a3076..f0685afb 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -329,6 +329,10 @@ func TestSplitIntHintProofMode(t *testing.T) { testProgramProof("split_int", t) } +func TestDivModN(t *testing.T) { + testProgram("div_mod_n", t) +} + func TestEcDoubleAssign(t *testing.T) { testProgram("ec_double_assign", t) } From e5fcc25b55f1ba863615e55197d86fe3c52c141b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Calv=C3=ADn=20Garc=C3=ADa?= Date: Thu, 5 Oct 2023 21:20:53 +0200 Subject: [PATCH 3/9] `uint256` hints (#279) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * draft uint256 add * implement hint uint256_add * add tests * add hint uint256_add_low * implement split_64 hint * change location of uint256 struct * implement auxiliar methods to ids manager to insert u256 structs * implement uint256sqrt hint * add unit test sqrt * fix unit test * add unit tests uint256_sqrt * implement hint uint256_signed_nn * add tests * implement UINT256_UNSIGNED_DIV_REM hint * add tests * implement hint and test * implement hint * add test and improve commit * fix test * add integration tests * fix unit and integration tests * improve code· * add uint256 utils * add test * fix comments * improve ToString method on Uint256 * fix test * fix hint * add extra test used to debug error * add hint uint256_sub * add newline --- cairo_programs/uint256.cairo | 115 +++ .../uint256_integration_tests.cairo | 151 ++++ cairo_programs/uint256_root.cairo | 12 + pkg/hints/hint_codes/uint256_hint_codes.go | 28 + pkg/hints/hint_processor.go | 20 + pkg/hints/hint_utils/ids_manager.go | 29 + pkg/hints/hint_utils/uint256_utils.go | 42 + pkg/hints/math_cmp_hints.go | 4 +- pkg/hints/uint256_hints.go | 316 ++++++++ pkg/hints/uint256_hints_test.go | 758 ++++++++++++++++++ pkg/lambdaworks/lambdaworks.go | 2 +- pkg/vm/cairo_run/cairo_run_test.go | 12 + pkg/vm/memory/memory.go | 2 +- 13 files changed, 1488 insertions(+), 3 deletions(-) create mode 100644 cairo_programs/uint256.cairo create mode 100644 cairo_programs/uint256_integration_tests.cairo create mode 100644 cairo_programs/uint256_root.cairo create mode 100644 pkg/hints/hint_codes/uint256_hint_codes.go create mode 100644 pkg/hints/hint_utils/uint256_utils.go create mode 100644 pkg/hints/uint256_hints.go create mode 100644 pkg/hints/uint256_hints_test.go diff --git a/cairo_programs/uint256.cairo b/cairo_programs/uint256.cairo new file mode 100644 index 00000000..f3e4daf1 --- /dev/null +++ b/cairo_programs/uint256.cairo @@ -0,0 +1,115 @@ +%builtins range_check + +from starkware.cairo.common.uint256 import ( + Uint256, + uint256_add, + split_64, + uint256_sqrt, + uint256_signed_nn, + uint256_unsigned_div_rem, + uint256_mul, + uint256_mul_div_mod +) +from starkware.cairo.common.alloc import alloc + +func fill_array{range_check_ptr: felt}( + array: Uint256*, base: Uint256, step: Uint256, array_length: felt, iterator: felt +) { + if (iterator == array_length) { + return (); + } + let (res, carry_high) = uint256_add(step, base); + let (sqrt) = uint256_sqrt(res); + + assert array[iterator] = sqrt; + return fill_array(array, base, array[iterator], array_length, iterator + 1); +} + +func main{range_check_ptr: felt}() { + let x: Uint256 = Uint256(5, 2); + let y = Uint256(3, 7); + let (res, carry_high) = uint256_add(x, y); + assert res.low = 8; + assert res.high = 9; + assert carry_high = 0; + + let (low, high) = split_64(850981239023189021389081239089023); + assert low = 7249717543555297151; + assert high = 46131785404667; + + let (root) = uint256_sqrt(Uint256(17, 7)); + assert root = Uint256(48805497317890012913, 0); + + let (signed_nn) = uint256_signed_nn(Uint256(5, 2)); + assert signed_nn = 1; + let (p) = uint256_signed_nn(Uint256(1, 170141183460469231731687303715884105728)); + assert p = 0; + let (q) = uint256_signed_nn(Uint256(1, 170141183460469231731687303715884105727)); + assert q = 1; + + let (a_quotient, a_remainder) = uint256_unsigned_div_rem(Uint256(89, 72), Uint256(3, 7)); + assert a_quotient = Uint256(10, 0); + assert a_remainder = Uint256(59, 2); + + let (b_quotient, b_remainder) = uint256_unsigned_div_rem( + Uint256(-3618502788666131213697322783095070105282824848410658236509717448704103809099, 2), + Uint256(5, 2), + ); + assert b_quotient = Uint256(1, 0); + assert b_remainder = Uint256(340282366920938463463374607431768211377, 0); + + let (c_quotient, c_remainder) = uint256_unsigned_div_rem( + Uint256(340282366920938463463374607431768211455, 340282366920938463463374607431768211455), + Uint256(1, 0), + ); + + assert c_quotient = Uint256(340282366920938463463374607431768211455, 340282366920938463463374607431768211455); + assert c_remainder = Uint256(0, 0); + + let (a_quotient_low, a_quotient_high, a_remainder) = uint256_mul_div_mod( + Uint256(89, 72), + Uint256(3, 7), + Uint256(107, 114), + ); + assert a_quotient_low = Uint256(143276786071974089879315624181797141668, 4); + assert a_quotient_high = Uint256(0, 0); + assert a_remainder = Uint256(322372768661941702228460154409043568767, 101); + + let (b_quotient_low, b_quotient_high, b_remainder) = uint256_mul_div_mod( + Uint256(-3618502788666131213697322783095070105282824848410658236509717448704103809099, 2), + Uint256(1, 1), + Uint256(5, 2), + ); + assert b_quotient_low = Uint256(170141183460469231731687303715884105688, 1); + assert b_quotient_high = Uint256(0, 0); + assert b_remainder = Uint256(170141183460469231731687303715884105854, 1); + + let (c_quotient_low, c_quotient_high, c_remainder) = uint256_mul_div_mod( + Uint256(340281070833283907490476236129005105807, 340282366920938463463374607431768211455), + Uint256(2447157533618445569039502, 0), + Uint256(0, 1), + ); + + assert c_quotient_low = Uint256(340282366920938463454053728725133866491, 2447157533618445569039501); + assert c_quotient_high = Uint256(0, 0); + assert c_remainder = Uint256(326588112914912836985603897252688572242, 0); + + let (mult_low_a, mult_high_a) = uint256_mul(Uint256(59, 2), Uint256(10, 0)); + assert mult_low_a = Uint256(590, 20); + assert mult_high_a = Uint256(0, 0); + + let (mult_low_b: Uint256, mult_high_b: Uint256) = uint256_mul( + Uint256(271442546951262198976322048597925888860, 0), + Uint256(271442546951262198976322048597925888860, 0), + ); + assert mult_low_b = Uint256( + 42047520920204780886066537579778623760, 216529163594619381764978757921136443390 + ); + assert mult_high_b = Uint256(0, 0); + + let array_length = 100; + let (sum_array: Uint256*) = alloc(); + fill_array(sum_array, Uint256(57, 8), Uint256(17, 7), array_length, 0); + + return (); +} diff --git a/cairo_programs/uint256_integration_tests.cairo b/cairo_programs/uint256_integration_tests.cairo new file mode 100644 index 00000000..70599483 --- /dev/null +++ b/cairo_programs/uint256_integration_tests.cairo @@ -0,0 +1,151 @@ +%builtins range_check bitwise + +from starkware.cairo.common.uint256 import ( + Uint256, + uint256_add, + split_64, + uint256_sqrt, + uint256_signed_nn, + uint256_unsigned_div_rem, + uint256_mul, + uint256_or, + uint256_reverse_endian, +) +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin + +func fill_array(array_start: felt*, base: felt, step: felt, iter: felt, last: felt) -> () { + if (iter == last) { + return (); + } + assert array_start[iter] = base + step; + return fill_array(array_start, base + step, step, iter + 1, last); +} + +func fill_uint256_array{range_check_ptr: felt}( + array: Uint256*, base: Uint256, step: Uint256, array_len: felt, iterator: felt +) { + if (iterator == array_len) { + return (); + } + let (res: Uint256, carry_high: felt) = uint256_add(step, base); + + assert array[iterator] = res; + return fill_uint256_array(array, base, array[iterator], array_len, iterator + 1); +} + +func test_sqrt{range_check_ptr}( + base_array: Uint256*, new_array: Uint256*, iter: felt, last: felt +) -> () { + alloc_locals; + + if (iter == last) { + return (); + } + + let res: Uint256 = uint256_sqrt(base_array[iter]); + assert new_array[iter] = res; + + return test_sqrt(base_array, new_array, iter + 1, last); +} + +func test_signed_nn{range_check_ptr}( + base_array: Uint256*, new_array: felt*, iter: felt, last: felt +) -> () { + alloc_locals; + + if (iter == last) { + return (); + } + + let res: felt = uint256_signed_nn(base_array[iter]); + assert res = 1; + assert new_array[iter] = res; + + return test_signed_nn(base_array, new_array, iter + 1, last); +} + +func test_unsigned_div_rem{range_check_ptr}( + base_array: Uint256*, new_array: Uint256*, iter: felt, last: felt +) -> () { + alloc_locals; + + if (iter == last) { + return (); + } + + let (quotient: Uint256, remainder: Uint256) = uint256_unsigned_div_rem( + base_array[iter], Uint256(7, 8) + ); + assert new_array[(iter * 2)] = quotient; + assert new_array[(iter * 2) + 1] = remainder; + + return test_unsigned_div_rem(base_array, new_array, iter + 1, last); +} + +func test_split_64{range_check_ptr}( + base_array: felt*, new_array: felt*, iter: felt, last: felt +) -> () { + alloc_locals; + + if (iter == last) { + return (); + } + + let (low: felt, high: felt) = split_64(base_array[iter]); + assert new_array[(iter * 2)] = low; + assert new_array[(iter * 2) + 1] = high; + return test_split_64(base_array, new_array, iter + 1, last); +} + +func test_integration{range_check_ptr, bitwise_ptr: BitwiseBuiltin*}( + base_array: Uint256*, new_array: Uint256*, iter: felt, last: felt +) -> () { + alloc_locals; + + if (iter == last) { + return (); + } + + let (add: Uint256, carry_high: felt) = uint256_add(base_array[iter], base_array[iter + 1]); + let (quotient: Uint256, remainder: Uint256) = uint256_unsigned_div_rem(add, Uint256(5, 3)); + let (low: Uint256, high: Uint256) = uint256_mul(quotient, remainder); + let (bitwise_or: Uint256) = uint256_or(low, high); + let (reverse_endian: Uint256) = uint256_reverse_endian(bitwise_or); + let (result: Uint256) = uint256_sqrt(reverse_endian); + + assert new_array[iter] = result; + return test_integration(base_array, new_array, iter + 1, last); +} + +func run_tests{range_check_ptr, bitwise_ptr: BitwiseBuiltin*}(array_len: felt) -> () { + alloc_locals; + let (uint256_array: Uint256*) = alloc(); + fill_uint256_array(uint256_array, Uint256(57, 8), Uint256(57, 101), array_len, 0); + + let (array_sqrt: Uint256*) = alloc(); + test_sqrt(uint256_array, array_sqrt, 0, array_len); + + let (array_signed_nn: felt*) = alloc(); + test_signed_nn(uint256_array, array_signed_nn, 0, array_len); + + let (array_unsigned_div_rem: Uint256*) = alloc(); + test_unsigned_div_rem(uint256_array, array_unsigned_div_rem, 0, array_len); + + let (felt_array: felt*) = alloc(); + fill_array(felt_array, 0, 3, 0, array_len); + + let (array_split_64: felt*) = alloc(); + test_split_64(felt_array, array_split_64, 0, array_len); + + let (array_test_integration: Uint256*) = alloc(); + test_integration(uint256_array, array_test_integration, 0, array_len - 1); + + return (); +} + +func main{range_check_ptr: felt, bitwise_ptr: BitwiseBuiltin*}() { + run_tests(10); + + return (); +} diff --git a/cairo_programs/uint256_root.cairo b/cairo_programs/uint256_root.cairo new file mode 100644 index 00000000..98b6b543 --- /dev/null +++ b/cairo_programs/uint256_root.cairo @@ -0,0 +1,12 @@ +%builtins range_check bitwise +from starkware.cairo.common.uint256 import ( + Uint256, + uint256_sqrt, +) +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin + +func main{range_check_ptr: felt, bitwise_ptr: BitwiseBuiltin*}() { + let n = Uint256(0, 157560248172239344387757911110183813120); + let res = uint256_sqrt(n); + return (); +} diff --git a/pkg/hints/hint_codes/uint256_hint_codes.go b/pkg/hints/hint_codes/uint256_hint_codes.go new file mode 100644 index 00000000..1881c125 --- /dev/null +++ b/pkg/hints/hint_codes/uint256_hint_codes.go @@ -0,0 +1,28 @@ +package hint_codes + +const UINT256_ADD = "sum_low = ids.a.low + ids.b.low\nids.carry_low = 1 if sum_low >= ids.SHIFT else 0\nsum_high = ids.a.high + ids.b.high + ids.carry_low\nids.carry_high = 1 if sum_high >= ids.SHIFT else 0" +const UINT256_ADD_LOW = "sum_low = ids.a.low + ids.b.low\nids.carry_low = 1 if sum_low >= ids.SHIFT else 0" +const SPLIT_64 = "ids.low = ids.a & ((1<<64) - 1)\nids.high = ids.a >> 64" +const UINT256_SQRT = "from starkware.python.math_utils import isqrt\nn = (ids.n.high << 128) + ids.n.low\nroot = isqrt(n)\nassert 0 <= root < 2 ** 128\nids.root.low = root\nids.root.high = 0" +const UINT256_SQRT_FELT = "from starkware.python.math_utils import isqrt\nn = (ids.n.high << 128) + ids.n.low\nroot = isqrt(n)\nassert 0 <= root < 2 ** 128\nids.root = root;" +const UINT256_SIGNED_NN = "memory[ap] = 1 if 0 <= (ids.a.high % PRIME) < 2 ** 127 else 0" +const UINT256_UNSIGNED_DIV_REM = "a = (ids.a.high << 128) + ids.a.low\ndiv = (ids.div.high << 128) + ids.div.low\nquotient, remainder = divmod(a, div)\n\nids.quotient.low = quotient & ((1 << 128) - 1)\nids.quotient.high = quotient >> 128\nids.remainder.low = remainder & ((1 << 128) - 1)\nids.remainder.high = remainder >> 128" +const UINT256_EXPANDED_UNSIGNED_DIV_REM = "a = (ids.a.high << 128) + ids.a.low\ndiv = (ids.div.b23 << 128) + ids.div.b01\nquotient, remainder = divmod(a, div)\n\nids.quotient.low = quotient & ((1 << 128) - 1)\nids.quotient.high = quotient >> 128\nids.remainder.low = remainder & ((1 << 128) - 1)\nids.remainder.high = remainder >> 128" +const UINT256_MUL_DIV_MOD = "a = (ids.a.high << 128) + ids.a.low\nb = (ids.b.high << 128) + ids.b.low\ndiv = (ids.div.high << 128) + ids.div.low\nquotient, remainder = divmod(a * b, div)\n\nids.quotient_low.low = quotient & ((1 << 128) - 1)\nids.quotient_low.high = (quotient >> 128) & ((1 << 128) - 1)\nids.quotient_high.low = (quotient >> 256) & ((1 << 128) - 1)\nids.quotient_high.high = quotient >> 384\nids.remainder.low = remainder & ((1 << 128) - 1)\nids.remainder.high = remainder >> 128" +const UINT256_SUB = `def split(num: int, num_bits_shift: int = 128, length: int = 2): + a = [] + for _ in range(length): + a.append( num & ((1 << num_bits_shift) - 1) ) + num = num >> num_bits_shift + return tuple(a) + +def pack(z, num_bits_shift: int = 128) -> int: + limbs = (z.low, z.high) + return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs)) + +a = pack(ids.a) +b = pack(ids.b) +res = (a - b)%2**256 +res_split = split(res) +ids.res.low = res_split[0] +ids.res.high = res_split[1]` diff --git a/pkg/hints/hint_processor.go b/pkg/hints/hint_processor.go index 9685de6e..669cc1af 100644 --- a/pkg/hints/hint_processor.go +++ b/pkg/hints/hint_processor.go @@ -188,6 +188,26 @@ 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 UINT256_ADD: + return uint256Add(data.Ids, vm, false) + case UINT256_ADD_LOW: + return uint256Add(data.Ids, vm, true) + case UINT256_SUB: + return uint256Sub(data.Ids, vm) + case SPLIT_64: + return split64(data.Ids, vm) + case UINT256_SQRT: + return uint256Sqrt(data.Ids, vm, false) + case UINT256_SQRT_FELT: + return uint256Sqrt(data.Ids, vm, true) + case UINT256_SIGNED_NN: + return uint256SignedNN(data.Ids, vm) + case UINT256_UNSIGNED_DIV_REM: + return uint256UnsignedDivRem(data.Ids, vm) + case UINT256_EXPANDED_UNSIGNED_DIV_REM: + return uint256ExpandedUnsignedDivRem(data.Ids, vm) + case UINT256_MUL_DIV_MOD: + return uint256MulDivMod(data.Ids, vm) case DIV_MOD_N_PACKED_DIVMOD_V1: return divModNPackedDivMod(data.Ids, vm, execScopes) case DIV_MOD_N_PACKED_DIVMOD_EXTERNAL_N: diff --git a/pkg/hints/hint_utils/ids_manager.go b/pkg/hints/hint_utils/ids_manager.go index ef2550b5..2394f8b1 100644 --- a/pkg/hints/hint_utils/ids_manager.go +++ b/pkg/hints/hint_utils/ids_manager.go @@ -76,6 +76,22 @@ func (ids *IdsManager) GetFelt(name string, vm *VirtualMachine) (lambdaworks.Fel return felt, nil } +func (ids *IdsManager) GetUint256(name string, vm *VirtualMachine) (Uint256, error) { + lowAddr, err := ids.GetAddr(name, vm) + if err != nil { + return Uint256{}, err + } + low, err := vm.Segments.Memory.GetFelt(lowAddr) + if err != nil { + return Uint256{}, err + } + high, err := vm.Segments.Memory.GetFelt(lowAddr.AddUint(1)) + if err != nil { + return Uint256{}, err + } + return Uint256{Low: low, High: high}, nil +} + // Returns the value of an identifier as a Relocatable func (ids *IdsManager) GetRelocatable(name string, vm *VirtualMachine) (Relocatable, error) { val, err := ids.Get(name, vm) @@ -217,6 +233,19 @@ func (ids *IdsManager) InsertStructField(name string, field_off uint, value *May return vm.Segments.Memory.Insert(addr.AddUint(field_off), value) } +// Inserts Uint256 value into an ids field (given the identifier is a Uint256) +func (ids *IdsManager) InsertUint256(name string, val Uint256, vm *VirtualMachine) error { + baseAddr, err := ids.GetAddr(name, vm) + if err != nil { + return err + } + err = vm.Segments.Memory.Insert(baseAddr, NewMaybeRelocatableFelt(val.Low)) + if err != nil { + return err + } + return vm.Segments.Memory.Insert(baseAddr.AddUint(1), NewMaybeRelocatableFelt(val.High)) +} + // Inserts value into the address of the given identifier func insertIdsFromReference(value *MaybeRelocatable, reference *HintReference, apTracking parser.ApTrackingData, vm *VirtualMachine) error { addr, ok := getAddressFromReference(reference, apTracking, vm) diff --git a/pkg/hints/hint_utils/uint256_utils.go b/pkg/hints/hint_utils/uint256_utils.go new file mode 100644 index 00000000..be26da96 --- /dev/null +++ b/pkg/hints/hint_utils/uint256_utils.go @@ -0,0 +1,42 @@ +package hint_utils + +import ( + "math/big" + + . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" +) + +type Uint256 struct { + Low Felt + High Felt +} + +func (u *Uint256) ToString() string { + return "Uint256 { low: " + u.Low.ToSignedFeltString() + ", high: " + u.High.ToSignedFeltString() + " }" +} + +/* +Returns a Uint256 as a big.Int + + res = high << 128 + low +*/ +func (u *Uint256) ToBigInt() *big.Int { + high := new(big.Int).Lsh(u.High.ToBigInt(), 128) + low := u.Low.ToBigInt() + res := new(big.Int).Add(high, low) + return res +} + +/* +Returns a big.Int as Uint256 +*/ +func ToUint256(a *big.Int) Uint256 { + maxU128, _ := new(big.Int).SetString("340282366920938463463374607431768211455", 10) + low := new(big.Int).And(a, maxU128) + high := new(big.Int).Rsh(a, 128) + return Uint256{Low: FeltFromBigInt(low), High: FeltFromBigInt(high)} +} + +func (u *Uint256) IsEqual(other Uint256) bool { + return u.Low.Cmp(other.Low) == 0 && u.High.Cmp(other.High) == 0 +} diff --git a/pkg/hints/math_cmp_hints.go b/pkg/hints/math_cmp_hints.go index 5e4e064b..2e22e667 100644 --- a/pkg/hints/math_cmp_hints.go +++ b/pkg/hints/math_cmp_hints.go @@ -26,7 +26,9 @@ func isNNOutOfRange(ids IdsManager, vm *VirtualMachine) error { if err != nil { return err } - if (FeltZero().Sub(a).Sub(FeltOne())).Bits() < builtins.RANGE_CHECK_N_PARTS*builtins.INNER_RC_BOUND_SHIFT { + op := FeltZero().Sub(a).Sub(FeltOne()) + bound := FeltOne().Shl(16 * builtins.RANGE_CHECK_N_PARTS) + if op.Cmp(bound) == -1 { return vm.Segments.Memory.Insert(vm.RunContext.Ap, NewMaybeRelocatableFelt(FeltZero())) } return vm.Segments.Memory.Insert(vm.RunContext.Ap, NewMaybeRelocatableFelt(FeltOne())) diff --git a/pkg/hints/uint256_hints.go b/pkg/hints/uint256_hints.go new file mode 100644 index 00000000..8e7f413c --- /dev/null +++ b/pkg/hints/uint256_hints.go @@ -0,0 +1,316 @@ +package hints + +import ( + "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" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" + "github.com/pkg/errors" +) + +func ErrRootOOR(root *big.Int) error { + return errors.Errorf("assert 0 <= %d < 2**128", root) +} + +/* +Implements hints: +%{ + sum_low = ids.a.low + ids.b.low + ids.carry_low = 1 if sum_low >= ids.SHIFT else 0 + sum_high = ids.a.high + ids.b.high + ids.carry_low + ids.carry_high = 1 if sum_high >= ids.SHIFT else 0 +%} +%{ + sum_low = ids.a.low + ids.b.low + ids.carry_low = 1 if sum_low >= ids.SHIFT else 0 +%} +*/ + +func uint256Add(ids IdsManager, vm *VirtualMachine, lowOnly bool) error { + shift := FeltOne().Shl(128) + a, err := ids.GetUint256("a", vm) + if err != nil { + return err + } + b, err := ids.GetUint256("b", vm) + if err != nil { + return err + } + + sumLow := a.Low.Add(b.Low) + carryLow := FeltZero() + if sumLow.Cmp(shift) != -1 { + carryLow = FeltOne() + } + + if !lowOnly { + sumHigh := a.High.Add(b.High.Add(carryLow)) + carryHigh := FeltZero() + if sumHigh.Cmp(shift) != -1 { + carryHigh = FeltOne() + } + err := ids.Insert("carry_high", NewMaybeRelocatableFelt(carryHigh), vm) + if err != nil { + return err + } + } + return ids.Insert("carry_low", NewMaybeRelocatableFelt(carryLow), vm) + +} + +/* +Implements hint: +%{ + def split(num: int, num_bits_shift: int = 128, length: int = 2): + a = [] + for _ in range(length): + a.append( num & ((1 << num_bits_shift) - 1) ) + num = num >> num_bits_shift + return tuple(a) + + def pack(z, num_bits_shift: int = 128) -> int: + limbs = (z.low, z.high) + return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs)) + + a = pack(ids.a) + b = pack(ids.b) + res = (a - b)%2**256 + res_split = split(res) + ids.res.low = res_split[0] + ids.res.high = res_split[1] +%} +*/ + +func uint256Sub(ids IdsManager, vm *VirtualMachine) error { + a, err := ids.GetUint256("a", vm) + if err != nil { + return err + } + b, err := ids.GetUint256("b", vm) + if err != nil { + return err + } + var resBigInt *big.Int + if a.ToBigInt().Cmp(b.ToBigInt()) != -1 { + resBigInt = new(big.Int).Sub(a.ToBigInt(), b.ToBigInt()) + } else { + mod256 := new(big.Int).Lsh(new(big.Int).SetUint64(1), 256) + if mod256.Cmp(b.ToBigInt()) != -1 { + resBigInt = new(big.Int).Sub(mod256, b.ToBigInt()) + resBigInt = new(big.Int).Add(resBigInt, a.ToBigInt()) + } else { + loweredB := new(big.Int).Mod(b.ToBigInt(), mod256) + if a.ToBigInt().Cmp(loweredB) != -1 { + resBigInt = new(big.Int).Sub(a.ToBigInt(), loweredB) + } else { + resBigInt = new(big.Int).Sub(mod256, loweredB) + resBigInt = new(big.Int).Add(resBigInt, a.ToBigInt()) + } + } + } + + res := ToUint256(resBigInt) + return ids.InsertUint256("res", res, vm) +} + +/* +Implements hint: + + %{ + ids.low = ids.a & ((1<<64) - 1) + ids.high = ids.a >> 64 + +%} +*/ +func split64(ids IdsManager, vm *VirtualMachine) error { + a, err := ids.GetFelt("a", vm) + if err != nil { + return err + } + flag := (FeltOne().Shl(64)).Sub(FeltOne()) // (1 << 64) - 1 + low := a.And(flag) + high := a.Shr(64) // a >> 64 + err = ids.Insert("low", NewMaybeRelocatableFelt(low), vm) + if err != nil { + return err + } + err = ids.Insert("high", NewMaybeRelocatableFelt(high), vm) + if err != nil { + return err + } + return nil + +} + +/* +Implements hint: + + %{ + from starkware.python.math_utils import isqrt + n = (ids.n.high << 128) + ids.n.low + root = isqrt(n) + assert 0 <= root < 2 ** 128 + ids.root.low = root + ids.root.high = 0 + +%} +*/ +func uint256Sqrt(ids IdsManager, vm *VirtualMachine, onlyLow bool) error { + uintN, err := ids.GetUint256("n", vm) + if err != nil { + return err + } + n := uintN.ToBigInt() + root := new(big.Int).Sqrt(n) + if root.BitLen() > 128 { + return ErrRootOOR(root) + } + + feltRoot := FeltFromBigInt(root) + if onlyLow { + return ids.Insert("root", NewMaybeRelocatableFelt(feltRoot), vm) + } else { + return ids.InsertUint256("root", Uint256{Low: feltRoot, High: FeltZero()}, vm) + } +} + +/* +Implements hint: +%{ memory[ap] = 1 if 0 <= (ids.a.high % PRIME) < 2 ** 127 else 0 %} +*/ +func uint256SignedNN(ids IdsManager, vm *VirtualMachine) error { + a, err := ids.GetUint256("a", vm) + if err != nil { + return err + } + i128Max := FeltFromDecString("170141183460469231731687303715884105727") + if a.High.Cmp(FeltZero()) != -1 && a.High.Cmp(i128Max) != 1 { + return vm.Segments.Memory.Insert(vm.RunContext.Ap, NewMaybeRelocatableFelt(FeltOne())) + } else { + return vm.Segments.Memory.Insert(vm.RunContext.Ap, NewMaybeRelocatableFelt(FeltZero())) + } +} + +/* +Implements hint: + + %{ + a = (ids.a.high << 128) + ids.a.low + div = (ids.div.high << 128) + ids.div.low + quotient, remainder = divmod(a, div) + + ids.quotient.low = quotient & ((1 << 128) - 1) + ids.quotient.high = quotient >> 128 + ids.remainder.low = remainder & ((1 << 128) - 1) + ids.remainder.high = remainder >> 128 + +%} +*/ +func uint256UnsignedDivRem(ids IdsManager, vm *VirtualMachine) error { + return uint256OfssetedUnisgnedDivRem(ids, vm, 0, 1) + +} + +/* +Implements hint: + + %{ + a = (ids.a.high << 128) + ids.a.low + div = (ids.div.b23 << 128) + ids.div.b01 + quotient, remainder = divmod(a, div) + + ids.quotient.low = quotient & ((1 << 128) - 1) + ids.quotient.high = quotient >> 128 + ids.remainder.low = remainder & ((1 << 128) - 1) + ids.remainder.high = remainder >> 128 + +%} +*/ +func uint256ExpandedUnsignedDivRem(ids IdsManager, vm *VirtualMachine) error { + return uint256OfssetedUnisgnedDivRem(ids, vm, 1, 3) +} + +func uint256OfssetedUnisgnedDivRem(ids IdsManager, vm *VirtualMachine, divOffsetLow uint, divOffsetHigh uint) error { + a, err := ids.GetUint256("a", vm) + if err != nil { + return err + } + divLow, err := ids.GetStructFieldFelt("div", divOffsetLow, vm) + if err != nil { + return err + } + divHigh, err := ids.GetStructFieldFelt("div", divOffsetHigh, vm) + if err != nil { + return err + } + div := Uint256{Low: divLow, High: divHigh} + q, r := new(big.Int).DivMod(a.ToBigInt(), div.ToBigInt(), new(big.Int)) + + err = ids.InsertUint256("quotient", ToUint256(q), vm) + if err != nil { + return err + } + return ids.InsertUint256("remainder", ToUint256(r), vm) + +} + +/* +Implements hint: + + %{ + a = (ids.a.high << 128) + ids.a.low + div = (ids.div.b23 << 128) + ids.div.b01 + quotient, remainder = divmod(a, div) + + ids.quotient.low = quotient & ((1 << 128) - 1) + ids.quotient.high = quotient >> 128 + ids.remainder.low = remainder & ((1 << 128) - 1) + ids.remainder.high = remainder >> 128 + +%} +*/ +func uint256MulDivMod(ids IdsManager, vm *VirtualMachine) error { + a, err := ids.GetUint256("a", vm) + if err != nil { + return err + } + b, err := ids.GetUint256("b", vm) + if err != nil { + return err + } + div, err := ids.GetUint256("div", vm) + if err != nil { + return err + } + + if div.ToBigInt().Cmp(big.NewInt(0)) == 0 { + return errors.Errorf("Attempted to divide by zero") + } + + mul := new(big.Int).Mul(a.ToBigInt(), b.ToBigInt()) + quotient, rem := new(big.Int).DivMod(mul, div.ToBigInt(), new(big.Int)) + + maxU128, _ := new(big.Int).SetString("340282366920938463463374607431768211455", 10) + + var quotientLow Uint256 + var quotientHigh Uint256 + var remainder Uint256 + quotientLow.Low = FeltFromBigInt(new(big.Int).And(quotient, maxU128)) // q & maxU128 + quotientLow.High = FeltFromBigInt(new(big.Int).And(new(big.Int).Rsh(quotient, 128), maxU128)) // q >> 128 & maxU128 + quotientHigh.Low = FeltFromBigInt(new(big.Int).And(new(big.Int).Rsh(quotient, 256), maxU128)) // q >> 256 & maxU128 + quotientHigh.High = FeltFromBigInt(new(big.Int).Rsh(quotient, 384)) // q >> 384 + remainder.Low = FeltFromBigInt(new(big.Int).And(rem, maxU128)) // rem & maxU128 + remainder.High = FeltFromBigInt(new(big.Int).And(new(big.Int).Rsh(rem, 128), maxU128)) // rem >> 128 & maxU128 + + err = ids.InsertUint256("quotient_low", quotientLow, vm) + if err != nil { + return err + } + err = ids.InsertUint256("quotient_high", quotientHigh, vm) + if err != nil { + return err + } + return ids.InsertUint256("remainder", remainder, vm) +} diff --git a/pkg/hints/uint256_hints_test.go b/pkg/hints/uint256_hints_test.go new file mode 100644 index 00000000..89f350f0 --- /dev/null +++ b/pkg/hints/uint256_hints_test.go @@ -0,0 +1,758 @@ +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/parser" + . "github.com/lambdaclass/cairo-vm.go/pkg/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/utils" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestUint256AddOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFeltFromUint64(2), + NewMaybeRelocatableFeltFromUint64(3), + }, + "b": { + NewMaybeRelocatableFeltFromUint64(4), + NewMaybeRelocatableFelt(FeltFromDecString("340282366920938463463374607431768211455")), + }, + "carry_low": {nil}, + "carry_high": {nil}, + }, + vm, + ) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_ADD, + }) + scopes := NewExecutionScopes() + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + carry_low, err := idsManager.GetFelt("carry_low", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + if carry_low.Cmp(FeltZero()) != 0 { + t.Errorf("expected carry_low: 0, got: %s", carry_low.ToSignedFeltString()) + } + carry_high, err := idsManager.GetFelt("carry_high", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + if carry_high.Cmp(FeltOne()) != 0 { + t.Errorf("expected carry_high: 0, got: %s", carry_high.ToSignedFeltString()) + } +} + +func TestUint256AddLowOnlyOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFeltFromUint64(2), + NewMaybeRelocatableFeltFromUint64(3), + }, + "b": { + NewMaybeRelocatableFeltFromUint64(4), + NewMaybeRelocatableFelt(FeltFromDecString("340282366920938463463374607431768211455")), + }, + "carry_low": {nil}, + }, + vm, + ) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_ADD_LOW, + }) + scopes := NewExecutionScopes() + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + carry_low, err := idsManager.GetFelt("carry_low", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + if carry_low.Cmp(FeltZero()) != 0 { + t.Errorf("expected carry_low: 0, got: %s", carry_low.ToSignedFeltString()) + } +} + +func TestUint256AddFailInsert(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFeltFromUint64(2), + NewMaybeRelocatableFeltFromUint64(3), + }, + "b": { + NewMaybeRelocatableFeltFromUint64(4), + NewMaybeRelocatableFeltFromUint64(2), + }, + "carry_low": {NewMaybeRelocatableFeltFromUint64(2)}, + }, + vm, + ) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_ADD_LOW, + }) + scopes := NewExecutionScopes() + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err == nil { + t.Errorf("should fail with error: ErrMemoryWriteOnce") + } + +} + +func TestSplit64Ok(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFelt(FeltFromDecString("-3")), + }, + "low": {nil}, + "high": {nil}, + }, + vm, + ) + + hintData := any(HintData{ + Ids: idsManager, + Code: SPLIT_64, + }) + scopes := NewExecutionScopes() + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + low, err := idsManager.GetFelt("low", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + expected_low := FeltFromDecString("-3").And(FeltOne().Shl(64).Sub(FeltOne())) + if low != expected_low { + t.Errorf("expected low: %d, got: %d", expected_low, low) + } + + high, err := idsManager.GetFelt("high", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + expected_high := FeltFromDecString("-3").Shr(64) + if high != expected_high { + t.Errorf("expected high: %d, got: %d", expected_high, high) + } + +} + +func TestSplit64BigA(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFelt(FeltFromDecString("400066369019890261321163226850167045262")), + }, + "low": {nil}, + "high": {nil}, + }, + vm, + ) + + hintData := any(HintData{ + Ids: idsManager, + Code: SPLIT_64, + }) + scopes := NewExecutionScopes() + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + low, err := idsManager.GetFelt("low", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + expected_low := FeltFromUint64(2279400676465785998) + if low.Cmp(expected_low) != 0 { + t.Errorf("expected low: %d, got: %d", expected_low, low) + } + high, err := idsManager.GetFelt("high", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + expected_high := FeltFromDecString("21687641321487626429") + if high.Cmp(expected_high) != 0 { + t.Errorf("expected high: %d, got: %d", expected_high, high) + } + +} + +func TestUint256SqrtOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n": { + NewMaybeRelocatableFelt(FeltFromUint64(17)), + NewMaybeRelocatableFelt(FeltFromUint64(7)), + }, + "root": {nil, nil}, + }, + vm, + ) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_SQRT, + }) + scopes := NewExecutionScopes() + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + expected_root, _ := new(big.Int).SetString("48805497317890012913", 10) + root, err := idsManager.GetFelt("root", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + if root != FeltFromBigInt(expected_root) { + t.Errorf("failed, expected root: %d, got: %d", FeltFromBigInt(expected_root), root) + } +} + +func TestUint256SqrtKo(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + + idsManager := SetupIdsForTest(map[string][]*MaybeRelocatable{ + "n": { + NewMaybeRelocatableFelt(FeltZero()), + NewMaybeRelocatableFelt(FeltFromDecString("340282366920938463463374607431768211458")), + }, + "root": {nil}, + }, vm) + + hintData := any(HintData{Ids: idsManager, Code: UINT256_SQRT}) + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, NewExecutionScopes()) + expectedRoot := FeltFromDecString("340282366920938463463374607431768211456") + if err.Error() != ErrRootOOR(expectedRoot.ToBigInt()).Error() { + t.Errorf("failed with error: %s", err) + } +} + +func TestUint256SqrtFeltOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n": { + NewMaybeRelocatableFelt(FeltFromUint64(879232)), + NewMaybeRelocatableFelt(FeltFromUint64(135906)), + }, + "root": {nil}, + }, + vm, + ) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_SQRT_FELT, + }) + scopes := NewExecutionScopes() + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err != nil { + t.Errorf("failed with error: %s", err) + } + expected_root, _ := new(big.Int).SetString("6800471701195223914689", 10) + expectedResult := FeltFromBigInt(expected_root) + root, err := idsManager.GetFelt("root", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + if root != expectedResult { + t.Errorf("failed, expected root: %d, got: %d", expectedResult, root) + } +} + +func TestUint256SignedNNOkResultOne(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments = AddNSegments(vm.Segments, 5) + ids := map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(1)), + }, + } + idsManager := SetupIdsForTest(ids, vm) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_SIGNED_NN, + }) + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + result, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + if result != FeltOne() { + t.Errorf("failed, expected result: %d, got: %d", FeltOne(), result) + } +} + +func TestUint256SignedNNOkResultZero(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.RunContext.Ap = NewRelocatable(0, 5) + ids := map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromDecString("-4")), + }, + } + idsManager := SetupIdsForTest(ids, vm) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_SIGNED_NN, + }) + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + result, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + if result != FeltZero() { + t.Errorf("failed, expected result: %d, got: %d", FeltZero(), result) + } +} + +func TestUint256SignedNNInvalidMemoryInser(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.RunContext.Ap = NewRelocatable(0, 5) + vm.Segments.Memory.Insert(vm.RunContext.Ap, NewMaybeRelocatableFeltFromUint64(10)) + ids := map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(1)), + }, + } + idsManager := SetupIdsForTest(ids, vm) + idsManager.HintApTracking = parser.ApTrackingData{Group: 4, Offset: 5} + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_SIGNED_NN, + }) + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + + expectedErr := ErrMemoryWriteOnce(NewRelocatable(0, 5), *NewMaybeRelocatableFeltFromUint64(10), *NewMaybeRelocatableFelt(FeltOne())) + if err.Error() != expectedErr.Error() { + t.Errorf("should fail with error: %s", err) + } +} + +func TestUint256UnsignedDivRemOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + ids := map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFeltFromUint64(89), + NewMaybeRelocatableFeltFromUint64(72), + }, + "div": { + NewMaybeRelocatableFeltFromUint64(3), + NewMaybeRelocatableFeltFromUint64(7), + }, + "quotient": {nil, nil}, + "remainder": {nil, nil}, + } + idsManager := SetupIdsForTest(ids, vm) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_UNSIGNED_DIV_REM, + }) + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + quotient, err := idsManager.GetUint256("quotient", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + expectedQuotient := Uint256{Low: FeltFromUint(10), High: FeltFromUint(0)} + if quotient != expectedQuotient { + t.Errorf("expected quotient: %s, got: %s", expectedQuotient.ToString(), quotient.ToString()) + } + + remainder, err := idsManager.GetUint256("remainder", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + expectedRemainder := Uint256{Low: FeltFromUint(59), High: FeltFromUint(2)} + if remainder != expectedRemainder { + t.Errorf("expected remainder: %s, got: %s", expectedRemainder.ToString(), remainder.ToString()) + } + +} + +func TestUint256UnsignedDivRemInvalidMemoryInsert(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + ids := map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFeltFromUint64(89), + NewMaybeRelocatableFeltFromUint64(72), + }, + "div": { + NewMaybeRelocatableFeltFromUint64(3), + NewMaybeRelocatableFeltFromUint64(7), + }, + "quotient": {NewMaybeRelocatableFeltFromUint64(2), NewMaybeRelocatableFelt(FeltZero())}, + "remainder": {nil, nil}, + } + idsManager := SetupIdsForTest(ids, vm) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_UNSIGNED_DIV_REM, + }) + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err == nil { + t.Errorf("this test should fail") + } +} + +func TestUint256ExpandedUnsignedDivRemOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + ids := map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFeltFromUint64(89), + NewMaybeRelocatableFeltFromUint64(72), + }, + + "div": { + NewMaybeRelocatableFelt(FeltFromDecString("55340232221128654848")), + NewMaybeRelocatableFeltFromUint64(3), + NewMaybeRelocatableFelt(FeltFromDecString("129127208515966861312")), + NewMaybeRelocatableFeltFromUint64(7), + }, + "quotient": {nil, nil}, + "remainder": {nil, nil}, + } + idsManager := SetupIdsForTest(ids, vm) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_EXPANDED_UNSIGNED_DIV_REM, + }) + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + quotient, err := idsManager.GetUint256("quotient", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + expectedQuotient := Uint256{Low: FeltFromUint(10), High: FeltFromUint(0)} + if quotient != expectedQuotient { + t.Errorf("expected quotient: %s, got: %s", expectedQuotient.ToString(), quotient.ToString()) + } + + remainder, err := idsManager.GetUint256("remainder", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + expectedRemainder := Uint256{Low: FeltFromUint(59), High: FeltFromUint(2)} + if remainder != expectedRemainder { + t.Errorf("expected remainder: %s, got: %s", expectedRemainder.ToString(), remainder.ToString()) + } + +} + +func TestUint256MulDivOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + ids := map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFeltFromUint64(89), + NewMaybeRelocatableFeltFromUint64(72), + }, + "b": { + NewMaybeRelocatableFeltFromUint64(3), + NewMaybeRelocatableFeltFromUint64(7), + }, + "div": { + NewMaybeRelocatableFeltFromUint64(107), + NewMaybeRelocatableFeltFromUint64(114), + }, + "quotient_low": {nil, nil}, + "quotient_high": {nil, nil}, + "remainder": {nil, nil}, + } + idsManager := SetupIdsForTest(ids, vm) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_MUL_DIV_MOD, + }) + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + quotientLow, err := idsManager.GetUint256("quotient_low", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + expectedQuotientLow := Uint256{Low: FeltFromDecString("143276786071974089879315624181797141668"), High: FeltFromUint(4)} + if !quotientLow.IsEqual(expectedQuotientLow) { + t.Errorf("expected quotient_low: %s, got: %s", expectedQuotientLow.ToString(), quotientLow.ToString()) + } + + quotientHigh, err := idsManager.GetUint256("quotient_high", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + expectedQuotientHigh := Uint256{Low: FeltFromUint(0), High: FeltFromUint(0)} + if !quotientHigh.IsEqual(expectedQuotientHigh) { + t.Errorf("expected quotient_high: %s, got: %s", expectedQuotientHigh.ToString(), quotientHigh.ToString()) + } + + remainder, err := idsManager.GetUint256("remainder", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + expectedRemainder := Uint256{Low: FeltFromDecString("322372768661941702228460154409043568767"), High: FeltFromUint(101)} + if !remainder.IsEqual(expectedRemainder) { + t.Errorf("expected remainder: %s, got: %s", expectedRemainder.ToString(), remainder.ToString()) + } +} + +func TestUint256SubNonNegativeOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + + ids := map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFeltFromUint64(12179), + NewMaybeRelocatableFeltFromUint64(13044), + }, + "b": { + NewMaybeRelocatableFeltFromUint64(1001), + NewMaybeRelocatableFeltFromUint64(6687), + }, + "res": {nil, nil}, + } + idsManager := SetupIdsForTest(ids, vm) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_SUB, + }) + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + result, err := idsManager.GetUint256("res", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + expectedResult := Uint256{Low: FeltFromUint(11178), High: FeltFromUint(6357)} + if !result.IsEqual(expectedResult) { + t.Errorf("expected result: %s, got: %s", expectedResult.ToString(), result.ToString()) + } +} + +func TestUint256SubNegativeOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + + ids := map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFeltFromUint64(1001), + NewMaybeRelocatableFeltFromUint64(6687), + }, + "b": { + NewMaybeRelocatableFeltFromUint64(12179), + NewMaybeRelocatableFeltFromUint64(13044), + }, + "res": {nil, nil}, + } + idsManager := SetupIdsForTest(ids, vm) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_SUB, + }) + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + result, err := idsManager.GetUint256("res", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + expectedResult := Uint256{Low: FeltFromDecString("340282366920938463463374607431768200278"), High: FeltFromDecString("340282366920938463463374607431768205098")} + if !result.IsEqual(expectedResult) { + t.Errorf("expected result: %s, got: %s", expectedResult.ToString(), result.ToString()) + } +} + +func TestUint256SubBHighGt256LteA(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + + ids := map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFelt(FeltFromDecString("340282366920938463463374607431768211456")), + NewMaybeRelocatableFelt(FeltZero()), + }, + "b": { + NewMaybeRelocatableFelt(FeltZero()), + NewMaybeRelocatableFelt(FeltFromDecString("340282366920938463463374607431768211457")), + }, + "res": {nil, nil}, + } + idsManager := SetupIdsForTest(ids, vm) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_SUB, + }) + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + result, err := idsManager.GetUint256("res", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + expectedResult := Uint256{Low: FeltZero(), High: FeltZero()} + if !result.IsEqual(expectedResult) { + t.Errorf("expected result: %s, got: %s", expectedResult.ToString(), result.ToString()) + } +} + +func TestUint256SubBHighGt256GtA(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + + ids := map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFelt(FeltOne()), + NewMaybeRelocatableFelt(FeltZero()), + }, + "b": { + NewMaybeRelocatableFelt(FeltZero()), + NewMaybeRelocatableFelt(FeltFromDecString("340282366920938463463374607431768211457")), + }, + "res": {nil, nil}, + } + idsManager := SetupIdsForTest(ids, vm) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_SUB, + }) + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("failed with error: %s", err) + } + + result, err := idsManager.GetUint256("res", vm) + if err != nil { + t.Errorf("failed with error: %s", err) + } + expectedResult := Uint256{Low: FeltOne(), High: FeltFromDecString("340282366920938463463374607431768211455")} + if !result.IsEqual(expectedResult) { + t.Errorf("expected result: %s, got: %s", expectedResult.ToString(), result.ToString()) + } +} + +func TestUint256SubMissingNumber(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + + ids := map[string][]*MaybeRelocatable{ + "a": { + NewMaybeRelocatableFelt(FeltOne()), + nil, + }, + } + idsManager := SetupIdsForTest(ids, vm) + hintData := any(HintData{ + Ids: idsManager, + Code: UINT256_SUB, + }) + hintProcessor := CairoVmHintProcessor{} + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err == nil { + t.Errorf("should fail with error: Memory Get: Value not found") + } +} diff --git a/pkg/lambdaworks/lambdaworks.go b/pkg/lambdaworks/lambdaworks.go index 3cd29d21..fb175502 100644 --- a/pkg/lambdaworks/lambdaworks.go +++ b/pkg/lambdaworks/lambdaworks.go @@ -353,7 +353,7 @@ func (a Felt) DivFloor(b Felt) Felt { } /* -Compares x and y and returns: +Compares a and b and returns: -1 if a < b 0 if a == b diff --git a/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index f0685afb..4aa6c0f3 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -352,3 +352,15 @@ func TestCairoKeccak(t *testing.T) { func TestKeccakAddUint256(t *testing.T) { testProgram("keccak_add_uint256", t) } + +func TestUint256Integration(t *testing.T) { + testProgram("uint256_integration_tests", t) +} + +func TestUint256(t *testing.T) { + testProgram("uint256", t) +} + +func TestUint256Root(t *testing.T) { + testProgram("uint256_root", t) +} diff --git a/pkg/vm/memory/memory.go b/pkg/vm/memory/memory.go index 4939a8a0..86aa49cf 100644 --- a/pkg/vm/memory/memory.go +++ b/pkg/vm/memory/memory.go @@ -117,7 +117,7 @@ func (m *Memory) Get(addr Relocatable) (*MaybeRelocatable, error) { value, ok := m.Data[addr] if !ok { - return nil, errors.New("Memory Get: Value not found") + return nil, errors.Errorf("Memory Get: Value not found in addr: %s", addr.ToString()) } return &value, nil From 5c151ff986f127d752b16a4a29506e84aef9001f Mon Sep 17 00:00:00 2001 From: greged93 <82421016+greged93@users.noreply.github.com> Date: Tue, 17 Oct 2023 15:18:03 +0200 Subject: [PATCH 4/9] fix constants for opcodes (#318) --- pkg/vm/instruction.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/vm/instruction.go b/pkg/vm/instruction.go index afe90f71..12069d53 100644 --- a/pkg/vm/instruction.go +++ b/pkg/vm/instruction.go @@ -95,9 +95,9 @@ type Opcode uint const ( NOp Opcode = 0 - AssertEq Opcode = 1 - Call Opcode = 2 - Ret Opcode = 4 + Call Opcode = 1 + Ret Opcode = 2 + AssertEq Opcode = 4 ) var ErrNonZeroHighBitError = errors.New("Instruction high bit was not set to zero") From 66a5863a6d3c4bd1609766695b6081172855b139 Mon Sep 17 00:00:00 2001 From: greged93 <82421016+greged93@users.noreply.github.com> Date: Mon, 23 Oct 2023 20:39:10 +0200 Subject: [PATCH 5/9] fix instruction doc (#317) Co-authored-by: Pedro Fontana --- pkg/vm/instruction.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/vm/instruction.go b/pkg/vm/instruction.go index 12069d53..9b83d46d 100644 --- a/pkg/vm/instruction.go +++ b/pkg/vm/instruction.go @@ -12,10 +12,10 @@ import ( // │ off_op0 (biased representation) │ // ├─────────────────────────────────────────────────────────────────────────┤ // │ off_op1 (biased representation) │ -// ├─────┬─────┬───────┬───────┬───────────┬────────┬───────────────────┬────┤ -// │ dst │ op0 │ op1 │ res │ pc │ ap │ opcode │ 0 │ -// │ reg │ reg │ src │ logic │ update │ update │ │ │ -// ├─────┼─────┼───┬───┼───┬───┼───┬───┬───┼───┬────┼────┬────┬────┬────┼────┤ +// ├─────┬─────┬───────────┬───────┬───────────┬─────────┬──────────────┬────┤ +// │ dst │ op0 │ op1 │ res │ pc │ ap │ opcode │ 0 │ +// │ reg │ reg │ src │ logic │ update │ update │ │ │ +// ├─────┼─────┼───┬───┬───┼───┬───┼───┬───┬───┼────┬────┼────┬────┬────┼────┤ // │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │ // └─────┴─────┴───┴───┴───┴───┴───┴───┴───┴───┴────┴────┴────┴────┴────┴────┘ From 62f6a551a4db40da0a0b43b9eec9b58bbc8dc2a9 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Mon, 23 Oct 2023 21:52:42 +0300 Subject: [PATCH 6/9] Implement Blake2s hash (#310) * Begin implemneting blake2s * Finish blake2s impl * Add unit test * Add more unit tests --------- Co-authored-by: Pedro Fontana --- pkg/hints/hint_utils/bake2s_hash_test.go | 79 ++++++++++++++ pkg/hints/hint_utils/blake2s_hash.go | 133 +++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 pkg/hints/hint_utils/bake2s_hash_test.go create mode 100644 pkg/hints/hint_utils/blake2s_hash.go diff --git a/pkg/hints/hint_utils/bake2s_hash_test.go b/pkg/hints/hint_utils/bake2s_hash_test.go new file mode 100644 index 00000000..0d5a57a0 --- /dev/null +++ b/pkg/hints/hint_utils/bake2s_hash_test.go @@ -0,0 +1,79 @@ +package hint_utils_test + +import ( + "reflect" + "testing" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" +) + +func TestBlake2sCompressA(t *testing.T) { + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + expectedState := []uint32{412110711, 3234706100, 3894970767, 982912411, 937789635, 742982576, 3942558313, 1407547065} + newState := Blake2sCompress(h, message, 2, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} + +func TestBlake2sCompressB(t *testing.T) { + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{456710651, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + expectedState := []uint32{1061041453, 3663967611, 2158760218, 836165556, 3696892209, 3887053585, 2675134684, 2201582556} + newState := Blake2sCompress(h, message, 2, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} + +func TestBlake2sCompressC(t *testing.T) { + //Hashing "Hello World" + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{1819043144, 1870078063, 6581362, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + expectedState := []uint32{939893662, 3935214984, 1704819782, 3912812968, 4211807320, 3760278243, 674188535, 2642110762} + newState := Blake2sCompress(h, message, 9, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} + +func TestBlake2sCompressD(t *testing.T) { + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{1819043144, 1870078063, 6581362, 274628678, 715791845, 175498643, 871587583, 0, 0, 0, 0, 0, 0, 0, 0, 0} + expectedState := []uint32{3980510537, 3982966407, 1593299263, 2666882356, 3288094120, 2682988286, 1666615862, 378086837} + newState := Blake2sCompress(h, message, 28, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} + +func TestBlake2sCompressE(t *testing.T) { + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{1819043144, 1870078063, 6581362, 274628678, 715791845, 175498643, 871587583, 635963558, 557369694, 1576875962, 215769785, 0, 0, 0, 0, 0} + expectedState := []uint32{3251785223, 1946079609, 2665255093, 3508191500, 3630835628, 3067307230, 3623370123, 656151356} + newState := Blake2sCompress(h, message, 44, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} + +func TestBlake2sCompressF(t *testing.T) { + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{1819043144, 1870078063, 6581362, 274628678, 715791845, 175498643, 871587583, 635963558, 557369694, 1576875962, 215769785, 152379578, 585849303, 764739320, 437383930, 74833930} + expectedState := []uint32{2593218707, 3238077801, 914875393, 3462286058, 4028447058, 3174734057, 2001070146, 3741410512} + newState := Blake2sCompress(h, message, 64, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} + +func TestBlake2sCompressG(t *testing.T) { + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{11563522, 43535528, 653255322, 274628678, 73471943, 17549868, 87158958, 635963558, 343656565, 1576875962, 215769785, 152379578, 585849303, 76473202, 437253230, 74833930} + expectedState := []uint32{3496615692, 3252241979, 3771521549, 2125493093, 3240605752, 2885407061, 3962009872, 3845288240} + newState := Blake2sCompress(h, message, 64, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} diff --git a/pkg/hints/hint_utils/blake2s_hash.go b/pkg/hints/hint_utils/blake2s_hash.go new file mode 100644 index 00000000..fd92b2ad --- /dev/null +++ b/pkg/hints/hint_utils/blake2s_hash.go @@ -0,0 +1,133 @@ +package hint_utils + +func IV() [8]uint32 { + return [8]uint32{ + 0x6A09E667, + 0xBB67AE85, + 0x3C6EF372, + 0xA54FF53A, + 0x510E527F, + 0x9B05688C, + 0x1F83D9AB, + 0x5BE0CD19} +} + +func SIGMA() [10][16]uint32 { + return [10][16]uint32{ + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, + } +} + +func rightRot(value uint32, n uint32) uint32 { + return (value >> n) | ((value & ((1 << n) - 1)) << (32 - n)) +} + +func mix(a uint32, b uint32, c uint32, d uint32, m0 uint32, m1 uint32) (uint32, uint32, uint32, uint32) { + a = a + b + m0 + d = rightRot(d^a, 16) + c = c + d + b = rightRot(b^c, 12) + a = a + b + m1 + d = rightRot(d^a, 8) + c = c + d + b = rightRot(b^c, 7) + return a, b, c, d +} + +func blakeRound(state []uint32, message [16]uint32, sigma [16]uint32) []uint32 { + state[0], state[4], state[8], state[12] = mix( + state[0], + state[4], + state[8], + state[12], + message[sigma[0]], + message[sigma[1]], + ) + state[1], state[5], state[9], state[13] = mix( + state[1], + state[5], + state[9], + state[13], + message[sigma[2]], + message[sigma[3]], + ) + state[2], state[6], state[10], state[14] = mix( + state[2], + state[6], + state[10], + state[14], + message[sigma[4]], + message[sigma[5]], + ) + state[3], state[7], state[11], state[15] = mix( + state[3], + state[7], + state[11], + state[15], + message[sigma[6]], + message[sigma[7]], + ) + state[0], state[5], state[10], state[15] = mix( + state[0], + state[5], + state[10], + state[15], + message[sigma[8]], + message[sigma[9]], + ) + state[1], state[6], state[11], state[12] = mix( + state[1], + state[6], + state[11], + state[12], + message[sigma[10]], + message[sigma[11]], + ) + state[2], state[7], state[8], state[13] = mix( + state[2], + state[7], + state[8], + state[13], + message[sigma[12]], + message[sigma[13]], + ) + state[3], state[4], state[9], state[14] = mix( + state[3], + state[4], + state[9], + state[14], + message[sigma[14]], + message[sigma[15]], + ) + return state +} + +func Blake2sCompress(h [8]uint32, message [16]uint32, t0 uint32, t1 uint32, f0 uint32, f1 uint32) []uint32 { + iv := IV() + + state := make([]uint32, 0, 16) + + state = append(state, h[:]...) + state = append(state, iv[:4]...) + state = append(state, iv[4]^t0, iv[5]^t1, iv[6]^f0, iv[7]^f1) + + for _, sigmaList := range SIGMA() { + state = blakeRound(state, message, sigmaList) + } + + newState := make([]uint32, 0, 8) + for i := 0; i < 8; i++ { + newState = append(newState, h[i]^state[i]^state[8+i]) + } + + return newState +} From a629107ce78e6a85781aad1b355b928ed4bd17cc Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Mon, 23 Oct 2023 22:45:43 +0300 Subject: [PATCH 7/9] Add `VerifySecureRunner` + `SecureRun` config flag (#303) * Add ExecutionResources struct * Fix: Remove vm argument from CairoRunner methods * Finish func + remove todos * Add unit test * Implement GenArg * Remove recursive processing * Add unit tests * Fix test values * Start fn * Add RunFromEntryPoint * Add test for RunFromEntryPoint * Add unit tests * Add comments * Add skeleton * Implement GetMemoryAccesses for BuiltinRunner * Start implementing RunSecurityChecks * Implement VerifyAutoDeductionsForAddr * Finish func + remove import cycle * Add fix + unit test * Fix logic, add test * Fix logic * Fix logic * Fix logic + add test * Add unit tests * Move RunSecurityChecks to external func in builtins package * Remove todos from BuiltinRunner interface * Finish verifySecureRunner * Add verifySecure to RunFromEntryPoint * Add unit tests * Add secure_run flag * Fix typo * Fix alias for new flag * Set secure_run to true in testProgram --------- Co-authored-by: Pedro Fontana --- cmd/cli/main.go | 14 +++- pkg/builtins/bitwise.go | 11 +++ pkg/builtins/builtin_runner.go | 99 ++++++++++++++++++++++---- pkg/builtins/builtin_runner_test.go | 105 ++++++++++++++++++++++++++++ pkg/builtins/ec_op.go | 11 +++ pkg/builtins/keccak.go | 11 +++ pkg/builtins/output.go | 16 +++++ pkg/builtins/pedersen.go | 11 +++ pkg/builtins/poseidon.go | 11 +++ pkg/builtins/range_check.go | 11 +++ pkg/builtins/signature.go | 11 +++ pkg/runners/cairo_runner.go | 7 +- pkg/runners/cairo_runner_test.go | 2 +- pkg/runners/security.go | 68 ++++++++++++++++++ pkg/runners/security_test.go | 55 +++++++++++++++ pkg/vm/cairo_run/cairo_run.go | 8 +++ pkg/vm/cairo_run/cairo_run_test.go | 2 +- 17 files changed, 435 insertions(+), 18 deletions(-) create mode 100644 pkg/builtins/builtin_runner_test.go create mode 100644 pkg/runners/security.go create mode 100644 pkg/runners/security_test.go diff --git a/cmd/cli/main.go b/cmd/cli/main.go index db77623b..094facea 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -17,7 +17,14 @@ func handleCommands(ctx *cli.Context) error { layout = "plain" } - cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, ProofMode: ctx.Bool("proof_mode"), Layout: layout} + proofMode := ctx.Bool("proof_mode") + + secureRun := !proofMode + if ctx.Bool("secure_run") { + secureRun = true + } + + cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, ProofMode: proofMode, Layout: layout, SecureRun: secureRun} cairoRunner, err := cairo_run.CairoRun(programPath, cairoRunConfig) if err != nil { @@ -51,6 +58,11 @@ func main() { Aliases: []string{"p"}, Usage: "Run in proof mode", }, + &cli.BoolFlag{ + Name: "secure_run", + Aliases: []string{"s"}, + Usage: "Run security checks. Default: true unless proof_mode is true", + }, &cli.StringFlag{ Name: "layout", Aliases: []string{"l"}, diff --git a/pkg/builtins/bitwise.go b/pkg/builtins/bitwise.go index b2e26a34..39a9a44c 100644 --- a/pkg/builtins/bitwise.go +++ b/pkg/builtins/bitwise.go @@ -111,6 +111,10 @@ func (b *BitwiseBuiltinRunner) CellsPerInstance() uint { return BITWISE_CELLS_PER_INSTANCE } +func (b *BitwiseBuiltinRunner) InputCellsPerInstance() uint { + return BIWISE_INPUT_CELLS_PER_INSTANCE +} + func (b *BitwiseBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -253,3 +257,10 @@ func (r *BitwiseBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentMa return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *BitwiseBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/builtin_runner.go b/pkg/builtins/builtin_runner.go index 64148d7f..c9e9c7ad 100644 --- a/pkg/builtins/builtin_runner.go +++ b/pkg/builtins/builtin_runner.go @@ -2,6 +2,7 @@ package builtins import ( "fmt" + "sort" "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" "github.com/pkg/errors" @@ -28,6 +29,10 @@ type BuiltinRunner interface { Base() memory.Relocatable // Returns the name of the builtin Name() string + // Cells per builtin instance + CellsPerInstance() uint + // Input cells per builtin instance + InputCellsPerInstance() uint // Creates a memory segment for the builtin and initializes its base InitializeSegments(*memory.MemorySegmentManager) // Returns the builtin's initial stack @@ -40,27 +45,97 @@ type BuiltinRunner interface { AddValidationRule(*memory.Memory) // Sets the inclusion of the Builtin Runner in the Cairo Runner Include(bool) - // TODO: Later additions -> Some of them could depend on a Default Implementation - // // Most of them depend on Layouts being implemented - // // Use cases: - // // I. PROOF_MODE // Returns the builtin's ratio, is zero if the layout is dynamic Ratio() uint // Returns the builtin's allocated memory units GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) - // // Returns the list of memory addresses used by the builtin + // Returns the list of memory addresses used by the builtin GetMemoryAccesses(*memory.MemorySegmentManager) ([]memory.Relocatable, error) GetRangeCheckUsage(*memory.Memory) (*uint, *uint) GetUsedPermRangeCheckLimits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) GetUsedDilutedCheckUnits(dilutedSpacing uint, dilutedNBits uint) uint GetUsedCellsAndAllocatedSizes(segments *memory.MemorySegmentManager, currentStep uint) (uint, uint, error) FinalStack(segments *memory.MemorySegmentManager, pointer memory.Relocatable) (memory.Relocatable, error) - // // II. SECURITY (secure-run flag cairo-run || verify-secure flag run_from_entrypoint) - // RunSecurityChecks(*vm.VirtualMachine) error // verify_secure_runner logic - // // Returns the base & stop_ptr, stop_ptr can be nil - // GetMemorySegmentAddresses() (memory.Relocatable, *memory.Relocatable) //verify_secure_runner logic - // // III. STARKNET-SPECIFIC + // Returns the base & stop_ptr + GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) + // Amount of builtin instances used GetUsedInstances(*memory.MemorySegmentManager) (uint, error) - // // IV. GENERAL CASE (but not critical) - // FinalStack(*memory.MemorySegmentManager, memory.Relocatable) (memory.Relocatable, error) // read_return_values +} + +func RunSecurityChecksForBuiltin(builtin BuiltinRunner, segments *memory.MemorySegmentManager) error { + if builtin.Name() == OUTPUT_BUILTIN_NAME { + return nil + } + + cellsPerInstance := builtin.CellsPerInstance() + nInputCells := builtin.InputCellsPerInstance() + builtinSegmentIndex := builtin.Base().SegmentIndex + + offsets := make([]int, 0) + // Collect the builtin segment's address' offsets + for addr := range segments.Memory.Data { + if addr.SegmentIndex == builtinSegmentIndex { + offsets = append(offsets, int(addr.Offset)) + } + } + + if len(offsets) == 0 { + // No checks to run for empty segment + return nil + } + // Sort offsets for easier comparison + sort.Ints(offsets) + // Obtain max offset + maxOffset := offsets[len(offsets)-1] + + n := (maxOffset / int(cellsPerInstance)) + 1 + //Verify that n is not too large to make sure the expectedOffsets list that is constructed below is not too large. + if n > len(offsets)/int(nInputCells) { + return errors.Errorf("Missing memory cells for %s", builtin.Name()) + } + + // Check that the two inputs (x and y) of each instance are set. + expectedOffsets := make([]int, 0) + for i := 0; i < n; i++ { + for j := 0; j < int(nInputCells); j++ { + expectedOffsets = append(expectedOffsets, int(cellsPerInstance)*i+j) + } + } + // Find the missing offsets (offsets in expectedOffsets but not in offsets) + missingOffsets := make([]int, 0) + j := 0 + i := 0 + for i < len(expectedOffsets) && j < len(offsets) { + if expectedOffsets[i] < offsets[j] { + missingOffsets = append(missingOffsets, expectedOffsets[i]) + } else { + j++ + } + i++ + } + for i < len(expectedOffsets) { + missingOffsets = append(missingOffsets, expectedOffsets[i]) + i++ + } + if len(missingOffsets) != 0 { + return errors.Errorf("Missing memory cells for builtin: %s: %v", builtin.Name(), missingOffsets) + } + + // Verify auto deduction rules for the unassigned output cells. + // Assigned output cells are checked as part of the call to VerifyAutoDeductions(). + for i := uint(0); i < uint(n); i++ { + for j := uint(nInputCells); j < cellsPerInstance; j++ { + addr := memory.NewRelocatable(builtinSegmentIndex, cellsPerInstance*i+j) + _, err := segments.Memory.Get(addr) + // Output cell not in memory + if err != nil { + _, err = builtin.DeduceMemoryCell(addr, &segments.Memory) + if err != nil { + return err + } + } + } + } + + return nil } diff --git a/pkg/builtins/builtin_runner_test.go b/pkg/builtins/builtin_runner_test.go new file mode 100644 index 00000000..210b25a2 --- /dev/null +++ b/pkg/builtins/builtin_runner_test.go @@ -0,0 +1,105 @@ +package builtins_test + +import ( + "testing" + + "github.com/lambdaclass/cairo-vm.go/pkg/builtins" + "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestRunSecurityChecksEmptyMemory(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err != nil { + t.Errorf("RunSecurityChecks failed with error: %s", err.Error()) + } +} + +func TestRunSecurityChecksMissingMemoryCells(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + + builtin.InitializeSegments(&segments) + builtinBase := builtin.Base() + // A bitwise cell consists of 5 elements: 2 input cells & 3 output cells + // In this test we insert cells 4-5 and leave the first input cell empty + // This will fail the security checks, as the memory cell with offset 0 will be missing + builtinSegment := []memory.MaybeRelocatable{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(1)), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(2)), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(3)), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(4)), + } + segments.LoadData(builtinBase.AddUint(1), &builtinSegment) + + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err == nil { + t.Errorf("RunSecurityChecks should have failed") + } +} + +func TestRunSecurityChecksMissingMemoryCellsNCheck(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + + builtin.InitializeSegments(&segments) + builtinBase := builtin.Base() + // n = max(offsets) // cellsPerInstance + 1 + // n = max[(0]) // 5 + 1 = 0 // 5 + 1 = 1 + // len(offsets) // inputCells = 1 // 2 + // This will fail the security checks, as n > len(offsets) // inputCells + builtinSegment := []memory.MaybeRelocatable{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(1)), + } + segments.LoadData(builtinBase, &builtinSegment) + + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err == nil { + t.Errorf("RunSecurityChecks should have failed") + } +} + +func TestRunSecurityChecksValidateOutputCellsNotDeducedOk(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + + builtin.InitializeSegments(&segments) + builtinBase := builtin.Base() + // A bitwise cell consists of 5 elements: 2 input cells & 3 output cells + // In this test we insert the input cells (1-2), but not the output cells (3-5) + // This will cause the security checks to run the auto-deductions for those output cells + builtinSegment := []memory.MaybeRelocatable{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(1)), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(2)), + } + segments.LoadData(builtinBase, &builtinSegment) + + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err != nil { + t.Errorf("RunSecurityChecks failed with error: %s", err.Error()) + } +} + +func TestRunSecurityChecksValidateOutputCellsNotDeducedErr(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + + builtin.InitializeSegments(&segments) + builtinBase := builtin.Base() + // A bitwise cell consists of 5 elements: 2 input cells & 3 output cells + // In this test we insert the input cells (1-2), but not the output cells (3-5) + // This will cause the security checks to run the auto-deductions for those output cells + // As we inserted an invalid value (PRIME -1) on the first input cell, this deduction will fail + builtinSegment := []memory.MaybeRelocatable{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromDecString("-1")), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(2)), + } + segments.LoadData(builtinBase, &builtinSegment) + + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err == nil { + t.Errorf("RunSecurityChecks should have failed") + } +} diff --git a/pkg/builtins/ec_op.go b/pkg/builtins/ec_op.go index 9053c25c..912445dd 100644 --- a/pkg/builtins/ec_op.go +++ b/pkg/builtins/ec_op.go @@ -147,6 +147,10 @@ func (r *EcOpBuiltinRunner) CellsPerInstance() uint { return CELLS_PER_EC_OP } +func (b *EcOpBuiltinRunner) InputCellsPerInstance() uint { + return INPUT_CELLS_PER_EC_OP +} + func (ec *EcOpBuiltinRunner) AddValidationRule(*memory.Memory) {} func (ec *EcOpBuiltinRunner) Base() memory.Relocatable { @@ -405,3 +409,10 @@ func (r *EcOpBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentManag return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *EcOpBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/keccak.go b/pkg/builtins/keccak.go index a5f9638e..ac11ae37 100644 --- a/pkg/builtins/keccak.go +++ b/pkg/builtins/keccak.go @@ -539,6 +539,10 @@ func (k *KeccakBuiltinRunner) CellsPerInstance() uint { return KECCAK_CELLS_PER_INSTANCE } +func (b *KeccakBuiltinRunner) InputCellsPerInstance() uint { + return KECCAK_INPUT_CELLS_PER_INSTANCE +} + func (k *KeccakBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -676,3 +680,10 @@ func (r *KeccakBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentMan return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *KeccakBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/output.go b/pkg/builtins/output.go index 945a772b..f445178b 100644 --- a/pkg/builtins/output.go +++ b/pkg/builtins/output.go @@ -5,6 +5,7 @@ import ( ) const OUTPUT_BUILTIN_NAME = "output" +const OUTPUT_CELLS_PER_INSTANCE = 1 type OutputBuiltinRunner struct { base memory.Relocatable @@ -133,3 +134,18 @@ func (r *OutputBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentMan return usedCells, nil } + +func (b *OutputBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} + +func (r *OutputBuiltinRunner) CellsPerInstance() uint { + return OUTPUT_CELLS_PER_INSTANCE +} + +func (b *OutputBuiltinRunner) InputCellsPerInstance() uint { + return OUTPUT_CELLS_PER_INSTANCE +} diff --git a/pkg/builtins/pedersen.go b/pkg/builtins/pedersen.go index baa1d14c..158ef68e 100644 --- a/pkg/builtins/pedersen.go +++ b/pkg/builtins/pedersen.go @@ -107,6 +107,10 @@ func (p *PedersenBuiltinRunner) CellsPerInstance() uint { return PEDERSEN_CELLS_PER_INSTANCE } +func (p *PedersenBuiltinRunner) InputCellsPerInstance() uint { + return PEDERSEN_INPUT_CELLS_PER_INSTANCE +} + func (p *PedersenBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -228,3 +232,10 @@ func (r *PedersenBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentM return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *PedersenBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/poseidon.go b/pkg/builtins/poseidon.go index 9bb09e7e..81a9a8e0 100644 --- a/pkg/builtins/poseidon.go +++ b/pkg/builtins/poseidon.go @@ -96,6 +96,10 @@ func (p *PoseidonBuiltinRunner) CellsPerInstance() uint { return POSEIDON_CELLS_PER_INSTANCE } +func (p *PoseidonBuiltinRunner) InputCellsPerInstance() uint { + return POSEIDON_INPUT_CELLS_PER_INSTANCE +} + func (p *PoseidonBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -217,3 +221,10 @@ func (r *PoseidonBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentM return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *PoseidonBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/range_check.go b/pkg/builtins/range_check.go index 8ee6ae65..ee913b94 100644 --- a/pkg/builtins/range_check.go +++ b/pkg/builtins/range_check.go @@ -110,6 +110,10 @@ func (r *RangeCheckBuiltinRunner) CellsPerInstance() uint { return CELLS_PER_RANGE_CHECK } +func (b *RangeCheckBuiltinRunner) InputCellsPerInstance() uint { + return CELLS_PER_RANGE_CHECK +} + func (r *RangeCheckBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -280,3 +284,10 @@ func (r *RangeCheckBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmen return usedCells, nil } + +func (b *RangeCheckBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/signature.go b/pkg/builtins/signature.go index 5e577b61..ce6865bd 100644 --- a/pkg/builtins/signature.go +++ b/pkg/builtins/signature.go @@ -131,6 +131,10 @@ func (r *SignatureBuiltinRunner) CellsPerInstance() uint { return SIGNATURE_CELLS_PER_INSTANCE } +func (r *SignatureBuiltinRunner) InputCellsPerInstance() uint { + return SIGNATURE_CELLS_PER_INSTANCE +} + func (r *SignatureBuiltinRunner) GetRangeCheckUsage(memory *memory.Memory) (*uint, *uint) { return nil, nil } @@ -235,3 +239,10 @@ func (r *SignatureBuiltinRunner) GetUsedInstances(segments *memory.MemorySegment return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *SignatureBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/runners/cairo_runner.go b/pkg/runners/cairo_runner.go index 32cb37d4..53354130 100644 --- a/pkg/runners/cairo_runner.go +++ b/pkg/runners/cairo_runner.go @@ -582,14 +582,13 @@ func (runner *CairoRunner) GetExecutionResources() (ExecutionResources, error) { }, nil } -// TODO: Add verifySecure once its implemented /* Runs a cairo program from a give entrypoint, indicated by its pc offset, with the given arguments. If `verifySecure` is set to true, [verifySecureRunner] will be called to run extra verifications. `programSegmentSize` is only used by the [verifySecureRunner] function and will be ignored if `verifySecure` is set to false. Each arg can be either MaybeRelocatable, []MaybeRelocatable or [][]MaybeRelocatable */ -func (runner *CairoRunner) RunFromEntrypoint(entrypoint uint, args []any, hintProcessor vm.HintProcessor) error { +func (runner *CairoRunner) RunFromEntrypoint(entrypoint uint, args []any, hintProcessor vm.HintProcessor, verifySecure bool, programSegmentSize *uint) error { stack := make([]memory.MaybeRelocatable, 0) for _, arg := range args { val, err := runner.Vm.Segments.GenArg(arg) @@ -615,6 +614,8 @@ func (runner *CairoRunner) RunFromEntrypoint(entrypoint uint, args []any, hintPr if err != nil { return err } - // TODO: verifySecureRunner + if verifySecure { + return VerifySecureRunner(runner, true, programSegmentSize) + } return nil } diff --git a/pkg/runners/cairo_runner_test.go b/pkg/runners/cairo_runner_test.go index 1c5b7130..0f5f34c6 100644 --- a/pkg/runners/cairo_runner_test.go +++ b/pkg/runners/cairo_runner_test.go @@ -743,7 +743,7 @@ func TestRunFromEntryPointFibonacci(t *testing.T) { runner.InitializeBuiltins() runner.InitializeSegments() - err := runner.RunFromEntrypoint(uint(entrypoint), args, &hintProcessor) + err := runner.RunFromEntrypoint(uint(entrypoint), args, &hintProcessor, true, nil) if err != nil { t.Errorf("Running fib entrypoint failed with error %s", err.Error()) diff --git a/pkg/runners/security.go b/pkg/runners/security.go new file mode 100644 index 00000000..c8c5d4c9 --- /dev/null +++ b/pkg/runners/security.go @@ -0,0 +1,68 @@ +package runners + +import ( + "github.com/lambdaclass/cairo-vm.go/pkg/builtins" + "github.com/pkg/errors" +) + +/* +Verify that the completed run in a runner is safe to be relocated and be +used by other Cairo programs. + +Checks include: + - (Only if `verifyBuiltins` is set to true) All accesses to the builtin segments must be within the range defined by + the builtins themselves. + - There must not be accesses to the program segment outside the program + data range. This check will use the `programSegmentSize` instead of the program data length if available. + - All addresses in memory must be real (not temporary) + +Note: Each builtin is responsible for checking its own segments' data. +*/ +func VerifySecureRunner(runner *CairoRunner, verifyBuiltins bool, programSegmentSize *uint) error { + programSize := uint(len(runner.Program.Data)) + if programSegmentSize != nil { + programSize = *programSegmentSize + } + // Get builtin segment info + builtinNames := make(map[int]string) + builtinSizes := make(map[int]uint) + if verifyBuiltins { + for i := 0; i < len(runner.Vm.BuiltinRunners); i++ { + base, stopPtr, err := runner.Vm.BuiltinRunners[i].GetMemorySegmentAddresses() + if err != nil { + return err + } + builtinNames[base.SegmentIndex] = runner.Vm.BuiltinRunners[i].Name() + builtinSizes[base.SegmentIndex] = stopPtr.Offset + } + } + // Run memory checks + for addr, val := range runner.Vm.Segments.Memory.Data { + // Check out of bound accesses to builtin segment + size, ok := builtinSizes[addr.SegmentIndex] + if ok && addr.Offset >= size { + return errors.Errorf("Out of bounds access to builtin segment %s at %s", builtinNames[addr.SegmentIndex], addr.ToString()) + } + // Check out of bound accesses to program segment + if addr.SegmentIndex == runner.ProgramBase.SegmentIndex && addr.SegmentIndex >= int(programSize) { + return errors.Errorf("Out of bounds access to program segment at %s", addr.ToString()) + } + // Check non-relocated temporary addresses + if addr.SegmentIndex < 0 { + return errors.Errorf("Security Error: Invalid Memory Value: temporary address not relocated: %s", addr.ToString()) + } + relVal, isRel := val.GetRelocatable() + if isRel && relVal.SegmentIndex < 0 { + return errors.Errorf("Security Error: Invalid Memory Value: temporary address not relocated: %s", relVal.ToString()) + } + } + // Run builtin-specific checks + for i := 0; i < len(runner.Vm.BuiltinRunners); i++ { + err := builtins.RunSecurityChecksForBuiltin(runner.Vm.BuiltinRunners[i], &runner.Vm.Segments) + if err != nil { + return err + } + } + + return nil +} diff --git a/pkg/runners/security_test.go b/pkg/runners/security_test.go new file mode 100644 index 00000000..dd808ac7 --- /dev/null +++ b/pkg/runners/security_test.go @@ -0,0 +1,55 @@ +package runners_test + +import ( + "testing" + + "github.com/lambdaclass/cairo-vm.go/pkg/builtins" + . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/runners" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestVerifySecureRunnerEmptyMemory(t *testing.T) { + runner, _ := NewCairoRunner(Program{}, "all_cairo", false) + runner.Initialize() + err := VerifySecureRunner(runner, true, nil) + if err != nil { + t.Errorf("VerifySecureRunner failed with error: %s", err.Error()) + } +} + +func TestVerifySecureRunnerOutOfBoundsAccessProgram(t *testing.T) { + runner, _ := NewCairoRunner(Program{}, "all_cairo", false) + runner.Initialize() + // Insert an element into the program segment to trigger out of bounds access to program segment error + runner.Vm.Segments.Memory.Insert(runner.ProgramBase, NewMaybeRelocatableFelt(FeltOne())) + err := VerifySecureRunner(runner, true, nil) + if err == nil { + t.Errorf("VerifySecureRunner should have failed") + } +} + +func TestVerifySecureRunnerOutOfBoundsAccessBuiltin(t *testing.T) { + runner, _ := NewCairoRunner(Program{Builtins: []string{builtins.OUTPUT_BUILTIN_NAME}}, "all_cairo", false) + runner.Initialize() + stopPtr := uint(0) + runner.Vm.BuiltinRunners[0].(*builtins.OutputBuiltinRunner).StopPtr = &stopPtr + // Insert an element into the output segment to trigger out of bounds access to builtin segment error + runner.Vm.Segments.Memory.Insert(runner.Vm.BuiltinRunners[0].Base(), NewMaybeRelocatableFelt(FeltOne())) + err := VerifySecureRunner(runner, true, nil) + if err == nil { + t.Errorf("VerifySecureRunner should have failed") + } +} + +func TestVerifySecureRunnerTemporaryVal(t *testing.T) { + runner, _ := NewCairoRunner(Program{}, "all_cairo", false) + runner.Initialize() + // Insert a temporary address into memory + runner.Vm.Segments.Memory.Insert(NewRelocatable(1, 7), NewMaybeRelocatableRelocatable(NewRelocatable(-1, 0))) + err := VerifySecureRunner(runner, true, nil) + if err == nil { + t.Errorf("VerifySecureRunner should have failed") + } +} diff --git a/pkg/vm/cairo_run/cairo_run.go b/pkg/vm/cairo_run/cairo_run.go index d54a1f99..db6b568a 100644 --- a/pkg/vm/cairo_run/cairo_run.go +++ b/pkg/vm/cairo_run/cairo_run.go @@ -24,6 +24,7 @@ type CairoRunConfig struct { DisableTracePadding bool ProofMode bool Layout string + SecureRun bool } func CairoRunError(err error) error { @@ -67,6 +68,13 @@ func CairoRun(programPath string, cairoRunConfig CairoRunConfig) (*runners.Cairo cairoRunner.FinalizeSegments() } + if cairoRunConfig.SecureRun { + err = runners.VerifySecureRunner(cairoRunner, true, nil) + if err != nil { + return nil, err + } + } + err = cairoRunner.Vm.Relocate() return cairoRunner, err } diff --git a/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index 4aa6c0f3..d04554d6 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -8,7 +8,7 @@ import ( ) func testProgram(programName string, t *testing.T) { - cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false} + cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false, SecureRun: true} _, err := cairo_run.CairoRun("../../../cairo_programs/"+programName+".json", cairoRunConfig) if err != nil { t.Errorf("Program execution failed with error: %s", err) From 5a2d812ab4973fe3a1900bc4d4e0e4a2a8b28d3e Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 24 Oct 2023 17:10:05 +0300 Subject: [PATCH 8/9] Implement Blake2s Hints (Part 1) (#311) * Begin implemneting blake2s * Finish blake2s impl * Add unit test * Add more unit tests * Add integration test * Implement BLAKE2S_COMPUTE * Add unit tests * Add unit tests * Add newline * Update cairo_run_test.go * fmt --------- Co-authored-by: Pedro Fontana Co-authored-by: Juan Bono --- cairo_programs/blake2s_hello_world_hash.cairo | 20 +++ pkg/hints/blake2s_hints.go | 84 ++++++++++ pkg/hints/blake2s_hints_test.go | 154 ++++++++++++++++++ pkg/hints/hint_codes/blake2s_hint_codes.go | 4 + pkg/hints/hint_processor.go | 2 + pkg/lambdaworks/lambdaworks.go | 10 ++ pkg/lambdaworks/lambdaworks_test.go | 34 ++++ pkg/vm/cairo_run/cairo_run_test.go | 4 + 8 files changed, 312 insertions(+) create mode 100644 cairo_programs/blake2s_hello_world_hash.cairo create mode 100644 pkg/hints/blake2s_hints.go create mode 100644 pkg/hints/blake2s_hints_test.go create mode 100644 pkg/hints/hint_codes/blake2s_hint_codes.go diff --git a/cairo_programs/blake2s_hello_world_hash.cairo b/cairo_programs/blake2s_hello_world_hash.cairo new file mode 100644 index 00000000..6d591cb9 --- /dev/null +++ b/cairo_programs/blake2s_hello_world_hash.cairo @@ -0,0 +1,20 @@ +%builtins range_check bitwise + +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.cairo_blake2s.blake2s import blake2s +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin + +// Computes the hash of "Hello World" +func main{range_check_ptr, bitwise_ptr: BitwiseBuiltin*}() { + alloc_locals; + let inputs: felt* = alloc(); + assert inputs[0] = 'Hell'; + assert inputs[1] = 'o Wo'; + assert inputs[2] = 'rld'; + let (local blake2s_ptr_start) = alloc(); + let blake2s_ptr = blake2s_ptr_start; + let (output) = blake2s{range_check_ptr=range_check_ptr, blake2s_ptr=blake2s_ptr}(inputs, 9); + assert output.low = 219917655069954262743903159041439073909; + assert output.high = 296157033687865319468534978667166017272; + return (); +} diff --git a/pkg/hints/blake2s_hints.go b/pkg/hints/blake2s_hints.go new file mode 100644 index 00000000..d66c4794 --- /dev/null +++ b/pkg/hints/blake2s_hints.go @@ -0,0 +1,84 @@ +package hints + +import ( + . "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" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func Uint32SliceToMRSlice(u32Slice []uint32) []MaybeRelocatable { + mRSlice := make([]MaybeRelocatable, 0, len(u32Slice)) + for _, u32 := range u32Slice { + mRSlice = append(mRSlice, *NewMaybeRelocatableFelt(FeltFromUint(uint(u32)))) + } + return mRSlice + +} + +func feltSliceToUint32Slice(feltSlice []Felt) ([]uint32, error) { + uint32Slice := make([]uint32, 0, len(feltSlice)) + for _, felt := range feltSlice { + val, err := felt.ToU32() + if err != nil { + return nil, err + } + uint32Slice = append(uint32Slice, val) + } + return uint32Slice, nil +} + +// Returns the range from (baseAddr - baseAddrOffset) to (baseAddr - baseAddrOffset + Size) +func getUint32MemoryRange(baseAddr Relocatable, baseAddrOffset uint, size uint, segments *MemorySegmentManager) ([]uint32, error) { + baseAddr, err := baseAddr.SubUint(baseAddrOffset) + if err != nil { + return nil, err + } + feltRange, err := segments.GetFeltRange(baseAddr, size) + if err != nil { + return nil, err + } + return feltSliceToUint32Slice(feltRange) +} + +// Returns the u32 value at memory[baseAddr - baseAddrOffset] +func getU32FromMemory(baseAddr Relocatable, baseAddrOffset uint, memory *Memory) (uint32, error) { + baseAddr, err := baseAddr.SubUint(baseAddrOffset) + if err != nil { + return 0, err + } + felt, err := memory.GetFelt(baseAddr) + if err != nil { + return 0, err + } + return felt.ToU32() +} + +func blake2sCompute(ids IdsManager, vm *VirtualMachine) error { + output, err := ids.GetRelocatable("output", vm) + if err != nil { + return err + } + h, err := getUint32MemoryRange(output, 26, 8, &vm.Segments) + if err != nil { + return err + } + message, err := getUint32MemoryRange(output, 18, 16, &vm.Segments) + if err != nil { + return err + } + t, err := getU32FromMemory(output, 2, &vm.Segments.Memory) + if err != nil { + return err + } + f, err := getU32FromMemory(output, 1, &vm.Segments.Memory) + if err != nil { + return err + } + + newState := Blake2sCompress([8]uint32(h), [16]uint32(message), t, 0, f, 0) + data := Uint32SliceToMRSlice(newState) + + _, err = vm.Segments.LoadData(output, &data) + return err +} diff --git a/pkg/hints/blake2s_hints_test.go b/pkg/hints/blake2s_hints_test.go new file mode 100644 index 00000000..42b88d26 --- /dev/null +++ b/pkg/hints/blake2s_hints_test.go @@ -0,0 +1,154 @@ +package hints_test + +import ( + "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/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestBlake2sComputeOutputOffsetZero(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + output := vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "output": {NewMaybeRelocatableRelocatable(output)}, + }, + vm, + ) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: BLAKE2S_COMPUTE, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err == nil { + t.Errorf("BLAKE2S_COMPUTE hint test should have failed") + } +} + +func TestBlake2sComputeOutputSegmentEmpty(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + output := vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "output": {NewMaybeRelocatableRelocatable(output.AddUint(26))}, + }, + vm, + ) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: BLAKE2S_COMPUTE, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err == nil { + t.Errorf("BLAKE2S_COMPUTE hint test should have failed") + } +} + +func TestBlake2sComputeBigOutput(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + output := vm.Segments.AddSegment() + data := []MaybeRelocatable{ + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + } + output, _ = vm.Segments.LoadData(output, &data) + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "output": {NewMaybeRelocatableRelocatable(output)}, + }, + vm, + ) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: BLAKE2S_COMPUTE, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err == nil { + t.Errorf("BLAKE2S_COMPUTE hint test should have failed") + } +} + +func TestBlake2sComputeOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + output := vm.Segments.AddSegment() + data := []MaybeRelocatable{ + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + } + output, _ = vm.Segments.LoadData(output, &data) + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "output": {NewMaybeRelocatableRelocatable(output)}, + }, + vm, + ) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: BLAKE2S_COMPUTE, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("BLAKE2S_COMPUTE hint test failed with error %s", err) + } +} diff --git a/pkg/hints/hint_codes/blake2s_hint_codes.go b/pkg/hints/hint_codes/blake2s_hint_codes.go new file mode 100644 index 00000000..fb26e1f5 --- /dev/null +++ b/pkg/hints/hint_codes/blake2s_hint_codes.go @@ -0,0 +1,4 @@ +package hint_codes + +const BLAKE2S_COMPUTE = `from starkware.cairo.common.cairo_blake2s.blake2s_utils import compute_blake2s_func +compute_blake2s_func(segments=segments, output_ptr=ids.output)` diff --git a/pkg/hints/hint_processor.go b/pkg/hints/hint_processor.go index 669cc1af..7abcc972 100644 --- a/pkg/hints/hint_processor.go +++ b/pkg/hints/hint_processor.go @@ -228,6 +228,8 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any, return fastEcAddAssignNewX(data.Ids, vm, execScopes, "pt0", "pt1", SECP_P()) case FAST_EC_ADD_ASSIGN_NEW_Y: return fastEcAddAssignNewY(execScopes) + case BLAKE2S_COMPUTE: + return blake2sCompute(data.Ids, vm) default: return errors.Errorf("Unknown Hint: %s", data.Code) } diff --git a/pkg/lambdaworks/lambdaworks.go b/pkg/lambdaworks/lambdaworks.go index fb175502..10bda142 100644 --- a/pkg/lambdaworks/lambdaworks.go +++ b/pkg/lambdaworks/lambdaworks.go @@ -8,6 +8,7 @@ package lambdaworks import "C" import ( + "math" "math/big" "reflect" "strings" @@ -101,6 +102,15 @@ func (felt Felt) ToUint() (uint, error) { return uint(felt_u64), nil } +// turns a felt to uint32 +func (felt Felt) ToU32() (uint32, error) { + feltU64, err := felt.ToU64() + if err != nil || feltU64 > math.MaxUint32 { + return 0, ConversionError(felt, "u32") + } + return uint32(feltU64), nil +} + func (felt Felt) ToLeBytes() *[32]byte { var result_c [32]C.uint8_t var value C.felt_t = felt.toC() diff --git a/pkg/lambdaworks/lambdaworks_test.go b/pkg/lambdaworks/lambdaworks_test.go index bf7c404d..fdd3cd89 100644 --- a/pkg/lambdaworks/lambdaworks_test.go +++ b/pkg/lambdaworks/lambdaworks_test.go @@ -427,6 +427,40 @@ func TestToU64Fail(t *testing.T) { } } +func TestToU321(t *testing.T) { + felt := lambdaworks.FeltOne() + result, err := felt.ToU32() + + var expected uint32 = 1 + + if expected != result { + t.Errorf("Error in conversion expected: %v, got %v with err: %v", expected, result, err) + } + +} + +func TestToU3210230(t *testing.T) { + felt := lambdaworks.FeltFromUint64(10230) + result, err := felt.ToU32() + + var expected uint32 = 10230 + + if expected != result { + t.Errorf("Error in conversion expected: %v, got %v with err: %v", expected, result, err) + } +} + +func TestTo32Fail(t *testing.T) { + felt := lambdaworks.FeltFromUint64(4294967297) + + _, err := felt.ToU32() + expected_err := lambdaworks.ConversionError(felt, "u32") + + if err.Error() != expected_err.Error() { + t.Errorf("Conversion test should fail with error: %v", expected_err) + } +} + func TestToUint1(t *testing.T) { felt := lambdaworks.FeltOne() result, err := felt.ToUint() diff --git a/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index d04554d6..4fc0fc75 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -353,6 +353,10 @@ func TestKeccakAddUint256(t *testing.T) { testProgram("keccak_add_uint256", t) } +func TestBlake2sHelloWorldHash(t *testing.T) { + testProgram("blake2s_hello_world_hash", t) +} + func TestUint256Integration(t *testing.T) { testProgram("uint256_integration_tests", t) } From 432dd89a7fbcf2bf254708a957f92ecfbdb3849e Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Wed, 25 Oct 2023 00:26:49 +0300 Subject: [PATCH 9/9] Add `RunResources` (#306) * Add ExecutionResources struct * Fix: Remove vm argument from CairoRunner methods * Finish func + remove todos * Add unit test * Implement GenArg * Remove recursive processing * Add unit tests * Fix test values * Start fn * Add RunFromEntryPoint * Add test for RunFromEntryPoint * Add unit tests * Add comments * Add skeleton * Implement GetMemoryAccesses for BuiltinRunner * Start implementing RunSecurityChecks * Implement VerifyAutoDeductionsForAddr * Finish func + remove import cycle * Add fix + unit test * Fix logic, add test * Fix logic * Fix logic * Fix logic + add test * Add unit tests * Move RunSecurityChecks to external func in builtins package * Remove todos from BuiltinRunner interface * Finish verifySecureRunner * Add verifySecure to RunFromEntryPoint * Add unit tests * Add secure_run flag * Fix typo * Add RunResources * Add RunResources * Fix bug * Use saturating subtraction --- pkg/runners/cairo_runner.go | 12 ++++++++++-- pkg/runners/cairo_runner_test.go | 26 +++++++++++++++++++++++++- pkg/vm/run_resources.go | 23 +++++++++++++++++++++++ pkg/vm/vm_core.go | 1 + 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 pkg/vm/run_resources.go diff --git a/pkg/runners/cairo_runner.go b/pkg/runners/cairo_runner.go index 53354130..5e3564fb 100644 --- a/pkg/runners/cairo_runner.go +++ b/pkg/runners/cairo_runner.go @@ -228,11 +228,18 @@ func (r *CairoRunner) RunUntilPC(end memory.Relocatable, hintProcessor vm.HintPr return err } constants := r.Program.ExtractConstants() - for r.Vm.RunContext.Pc != end { + for r.Vm.RunContext.Pc != end && + (r.Vm.RunResources == nil || !r.Vm.RunResources.Consumed()) { err := r.Vm.Step(hintProcessor, &hintDataMap, &constants, &r.execScopes) if err != nil { return err } + if r.Vm.RunResources != nil { + r.Vm.RunResources.ConsumeStep() + } + } + if r.Vm.RunContext.Pc != end { + return errors.New("Could not reach the end of the program. RunResources has no remaining steps.") } return nil } @@ -588,7 +595,8 @@ If `verifySecure` is set to true, [verifySecureRunner] will be called to run ext `programSegmentSize` is only used by the [verifySecureRunner] function and will be ignored if `verifySecure` is set to false. Each arg can be either MaybeRelocatable, []MaybeRelocatable or [][]MaybeRelocatable */ -func (runner *CairoRunner) RunFromEntrypoint(entrypoint uint, args []any, hintProcessor vm.HintProcessor, verifySecure bool, programSegmentSize *uint) error { +func (runner *CairoRunner) RunFromEntrypoint(entrypoint uint, args []any, hintProcessor vm.HintProcessor, runResources *vm.RunResources, verifySecure bool, programSegmentSize *uint) error { + runner.Vm.RunResources = runResources stack := make([]memory.MaybeRelocatable, 0) for _, arg := range args { val, err := runner.Vm.Segments.GenArg(arg) diff --git a/pkg/runners/cairo_runner_test.go b/pkg/runners/cairo_runner_test.go index 0f5f34c6..8ca5ae54 100644 --- a/pkg/runners/cairo_runner_test.go +++ b/pkg/runners/cairo_runner_test.go @@ -743,7 +743,7 @@ func TestRunFromEntryPointFibonacci(t *testing.T) { runner.InitializeBuiltins() runner.InitializeSegments() - err := runner.RunFromEntrypoint(uint(entrypoint), args, &hintProcessor, true, nil) + err := runner.RunFromEntrypoint(uint(entrypoint), args, &hintProcessor, nil, true, nil) if err != nil { t.Errorf("Running fib entrypoint failed with error %s", err.Error()) @@ -759,3 +759,27 @@ func TestRunFromEntryPointFibonacci(t *testing.T) { } } + +func TestRunFromEntryPointFibonacciNotEnoughSteps(t *testing.T) { + compiledProgram, _ := parser.Parse("../../cairo_programs/fibonacci.json") + programJson := vm.DeserializeProgramJson(compiledProgram) + + entrypoint := programJson.Identifiers["__main__.fib"].PC + args := []any{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltOne()), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltOne()), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(10)), + } + runner, _ := runners.NewCairoRunner(programJson, "all_cairo", false) + hintProcessor := hints.CairoVmHintProcessor{} + + runner.InitializeBuiltins() + runner.InitializeSegments() + runResources := vm.NewRunResources(2) + err := runner.RunFromEntrypoint(uint(entrypoint), args, &hintProcessor, &runResources, true, nil) + + if err == nil { + t.Errorf("Running fib entrypoint should have failed") + } + +} diff --git a/pkg/vm/run_resources.go b/pkg/vm/run_resources.go new file mode 100644 index 00000000..e9da4247 --- /dev/null +++ b/pkg/vm/run_resources.go @@ -0,0 +1,23 @@ +package vm + +type RunResources struct { + nSteps *uint +} + +func NewRunResources(nSteps uint) RunResources { + return RunResources{nSteps: &nSteps} +} + +func (r *RunResources) Consumed() bool { + return r.nSteps != nil && *r.nSteps == 0 +} + +func (r *RunResources) ConsumeStep() { + if r.nSteps != nil && *r.nSteps != 0 { + *r.nSteps-- + } +} + +func (r *RunResources) GetNSteps() *uint { + return r.nSteps +} diff --git a/pkg/vm/vm_core.go b/pkg/vm/vm_core.go index c5cb69b1..7bbe3547 100644 --- a/pkg/vm/vm_core.go +++ b/pkg/vm/vm_core.go @@ -35,6 +35,7 @@ type VirtualMachine struct { RunFinished bool RcLimitsMin *int RcLimitsMax *int + RunResources *RunResources } func NewVirtualMachine() *VirtualMachine {