diff --git a/cairo_programs/split_int.cairo b/cairo_programs/split_int.cairo new file mode 100644 index 00000000..1c871223 --- /dev/null +++ b/cairo_programs/split_int.cairo @@ -0,0 +1,18 @@ +%builtins range_check + +from starkware.cairo.common.math import split_int +from starkware.cairo.common.alloc import alloc + +func main{range_check_ptr: felt}() { + alloc_locals; + let value = 456; + let n = 3; + let base = 10; + let bound = 1000; + let output: felt* = alloc(); + split_int(value, n, base, bound, output); + assert output[0] = 6; + assert output[1] = 5; + assert output[2] = 4; + return (); +} diff --git a/pkg/hints/hint_codes/math_hint_codes.go b/pkg/hints/hint_codes/math_hint_codes.go index 916ea459..de6083ff 100644 --- a/pkg/hints/hint_codes/math_hint_codes.go +++ b/pkg/hints/hint_codes/math_hint_codes.go @@ -38,3 +38,7 @@ const ASSERT_LT_FELT = "from starkware.cairo.common.math_utils import assert_int const ASSERT_250_BITS = "from starkware.cairo.common.math_utils import as_int\n\n# Correctness check.\nvalue = as_int(ids.value, PRIME) % PRIME\nassert value < ids.UPPER_BOUND, f'{value} is outside of the range [0, 2**250).'\n\n# Calculation for the assertion.\nids.high, ids.low = divmod(ids.value, ids.SHIFT)" const SPLIT_FELT = "from starkware.cairo.common.math_utils import assert_integer\nassert ids.MAX_HIGH < 2**128 and ids.MAX_LOW < 2**128\nassert PRIME - 1 == ids.MAX_HIGH * 2**128 + ids.MAX_LOW\nassert_integer(ids.value)\nids.low = ids.value & ((1 << 128) - 1)\nids.high = ids.value >> 128" + +const SPLIT_INT = "memory[ids.output] = res = (int(ids.value) % PRIME) % ids.base\nassert res < ids.bound, f'split_int(): Limb {res} is out of range.'" + +const SPLIT_INT_ASSERT_RANGE = "assert ids.value == 0, 'split_int(): value is out of range.'" diff --git a/pkg/hints/hint_processor.go b/pkg/hints/hint_processor.go index 4a750f03..b571bc83 100644 --- a/pkg/hints/hint_processor.go +++ b/pkg/hints/hint_processor.go @@ -132,6 +132,10 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any, return Assert250Bit(data.Ids, vm, constants) case SPLIT_FELT: return SplitFelt(data.Ids, vm, constants) + case SPLIT_INT: + return splitInt(data.Ids, vm) + case SPLIT_INT_ASSERT_RANGE: + return splitIntAssertRange(data.Ids, vm) default: return errors.Errorf("Unknown Hint: %s", data.Code) } diff --git a/pkg/hints/math_hints.go b/pkg/hints/math_hints.go index c9bd5a26..03b239c3 100644 --- a/pkg/hints/math_hints.go +++ b/pkg/hints/math_hints.go @@ -542,3 +542,66 @@ func SplitFelt(ids IdsManager, vm *VirtualMachine, constants *map[string]Felt) e return nil } + +/* +Implements hint: + + %{ + memory[ids.output] = res = (int(ids.value) % PRIME) % ids.base + assert res < ids.bound, f'split_int(): Limb {res} is out of range.'" + %} +*/ +func splitInt(ids IdsManager, vm *VirtualMachine) error { + value, err := ids.GetFelt("value", vm) + if err != nil { + return err + } + + base, err := ids.GetFelt("base", vm) + if err != nil { + return err + } + + bound, err := ids.GetFelt("bound", vm) + if err != nil { + return err + } + + output, err := ids.GetRelocatable("output", vm) + if err != nil { + return err + } + + res := value.ModFloor(base) + + if res.Cmp(bound) == 1 { + return errors.Errorf("split_int(): Limb %d is out of range", res.ToBigInt()) + } + + err = vm.Segments.Memory.Insert(output, NewMaybeRelocatableFelt(res)) + if err != nil { + return err + } + + return nil +} + +/* +Implements hint: + + %{ + assert ids.value == 0, 'split_int(): value is out of range.' + %} +*/ +func splitIntAssertRange(ids IdsManager, vm *VirtualMachine) error { + value, err := ids.GetFelt("value", vm) + if err != nil { + return err + } + + if !value.IsZero() { + return errors.Errorf("split_int(): value is out of range") + } + + return nil +} diff --git a/pkg/hints/math_hints_test.go b/pkg/hints/math_hints_test.go index d640beed..296dfb8e 100644 --- a/pkg/hints/math_hints_test.go +++ b/pkg/hints/math_hints_test.go @@ -1035,3 +1035,106 @@ func TestSplitFeltSuccess(t *testing.T) { t.Errorf("Expected low == 0. Got: %v", low) } } + +func TestSplitIntHintSuccess(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "value": {NewMaybeRelocatableFelt(FeltFromDecString("6"))}, + "base": {NewMaybeRelocatableFelt(FeltFromDecString("4"))}, + "bound": {NewMaybeRelocatableFelt(FeltFromDecString("58"))}, + "output": {NewMaybeRelocatableRelocatable(NewRelocatable(0, 4))}, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: SPLIT_INT, + }) + + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("SPLIT_INT hint failed with error %s", err) + } + + res, err := vm.Segments.Memory.GetFelt(NewRelocatable(0, 4)) + if err != nil { + t.Errorf("SPLIT_INT hint failed, `res` value not inserted") + } + + if res.Cmp(lambdaworks.FeltFromUint64(2)) != 0 { + t.Errorf("SPLIT_INT hint failed. Expected 2, got: %d", res.ToBigInt()) + } +} + +func TestSplitIntHintOutOfBoundsError(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "value": {NewMaybeRelocatableFelt(FeltFromDecString("17"))}, + "base": {NewMaybeRelocatableFelt(FeltFromDecString("9"))}, + "bound": {NewMaybeRelocatableFelt(FeltFromDecString("5"))}, + "output": {NewMaybeRelocatableRelocatable(NewRelocatable(0, 4))}, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: SPLIT_INT, + }) + + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err == nil { + t.Errorf("SPLIT_INT hint should have failed") + } +} + +func TestSplitIntAssertRangeHintSuccess(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "value": {NewMaybeRelocatableFelt(FeltFromDecString("0"))}, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: SPLIT_INT_ASSERT_RANGE, + }) + + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("SPLIT_INT_ASSERT_RANGE hint failed with error: %s", err) + } +} + +func TestSplitIntAssertRangeHintOutOfRangeError(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "value": {NewMaybeRelocatableFelt(FeltFromDecString("3"))}, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: SPLIT_INT_ASSERT_RANGE, + }) + + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err == nil { + t.Errorf("SPLIT_INT_ASSERT_RANGE hint should have failed") + } +} diff --git a/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index 0284e120..0de36ae1 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -189,3 +189,11 @@ func TestDictSquash(t *testing.T) { func TestSplitFeltHint(t *testing.T) { testProgram("split_felt", t) } + +func TestSplitIntHint(t *testing.T) { + cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false} + _, err := cairo_run.CairoRun("../../../cairo_programs/split_int.json", cairoRunConfig) + if err != nil { + t.Errorf("Program execution failed with error: %s", err) + } +}