diff --git a/Makefile b/Makefile index 1738d18f..9954a52f 100644 --- a/Makefile +++ b/Makefile @@ -104,6 +104,7 @@ clean: clean_files: rm -f $(TEST_DIR)/*.json + rm -f $(TEST_DIR)/proof_programs/*.json rm -f $(TEST_DIR)/*.memory rm -f $(TEST_DIR)/*.trace diff --git a/README.md b/README.md index bab2fb08..3b0b14c3 100644 --- a/README.md +++ b/README.md @@ -1061,122 +1061,124 @@ func (v *VirtualMachine) RunInstruction(instruction *Instruction) error { Once the instruction has been decoded, it is executed by `RunInstruction` whose first function is to compute operands. This function is in charge of calculating the addresses of the operands and fetching them from memory. If the function could not fetch the operands then they are deduced from the other operands, -taking in consideration what kind of opcode is being executed. +taking in consideration what kind of opcode is being executed. ```go func (vm *VirtualMachine) ComputeOperands(instruction Instruction) (Operands, error) { - var res *memory.MaybeRelocatable + var res *memory.MaybeRelocatable - dst_addr, err := vm.RunContext.ComputeDstAddr(instruction) - if err != nil { - return Operands{}, errors.New("FailedToComputeDstAddr") - } - dst, _ := vm.Segments.Memory.Get(dst_addr) + dst_addr, err := vm.RunContext.ComputeDstAddr(instruction) + if err != nil { + return Operands{}, errors.New("FailedToComputeDstAddr") + } + dst, _ := vm.Segments.Memory.Get(dst_addr) - op0_addr, err := vm.RunContext.ComputeOp0Addr(instruction) - if err != nil { - return Operands{}, fmt.Errorf("FailedToComputeOp0Addr: %s", err) - } - op0, _ := vm.Segments.Memory.Get(op0_addr) + op0_addr, err := vm.RunContext.ComputeOp0Addr(instruction) + if err != nil { + return Operands{}, fmt.Errorf("FailedToComputeOp0Addr: %s", err) + } + op0, _ := vm.Segments.Memory.Get(op0_addr) - op1_addr, err := vm.RunContext.ComputeOp1Addr(instruction, op0) - if err != nil { - return Operands{}, fmt.Errorf("FailedToComputeOp1Addr: %s", err) - } - op1, _ := vm.Segments.Memory.Get(op1_addr) - - if op0 == nil { - deducedOp0, deducedRes, err := vm.DeduceOp0(&instruction, dst, op1) - if err != nil { - return Operands{}, err - } - op0 = deducedOp0 - if op0 != nil { - vm.Segments.Memory.Insert(op0_addr, op0) - } - res = deducedRes - } + op1_addr, err := vm.RunContext.ComputeOp1Addr(instruction, op0) + if err != nil { + return Operands{}, fmt.Errorf("FailedToComputeOp1Addr: %s", err) + } + op1, _ := vm.Segments.Memory.Get(op1_addr) - if op1 == nil { - deducedOp1, deducedRes, err := vm.DeduceOp1(instruction, dst, op0) - if err != nil { - return Operands{}, err - } - op1 = deducedOp1 - if op1 != nil { - vm.Segments.Memory.Insert(op1_addr, op1) - } - if res == nil { - res = deducedRes - } - } + if op0 == nil { + deducedOp0, deducedRes, err := vm.DeduceOp0(&instruction, dst, op1) + if err != nil { + return Operands{}, err + } + op0 = deducedOp0 + if op0 != nil { + vm.Segments.Memory.Insert(op0_addr, op0) + } + res = deducedRes + } - if res == nil { - res, err = vm.ComputeRes(instruction, *op0, *op1) + if op1 == nil { + deducedOp1, deducedRes, err := vm.DeduceOp1(instruction, dst, op0) + if err != nil { + return Operands{}, err + } + op1 = deducedOp1 + if op1 != nil { + vm.Segments.Memory.Insert(op1_addr, op1) + } + if res == nil { + res = deducedRes + } + } - if err != nil { - return Operands{}, err - } - } + if res == nil { + res, err = vm.ComputeRes(instruction, *op0, *op1) - if dst == nil { - deducedDst := vm.DeduceDst(instruction, res) - dst = deducedDst - if dst != nil { - vm.Segments.Memory.Insert(dst_addr, dst) - } - } + if err != nil { + return Operands{}, err + } + } - operands := Operands{ - Dst: *dst, - Op0: *op0, - Op1: *op1, - Res: res, - } - return operands, nil + if dst == nil { + deducedDst := vm.DeduceDst(instruction, res) + dst = deducedDst + if dst != nil { + vm.Segments.Memory.Insert(dst_addr, dst) + } + } + + operands := Operands{ + Dst: *dst, + Op0: *op0, + Op1: *op1, + Res: res, + } + return operands, nil } ``` ##### ComputeDstAddr + The method `ComputeDstAddr` computes the address of the value that will be stored in the Destination (dst) operand. It checks which register its is relative to (wether ap or fp) and gets the direction by adding the instruction's first offset(off0) to the corresponding register. ```go func (run_context RunContext) ComputeDstAddr(instruction Instruction) (memory.Relocatable, error) { - var base_addr memory.Relocatable - switch instruction.DstReg { - case AP: - base_addr = run_context.Ap - case FP: - base_addr = run_context.Fp - } + var base_addr memory.Relocatable + switch instruction.DstReg { + case AP: + base_addr = run_context.Ap + case FP: + base_addr = run_context.Fp + } - if instruction.Off0 < 0 { - return base_addr.SubUint(uint(math.Abs(float64(instruction.Off0)))) - } else { - return base_addr.AddUint(uint(instruction.Off0)) - } + if instruction.Off0 < 0 { + return base_addr.SubUint(uint(math.Abs(float64(instruction.Off0)))) + } else { + return base_addr.AddUint(uint(instruction.Off0)) + } } ``` + ##### ComputeOp0Addr This method is similar to `ComputeDstAddr` but it uses the instruction second offset (off1) to add to the selected register (ap or fp) ```go func (run_context RunContext) ComputeOp0Addr(instruction Instruction) (memory.Relocatable, error) { - var base_addr memory.Relocatable - switch instruction.Op0Reg { - case AP: - base_addr = run_context.Ap - case FP: - base_addr = run_context.Fp - } + var base_addr memory.Relocatable + switch instruction.Op0Reg { + case AP: + base_addr = run_context.Ap + case FP: + base_addr = run_context.Fp + } - if instruction.Off1 < 0 { - return base_addr.SubUint(uint(math.Abs(float64(instruction.Off1)))) - } else { - return base_addr.AddUint(uint(instruction.Off1)) - } + if instruction.Off1 < 0 { + return base_addr.SubUint(uint(math.Abs(float64(instruction.Off1)))) + } else { + return base_addr.AddUint(uint(instruction.Off1)) + } } ``` @@ -1184,46 +1186,47 @@ func (run_context RunContext) ComputeOp0Addr(instruction Instruction) (memory.Re ##### ComputeOp1Addr It computes the address of `Op1` based on the `Op0` operand and the kind of Address the instruction has for `Op1`. + - If its address is `Op1SrcFp` it calculates the direction from Fp register. -- if it is `Op1SrcAp` then if calculates it if from Ap register. -- If it is an immediate then checks if the offset 2 is 1 and calculates it from the `Pc`. +- if it is `Op1SrcAp` then if calculates it if from Ap register. +- If it is an immediate then checks if the offset 2 is 1 and calculates it from the `Pc`. - If it is an `Op1SrcOp0` it checks the `Op0` and calculates the direction from it. Then it performs and addition or a substraction if the `Off2` is negative or positive. ```go func (run_context RunContext) ComputeOp1Addr(instruction Instruction, op0 *memory.MaybeRelocatable) (memory.Relocatable, error) { - var base_addr memory.Relocatable - - switch instruction.Op1Addr { - case Op1SrcFP: - base_addr = run_context.Fp - case Op1SrcAP: - base_addr = run_context.Ap - case Op1SrcImm: - if instruction.Off2 == 1 { - base_addr = run_context.Pc - } else { - base_addr = memory.NewRelocatable(0, 0) - return memory.Relocatable{}, &VirtualMachineError{Msg: "UnknownOp0"} - } - case Op1SrcOp0: - if op0 == nil { - return memory.Relocatable{}, errors.New("Unknown Op0") - } - rel, is_rel := op0.GetRelocatable() - if is_rel { - base_addr = rel - } else { - return memory.Relocatable{}, errors.New("AddressNotRelocatable") - } - } + var base_addr memory.Relocatable + + switch instruction.Op1Addr { + case Op1SrcFP: + base_addr = run_context.Fp + case Op1SrcAP: + base_addr = run_context.Ap + case Op1SrcImm: + if instruction.Off2 == 1 { + base_addr = run_context.Pc + } else { + base_addr = memory.NewRelocatable(0, 0) + return memory.Relocatable{}, &VirtualMachineError{Msg: "UnknownOp0"} + } + case Op1SrcOp0: + if op0 == nil { + return memory.Relocatable{}, errors.New("Unknown Op0") + } + rel, is_rel := op0.GetRelocatable() + if is_rel { + base_addr = rel + } else { + return memory.Relocatable{}, errors.New("AddressNotRelocatable") + } + } - if instruction.Off2 < 0 { - return base_addr.SubUint(uint(math.Abs(float64(instruction.Off2)))) - } else { - return base_addr.AddUint(uint(instruction.Off2)) - } + if instruction.Off2 < 0 { + return base_addr.SubUint(uint(math.Abs(float64(instruction.Off2)))) + } else { + return base_addr.AddUint(uint(instruction.Off2)) + } } ``` @@ -1231,136 +1234,141 @@ func (run_context RunContext) ComputeOp1Addr(instruction Instruction, op0 *memor The method deduces the value of `Op0` if possible (based on `dst` and `Op1`). If Instruction's opcode is a `Call` `Op0` is deduced by adding the instruction size to the program counter. + - If it is an `AssertEq` then a second switch case is used to check what `ResLogic` is. -- If it is `ResAdd` `Op0` is deduced from the substraction of `Op1` from `Dst`, if is is `Resmul` the `Op0` is deduced from the division of `Dst` and `Op1` (both felt values). +- If it is `ResAdd` `Op0` is deduced from the substraction of `Op1` from `Dst`, if is is `Resmul` the `Op0` is deduced from the division of `Dst` and `Op1` (both felt values). - Otherwise op0 is nil. The method also deduces `res` by using the value of `dst` ```go func (vm *VirtualMachine) DeduceOp0(instruction *Instruction, dst *memory.MaybeRelocatable, op1 *memory.MaybeRelocatable) (deduced_op0 *memory.MaybeRelocatable, deduced_res *memory.MaybeRelocatable, error error) { - switch instruction.Opcode { - case Call: - deduced_op0 := vm.RunContext.Pc - deduced_op0.Offset += instruction.Size() - return memory.NewMaybeRelocatableRelocatable(deduced_op0), nil, nil - case AssertEq: - switch instruction.ResLogic { - case ResAdd: - if dst != nil && op1 != nil { - deduced_op0, err := dst.Sub(*op1) - if err != nil { - return nil, nil, err - } - return &deduced_op0, dst, nil - } - case ResMul: - if dst != nil && op1 != nil { - dst_felt, dst_is_felt := dst.GetFelt() - op1_felt, op1_is_felt := op1.GetFelt() - if dst_is_felt && op1_is_felt && !op1_felt.IsZero() { - return memory.NewMaybeRelocatableFelt(dst_felt.Div(op1_felt)), dst, nil - - } - } - } - } - return nil, nil, nil + switch instruction.Opcode { + case Call: + deduced_op0 := vm.RunContext.Pc + deduced_op0.Offset += instruction.Size() + return memory.NewMaybeRelocatableRelocatable(deduced_op0), nil, nil + case AssertEq: + switch instruction.ResLogic { + case ResAdd: + if dst != nil && op1 != nil { + deduced_op0, err := dst.Sub(*op1) + if err != nil { + return nil, nil, err + } + return &deduced_op0, dst, nil + } + case ResMul: + if dst != nil && op1 != nil { + dst_felt, dst_is_felt := dst.GetFelt() + op1_felt, op1_is_felt := op1.GetFelt() + if dst_is_felt && op1_is_felt && !op1_felt.IsZero() { + return memory.NewMaybeRelocatableFelt(dst_felt.Div(op1_felt)), dst, nil + + } + } + } + } + return nil, nil, nil } ``` ##### DeduceOp1 The method deduces the value of `Op1` if possible (based on `dst` and `Op0`) it also deduces `res` if possible. + - If the instruction opcode is `AssertEq` a switch case is used to check what the `ResLogic` is. - If it is a `ResOp1` then the value of op1 is equal to the dst operand. - If `ResLogic` is `ResAdd` op1 is deduced from the substraction of `op0` from `dst`. -- If it is `ResMul` `op1` is deduced from the division of `dst` by `op0`, +- If it is `ResMul` `op1` is deduced from the division of `dst` by `op0`, In all the cases `res` is equal to `dst`. if none of the former cases apply then nil is returned. ```go func (vm *VirtualMachine) DeduceOp1(instruction Instruction, dst *memory.MaybeRelocatable, op0 *memory.MaybeRelocatable) (*memory.MaybeRelocatable, *memory.MaybeRelocatable, error) { - if instruction.Opcode == AssertEq { - switch instruction.ResLogic { - case ResOp1: - return dst, dst, nil - case ResAdd: - if op0 != nil && dst != nil { - dst_rel, err := dst.Sub(*op0) - if err != nil { - return nil, nil, err - } - return &dst_rel, dst, nil - } - case ResMul: - dst_felt, dst_is_felt := dst.GetFelt() - op0_felt, op0_is_felt := op0.GetFelt() - if dst_is_felt && op0_is_felt && !op0_felt.IsZero() { - res := memory.NewMaybeRelocatableFelt(dst_felt.Div(op0_felt)) - return res, dst, nil - } - } - } - return nil, nil, nil + if instruction.Opcode == AssertEq { + switch instruction.ResLogic { + case ResOp1: + return dst, dst, nil + case ResAdd: + if op0 != nil && dst != nil { + dst_rel, err := dst.Sub(*op0) + if err != nil { + return nil, nil, err + } + return &dst_rel, dst, nil + } + case ResMul: + dst_felt, dst_is_felt := dst.GetFelt() + op0_felt, op0_is_felt := op0.GetFelt() + if dst_is_felt && op0_is_felt && !op0_felt.IsZero() { + res := memory.NewMaybeRelocatableFelt(dst_felt.Div(op0_felt)) + return res, dst, nil + } + } + } + return nil, nil, nil } ``` + ##### ComputeRes If the Res value has not been deduced in the previous steps then it is computed based on the `Op0` and `Op1` values. -- If `ResLogic` is `ResOp1` then `res` is equal to `op1`. -- If it is `ResAdd` then `res` is deduced from the addition of `op0` and `op1`. + +- If `ResLogic` is `ResOp1` then `res` is equal to `op1`. +- If it is `ResAdd` then `res` is deduced from the addition of `op0` and `op1`. - If it is `ResMul` `res` is deduced from the multiplication of `op0` and `op1`. - Otherwise `res` is nil. ```go func (vm *VirtualMachine) ComputeRes(instruction Instruction, op0 memory.MaybeRelocatable, op1 memory.MaybeRelocatable) (*memory.MaybeRelocatable, error) { - switch instruction.ResLogic { - case ResOp1: - return &op1, nil - - case ResAdd: - maybe_rel, err := op0.Add(op1) - if err != nil { - return nil, err - } - return &maybe_rel, nil - - case ResMul: - num_op0, m_type := op0.GetFelt() - num_op1, other_type := op1.GetFelt() - if m_type && other_type { - result := memory.NewMaybeRelocatableFelt(num_op0.Mul(num_op1)) - return result, nil - } else { - return nil, errors.New("ComputeResRelocatableMul") - } - - case ResUnconstrained: - return nil, nil - } - return nil, nil + switch instruction.ResLogic { + case ResOp1: + return &op1, nil + + case ResAdd: + maybe_rel, err := op0.Add(op1) + if err != nil { + return nil, err + } + return &maybe_rel, nil + + case ResMul: + num_op0, m_type := op0.GetFelt() + num_op1, other_type := op1.GetFelt() + if m_type && other_type { + result := memory.NewMaybeRelocatableFelt(num_op0.Mul(num_op1)) + return result, nil + } else { + return nil, errors.New("ComputeResRelocatableMul") + } + + case ResUnconstrained: + return nil, nil + } + return nil, nil } ``` ##### DeduceDst If the destination value has not been calculated before then it is deduced based on the Res operand. If the opcode is an `AssertEq` then dst is equal res. -If it is a `Call` then its value is taken from the `Fp` register +If it is a `Call` then its value is taken from the `Fp` register ```go func (vm *VirtualMachine) DeduceDst(instruction Instruction, res *memory.MaybeRelocatable) *memory.MaybeRelocatable { - switch instruction.Opcode { - case AssertEq: - return res - case Call: - return memory.NewMaybeRelocatableRelocatable(vm.RunContext.Fp) + switch instruction.Opcode { + case AssertEq: + return res + case Call: + return memory.NewMaybeRelocatableRelocatable(vm.RunContext.Fp) - } - return nil + } + return nil } ``` + #### Opcode assertions Once we have the instruction's operands to work with, we have to ensure the correctness of them. The first thing we need to differentiate is which type of instruction are we running, we do this by looking at the instruction's opcode. @@ -1669,9 +1677,374 @@ func (r *CairoRunner) RunUntilPC(end memory.Relocatable) error { return nil ``` -#### Memory Relocation - function +Once we are done executing, we can relocate our memory and trace and output them into files. -TODO +#### Relocate + +This method will relocate the memory and trace generated by the program execution. Relocating means that our VM transforms a two-dimensional memory (aka a memory divided by segments) to a continuous, one-dimensional memory. +In this section, we will refer to the two-dimensional memory as the original memory and the one-dimensional memory as the relocated memory. +The memory relocation process is explained at a high level [here](#memory-relocation). + +The original memory is accessed using `Relocatable`s while the relocated memory is accessed using uints. Also, the original memory values can be either `Felt`s of `Relocatable`s while the relocated memory values are all `Felt`s. + +The relocation process is divided into 4 steps: + +1. Compute the sizes of each memory segment. +2. Build an array that contains the first relocated address of each segment. +3. Creates the relocated memory transforming the original memory by using the array built in 2. +4. Creates the relocated trace transforming the original trace by using the array built in 2. + +The function that does the relocation process is shown below: + +```go +func (v *VirtualMachine) Relocate() error { + v.Segments.ComputeEffectiveSizes() + if len(v.Trace) == 0 { + return nil + } + + relocationTable, ok := v.Segments.RelocateSegments() + // This should be unreachable + if !ok { + return errors.New("ComputeEffectiveSizes called but RelocateSegments still returned error") + } + + relocatedMemory, err := v.Segments.RelocateMemory(&relocationTable) + if err != nil { + return err + } + + v.RelocateTrace(&relocationTable) + v.RelocatedMemory = relocatedMemory + return nil +} +``` + +Notice that each step is encapsulated into a function. +We need to add the relocated trace and relocated memory fields to the `VirtualMachine` struct: + +```go +type VirtualMachine struct { + RunContext RunContext + currentStep uint + Segments memory.MemorySegmentManager + Trace []TraceEntry + RelocatedTrace []RelocatedTraceEntry + RelocatedMemory map[uint]lambdaworks.Felt +} +``` + +Also we need to add some methods to `MemorySegmentManager` and to `VirtualMachine`. +We will look into each one in the following subsections. + +#### ComputeEffectiveSizes + +To relocate the memory segments, we first need to know the size of each segment of the memory. +This method computes those sizes by returning a map whose keys are segment indexes and its values are the segment sizes: + +```go +func (m *MemorySegmentManager) ComputeEffectiveSizes() map[uint]uint { + if len(m.SegmentSizes) == 0 { + + for ptr := range m.Memory.data { + segmentIndex := uint(ptr.SegmentIndex) + segmentMaxSize := m.SegmentSizes[segmentIndex] + segmentSize := ptr.Offset + 1 + if segmentSize > segmentMaxSize { + m.SegmentSizes[segmentIndex] = segmentSize + } + } + } + + return m.SegmentSizes +} +``` + +Notice that this method will only do something once: consecutive calls won't do anything. +This is because the relocation process will happen only after the program execution and never again. +The `Relocate` function shouldn't be called more than once. + +#### RelocateSegments + +Once we have the segment sizes, we need to know where to relocate the segments. +Because we want the relocated memory to be continuous, the segments should be placed one after the other. +This means that the last address of the segment `i` is followed by the first address of the segment `i + 1`. +To know where to relocate the segments, we need to know the first address of each segment as if they were already relocated. +We also need to enforce that `RelocateSegments` is called after `ComputeEffectiveSizes`: + +```go +func (m *MemorySegmentManager) RelocateSegments() ([]uint, bool) { + if m.SegmentSizes == nil { + return nil, false + } + + first_addr := uint(1) + relocation_table := []uint{first_addr} + + for i := uint(0); i < m.Memory.NumSegments(); i++ { + new_addr := relocation_table[i] + m.SegmentSizes[i] + relocation_table = append(relocation_table, new_addr) + } + relocation_table = relocation_table[:len(relocation_table)-1] + + return relocation_table, true +} +``` + +#### RelocateMemory + +Once we have where each segment should go in the relocated memory, we can relocate the memory segments. + +```go +func (s *MemorySegmentManager) RelocateMemory(relocationTable *[]uint) (map[uint]lambdaworks.Felt, error) { + relocatedMemory := make(map[uint]lambdaworks.Felt, 0) + + for i := uint(0); i < s.Memory.NumSegments(); i++ { + for j := uint(0); j < s.SegmentSizes[i]; j++ { + ptr := NewRelocatable(int(i), j) + cell, err := s.Memory.Get(ptr) + if err == nil { + relocatedAddr := ptr.RelocateAddress(relocationTable) + value, err := cell.RelocateValue(relocationTable) + if err != nil { + return nil, err + } + relocatedMemory[relocatedAddr] = value + } + } + } + + return relocatedMemory, nil +} +``` + +This method calls two new methods: `RelocateAddress` from `Relocatable` and `RelocateValue` from `MaybeRelocatable`. +Let's implement them. + +##### RelocateAddress + +This `Relocatable`'s method transforms an address of the original memory into an address of the relocated memory. +Because the relocated memory is one-dimensional and not divided into segments, the memory addresses are not of type `Relocatable` but +`uint`. + +```go +func (r *Relocatable) RelocateAddress(relocationTable *[]uint) uint { + return (*relocationTable)[r.SegmentIndex] + r.Offset +} +``` + +##### RelocateValue + +This `MaybeRelocatable`'s method transforms a value of the original memory into a value of the relocated memory. + +- If the value is a `Felt`, the method doesn't transform it and returns the value as is. +- If the value is a `Relocatable`, the method transforms it to an address of the relocated memory. +- If the value is any other type, the method returns an error. + +```go +func (m *MaybeRelocatable) RelocateValue(relocationTable *[]uint) (lambdaworks.Felt, error) { + inner_felt, ok := m.GetFelt() + if ok { + return inner_felt, nil + } + + inner_relocatable, ok := m.GetRelocatable() + if ok { + return lambdaworks.FeltFromUint64(uint64(inner_relocatable.RelocateAddress(relocationTable))), nil + } + + return lambdaworks.FeltZero(), errors.New(fmt.Sprintf("Unexpected type %T", m.inner)) +} +``` + +#### RelocateTrace + +After running `RelocateMemory` and returning the relocated memory without errors, the `Relocate` function calls `RelocateTrace`. +This method transforms the fields of each trace entry from `Relocatable`s to `Felt`s. +The fields of each entry are pc, ap and fp. +Because those fields are address, the trace relocation process involves relocating addresses. +That's why, this method also calls `RelocateAddress`: + +```go +func (v *VirtualMachine) RelocateTrace(relocationTable *[]uint) error { + if len(*relocationTable) < 2 { + return errors.New("no relocation found for execution segment") + } + + for _, entry := range v.Trace { + v.RelocatedTrace = append(v.RelocatedTrace, RelocatedTraceEntry{ + Pc: lambdaworks.FeltFromUint64(uint64(entry.Pc.RelocateAddress(relocationTable))), + Ap: lambdaworks.FeltFromUint64(uint64(entry.Ap.RelocateAddress(relocationTable))), + Fp: lambdaworks.FeltFromUint64(uint64(entry.Fp.RelocateAddress(relocationTable))), + }) + } + + return nil +} +``` + +Like in `RelocateMemory`, we need to check that the `RelocateSegments` function was called before. + +Once the program was executed, the `VirtualMachine` will have generated a relocated trace and a relocated memory for that execution. +Both trace and memory should be outputs of the execution, so we will write two functions that respectively write the trace and memory +into a file. +We will create a package called `cairo_run` and create those functions there. + +#### Writing the trace into a file + +First, we will add a function in our new `cairo_run` package to write the relocated trace into a buffer. +This method converts the pc, fp and ap fields of each trace entry into a `uint64` and writes each integer into the buffer in little endian: + +```go +func WriteEncodedTrace(relocatedTrace []vm.RelocatedTraceEntry, dest io.Writer) error { + for i, entry := range relocatedTrace { + ap_buffer := make([]byte, 8) + ap, err := entry.Ap.ToU64() + if err != nil { + return err + } + binary.LittleEndian.PutUint64(ap_buffer, ap) + _, err = dest.Write(ap_buffer) + if err != nil { + return encodeTraceError(i, err) + } + + fp_buffer := make([]byte, 8) + fp, err := entry.Fp.ToU64() + if err != nil { + return err + } + binary.LittleEndian.PutUint64(fp_buffer, fp) + _, err = dest.Write(fp_buffer) + if err != nil { + return encodeTraceError(i, err) + } + + pc_buffer := make([]byte, 8) + pc, err := entry.Pc.ToU64() + if err != nil { + return err + } + binary.LittleEndian.PutUint64(pc_buffer, pc) + _, err = dest.Write(pc_buffer) + if err != nil { + return encodeTraceError(i, err) + } + } + + return nil +} +``` + +#### Writing the memory into a file + +Second, we will add a function in the `cairo_run` package to write the relocated memory into another buffer. +We want to write into the buffer the pairs of addresses and values by following a specific order. +In this case, we want those pairs to be incrementally ordered by address. +Because we implemented the relocated memory as a map and we store the addresses as keys, if we iterate over the relocated memory using +`range`, we will end up storing the pairs of addresses and values without following any specific order. +So, first we sort the relocated memory addresses and then we iterate those addresses to write each pair into the buffer. +Before writing the relocated memory values into the buffer, we have to convert them to bytes in little endian by using the lambdaworks +method `ToLeBytes`. +That method returns an array and `io.Writer.Write` expects a slice, so we convert the array that `ToLeBytes` returns to a slice. + +```go +func WriteEncodedMemory(relocatedMemory map[uint]lambdaworks.Felt, dest io.Writer) error { + // create a slice to store keys of the relocatedMemory map + keysMap := make([]uint, 0, len(relocatedMemory)) + for k := range relocatedMemory { + keysMap = append(keysMap, k) + } + + // sort the keys + sort.Slice(keysMap, func(i, j int) bool { return keysMap[i] < keysMap[j] }) + + // iterate over the `relocatedMemory` map in sorted key order + for _, k := range keysMap { + // write the key + keyArray := make([]byte, 8) + binary.LittleEndian.PutUint64(keyArray, uint64(k)) + _, err := dest.Write(keyArray) + if err != nil { + return encodeMemoryError(k, err) + } + + // write the value + valueArray := relocatedMemory[k].ToLeBytes() + + _, err = dest.Write(valueArray[:]) + if err != nil { + return encodeMemoryError(k, err) + } + } + + return nil +} +``` + +#### Putting it all together + +Now it's time to finally add our `main` function to the project! +The CLI will receive one argument: the program path. +This path will be passed as argument to a new function called `CairoRun`. +This new function will return the cairo runner and an error, if it exists one during the program execution. +If the program execution ends successfully, we write the relocated trace and memory in files respectively and print that the execution was ended with no errors. +If the program execution ends with an error, we don't write any file and print the corresponding error. + +```go +func main() { + if len(os.Args) < 2 { + fmt.Println("Wrong argument count: Use go run cmd/cli/main.go COMPILED_JSON") + return + } + cli_args := os.Args[1:] + programPath := cli_args[0] + cairoRunner, err := cairo_run.CairoRun(programPath) + if err != nil { + fmt.Printf("Failed with error: %s", err) + return + } + traceFilePath := strings.Replace(programPath, ".json", ".go.trace", 1) + traceFile, err := os.OpenFile(traceFilePath, os.O_RDWR|os.O_CREATE, 0644) + defer traceFile.Close() + + memoryFilePath := strings.Replace(programPath, ".json", ".go.memory", 1) + memoryFile, err := os.OpenFile(memoryFilePath, os.O_RDWR|os.O_CREATE, 0644) + defer memoryFile.Close() + + cairo_run.WriteEncodedTrace(cairoRunner.Vm.RelocatedTrace, traceFile) + cairo_run.WriteEncodedMemory(cairoRunner.Vm.RelocatedMemory, memoryFile) + + println("Done!") +} +``` + +To make our `main` function work, we still need to add another function to the `cairo_run` package. +The `CairoRun` function will be responsible of integrating the initialization, execution and relocation. + +```go +func CairoRun(programPath string) (*runners.CairoRunner, error) { + compiledProgram := parser.Parse(programPath) + programJson := vm.DeserializeProgramJson(compiledProgram) + + cairoRunner, err := runners.NewCairoRunner(programJson) + if err != nil { + return nil, err + } + end, err := cairoRunner.Initialize() + if err != nil { + return nil, err + } + err = cairoRunner.RunUntilPC(end) + if err != nil { + return nil, err + } + err = cairoRunner.Vm.Relocate() + return cairoRunner, err +} +``` + +TODO: add parsing and deserializing #### Builtins @@ -1709,6 +2082,9 @@ type VirtualMachine struct { RunContext RunContext currentStep uint Segments memory.MemorySegmentManager + Trace []TraceEntry + RelocatedTrace []RelocatedTraceEntry + RelocatedMemory map[uint]lambdaworks.Felt BuiltinRunners []builtins.BuiltinRunner } ``` diff --git a/cairo_programs/cairo_keccak.cairo b/cairo_programs/cairo_keccak.cairo new file mode 100644 index 00000000..8adcd515 --- /dev/null +++ b/cairo_programs/cairo_keccak.cairo @@ -0,0 +1,29 @@ +%builtins range_check bitwise + +from starkware.cairo.common.cairo_keccak.keccak import cairo_keccak, finalize_keccak +from starkware.cairo.common.uint256 import Uint256 +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin +from starkware.cairo.common.alloc import alloc + +func main{range_check_ptr: felt, bitwise_ptr: BitwiseBuiltin*}() { + alloc_locals; + + let (keccak_ptr: felt*) = alloc(); + let keccak_ptr_start = keccak_ptr; + + let (inputs: felt*) = alloc(); + + assert inputs[0] = 8031924123371070792; + assert inputs[1] = 560229490; + + let n_bytes = 16; + + let (res: Uint256) = cairo_keccak{keccak_ptr=keccak_ptr}(inputs=inputs, n_bytes=n_bytes); + + assert res.low = 293431514620200399776069983710520819074; + assert res.high = 317109767021952548743448767588473366791; + + finalize_keccak(keccak_ptr_start=keccak_ptr_start, keccak_ptr_end=keccak_ptr); + + return (); +} diff --git a/cairo_programs/ec_double_slope.cairo b/cairo_programs/ec_double_slope.cairo new file mode 100644 index 00000000..10177482 --- /dev/null +++ b/cairo_programs/ec_double_slope.cairo @@ -0,0 +1,212 @@ +%builtins range_check + +// Source: https://github.com/rdubois-crypto/efficient-secp256r1/blob/4b74807c5e91f1ed4cb00a1c973be05c63986e61/src/secp256r1/ec.cairo +from starkware.cairo.common.cairo_secp.bigint import BigInt3, UnreducedBigInt3, nondet_bigint3 +from starkware.cairo.common.cairo_secp.ec import EcPoint + +// src.secp256r1.constants +// SECP_REM is defined by the equation: +// secp256r1_prime = 2 ** 256 - SECP_REM. +const SECP_REM = 2 ** 224 - 2 ** 192 - 2 ** 96 + 1; + +const BASE = 2 ** 86; + +// A = 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc +const A0 = 0x3ffffffffffffffffffffc; +const A1 = 0x3ff; +const A2 = 0xffffffff0000000100000; + +// Constants for unreduced_mul/sqr +const s2 = (-(2 ** 76)) - 2 ** 12; +const s1 = (-(2 ** 66)) + 4; +const s0 = 2 ** 56; + +const r2 = 2 ** 54 - 2 ** 22; +const r1 = -(2 ** 12); +const r0 = 4; + +// src.secp256r1.field +// Adapt from starkware.cairo.common.math's assert_250_bit +func assert_165_bit{range_check_ptr}(value) { + const UPPER_BOUND = 2 ** 165; + const SHIFT = 2 ** 128; + const HIGH_BOUND = UPPER_BOUND / SHIFT; + + let low = [range_check_ptr]; + let high = [range_check_ptr + 1]; + + %{ + from starkware.cairo.common.math_utils import as_int + + # Correctness check. + value = as_int(ids.value, PRIME) % PRIME + assert value < ids.UPPER_BOUND, f'{value} is outside of the range [0, 2**250).' + + # Calculation for the assertion. + ids.high, ids.low = divmod(ids.value, ids.SHIFT) + %} + + assert [range_check_ptr + 2] = HIGH_BOUND - 1 - high; + + assert value = high * SHIFT + low; + + let range_check_ptr = range_check_ptr + 3; + return (); +} + +// src.secp256r1.field +// Computes the multiplication of two big integers, given in BigInt3 representation, modulo the +// secp256r1 prime. +// +// Arguments: +// x, y - the two BigInt3 to operate on. +// +// Returns: +// x * y in an UnreducedBigInt3 representation (the returned limbs may be above 3 * BASE). +// +// This means that if unreduced_mul is called on the result of nondet_bigint3, or the difference +// between two such results, we have: +// Soundness guarantee: the limbs are in the range (). +// Completeness guarantee: the limbs are in the range (). +func unreduced_mul(a: BigInt3, b: BigInt3) -> (res_low: UnreducedBigInt3) { + tempvar twice_d2 = a.d2 * b.d2; + tempvar d1d2 = a.d2 * b.d1 + a.d1 * b.d2; + return ( + UnreducedBigInt3( + d0=a.d0 * b.d0 + s0 * twice_d2 + r0 * d1d2, + d1=a.d1 * b.d0 + a.d0 * b.d1 + s1 * twice_d2 + r1 * d1d2, + d2=a.d2 * b.d0 + a.d1 * b.d1 + a.d0 * b.d2 + s2 * twice_d2 + r2 * d1d2, + ), + ); +} + +// src.secp256r1.field +// Computes the square of a big integer, given in BigInt3 representation, modulo the +// secp256r1 prime. +// +// Has the same guarantees as in unreduced_mul(a, a). +func unreduced_sqr(a: BigInt3) -> (res_low: UnreducedBigInt3) { + tempvar twice_d2 = a.d2 * a.d2; + tempvar twice_d1d2 = a.d2 * a.d1 + a.d1 * a.d2; + tempvar d1d0 = a.d1 * a.d0; + return ( + UnreducedBigInt3( + d0=a.d0 * a.d0 + s0 * twice_d2 + r0 * twice_d1d2, + d1=d1d0 + d1d0 + s1 * twice_d2 + r1 * twice_d1d2, + d2=a.d2 * a.d0 + a.d1 * a.d1 + a.d0 * a.d2 + s2 * twice_d2 + r2 * twice_d1d2, + ), + ); +} + +// src.secp256r1.field +// Verifies that the given unreduced value is equal to zero modulo the secp256r1 prime. +// +// Completeness assumption: val's limbs are in the range (-2**210.99, 2**210.99). +// Soundness assumption: val's limbs are in the range (-2**250, 2**250). +func verify_zero{range_check_ptr}(val: UnreducedBigInt3) { + alloc_locals; + local q; + // local q_sign; + let q_sign = 1; + // original: + // %{ from starkware.cairo.common.cairo_secp.secp_utils import SECP256R1_P as SECP_P %} + // %{ + // from starkware.cairo.common.cairo_secp.secp_utils import pack + + // q, r = divmod(pack(ids.val, PRIME), SECP_P) + // assert r == 0, f"verify_zero: Invalid input {ids.val.d0, ids.val.d1, ids.val.d2}." + // if q >= 0: + // ids.q = q % PRIME + // ids.q_sign = 1 + // else: + // ids.q = (0-q) % PRIME + // ids.q_sign = -1 % PRIME + // %} + %{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P %} + %{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + + q, r = divmod(pack(ids.val, PRIME), SECP_P) + assert r == 0, f"verify_zero: Invalid input {ids.val.d0, ids.val.d1, ids.val.d2}." + ids.q = q % PRIME + %} + // assert_250_bit(q); // 256K steps + // assert_le_felt(q, 2**165); // 275K steps + assert_165_bit(q); + assert q_sign * (val.d2 + val.d1 / BASE + val.d0 / BASE ** 2) = q * ( + (BASE / 4) - SECP_REM / BASE ** 2 + ); + // Multiply by BASE**2 both sides: + // (q_sign) * val = q * (BASE**3 / 4 - SECP_REM) + // = q * (2**256 - SECP_REM) = q * secp256r1_prime = 0 mod secp256r1_prime + return (); +} + +// Computes the slope of the elliptic curve at a given point. +// The slope is used to compute point + point. +// +// Arguments: +// point - the point to operate on. +// +// Returns: +// slope - the slope of the curve at point, in BigInt3 representation. +// +// Assumption: point != 0. +func compute_doubling_slope{range_check_ptr}(point: EcPoint) -> (slope: BigInt3) { + // Note that y cannot be zero: assume that it is, then point = -point, so 2 * point = 0, which + // contradicts the fact that the size of the curve is odd. + // originals: + // %{ from starkware.cairo.common.cairo_secp.secp_utils import SECP256R1_P as SECP_P %} + // %{ from starkware.cairo.common.cairo_secp.secp_utils import SECP256R1_ALPHA as ALPHA %} + %{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P %} + %{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_ALPHA as ALPHA %} + %{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + from starkware.python.math_utils import ec_double_slope + + # Compute the slope. + x = pack(ids.point.x, PRIME) + y = pack(ids.point.y, PRIME) + value = slope = ec_double_slope(point=(x, y), alpha=ALPHA, p=SECP_P) + %} + let (slope: BigInt3) = nondet_bigint3(); + + let (x_sqr: UnreducedBigInt3) = unreduced_sqr(point.x); + let (slope_y: UnreducedBigInt3) = unreduced_mul(slope, point.y); + verify_zero( + UnreducedBigInt3( + d0=3 * x_sqr.d0 + A0 - 2 * slope_y.d0, + d1=3 * x_sqr.d1 + A1 - 2 * slope_y.d1, + d2=3 * x_sqr.d2 + A2 - 2 * slope_y.d2, + ), + ); + + return (slope=slope); +} + +func test_doubling_slope{range_check_ptr}() { + let point = EcPoint(BigInt3(614323, 5456867, 101208), BigInt3(773712524, 77371252, 5298795)); + + let (slope) = compute_doubling_slope(point); + + assert slope = BigInt3( + 64081873649130491683833713, 34843994309543177837008178, 16548672716077616016846383 + ); + + let point = EcPoint( + BigInt3(51215, 36848548548458, 634734734), BigInt3(26362, 263724839599, 901297012) + ); + + let (slope) = compute_doubling_slope(point); + + assert slope = BigInt3( + 71848883893335852660776740, 75644451964360469099209675, 547087410329256463669633 + ); + + return (); +} + +func main{range_check_ptr}() { + test_doubling_slope(); + return (); +} diff --git a/cairo_programs/find_element.cairo b/cairo_programs/find_element.cairo new file mode 100644 index 00000000..f609f32a --- /dev/null +++ b/cairo_programs/find_element.cairo @@ -0,0 +1,27 @@ +%builtins range_check +from starkware.cairo.common.find_element import find_element +from starkware.cairo.common.alloc import alloc + +struct MyStruct { + a: felt, + b: felt, +} + +func main{range_check_ptr}() -> () { + // Create an array with MyStruct elements (1,2), (3,4), (5,6). + alloc_locals; + let (local array_ptr: MyStruct*) = alloc(); + assert array_ptr[0] = MyStruct(a=1, b=2); + assert array_ptr[1] = MyStruct(a=3, b=4); + assert array_ptr[2] = MyStruct(a=5, b=6); + + // Find any element with key '5'. + let (element_ptr: MyStruct*) = find_element( + array_ptr=array_ptr, elm_size=MyStruct.SIZE, n_elms=3, key=5 + ); + // A pointer to the element with index 2 is returned. + assert element_ptr.a = 5; + assert element_ptr.b = 6; + + return (); +} diff --git a/cairo_programs/keccak_add_uint256.cairo b/cairo_programs/keccak_add_uint256.cairo new file mode 100644 index 00000000..34ef99a9 --- /dev/null +++ b/cairo_programs/keccak_add_uint256.cairo @@ -0,0 +1,31 @@ +%builtins output range_check bitwise + +from starkware.cairo.common.keccak_utils.keccak_utils import keccak_add_uint256 +from starkware.cairo.common.uint256 import Uint256 +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.serialize import serialize_word + +func main{output_ptr: felt*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*}() { + alloc_locals; + + let (inputs) = alloc(); + let inputs_start = inputs; + + let num = Uint256(34623634663146736, 598249824422424658356); + + keccak_add_uint256{inputs=inputs_start}(num=num, bigend=0); + + assert inputs[0] = 34623634663146736; + assert inputs[1] = 0; + assert inputs[2] = 7954014063719006644; + assert inputs[3] = 32; + + serialize_word(inputs[0]); + serialize_word(inputs[1]); + serialize_word(inputs[2]); + serialize_word(inputs[3]); + + return (); +} + diff --git a/cairo_programs/keccak_integration_tests.cairo b/cairo_programs/keccak_integration_tests.cairo new file mode 100644 index 00000000..7f476245 --- /dev/null +++ b/cairo_programs/keccak_integration_tests.cairo @@ -0,0 +1,107 @@ +%builtins range_check bitwise + +from starkware.cairo.common.keccak import unsafe_keccak, unsafe_keccak_finalize, KeccakState +from starkware.cairo.common.cairo_keccak.keccak import cairo_keccak, finalize_keccak +from starkware.cairo.common.keccak_utils.keccak_utils import keccak_add_uint256 +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.uint256 import Uint256 +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin +from starkware.cairo.common.math import unsigned_div_rem + +func fill_array(array: felt*, base: felt, step: felt, array_length: felt, iter: felt) { + if (iter == array_length) { + return (); + } + assert array[iter] = base + step * iter; + return fill_array(array, base, step, array_length, iter + 1); +} + +func test_integration{range_check_ptr: felt, bitwise_ptr: BitwiseBuiltin*}(iter: felt, last: felt) { + alloc_locals; + if (iter == last) { + return (); + } + + let (data_1: felt*) = alloc(); + let data_len: felt = 15; + let chunk_len: felt = 5; + + fill_array(data_1, iter, iter + 1, data_len, 0); + + let (low_1: felt, high_1: felt) = unsafe_keccak(data_1, chunk_len); + let (low_2: felt, high_2: felt) = unsafe_keccak(data_1 + chunk_len, chunk_len); + let (low_3: felt, high_3: felt) = unsafe_keccak(data_1 + 2 * chunk_len, chunk_len); + + // With the results of unsafe_keccak, create an array to pass to unsafe_keccak_finalize + // through a KeccakState + let (data_2: felt*) = alloc(); + assert data_2[0] = low_1; + assert data_2[1] = high_1; + assert data_2[2] = low_2; + assert data_2[3] = high_2; + assert data_2[4] = low_3; + assert data_2[5] = high_3; + + let keccak_state: KeccakState = KeccakState(start_ptr=data_2, end_ptr=data_2 + 6); + let res_1: Uint256 = unsafe_keccak_finalize(keccak_state); + + let (data_3: felt*) = alloc(); + + // This is done to make sure that the numbers inserted in data_3 + // fit in a u64 + let (q, r) = unsigned_div_rem(res_1.low, 18446744073709551615); + assert data_3[0] = q; + let (q, r) = unsigned_div_rem(res_1.high, 18446744073709551615); + assert data_3[1] = q; + + let (keccak_ptr: felt*) = alloc(); + let keccak_ptr_start = keccak_ptr; + + let res_2: Uint256 = cairo_keccak{keccak_ptr=keccak_ptr}(data_3, 16); + + finalize_keccak(keccak_ptr_start=keccak_ptr_start, keccak_ptr_end=keccak_ptr); + + let (inputs) = alloc(); + let inputs_start = inputs; + keccak_add_uint256{inputs=inputs_start}(num=res_2, bigend=0); + + // These values are hardcoded for last = 10 + // Since we are dealing with hash functions and using the output of one of them + // as the input of the other, asserting only the last results of the iteration + // should be enough + if (iter == last - 1 and last == 10) { + assert res_2.low = 3896836249413878817054429671793519200; + assert res_2.high = 253424239110447628170109510737834198489; + + assert inputs[0] = 16681956707691293280; + assert inputs[1] = 211247916371739620; + assert inputs[2] = 6796127878994642393; + assert inputs[3] = 13738155530201662906; + } + + // These values are hardcoded for last = 100 + // This should be used for benchmarking. + if (iter == last - 1 and last == 100) { + assert res_2.low = 52798800345724801884797411011515944813; + assert res_2.high = 159010026777930121161844734347918361509; + + assert inputs[0] = 14656556134934286189; + assert inputs[1] = 2862228701973161639; + assert inputs[2] = 206697371206337445; + assert inputs[3] = 8619950823980503604; + } + + return test_integration{range_check_ptr=range_check_ptr, bitwise_ptr=bitwise_ptr}( + iter + 1, last + ); +} + +func run_test{range_check_ptr: felt, bitwise_ptr: BitwiseBuiltin*}(last: felt) { + test_integration(0, last); + return (); +} + +func main{range_check_ptr: felt, bitwise_ptr: BitwiseBuiltin*}() { + run_test(10); + return (); +} diff --git a/cairo_programs/proof_programs/assert_250_bit_element_array.cairo b/cairo_programs/proof_programs/assert_250_bit_element_array.cairo new file mode 120000 index 00000000..1b3b523a --- /dev/null +++ b/cairo_programs/proof_programs/assert_250_bit_element_array.cairo @@ -0,0 +1 @@ +../assert_250_bit_element_array.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/assert_le_felt.cairo b/cairo_programs/proof_programs/assert_le_felt.cairo new file mode 120000 index 00000000..ddeda7ca --- /dev/null +++ b/cairo_programs/proof_programs/assert_le_felt.cairo @@ -0,0 +1 @@ +../assert_le_felt.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/assert_lt_felt.cairo b/cairo_programs/proof_programs/assert_lt_felt.cairo new file mode 120000 index 00000000..c9049280 --- /dev/null +++ b/cairo_programs/proof_programs/assert_lt_felt.cairo @@ -0,0 +1 @@ +../assert_lt_felt.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/common_signature.cairo b/cairo_programs/proof_programs/common_signature.cairo new file mode 120000 index 00000000..9a92f8aa --- /dev/null +++ b/cairo_programs/proof_programs/common_signature.cairo @@ -0,0 +1 @@ +../common_signature.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/dict_squash.cairo b/cairo_programs/proof_programs/dict_squash.cairo new file mode 120000 index 00000000..2191191a --- /dev/null +++ b/cairo_programs/proof_programs/dict_squash.cairo @@ -0,0 +1 @@ +../dict_squash.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/dict_update.cairo b/cairo_programs/proof_programs/dict_update.cairo new file mode 120000 index 00000000..268649e2 --- /dev/null +++ b/cairo_programs/proof_programs/dict_update.cairo @@ -0,0 +1 @@ +../dict_update.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/is_quad_residue.cairo b/cairo_programs/proof_programs/is_quad_residue.cairo deleted file mode 100644 index 7b78ec6e..00000000 --- a/cairo_programs/proof_programs/is_quad_residue.cairo +++ /dev/null @@ -1,43 +0,0 @@ -%builtins output -from starkware.cairo.common.serialize import serialize_word -from starkware.cairo.common.math import is_quad_residue -from starkware.cairo.common.alloc import alloc - -func fill_array(array_start: felt*, iter: felt) -> () { - if (iter == 8) { - return (); - } - assert array_start[iter] = iter; - return fill_array(array_start, iter + 1); -} - -func check_quad_res{output_ptr: felt*}(inputs: felt*, expected: felt*, iter: felt) { - if (iter == 8) { - return (); - } - serialize_word(inputs[iter]); - serialize_word(expected[iter]); - - assert is_quad_residue(inputs[iter]) = expected[iter]; - return check_quad_res(inputs, expected, iter + 1); -} - -func main{output_ptr: felt*}() { - alloc_locals; - let (inputs: felt*) = alloc(); - fill_array(inputs, 0); - - let (expected: felt*) = alloc(); - assert expected[0] = 1; - assert expected[1] = 1; - assert expected[2] = 1; - assert expected[3] = 0; - assert expected[4] = 1; - assert expected[5] = 1; - assert expected[6] = 0; - assert expected[7] = 1; - - check_quad_res(inputs, expected, 0); - - return (); -} diff --git a/cairo_programs/proof_programs/is_quad_residue.cairo b/cairo_programs/proof_programs/is_quad_residue.cairo new file mode 120000 index 00000000..18019aa0 --- /dev/null +++ b/cairo_programs/proof_programs/is_quad_residue.cairo @@ -0,0 +1 @@ +../is_quad_residue.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/math_cmp.cairo b/cairo_programs/proof_programs/math_cmp.cairo new file mode 120000 index 00000000..431fdecc --- /dev/null +++ b/cairo_programs/proof_programs/math_cmp.cairo @@ -0,0 +1 @@ +../math_cmp.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/memcpy_test.cairo b/cairo_programs/proof_programs/memcpy_test.cairo new file mode 120000 index 00000000..8fe365be --- /dev/null +++ b/cairo_programs/proof_programs/memcpy_test.cairo @@ -0,0 +1 @@ +../memcpy_test.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/memset.cairo b/cairo_programs/proof_programs/memset.cairo new file mode 120000 index 00000000..72599c1e --- /dev/null +++ b/cairo_programs/proof_programs/memset.cairo @@ -0,0 +1 @@ +../memset.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/pow.cairo b/cairo_programs/proof_programs/pow.cairo new file mode 120000 index 00000000..501ee614 --- /dev/null +++ b/cairo_programs/proof_programs/pow.cairo @@ -0,0 +1 @@ +../pow.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/signed_div_rem.cairo b/cairo_programs/proof_programs/signed_div_rem.cairo new file mode 120000 index 00000000..2013d643 --- /dev/null +++ b/cairo_programs/proof_programs/signed_div_rem.cairo @@ -0,0 +1 @@ +../signed_div_rem.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/split_felt.cairo b/cairo_programs/proof_programs/split_felt.cairo new file mode 120000 index 00000000..ba534b7c --- /dev/null +++ b/cairo_programs/proof_programs/split_felt.cairo @@ -0,0 +1 @@ +../split_felt.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/split_int.cairo b/cairo_programs/proof_programs/split_int.cairo new file mode 120000 index 00000000..98218d4d --- /dev/null +++ b/cairo_programs/proof_programs/split_int.cairo @@ -0,0 +1 @@ +../split_int.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/sqrt.cairo b/cairo_programs/proof_programs/sqrt.cairo new file mode 120000 index 00000000..5c171547 --- /dev/null +++ b/cairo_programs/proof_programs/sqrt.cairo @@ -0,0 +1 @@ +../sqrt.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/squash_dict.cairo b/cairo_programs/proof_programs/squash_dict.cairo new file mode 120000 index 00000000..1c224560 --- /dev/null +++ b/cairo_programs/proof_programs/squash_dict.cairo @@ -0,0 +1 @@ +../squash_dict.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/unsafe_keccak.cairo b/cairo_programs/proof_programs/unsafe_keccak.cairo new file mode 120000 index 00000000..847d284f --- /dev/null +++ b/cairo_programs/proof_programs/unsafe_keccak.cairo @@ -0,0 +1 @@ +../unsafe_keccak.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/unsafe_keccak_finalize.cairo b/cairo_programs/proof_programs/unsafe_keccak_finalize.cairo new file mode 120000 index 00000000..892b2bb4 --- /dev/null +++ b/cairo_programs/proof_programs/unsafe_keccak_finalize.cairo @@ -0,0 +1 @@ +../unsafe_keccak_finalize.cairo \ No newline at end of file diff --git a/cairo_programs/proof_programs/unsigned_div_rem.cairo b/cairo_programs/proof_programs/unsigned_div_rem.cairo new file mode 120000 index 00000000..d814de6b --- /dev/null +++ b/cairo_programs/proof_programs/unsigned_div_rem.cairo @@ -0,0 +1 @@ +../unsigned_div_rem.cairo \ No newline at end of file diff --git a/cairo_programs/search_sorted_lower.cairo b/cairo_programs/search_sorted_lower.cairo new file mode 100644 index 00000000..0ef26bef --- /dev/null +++ b/cairo_programs/search_sorted_lower.cairo @@ -0,0 +1,23 @@ +%builtins range_check +from starkware.cairo.common.find_element import search_sorted_lower +from starkware.cairo.common.alloc import alloc + +struct MyStruct { + a: felt, + b: felt, +} + +func main{range_check_ptr}() -> () { + // Create an array with MyStruct elements (1,2), (3,4), (5,6). + alloc_locals; + let (local array_ptr: MyStruct*) = alloc(); + assert array_ptr[0] = MyStruct(a=1, b=2); + assert array_ptr[1] = MyStruct(a=3, b=4); + assert array_ptr[2] = MyStruct(a=5, b=6); + let (smallest_ptr: MyStruct*) = search_sorted_lower( + array_ptr=array_ptr, elm_size=2, n_elms=3, key=2 + ); + assert smallest_ptr.a = 3; + assert smallest_ptr.b = 4; + return (); +} diff --git a/cairo_programs/set_add.cairo b/cairo_programs/set_add.cairo new file mode 100644 index 00000000..4113901d --- /dev/null +++ b/cairo_programs/set_add.cairo @@ -0,0 +1,30 @@ +%builtins range_check + +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.set import set_add + +struct MyStruct { + a: felt, + b: felt, +} + +func main{range_check_ptr}() { + alloc_locals; + + // An array containing two structs. + let (local my_list: MyStruct*) = alloc(); + assert my_list[0] = MyStruct(a=1, b=3); + assert my_list[1] = MyStruct(a=5, b=7); + + // Suppose that we want to add the element + // MyStruct(a=2, b=3) to my_list, but only if it is not already + // present (for the purpose of the example the contents of the + // array are known, but this doesn't have to be the case) + let list_end: felt* = &my_list[2]; + let (new_elm: MyStruct*) = alloc(); + assert new_elm[0] = MyStruct(a=2, b=3); + + set_add{set_end_ptr=list_end}(set_ptr=my_list, elm_size=MyStruct.SIZE, elm_ptr=new_elm); + assert my_list[2] = MyStruct(a=2, b=3); + return (); +} diff --git a/pkg/builtins/keccak.go b/pkg/builtins/keccak.go index 46b53809..a5f9638e 100644 --- a/pkg/builtins/keccak.go +++ b/pkg/builtins/keccak.go @@ -101,7 +101,7 @@ func (k *KeccakBuiltinRunner) DeduceMemoryCell(address Relocatable, mem *Memory) for i := 0; i < 25; i++ { output_message_u64[i] = binary.LittleEndian.Uint64(output_message_bytes[8*i : 8*i+8]) } - keccakF1600(&output_message_u64) + KeccakF1600(&output_message_u64) // Convert back to bytes output_message := make([]byte, 0, 200) @@ -152,7 +152,7 @@ var rc = [24]uint64{ // keccakF1600 applies the Keccak permutation to a 1600b-wide // state represented as a slice of 25 uint64s. -func keccakF1600(a *[25]uint64) { +func KeccakF1600(a *[25]uint64) { // Implementation translated from Keccak-inplace.c // in the keccak reference code. var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64 diff --git a/pkg/hints/bigint_hint.go b/pkg/hints/bigint_hint.go new file mode 100644 index 00000000..a13fc098 --- /dev/null +++ b/pkg/hints/bigint_hint.go @@ -0,0 +1,56 @@ +package hints + +import ( + "errors" + "math/big" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +/* +Implements hint: +%{ + from starkware.cairo.common.cairo_secp.secp_utils import split + + segments.write_arg(ids.res.address_, split(value)) +%} +*/ + +func NondetBigInt3(virtual_machine VirtualMachine, execScopes ExecutionScopes, idsData IdsManager) error { + resRelloc, err := idsData.GetAddr("res", &virtual_machine) + if err != nil { + return err + } + + valueUncast, err := execScopes.Get("value") + if err != nil { + return err + } + value, ok := valueUncast.(big.Int) + if !ok { + return errors.New("Could not cast value into big int") + } + + bigint3Split, err := Bigint3Split(value) + if err != nil { + return err + } + + arg := make([]memory.MaybeRelocatable, 0) + + for i := 0; i < 3; i++ { + m := memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromBigInt(&bigint3Split[i])) + arg = append(arg, *m) + } + + _, loadErr := virtual_machine.Segments.LoadData(resRelloc, &arg) + if loadErr != nil { + return loadErr + } + + return nil +} diff --git a/pkg/hints/bigint_hint_test.go b/pkg/hints/bigint_hint_test.go new file mode 100644 index 00000000..27bc55b4 --- /dev/null +++ b/pkg/hints/bigint_hint_test.go @@ -0,0 +1,75 @@ +package hints_test + +import ( + "math/big" + "testing" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints" + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_codes" + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestNonDetBigInt3Ok(t *testing.T) { + vm := NewVirtualMachine() + + vm.Segments.AddSegment() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Pc = NewRelocatable(0, 0) + vm.RunContext.Ap = NewRelocatable(1, 6) + vm.RunContext.Fp = NewRelocatable(1, 6) + + value, _ := new(big.Int).SetString("7737125245533626718119526477371252455336267181195264773712524553362", 10) + execScopes := NewExecutionScopes() + + execScopes.AssignOrUpdateVariable("value", *value) + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "res": {nil}, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: NONDET_BIGINT3_V1, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("Non Det Big Int 3 hint test failed with error: %s", err) + } else { + valueInStruct0, err := idsManager.GetStructFieldFelt("res", 0, vm) + expected0 := lambdaworks.FeltFromDecString("773712524553362") + if err != nil { + t.Errorf("error fetching from ids manager : %s", err) + } + if valueInStruct0 != expected0 { + t.Errorf(" Incorrect field value %s, expected %s", valueInStruct0.ToBigInt().Text(10), expected0.ToBigInt().Text(10)) + } + + valueInStruct1, err := idsManager.GetStructFieldFelt("res", 1, vm) + expected1 := lambdaworks.FeltFromDecString("57408430697461422066401280") + if err != nil { + t.Errorf("error fetching from ids manager : %s", err) + } + if valueInStruct1 != expected1 { + t.Errorf(" Incorrect field value %s, expected %s", valueInStruct1.ToBigInt().Text(10), expected1.ToBigInt().Text(10)) + } + + valueInStruct2, err := idsManager.GetStructFieldFelt("res", 2, vm) + expected2 := lambdaworks.FeltFromDecString("1292469707114105") + if err != nil { + t.Errorf("error fetching from ids manager : %s", err) + } + if valueInStruct2 != expected2 { + t.Errorf(" Incorrect field value %s, expected %s", valueInStruct2.ToBigInt().Text(10), expected2.ToBigInt().Text(10)) + } + } +} diff --git a/pkg/hints/ec_hint.go b/pkg/hints/ec_hint.go index e7c15154..6abb144f 100644 --- a/pkg/hints/ec_hint.go +++ b/pkg/hints/ec_hint.go @@ -4,58 +4,34 @@ import ( "errors" "math/big" + "github.com/lambdaclass/cairo-vm.go/pkg/builtins" "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + . "github.com/lambdaclass/cairo-vm.go/pkg/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/types" "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" ) -type BigInt3 struct { - Limbs []lambdaworks.Felt -} - type EcPoint struct { X BigInt3 Y BigInt3 } -func (val *BigInt3) Pack86() big.Int { - sum := big.NewInt(0) - for i := 0; i < 3; i++ { - felt := val.Limbs[i] - signed := felt.ToSigned() - shifed := new(big.Int).Lsh(signed, uint(i*86)) - sum.Add(sum, shifed) - } - return *sum -} - -func BigInt3FromBaseAddr(addr memory.Relocatable, virtual_machine vm.VirtualMachine) (BigInt3, error) { - limbs := make([]lambdaworks.Felt, 0) - for i := 0; i < 3; i++ { - felt, err := virtual_machine.Segments.Memory.GetFelt(addr.AddUint(uint(i))) - if err == nil { - limbs = append(limbs, felt) - } else { - return BigInt3{}, errors.New("Identifier has no member") - } - } - return BigInt3{Limbs: limbs}, nil -} - -func BigInt3FromVarName(name string, virtual_machine vm.VirtualMachine, ids_data hint_utils.IdsManager) (EcPoint, error) { - point_addr, err := ids_data.GetAddr(name, &virtual_machine) +func EcPointFromVarName(name string, vm *VirtualMachine, idsData IdsManager) (EcPoint, error) { + pointAddr, err := idsData.GetAddr(name, vm) if err != nil { return EcPoint{}, err } - x, err := BigInt3FromBaseAddr(point_addr, virtual_machine) + x, err := BigInt3FromBaseAddr(pointAddr, name+".x", vm) if err != nil { return EcPoint{}, err } - y, err := BigInt3FromBaseAddr(point_addr.AddUint(3), virtual_machine) + y, err := BigInt3FromBaseAddr(pointAddr.AddUint(3), name+".y", vm) if err != nil { return EcPoint{}, err } @@ -66,32 +42,33 @@ func BigInt3FromVarName(name string, virtual_machine vm.VirtualMachine, ids_data /* Implements main logic for `EC_NEGATE` and `EC_NEGATE_EMBEDDED_SECP` hints */ -func ecNegate(virtual_machine vm.VirtualMachine, exec_scopes types.ExecutionScopes, ids_data hint_utils.IdsManager, secp_p big.Int) error { - point, err := ids_data.GetRelocatable("point", &virtual_machine) + +func ecNegate(vm *vm.VirtualMachine, execScopes types.ExecutionScopes, ids hint_utils.IdsManager, secpP big.Int) error { + point, err := ids.GetRelocatable("point", vm) if err != nil { return err } - point_y, err := point.AddInt(3) + pointY, err := point.AddInt(3) if err != nil { return err } - y_bigint3, err := BigInt3FromBaseAddr(point_y, virtual_machine) + yBigint3, err := BigInt3FromBaseAddr(pointY, "point.y", vm) if err != nil { return err } - y := y_bigint3.Pack86() + y := yBigint3.Pack86() value := new(big.Int).Neg(&y) - value.Mod(value, &secp_p) + value.Mod(value, &secpP) - exec_scopes.AssignOrUpdateVariable("value", value) - exec_scopes.AssignOrUpdateVariable("SECP_P", secp_p) + execScopes.AssignOrUpdateVariable("value", value) + execScopes.AssignOrUpdateVariable("SECP_P", secpP) return nil } -func ecNegateImportSecpP(virtual_machine vm.VirtualMachine, exec_scopes types.ExecutionScopes, ids_data hint_utils.IdsManager) error { +func ecNegateImportSecpP(virtual_machine *vm.VirtualMachine, exec_scopes types.ExecutionScopes, ids_data hint_utils.IdsManager) error { secp_p, _ := new(big.Int).SetString("115792089237316195423570985008687907853269984665640564039457584007908834671663", 10) return ecNegate(virtual_machine, exec_scopes, ids_data, *secp_p) } @@ -108,7 +85,7 @@ Implements hint: %} */ -func ecNegateEmbeddedSecpP(virtual_machine vm.VirtualMachine, exec_scopes types.ExecutionScopes, ids_data hint_utils.IdsManager) error { +func ecNegateEmbeddedSecpP(virtual_machine *vm.VirtualMachine, exec_scopes types.ExecutionScopes, ids_data hint_utils.IdsManager) error { secp_p := big.NewInt(1) secp_p.Lsh(secp_p, 255) secp_p.Sub(secp_p, big.NewInt(19)) @@ -131,3 +108,274 @@ func ecMulInner(virtualMachine *vm.VirtualMachine, ids hint_utils.IdsManager) er return nil } + +/* +Implements hint: + + %{ + from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack + from starkware.python.math_utils import ec_double_slope + + # Compute the slope. + x = pack(ids.point.x, PRIME) + y = pack(ids.point.y, PRIME) + value = slope = ec_double_slope(point=(x, y), alpha=0, p=SECP_P) + +%} +*/ +func computeDoublingSlope(vm *VirtualMachine, execScopes ExecutionScopes, idsData IdsManager, pointAlias string, SecpP big.Int, alpha big.Int) error { + execScopes.AssignOrUpdateVariable("SECP_P", SecpP) + + point, err := EcPointFromVarName(pointAlias, vm, idsData) + if err != nil { + return err + } + + x := point.X.Pack86() + y := point.Y.Pack86() + doublePoint := builtins.DoublePointB{X: x, Y: y} + + value, err := builtins.EcDoubleSlope(doublePoint, alpha, SecpP) + if err != nil { + return err + } + + execScopes.AssignOrUpdateVariable("value", value) + execScopes.AssignOrUpdateVariable("slope", value) + + return nil +} + +/* +Implements hint: +%{ + from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack + from starkware.python.math_utils import line_slope + + # Compute the slope. + x0 = pack(ids.point0.x, PRIME) + y0 = pack(ids.point0.y, PRIME) + x1 = pack(ids.point1.x, PRIME) + y1 = pack(ids.point1.y, PRIME) + value = slope = line_slope(point1=(x0, y0), point2=(x1, y1), p=SECP_P) +%} +*/ + +func computeSlopeAndAssingSecpP(vm *VirtualMachine, execScopes ExecutionScopes, idsData IdsManager, point0Alias string, point1Alias string, secpP big.Int) error { + execScopes.AssignOrUpdateVariable("SECP_P", secpP) + return computeSlope(vm, execScopes, idsData, point0Alias, point1Alias) +} + +func computeSlope(vm *VirtualMachine, execScopes ExecutionScopes, idsData IdsManager, point0Alias string, point1Alias string) error { + point0, err := EcPointFromVarName(point0Alias, vm, idsData) + if err != nil { + return err + } + point1, err := EcPointFromVarName(point1Alias, vm, idsData) + if err != nil { + return err + } + + secpP, err := execScopes.Get("SECP_P") + if err != nil { + return err + } + secp := secpP.(big.Int) + + // build partial sum + x0 := point0.X.Pack86() + y0 := point0.Y.Pack86() + point_a := builtins.PartialSumB{X: x0, Y: y0} + + // build double point + x1 := point1.X.Pack86() + y1 := point1.Y.Pack86() + point_b := builtins.DoublePointB{X: x1, Y: y1} + + value, err := builtins.LineSlope(point_a, point_b, secp) + if err != nil { + return err + } + + execScopes.AssignOrUpdateVariable("value", value) + execScopes.AssignOrUpdateVariable("slope", value) + + return nil +} + +/* +Implements hint: +%{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_ALPHA as ALPHA %} +*/ + +func importSecp256r1Alpha(execScopes ExecutionScopes) error { + execScopes.AssignOrUpdateVariable("ALPHA", SECP256R1_ALPHA()) + return nil +} + +/* +Implements hint: +%{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_N as N %} +*/ +func importSECP256R1N(execScopes ExecutionScopes) error { + execScopes.AssignOrUpdateVariable("N", SECP256R1_N()) + return nil +} + +/* +Implements hint: +%{ +from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P +%} +*/ + +func importSECP256R1P(execScopes ExecutionScopes) error { + execScopes.AssignOrUpdateVariable("SECP_P", SECP256R1_P()) + return nil +} + +/* +Implements hint: + + %{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + from starkware.python.math_utils import ec_double_slope + # Compute the slope. + x = pack(ids.point.x, PRIME) + y = pack(ids.point.y, PRIME) + value = slope = ec_double_slope(point=(x, y), alpha=ALPHA, p=SECP_P) + +%} +*/ +func computeDoublingSlopeExternalConsts(vm VirtualMachine, execScopes ExecutionScopes, ids_data IdsManager) error { + // ids.point + point, err := EcPointFromVarName("point", &vm, ids_data) + if err != nil { + return err + } + + secpPuncast, err := execScopes.Get("SECP_P") + if err != nil { + return err + } + secpP, ok := secpPuncast.(big.Int) + if !ok { + return errors.New("Could not cast secp into big int") + } + + alphaUncast, err := execScopes.Get("ALPHA") + if err != nil { + return nil + } + + alpha, ok := alphaUncast.(big.Int) + if !ok { + return errors.New("Could not cast alpha into big int") + } + + doublePoint_b := builtins.DoublePointB{X: point.X.Pack86(), Y: point.Y.Pack86()} + + value, err := builtins.EcDoubleSlope(doublePoint_b, alpha, secpP) + execScopes.AssignOrUpdateVariable("value", value) + execScopes.AssignOrUpdateVariable("slope", value) + return nil +} + +/* + %{ + from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack + + slope = pack(ids.slope, PRIME) + x0 = pack(ids.point0.x, PRIME) + x1 = pack(ids.point1.x, PRIME) + y0 = pack(ids.point0.y, PRIME) + + value = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P" + +%} +*/ +func fastEcAddAssignNewX(ids IdsManager, vm *VirtualMachine, execScopes *ExecutionScopes, point0Alias string, point1Alias string, secpP big.Int) error { + execScopes.AssignOrUpdateVariable("SECP_P", secpP) + + point0, err := EcPointFromVarName(point0Alias, vm, ids) + point1, err := EcPointFromVarName(point1Alias, vm, ids) + if err != nil { + return err + } + slopeUnpacked, err := BigInt3FromVarName("slope", ids, vm) + if err != nil { + return err + } + + slope := slopeUnpacked.Pack86() + slope = *new(big.Int).Mod(&slope, &secpP) + + x0 := point0.X.Pack86() + x0 = *new(big.Int).Mod(&x0, &secpP) + + x1 := point1.X.Pack86() + x1 = *new(big.Int).Mod(&x1, &secpP) + + y0 := point0.Y.Pack86() + y0 = *new(big.Int).Mod(&y0, &secpP) + + slopeSquared := new(big.Int).Mul(&slope, &slope) + x0PlusX1 := new(big.Int).Add(&x0, &x1) + + value := *new(big.Int).Sub(slopeSquared, x0PlusX1) + value = *new(big.Int).Mod(&value, &secpP) + + execScopes.AssignOrUpdateVariable("slope", slope) + execScopes.AssignOrUpdateVariable("x0", x0) + execScopes.AssignOrUpdateVariable("y0", y0) + execScopes.AssignOrUpdateVariable("value", value) + execScopes.AssignOrUpdateVariable("new_x", value) + + return nil +} + +/* +Implements hint: + + %{ value = new_y = (slope * (x0 - new_x) - y0) % SECP_P %} +*/ +func fastEcAddAssignNewY(execScopes *ExecutionScopes) error { + slope, err := execScopes.Get("slope") + if err != nil { + return err + } + slopeBigInt := slope.(big.Int) + x0, err := execScopes.Get("x0") + if err != nil { + return err + } + x0BigInt := x0.(big.Int) + + newX, err := execScopes.Get("new_x") + if err != nil { + return err + } + newXBigInt := newX.(big.Int) + + y0, err := execScopes.Get("y0") + if err != nil { + return err + } + y0BigInt := y0.(big.Int) + + secpP, err := execScopes.Get("SECP_P") + if err != nil { + return err + } + secpBigInt := secpP.(big.Int) + + x0MinusNewX := *new(big.Int).Sub(&x0BigInt, &newXBigInt) + x0MinusNewXMinusY0 := *new(big.Int).Sub(&x0MinusNewX, &y0BigInt) + valueBeforeMod := *new(big.Int).Mul(&slopeBigInt, &x0MinusNewXMinusY0) + value := *new(big.Int).Mod(&valueBeforeMod, &secpBigInt) + + execScopes.AssignOrUpdateVariable("value", value) + execScopes.AssignOrUpdateVariable("new_y", value) + + return nil +} diff --git a/pkg/hints/ec_hint_test.go b/pkg/hints/ec_hint_test.go index 22303c4a..b52d37d7 100644 --- a/pkg/hints/ec_hint_test.go +++ b/pkg/hints/ec_hint_test.go @@ -55,13 +55,13 @@ func TestRunEcNegateOk(t *testing.T) { Ids: idsManager, Code: EC_NEGATE, }) - exec_scopes := types.NewExecutionScopes() - err := hintProcessor.ExecuteHint(vm, &hintData, nil, exec_scopes) + execScopes := types.NewExecutionScopes() + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) if err != nil { t.Errorf("Ec Negative hint test failed with error %s", err) } else { // Check ids.is_positive - value, err := exec_scopes.Get("value") + value, err := execScopes.Get("value") val := value.(*big.Int) expected, _ := new(big.Int).SetString("115792089237316195423569751828682367333329274433232027476421668138471189901786", 10) @@ -206,3 +206,404 @@ func TestEcMulInnerSuccessOdd(t *testing.T) { t.Errorf("EC_MUL_INNER should have returned 1 but got %v", resultFelt) } } + +func TestComputeDoublingSlopeOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Fp = NewRelocatable(1, 1) + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "point": { + NewMaybeRelocatableFelt(FeltFromUint64(614323)), + NewMaybeRelocatableFelt(FeltFromUint64(5456867)), + NewMaybeRelocatableFelt(FeltFromUint64(101208)), + NewMaybeRelocatableFelt(FeltFromUint64(773712524)), + NewMaybeRelocatableFelt(FeltFromUint64(77371252)), + NewMaybeRelocatableFelt(FeltFromUint64(5298795)), + }, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: EC_DOUBLE_SLOPE_V1, + }) + + exec_scopes := types.NewExecutionScopes() + err := hintProcessor.ExecuteHint(vm, &hintData, nil, exec_scopes) + if err != nil { + t.Errorf("EC_DOUBLE_SLOPE_V1 hint test failed with error %s", err) + } else { + value, _ := exec_scopes.Get("value") + val := value.(big.Int) + + slope_res, _ := exec_scopes.Get("slope") + slope := slope_res.(big.Int) + + // expected values + expectedVal, _ := new(big.Int).SetString("40442433062102151071094722250325492738932110061897694430475034100717288403728", 10) + + expectedSlope, _ := new(big.Int).SetString("40442433062102151071094722250325492738932110061897694430475034100717288403728", 10) + + if expectedVal.Cmp(&val) != 0 || expectedSlope.Cmp(&slope) != 0 { + t.Errorf("EC_DOUBLE_SLOPE_V1 hint test incorrect value for exec_scopes.value or exec_scopes.slope") + } + } +} + +func TestRunComputeSlopeOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Fp = NewRelocatable(1, 14) + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "point0": { + NewMaybeRelocatableFelt(FeltFromUint64(134)), + NewMaybeRelocatableFelt(FeltFromUint64(5123)), + NewMaybeRelocatableFelt(FeltFromUint64(140)), + NewMaybeRelocatableFelt(FeltFromUint64(1232)), + NewMaybeRelocatableFelt(FeltFromUint64(4652)), + NewMaybeRelocatableFelt(FeltFromUint64(720)), + }, + "point1": { + NewMaybeRelocatableFelt(FeltFromUint64(156)), + NewMaybeRelocatableFelt(FeltFromUint64(6545)), + NewMaybeRelocatableFelt(FeltFromUint64(100010)), + NewMaybeRelocatableFelt(FeltFromUint64(1123)), + NewMaybeRelocatableFelt(FeltFromUint64(1325)), + NewMaybeRelocatableFelt(FeltFromUint64(910)), + }, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: COMPUTE_SLOPE_V1, + }) + + execScopes := types.NewExecutionScopes() + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("EC_DOUBLE_SLOPE_V1 hint test failed with error %s", err) + } else { + value, _ := execScopes.Get("value") + val := value.(big.Int) + + slope_res, _ := execScopes.Get("slope") + slope := slope_res.(big.Int) + + // expected values + expectedVal, _ := new(big.Int).SetString("41419765295989780131385135514529906223027172305400087935755859001910844026631", 10) + + expectedSlope, _ := new(big.Int).SetString("41419765295989780131385135514529906223027172305400087935755859001910844026631", 10) + + if expectedVal.Cmp(&val) != 0 || expectedSlope.Cmp(&slope) != 0 { + t.Errorf("EC_DOUBLE_SLOPE_V1 hint test incorrect value for exec_scopes.value or exec_scopes.slope") + } + } +} + +func TestFastEcAddAssignNewXHint(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Fp = NewRelocatable(1, 14) + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "point0": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(2)), + NewMaybeRelocatableFelt(FeltFromUint64(3)), + NewMaybeRelocatableFelt(FeltFromUint64(4)), + NewMaybeRelocatableFelt(FeltFromUint64(5)), + NewMaybeRelocatableFelt(FeltFromUint64(6)), + }, + "point1": { + NewMaybeRelocatableFelt(FeltFromUint64(7)), + NewMaybeRelocatableFelt(FeltFromUint64(8)), + NewMaybeRelocatableFelt(FeltFromUint64(9)), + NewMaybeRelocatableFelt(FeltFromUint64(10)), + NewMaybeRelocatableFelt(FeltFromUint64(11)), + NewMaybeRelocatableFelt(FeltFromUint64(12)), + }, + "slope": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + }, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: FAST_EC_ADD_ASSIGN_NEW_X, + }) + + execScopes := types.NewExecutionScopes() + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("FAST_EC_ADD_ASSIGN_NEW_X hint test failed with error %s", err) + } + + slope, _ := execScopes.Get("slope") + slopeRes := slope.(big.Int) + + x0, _ := execScopes.Get("x0") + x0Res := x0.(big.Int) + + y0, _ := execScopes.Get("y0") + y0Res := y0.(big.Int) + + value, _ := execScopes.Get("value") + valueRes := value.(big.Int) + + // expected values + expectedSlope, _ := new(big.Int).SetString("1", 10) + expectedX0, _ := new(big.Int).SetString("17958932119522135058886879379160190656204633450479617", 10) + expectedY0, _ := new(big.Int).SetString("35917864239044270117773758835691633767745534082154500", 10) + expectedValue, _ := new(big.Int).SetString("115792089237316195423570913172959429764729749118122892656190048516840670362664", 10) + + if expectedValue.Cmp(&valueRes) != 0 { + t.Errorf("expected value=%v, got: value=%v", expectedValue, valueRes) + } + + if expectedSlope.Cmp(&slopeRes) != 0 { + t.Errorf("expected slope=%v, got: slope=%v", expectedSlope, slopeRes) + } + + if expectedX0.Cmp(&x0Res) != 0 { + t.Errorf("expected x0=%v, got: x0=%v", expectedX0, x0Res) + } + + if expectedY0.Cmp(&y0Res) != 0 { + t.Errorf("expected y0=%v, got: y0=%v", expectedY0, y0Res) + } +} + +func TestFastEcAddAssignNewXV2Hint(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Fp = NewRelocatable(1, 14) + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "point0": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(2)), + NewMaybeRelocatableFelt(FeltFromUint64(3)), + NewMaybeRelocatableFelt(FeltFromUint64(4)), + NewMaybeRelocatableFelt(FeltFromUint64(5)), + NewMaybeRelocatableFelt(FeltFromUint64(6)), + }, + "point1": { + NewMaybeRelocatableFelt(FeltFromUint64(7)), + NewMaybeRelocatableFelt(FeltFromUint64(8)), + NewMaybeRelocatableFelt(FeltFromUint64(9)), + NewMaybeRelocatableFelt(FeltFromUint64(10)), + NewMaybeRelocatableFelt(FeltFromUint64(11)), + NewMaybeRelocatableFelt(FeltFromUint64(12)), + }, + "slope": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + }, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: FAST_EC_ADD_ASSIGN_NEW_X_V2, + }) + + execScopes := types.NewExecutionScopes() + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("FAST_EC_ADD_ASSIGN_NEW_X_V2 hint test failed with error %s", err) + } + + slope, _ := execScopes.Get("slope") + slopeRes := slope.(big.Int) + + x0, _ := execScopes.Get("x0") + x0Res := x0.(big.Int) + + y0, _ := execScopes.Get("y0") + y0Res := y0.(big.Int) + + value, _ := execScopes.Get("value") + valueRes := value.(big.Int) + + // expected values + expectedSlope, _ := new(big.Int).SetString("1", 10) + expectedX0, _ := new(big.Int).SetString("17958932119522135058886879379160190656204633450479617", 10) + expectedY0, _ := new(big.Int).SetString("35917864239044270117773758835691633767745534082154500", 10) + expectedValue, _ := new(big.Int).SetString("57896044618658097711785420668615475838094756785302610636461256512888400510950", 10) + + if expectedValue.Cmp(&valueRes) != 0 { + t.Errorf("expected value=%v, got: value=%v", expectedValue, valueRes) + } + + if expectedSlope.Cmp(&slopeRes) != 0 { + t.Errorf("expected slope=%v, got: slope=%v", expectedValue, valueRes) + } + + if expectedX0.Cmp(&x0Res) != 0 { + t.Errorf("expected x0=%v, got: x0=%v", expectedX0, x0Res) + } + + if expectedY0.Cmp(&y0Res) != 0 { + t.Errorf("expected y0 to be %v, got: y0=%v", expectedY0, y0Res) + } +} + +func TestFastEcAddAssignNewXV3Hint(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Fp = NewRelocatable(1, 14) + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "pt0": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(2)), + NewMaybeRelocatableFelt(FeltFromUint64(3)), + NewMaybeRelocatableFelt(FeltFromUint64(4)), + NewMaybeRelocatableFelt(FeltFromUint64(5)), + NewMaybeRelocatableFelt(FeltFromUint64(6)), + }, + "pt1": { + NewMaybeRelocatableFelt(FeltFromUint64(7)), + NewMaybeRelocatableFelt(FeltFromUint64(8)), + NewMaybeRelocatableFelt(FeltFromUint64(9)), + NewMaybeRelocatableFelt(FeltFromUint64(10)), + NewMaybeRelocatableFelt(FeltFromUint64(11)), + NewMaybeRelocatableFelt(FeltFromUint64(12)), + }, + "slope": { + NewMaybeRelocatableFelt(FeltFromUint64(1)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + NewMaybeRelocatableFelt(FeltFromUint64(0)), + }, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: FAST_EC_ADD_ASSIGN_NEW_X_V3, + }) + + execScopes := types.NewExecutionScopes() + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("FAST_EC_ADD_ASSIGN_NEW_X_V3 hint test failed with error %s", err) + } + + slope, _ := execScopes.Get("slope") + slopeRes := slope.(big.Int) + + x0, _ := execScopes.Get("x0") + x0Res := x0.(big.Int) + + y0, _ := execScopes.Get("y0") + y0Res := y0.(big.Int) + + value, _ := execScopes.Get("value") + valueRes := value.(big.Int) + + // expected values + expectedSlope, _ := new(big.Int).SetString("1", 10) + expectedX0, _ := new(big.Int).SetString("17958932119522135058886879379160190656204633450479617", 10) + expectedY0, _ := new(big.Int).SetString("35917864239044270117773758835691633767745534082154500", 10) + expectedValue, _ := new(big.Int).SetString("115792089237316195423570913172959429764729749118122892656190048516840670362664", 10) + + if expectedValue.Cmp(&valueRes) != 0 { + t.Errorf("expected value=%v, got: value=%v", expectedValue, valueRes) + } + + if expectedSlope.Cmp(&slopeRes) != 0 { + t.Errorf("expected slope=%v, got: slope=%v", expectedValue, valueRes) + } + + if expectedX0.Cmp(&x0Res) != 0 { + t.Errorf("expected x0=%v, got: x0=%v", expectedX0, x0Res) + } + + if expectedY0.Cmp(&y0Res) != 0 { + t.Errorf("expected y0 to be %v, got: y0=%v", expectedY0, y0Res) + } +} + +func TestFastEcAddAssignNewY(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Fp = NewRelocatable(1, 14) + + idsManager := IdsManager{} + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: FAST_EC_ADD_ASSIGN_NEW_Y, + }) + + x0, _ := new(big.Int).SetString("17958932119522135058886879379160190656204633450479617", 10) + y0, _ := new(big.Int).SetString("35917864239044270117773758835691633767745534082154500", 10) + slope, _ := new(big.Int).SetString("1", 10) + newX, _ := new(big.Int).SetString("115792089237316195423570913172959429764729749118122892656190048516840670362664", 10) + secpP, _ := new(big.Int).SetString("115792089237316195423570985008687907853269984665640564039457584007908834671663", 10) + + execScopes := types.NewExecutionScopes() + execScopes.AssignOrUpdateVariable("x0", *x0) + execScopes.AssignOrUpdateVariable("y0", *y0) + execScopes.AssignOrUpdateVariable("slope", *slope) + execScopes.AssignOrUpdateVariable("new_x", *newX) + execScopes.AssignOrUpdateVariable("SECP_P", *secpP) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("FAST_EC_ADD_ASSIGN_NEW_Y hint test failed with error %s", err) + } + + // Result values + newY, _ := execScopes.Get("new_y") + newYRes := newY.(big.Int) + + value, _ := execScopes.Get("value") + valueRes := value.(big.Int) + + // Expected values + expectedNewY, _ := new(big.Int).SetString("53876796358566405176660638214851824423950167532634116", 10) + expectedValue, _ := new(big.Int).SetString("53876796358566405176660638214851824423950167532634116", 10) + + if expectedValue.Cmp(&valueRes) != 0 { + t.Errorf("expected value=%v, got: value=%v", expectedValue, valueRes) + } + + if expectedNewY.Cmp(&newYRes) != 0 { + t.Errorf("expected new_y=%v, got: new_y=%v", expectedValue, valueRes) + } +} diff --git a/pkg/hints/field_utils.go b/pkg/hints/field_utils.go new file mode 100644 index 00000000..bb81d791 --- /dev/null +++ b/pkg/hints/field_utils.go @@ -0,0 +1,55 @@ +package hints + +import ( + "errors" + "math/big" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" + + . "github.com/lambdaclass/cairo-vm.go/pkg/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" +) + +/* +Implements hint: +%{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + + q, r = divmod(pack(ids.val, PRIME), SECP_P) + assert r == 0, f"verify_zero: Invalid input {ids.val.d0, ids.val.d1, ids.val.d2}." + ids.q = q % PRIME +%} +*/ + +func verifyZeroWithExternalConst(vm VirtualMachine, execScopes ExecutionScopes, idsData IdsManager) error { + secpPuncast, err := execScopes.Get("SECP_P") + if err != nil { + return err + } + secpP, ok := secpPuncast.(big.Int) + if !ok { + return errors.New("Could not cast secpP into big int") + } + + addr, err := idsData.GetAddr("val", &vm) + if err != nil { + return err + } + + val, err := BigInt3FromBaseAddr(addr, "val", &vm) + if err != nil { + return err + } + + v := val.Pack86() + q, r := v.DivMod(&v, &secpP, new(big.Int)) + + if r.Cmp(big.NewInt(0)) != 0 { + return errors.New("verify remainder is not zero: Invalid input") + } + + quotient := memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromBigInt(q)) + return idsData.Insert("q", quotient, &vm) +} diff --git a/pkg/hints/field_utils_test.go b/pkg/hints/field_utils_test.go new file mode 100644 index 00000000..534f8eff --- /dev/null +++ b/pkg/hints/field_utils_test.go @@ -0,0 +1,56 @@ +package hints_test + +import ( + "math/big" + "testing" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints" + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_codes" + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestVerifyZeroWithExternalConst(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + + vm.RunContext.Pc = NewRelocatable(0, 0) + vm.RunContext.Ap = NewRelocatable(1, 9) + vm.RunContext.Fp = NewRelocatable(1, 9) + + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "val": {NewMaybeRelocatableFelt(lambdaworks.FeltFromUint64(55)), NewMaybeRelocatableFelt(lambdaworks.FeltFromUint64(0)), NewMaybeRelocatableFelt(lambdaworks.FeltFromUint64(0))}, + "q": {nil}, + }, + vm, + ) + + newScepP := big.NewInt(55) + execScopes := NewExecutionScopes() + + execScopes.AssignOrUpdateVariable("SECP_P", *newScepP) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: VERIFY_ZERO_EXTERNAL_SECP, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("verifyZeroWithExternalConst hint test failed with error: %s", err) + } else { + valueInMemory, err := idsManager.GetFelt("q", vm) + if err != nil { + t.Errorf("could not fetch value with error: %s", err) + } + if valueInMemory != FeltFromUint64(1) { + t.Errorf("value in memory is not the expected") + } + } +} diff --git a/pkg/hints/find_element_hints.go b/pkg/hints/find_element_hints.go new file mode 100644 index 00000000..303003a1 --- /dev/null +++ b/pkg/hints/find_element_hints.go @@ -0,0 +1,151 @@ +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/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" + "github.com/pkg/errors" +) + +func findElement(ids IdsManager, vm *VirtualMachine, execScopes ExecutionScopes) error { + arrayPtr, err := ids.GetRelocatable("array_ptr", vm) + if err != nil { + return err + } + + key, err := ids.GetFelt("key", vm) + if err != nil { + return err + } + + elmSizeFelt, err := ids.GetFelt("elm_size", vm) + if err != nil { + return err + } + elmSize, err := elmSizeFelt.ToUint() + if err != nil { + return err + } + + nElms, err := ids.GetFelt("n_elms", vm) + if err != nil { + return err + } + nElmsIter, err := nElms.ToUint() + if err != nil { + return err + } + + findElementIndexUncast, err := execScopes.Get("find_element_index") + if err == nil { + findElementIndex, ok := findElementIndexUncast.(Felt) + if !ok { + return ConversionError(findElementIndex, "felt") + } + position, err := arrayPtr.AddFelt(findElementIndex.Mul(elmSizeFelt)) + if err != nil { + return err + } + + foundKey, err := vm.Segments.Memory.GetFelt(position) + if err != nil { + return err + } + if foundKey != key { + return errors.Errorf( + "Invalid index found in find_element_index. Index: %s.\nExpected key: %s, found_key %s", + findElementIndex.ToSignedFeltString(), + key.ToSignedFeltString(), + foundKey.ToSignedFeltString(), + ) + } + execScopes.DeleteVariable("find_element_index") + return ids.Insert("index", NewMaybeRelocatableFelt(findElementIndex), vm) + } + + findElementMaxSizeUncast, err := execScopes.Get("find_element_max_size") + if err == nil { + findElementMaxSize, ok := findElementMaxSizeUncast.(Felt) + if !ok { + return ConversionError(findElementMaxSize, "felt") + } + if nElms.Cmp(findElementMaxSize) == 1 { + return errors.Errorf( + "find_element() can only be used with n_elms <= %s.\nGot: n_elms = %s", + findElementMaxSize.ToSignedFeltString(), + nElms.ToSignedFeltString(), + ) + } + } + + for i := uint(0); i < nElmsIter; i++ { + iterKey, err := vm.Segments.Memory.GetFelt(arrayPtr.AddUint(i * elmSize)) + if err != nil { + return err + } + if iterKey == key { + return ids.Insert("index", NewMaybeRelocatableFelt(FeltFromUint(i)), vm) + } + } + + return errors.Errorf("Key: %v was not found", key) +} + +func searchSortedLower(ids IdsManager, vm *VirtualMachine, execScopes ExecutionScopes) error { + arrayPtr, err := ids.GetRelocatable("array_ptr", vm) + if err != nil { + return err + } + + key, err := ids.GetFelt("key", vm) + if err != nil { + return err + } + + elmSizeFelt, err := ids.GetFelt("elm_size", vm) + if err != nil { + return err + } + elmSize, err := elmSizeFelt.ToUint() + if err != nil { + return err + } + + nElms, err := ids.GetFelt("n_elms", vm) + if err != nil { + return err + } + nElmsIter, err := nElms.ToUint() + if err != nil { + return err + } + + findElementMaxSizeUncast, err := execScopes.Get("find_element_max_size") + if err == nil { + findElementMaxSize, ok := findElementMaxSizeUncast.(Felt) + if !ok { + return ConversionError(findElementMaxSize, "felt") + } + if nElms.Cmp(findElementMaxSize) == 1 { + return errors.Errorf( + "find_element() can only be used with n_elms <= %s.\nGot: n_elms = %s", + findElementMaxSize.ToSignedFeltString(), + nElms.ToSignedFeltString(), + ) + } + } + + for i := uint(0); i < nElmsIter; i++ { + iterKey, err := vm.Segments.Memory.GetFelt(arrayPtr.AddUint(i * elmSize)) + if err != nil { + return err + } + if iterKey == key || iterKey.Cmp(key) == 1 { + return ids.Insert("index", NewMaybeRelocatableFelt(FeltFromUint(i)), vm) + } + } + + return errors.Errorf("Key: %v was not found", key) +} diff --git a/pkg/hints/find_element_hints_test.go b/pkg/hints/find_element_hints_test.go new file mode 100644 index 00000000..a9e74eda --- /dev/null +++ b/pkg/hints/find_element_hints_test.go @@ -0,0 +1,287 @@ +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/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestFindElementHintOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + vm.Segments.Memory.Insert(NewRelocatable(1, 0), NewMaybeRelocatableFelt(FeltFromUint64(1))) + vm.Segments.Memory.Insert(NewRelocatable(1, 1), NewMaybeRelocatableFelt(FeltFromUint64(2))) + vm.Segments.Memory.Insert(NewRelocatable(1, 2), NewMaybeRelocatableFelt(FeltFromUint64(3))) + vm.Segments.Memory.Insert(NewRelocatable(1, 3), NewMaybeRelocatableFelt(FeltFromUint64(4))) + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "array_ptr": {NewMaybeRelocatableRelocatable(NewRelocatable(1, 0))}, + "elm_size": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "n_elms": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "key": {NewMaybeRelocatableFelt(FeltFromUint64(3))}, + "index": {nil}, + }, + vm, + ) + + execScopes := NewExecutionScopes() + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: FIND_ELEMENT, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("FIND_ELEMENT hint test failed with error: %s", err) + } + index, err := idsManager.GetFelt("index", vm) + if err != nil { + t.Errorf("%s", err) + } + if index.Cmp(FeltFromUint64(1)) != 0 { + t.Errorf("Index was expected to be 1, got %s", index.ToSignedFeltString()) + } +} + +func TestFindElementWithFindElementIndex(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + vm.Segments.Memory.Insert(NewRelocatable(1, 0), NewMaybeRelocatableFelt(FeltFromUint64(1))) + vm.Segments.Memory.Insert(NewRelocatable(1, 1), NewMaybeRelocatableFelt(FeltFromUint64(2))) + vm.Segments.Memory.Insert(NewRelocatable(1, 2), NewMaybeRelocatableFelt(FeltFromUint64(3))) + vm.Segments.Memory.Insert(NewRelocatable(1, 3), NewMaybeRelocatableFelt(FeltFromUint64(4))) + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "array_ptr": {NewMaybeRelocatableRelocatable(NewRelocatable(1, 0))}, + "elm_size": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "n_elms": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "key": {NewMaybeRelocatableFelt(FeltFromUint64(3))}, + "index": {nil}, + }, + vm, + ) + + execScopes := NewExecutionScopes() + scope := make(map[string]interface{}) + scope["find_element_index"] = FeltOne() + execScopes.EnterScope(scope) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: FIND_ELEMENT, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("FIND_ELEMENT hint test failed with error: %s", err) + } + index, err := idsManager.GetFelt("index", vm) + if err != nil { + t.Errorf("%s", err) + } + if index.Cmp(FeltFromUint64(1)) != 0 { + t.Errorf("Index was expected to be 1, got %s", index.ToSignedFeltString()) + } +} + +func TestFindElementFindElementMaxSizeOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + vm.Segments.Memory.Insert(NewRelocatable(1, 0), NewMaybeRelocatableFelt(FeltFromUint64(1))) + vm.Segments.Memory.Insert(NewRelocatable(1, 1), NewMaybeRelocatableFelt(FeltFromUint64(2))) + vm.Segments.Memory.Insert(NewRelocatable(1, 2), NewMaybeRelocatableFelt(FeltFromUint64(3))) + vm.Segments.Memory.Insert(NewRelocatable(1, 3), NewMaybeRelocatableFelt(FeltFromUint64(4))) + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "array_ptr": {NewMaybeRelocatableRelocatable(NewRelocatable(1, 0))}, + "elm_size": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "n_elms": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "key": {NewMaybeRelocatableFelt(FeltFromUint64(3))}, + "index": {nil}, + }, + vm, + ) + + execScopes := NewExecutionScopes() + scope := make(map[string]interface{}) + scope["find_element_max_size"] = FeltFromUint64(2) + execScopes.EnterScope(scope) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: FIND_ELEMENT, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("FIND_ELEMENT hint test failed with error: %s", err) + } + index, err := idsManager.GetFelt("index", vm) + if err != nil { + t.Errorf("%s", err) + } + if index.Cmp(FeltFromUint64(1)) != 0 { + t.Errorf("Index was expected to be 1, got %s", index.ToSignedFeltString()) + } +} + +func TestFindElementFindElementMaxSizeLessThanNeeded(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + vm.Segments.Memory.Insert(NewRelocatable(1, 0), NewMaybeRelocatableFelt(FeltFromUint64(1))) + vm.Segments.Memory.Insert(NewRelocatable(1, 1), NewMaybeRelocatableFelt(FeltFromUint64(2))) + vm.Segments.Memory.Insert(NewRelocatable(1, 2), NewMaybeRelocatableFelt(FeltFromUint64(3))) + vm.Segments.Memory.Insert(NewRelocatable(1, 3), NewMaybeRelocatableFelt(FeltFromUint64(4))) + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "array_ptr": {NewMaybeRelocatableRelocatable(NewRelocatable(1, 0))}, + "elm_size": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "n_elms": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "key": {NewMaybeRelocatableFelt(FeltFromUint64(3))}, + "index": {nil}, + }, + vm, + ) + + execScopes := NewExecutionScopes() + scope := make(map[string]interface{}) + scope["find_element_max_size"] = FeltOne() + execScopes.EnterScope(scope) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: FIND_ELEMENT, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err == nil { + t.Errorf("FIND_ELEMENT hint expected to fail with find_element_max_size < n_elms") + } +} + +func TestSearchSortedLowerHintOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + vm.Segments.Memory.Insert(NewRelocatable(1, 0), NewMaybeRelocatableFelt(FeltFromUint64(1))) + vm.Segments.Memory.Insert(NewRelocatable(1, 1), NewMaybeRelocatableFelt(FeltFromUint64(2))) + vm.Segments.Memory.Insert(NewRelocatable(1, 2), NewMaybeRelocatableFelt(FeltFromUint64(3))) + vm.Segments.Memory.Insert(NewRelocatable(1, 3), NewMaybeRelocatableFelt(FeltFromUint64(4))) + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "array_ptr": {NewMaybeRelocatableRelocatable(NewRelocatable(1, 0))}, + "elm_size": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "n_elms": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "key": {NewMaybeRelocatableFelt(FeltFromUint64(0))}, + "index": {nil}, + }, + vm, + ) + + execScopes := NewExecutionScopes() + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: SEARCH_SORTED_LOWER, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("FIND_ELEMENT hint test failed with error: %s", err) + } + index, err := idsManager.GetFelt("index", vm) + if err != nil { + t.Errorf("%s", err) + } + if index.Cmp(FeltZero()) != 0 { + t.Errorf("Index was expected to be 0, got %s", index.ToSignedFeltString()) + } +} + +func TestSearchSortedLowerFindElementMaxSizeOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + vm.Segments.Memory.Insert(NewRelocatable(1, 0), NewMaybeRelocatableFelt(FeltFromUint64(1))) + vm.Segments.Memory.Insert(NewRelocatable(1, 1), NewMaybeRelocatableFelt(FeltFromUint64(2))) + vm.Segments.Memory.Insert(NewRelocatable(1, 2), NewMaybeRelocatableFelt(FeltFromUint64(3))) + vm.Segments.Memory.Insert(NewRelocatable(1, 3), NewMaybeRelocatableFelt(FeltFromUint64(4))) + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "array_ptr": {NewMaybeRelocatableRelocatable(NewRelocatable(1, 0))}, + "elm_size": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "n_elms": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "key": {NewMaybeRelocatableFelt(FeltFromUint64(3))}, + "index": {nil}, + }, + vm, + ) + + execScopes := NewExecutionScopes() + scope := make(map[string]interface{}) + scope["find_element_max_size"] = FeltFromUint64(2) + execScopes.EnterScope(scope) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: SEARCH_SORTED_LOWER, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err != nil { + t.Errorf("FIND_ELEMENT hint test failed with error: %s", err) + } + index, err := idsManager.GetFelt("index", vm) + if err != nil { + t.Errorf("%s", err) + } + if index.Cmp(FeltFromUint64(1)) != 0 { + t.Errorf("Index was expected to be 1, got %s", index.ToSignedFeltString()) + } +} + +func TestSearchSortedLowerFindElementMaxSizeLessThanNeeded(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + vm.Segments.AddSegment() + vm.Segments.Memory.Insert(NewRelocatable(1, 0), NewMaybeRelocatableFelt(FeltFromUint64(1))) + vm.Segments.Memory.Insert(NewRelocatable(1, 1), NewMaybeRelocatableFelt(FeltFromUint64(2))) + vm.Segments.Memory.Insert(NewRelocatable(1, 2), NewMaybeRelocatableFelt(FeltFromUint64(3))) + vm.Segments.Memory.Insert(NewRelocatable(1, 3), NewMaybeRelocatableFelt(FeltFromUint64(4))) + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "array_ptr": {NewMaybeRelocatableRelocatable(NewRelocatable(1, 0))}, + "elm_size": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "n_elms": {NewMaybeRelocatableFelt(FeltFromUint64(2))}, + "key": {NewMaybeRelocatableFelt(FeltFromUint64(3))}, + "index": {nil}, + }, + vm, + ) + + execScopes := NewExecutionScopes() + scope := make(map[string]interface{}) + scope["find_element_max_size"] = FeltOne() + execScopes.EnterScope(scope) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: SEARCH_SORTED_LOWER, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, execScopes) + if err == nil { + t.Errorf("FIND_ELEMENT hint expected to fail with find_element_max_size < n_elms") + } +} diff --git a/pkg/hints/hint_codes/ec_op_hints.go b/pkg/hints/hint_codes/ec_op_hints.go index cfe1f9a5..a7c8600f 100644 --- a/pkg/hints/hint_codes/ec_op_hints.go +++ b/pkg/hints/hint_codes/ec_op_hints.go @@ -3,3 +3,28 @@ package hint_codes const EC_NEGATE = "from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack\n\ny = pack(ids.point.y, PRIME) % SECP_P\n# The modulo operation in python always returns a nonnegative number.\nvalue = (-y) % SECP_P" const EC_NEGATE_EMBEDDED_SECP = "from starkware.cairo.common.cairo_secp.secp_utils import pack\nSECP_P = 2**255-19\n\ny = pack(ids.point.y, PRIME) % SECP_P\n# The modulo operation in python always returns a nonnegative number.\nvalue = (-y) % SECP_P" const EC_MUL_INNER = "memory[ap] = (ids.scalar % PRIME) % 2" +const EC_DOUBLE_SLOPE_V1 = "from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack\nfrom starkware.python.math_utils import ec_double_slope\n\n# Compute the slope.\nx = pack(ids.point.x, PRIME)\ny = pack(ids.point.y, PRIME)\nvalue = slope = ec_double_slope(point=(x, y), alpha=0, p=SECP_P)" +const COMPUTE_SLOPE_V1 = "from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack\nfrom starkware.python.math_utils import line_slope\n\n# Compute the slope.\nx0 = pack(ids.point0.x, PRIME)\ny0 = pack(ids.point0.y, PRIME)\nx1 = pack(ids.point1.x, PRIME)\ny1 = pack(ids.point1.y, PRIME)\nvalue = slope = line_slope(point1=(x0, y0), point2=(x1, y1), p=SECP_P)" +const EC_DOUBLE_SLOPE_EXTERNAL_CONSTS = "from starkware.cairo.common.cairo_secp.secp_utils import pack\nfrom starkware.python.math_utils import ec_double_slope\n\n# Compute the slope.\nx = pack(ids.point.x, PRIME)\ny = pack(ids.point.y, PRIME)\nvalue = slope = ec_double_slope(point=(x, y), alpha=ALPHA, p=SECP_P)" +const NONDET_BIGINT3_V1 = "from starkware.cairo.common.cairo_secp.secp_utils import split\n\nsegments.write_arg(ids.res.address_, split(value))" +const FAST_EC_ADD_ASSIGN_NEW_X = `"from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack + +slope = pack(ids.slope, PRIME) +x0 = pack(ids.point0.x, PRIME) +x1 = pack(ids.point1.x, PRIME) +y0 = pack(ids.point0.y, PRIME) + +value = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P` + +const FAST_EC_ADD_ASSIGN_NEW_X_V2 = "from starkware.cairo.common.cairo_secp.secp_utils import pack\nSECP_P = 2**255-19\n\nslope = pack(ids.slope, PRIME)\nx0 = pack(ids.point0.x, PRIME)\nx1 = pack(ids.point1.x, PRIME)\ny0 = pack(ids.point0.y, PRIME)\n\nvalue = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P" + +const FAST_EC_ADD_ASSIGN_NEW_X_V3 = `"from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack + +slope = pack(ids.slope, PRIME) +x0 = pack(ids.pt0.x, PRIME) +x1 = pack(ids.pt1.x, PRIME) +y0 = pack(ids.pt0.y, PRIME) + +value = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P"` + +const FAST_EC_ADD_ASSIGN_NEW_Y = "value = new_y = (slope * (x0 - new_x) - y0) % SECP_P" diff --git a/pkg/hints/hint_codes/find_element_hint_codes.go b/pkg/hints/hint_codes/find_element_hint_codes.go new file mode 100644 index 00000000..30bea4d2 --- /dev/null +++ b/pkg/hints/hint_codes/find_element_hint_codes.go @@ -0,0 +1,51 @@ +package hint_codes + +const FIND_ELEMENT = `array_ptr = ids.array_ptr +elm_size = ids.elm_size +assert isinstance(elm_size, int) and elm_size > 0, \ + f'Invalid value for elm_size. Got: {elm_size}.' +key = ids.key + +if '__find_element_index' in globals(): + ids.index = __find_element_index + found_key = memory[array_ptr + elm_size * __find_element_index] + assert found_key == key, \ + f'Invalid index found in __find_element_index. index: {__find_element_index}, ' \ + f'expected key {key}, found key: {found_key}.' + # Delete __find_element_index to make sure it's not used for the next calls. + del __find_element_index +else: + n_elms = ids.n_elms + assert isinstance(n_elms, int) and n_elms >= 0, \ + f'Invalid value for n_elms. Got: {n_elms}.' + if '__find_element_max_size' in globals(): + assert n_elms <= __find_element_max_size, \ + f'find_element() can only be used with n_elms<={__find_element_max_size}. ' \ + f'Got: n_elms={n_elms}.' + + for i in range(n_elms): + if memory[array_ptr + elm_size * i] == key: + ids.index = i + break + else: + raise ValueError(f'Key {key} was not found.')` + +const SEARCH_SORTED_LOWER = `array_ptr = ids.array_ptr +elm_size = ids.elm_size +assert isinstance(elm_size, int) and elm_size > 0, \ + f'Invalid value for elm_size. Got: {elm_size}.' + +n_elms = ids.n_elms +assert isinstance(n_elms, int) and n_elms >= 0, \ + f'Invalid value for n_elms. Got: {n_elms}.' +if '__find_element_max_size' in globals(): + assert n_elms <= __find_element_max_size, \ + f'find_element() can only be used with n_elms<={__find_element_max_size}. ' \ + f'Got: n_elms={n_elms}.' + +for i in range(n_elms): + if memory[array_ptr + elm_size * i] >= ids.key: + ids.index = i + break +else: + ids.index = n_elms` diff --git a/pkg/hints/hint_codes/keccak_hint_codes.go b/pkg/hints/hint_codes/keccak_hint_codes.go index c9cc1ea1..9927b27b 100644 --- a/pkg/hints/hint_codes/keccak_hint_codes.go +++ b/pkg/hints/hint_codes/keccak_hint_codes.go @@ -3,3 +3,36 @@ package hint_codes const UNSAFE_KECCAK = "from eth_hash.auto import keccak\n\ndata, length = ids.data, ids.length\n\nif '__keccak_max_size' in globals():\n assert length <= __keccak_max_size, \\\n f'unsafe_keccak() can only be used with length<={__keccak_max_size}. ' \\\n f'Got: length={length}.'\n\nkeccak_input = bytearray()\nfor word_i, byte_i in enumerate(range(0, length, 16)):\n word = memory[data + word_i]\n n_bytes = min(16, length - byte_i)\n assert 0 <= word < 2 ** (8 * n_bytes)\n keccak_input += word.to_bytes(n_bytes, 'big')\n\nhashed = keccak(keccak_input)\nids.high = int.from_bytes(hashed[:16], 'big')\nids.low = int.from_bytes(hashed[16:32], 'big')" const UNSAFE_KECCAK_FINALIZE = "from eth_hash.auto import keccak\nkeccak_input = bytearray()\nn_elms = ids.keccak_state.end_ptr - ids.keccak_state.start_ptr\nfor word in memory.get_range(ids.keccak_state.start_ptr, n_elms):\n keccak_input += word.to_bytes(16, 'big')\nhashed = keccak(keccak_input)\nids.high = int.from_bytes(hashed[:16], 'big')\nids.low = int.from_bytes(hashed[16:32], 'big')" + +const COMPARE_BYTES_IN_WORD_NONDET = "memory[ap] = to_felt_or_relocatable(ids.n_bytes < ids.BYTES_IN_WORD)" + +const COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET = "memory[ap] = to_felt_or_relocatable(ids.n_bytes >= ids.KECCAK_FULL_RATE_IN_BYTES)" + +const BLOCK_PERMUTATION = `from starkware.cairo.common.keccak_utils.keccak_utils import keccak_func +_keccak_state_size_felts = int(ids.KECCAK_STATE_SIZE_FELTS) +assert 0 <= _keccak_state_size_felts < 100 + +output_values = keccak_func(memory.get_range( + ids.keccak_ptr - _keccak_state_size_felts, _keccak_state_size_felts)) +segments.write_arg(ids.keccak_ptr, output_values)` + +const CAIRO_KECCAK_FINALIZE_V1 = `# Add dummy pairs of input and output. +_keccak_state_size_felts = int(ids.KECCAK_STATE_SIZE_FELTS) +_block_size = int(ids.BLOCK_SIZE) +assert 0 <= _keccak_state_size_felts < 100 +assert 0 <= _block_size < 10 +inp = [0] * _keccak_state_size_felts +padding = (inp + keccak_func(inp)) * _block_size +segments.write_arg(ids.keccak_ptr_end, padding)` + +const CAIRO_KECCAK_FINALIZE_V2 = `# Add dummy pairs of input and output. +_keccak_state_size_felts = int(ids.KECCAK_STATE_SIZE_FELTS) +_block_size = int(ids.BLOCK_SIZE) +assert 0 <= _keccak_state_size_felts < 100 +assert 0 <= _block_size < 1000 +inp = [0] * _keccak_state_size_felts +padding = (inp + keccak_func(inp)) * _block_size +segments.write_arg(ids.keccak_ptr_end, padding)` + +const KECCAK_WRITE_ARGS = `segments.write_arg(ids.inputs, [ids.low % 2 ** 64, ids.low // 2 ** 64]) +segments.write_arg(ids.inputs + 2, [ids.high % 2 ** 64, ids.high // 2 ** 64])` diff --git a/pkg/hints/hint_codes/secp_p_hint.go b/pkg/hints/hint_codes/secp_p_hint.go new file mode 100644 index 00000000..2d6dac14 --- /dev/null +++ b/pkg/hints/hint_codes/secp_p_hint.go @@ -0,0 +1,6 @@ +package hint_codes + +const IMPORT_SECP256R1_ALPHA = "from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_ALPHA as ALPHA" +const IMPORT_SECP256R1_N = "from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_N as N" +const IMPORT_SECP256R1_P = "from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P" +const VERIFY_ZERO_EXTERNAL_SECP = "from starkware.cairo.common.cairo_secp.secp_utils import pack\n\nq, r = divmod(pack(ids.val, PRIME), SECP_P)\nassert r == 0, f\"verify_zero: Invalid input {ids.val.d0, ids.val.d1, ids.val.d2}.\"\nids.q = q % PRIME" diff --git a/pkg/hints/hint_codes/set_hint_codes.go b/pkg/hints/hint_codes/set_hint_codes.go new file mode 100644 index 00000000..53354ac4 --- /dev/null +++ b/pkg/hints/hint_codes/set_hint_codes.go @@ -0,0 +1,3 @@ +package hint_codes + +const SET_ADD = "assert ids.elm_size > 0\nassert ids.set_ptr <= ids.set_end_ptr\nelm_list = memory.get_range(ids.elm_ptr, ids.elm_size)\nfor i in range(0, ids.set_end_ptr - ids.set_ptr, ids.elm_size):\n if memory.get_range(ids.set_ptr + i, ids.elm_size) == elm_list:\n ids.index = i // ids.elm_size\n ids.is_elm_in_set = 1\n break\nelse:\n ids.is_elm_in_set = 0" diff --git a/pkg/hints/hint_processor.go b/pkg/hints/hint_processor.go index 606cda0a..b0c5165a 100644 --- a/pkg/hints/hint_processor.go +++ b/pkg/hints/hint_processor.go @@ -89,9 +89,9 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any, case ASSERT_NOT_EQUAL: return assert_not_equal(data.Ids, vm) case EC_NEGATE: - return ecNegateImportSecpP(*vm, *execScopes, data.Ids) + return ecNegateImportSecpP(vm, *execScopes, data.Ids) case EC_NEGATE_EMBEDDED_SECP: - return ecNegateEmbeddedSecpP(*vm, *execScopes, data.Ids) + return ecNegateEmbeddedSecpP(vm, *execScopes, data.Ids) case POW: return pow(data.Ids, vm) case SQRT: @@ -106,10 +106,32 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any, return memset_step_loop(data.Ids, vm, execScopes, "continue_loop") case VM_ENTER_SCOPE: return vm_enter_scope(execScopes) + case SET_ADD: + return setAdd(data.Ids, vm) + case FIND_ELEMENT: + return findElement(data.Ids, vm, *execScopes) + case SEARCH_SORTED_LOWER: + return searchSortedLower(data.Ids, vm, *execScopes) + case COMPUTE_SLOPE_V1: + return computeSlopeAndAssingSecpP(vm, *execScopes, data.Ids, "point0", "point1", SECP_P()) + case EC_DOUBLE_SLOPE_V1: + return computeDoublingSlope(vm, *execScopes, data.Ids, "point", SECP_P(), ALPHA()) case UNSAFE_KECCAK: return unsafeKeccak(data.Ids, vm, *execScopes) case UNSAFE_KECCAK_FINALIZE: return unsafeKeccakFinalize(data.Ids, vm) + case COMPARE_BYTES_IN_WORD_NONDET: + return compareBytesInWordNondet(data.Ids, vm, constants) + case COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET: + return compareKeccakFullRateInBytesNondet(data.Ids, vm, constants) + case BLOCK_PERMUTATION: + return blockPermutation(data.Ids, vm, constants) + case CAIRO_KECCAK_FINALIZE_V1: + return cairoKeccakFinalize(data.Ids, vm, constants, 10) + case CAIRO_KECCAK_FINALIZE_V2: + return cairoKeccakFinalize(data.Ids, vm, constants, 1000) + case KECCAK_WRITE_ARGS: + return keccakWriteArgs(data.Ids, vm) case UNSIGNED_DIV_REM: return unsignedDivRem(data.Ids, vm) case SIGNED_DIV_REM: @@ -134,12 +156,32 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any, return Assert250Bit(data.Ids, vm, constants) case SPLIT_FELT: return SplitFelt(data.Ids, vm, constants) + case IMPORT_SECP256R1_ALPHA: + return importSecp256r1Alpha(*execScopes) + case IMPORT_SECP256R1_N: + return importSECP256R1N(*execScopes) + case IMPORT_SECP256R1_P: + return importSECP256R1P(*execScopes) + case EC_DOUBLE_SLOPE_EXTERNAL_CONSTS: + return computeDoublingSlopeExternalConsts(*vm, *execScopes, data.Ids) + case NONDET_BIGINT3_V1: + return NondetBigInt3(*vm, *execScopes, data.Ids) case SPLIT_INT: return splitInt(data.Ids, vm) case SPLIT_INT_ASSERT_RANGE: return splitIntAssertRange(data.Ids, vm) case EC_MUL_INNER: return ecMulInner(vm, data.Ids) + case VERIFY_ZERO_EXTERNAL_SECP: + return verifyZeroWithExternalConst(*vm, *execScopes, data.Ids) + case FAST_EC_ADD_ASSIGN_NEW_X: + return fastEcAddAssignNewX(data.Ids, vm, execScopes, "point0", "point1", SECP_P()) + case FAST_EC_ADD_ASSIGN_NEW_X_V2: + return fastEcAddAssignNewX(data.Ids, vm, execScopes, "point0", "point1", SECP_P_V2()) + case FAST_EC_ADD_ASSIGN_NEW_X_V3: + return fastEcAddAssignNewX(data.Ids, vm, execScopes, "pt0", "pt1", SECP_P()) + case FAST_EC_ADD_ASSIGN_NEW_Y: + return fastEcAddAssignNewY(execScopes) default: return errors.Errorf("Unknown Hint: %s", data.Code) } diff --git a/pkg/hints/hint_utils/bigint_utils.go b/pkg/hints/hint_utils/bigint_utils.go new file mode 100644 index 00000000..9a900443 --- /dev/null +++ b/pkg/hints/hint_utils/bigint_utils.go @@ -0,0 +1,110 @@ +package hint_utils + +import ( + "math/big" + + . "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" +) + +// In cairo, various structs are used to represent big integers, all of them have numbered fields of Felt type (d0, d1,...) and share the same behaviours +// This file contains an implementation of each behaviour at the limbs level, and the wrappers for each specific type + +// Generic methods for all types +func limbsFromVarName(nLimbs int, name string, ids IdsManager, vm *VirtualMachine) ([]Felt, error) { + baseAddr, err := ids.GetAddr(name, vm) + if err != nil { + return nil, err + } + return limbsFromBaseAddress(nLimbs, name, baseAddr, vm) +} + +func limbsFromBaseAddress(nLimbs int, name string, addr Relocatable, vm *VirtualMachine) ([]Felt, error) { + limbs := make([]Felt, 0) + for i := 0; i < nLimbs; i++ { + felt, err := vm.Segments.Memory.GetFelt(addr.AddUint(uint(i))) + if err == nil { + limbs = append(limbs, felt) + } else { + return nil, errors.Errorf("Identifier %s has no member d%d", name, i) + } + } + return limbs, nil +} + +func limbsPack86(limbs []Felt) big.Int { + sum := big.NewInt(0) + for i := 0; i < 3; i++ { + felt := limbs[i] + shifed := new(big.Int).Lsh(felt.ToSigned(), uint(i*86)) + sum.Add(sum, shifed) + } + return *sum +} + +func limbsPack(limbs []Felt) big.Int { + sum := big.NewInt(0) + for i := 0; i < len(limbs); i++ { + felt := limbs[i] + shifed := new(big.Int).Lsh(felt.ToSigned(), uint(i*128)) + sum.Add(sum, shifed) + } + return *sum +} + +func limbsInsertFromVarName(limbs []Felt, name string, ids IdsManager, vm *VirtualMachine) error { + baseAddr, err := ids.GetAddr(name, vm) + if err != nil { + return err + } + for i := 0; i < len(limbs); i++ { + err = vm.Segments.Memory.Insert(baseAddr.AddUint(uint(i)), NewMaybeRelocatableFelt(limbs[i])) + if err != nil { + return err + } + } + return nil +} + +func splitIntoLimbs(num *big.Int, numLimbs int) []Felt { + limbs := make([]Felt, 0, numLimbs) + bitmask := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 128), big.NewInt(1)) + for i := 0; i < numLimbs; i++ { + limbs[i] = FeltFromBigInt(new(big.Int).Lsh(new(big.Int).And(num, bitmask), 128)) + } + return limbs +} + +// Concrete type definitions + +// BigInt3 + +type BigInt3 struct { + Limbs []Felt +} + +func (b *BigInt3) Pack86() big.Int { + return limbsPack86(b.Limbs) +} + +func BigInt3FromBaseAddr(addr Relocatable, name string, vm *VirtualMachine) (BigInt3, error) { + limbs, err := limbsFromBaseAddress(3, name, addr, vm) + return BigInt3{Limbs: limbs}, err +} + +func BigInt3FromVarName(name string, ids IdsManager, vm *VirtualMachine) (BigInt3, error) { + bigIntAddr, err := ids.GetAddr(name, vm) + if err != nil { + return BigInt3{}, err + } + + bigInt, err := BigInt3FromBaseAddr(bigIntAddr, name, vm) + if err != nil { + return BigInt3{}, err + } + + return bigInt, err +} diff --git a/pkg/hints/hint_utils/hint_reference.go b/pkg/hints/hint_utils/hint_reference.go index 27149cd6..4954cb91 100644 --- a/pkg/hints/hint_utils/hint_reference.go +++ b/pkg/hints/hint_utils/hint_reference.go @@ -204,6 +204,18 @@ func ParseHintReference(reference parser.Reference) HintReference { ValueType: valueType, } } + // Reference no dereference 2 offsets - + : cast(reg - off1 + off2, type) + _, err = fmt.Sscanf(valueString, "cast%c%c - %d + %d, %s", &off1Reg0, &off1Reg1, &off1, &off2, &valueType) + if err == nil { + off1Reg := getRegister(off1Reg0, off1Reg1) + return HintReference{ + ApTrackingData: reference.ApTrackingData, + Offset1: OffsetValue{ValueType: Reference, Register: off1Reg, Value: -off1}, + Offset2: OffsetValue{Value: off2}, + Dereference: dereference, + ValueType: valueType, + } + } // No matches (aka wrong format) return HintReference{ApTrackingData: reference.ApTrackingData} } diff --git a/pkg/hints/hint_utils/hint_reference_test.go b/pkg/hints/hint_utils/hint_reference_test.go index 868274dd..98a5b983 100644 --- a/pkg/hints/hint_utils/hint_reference_test.go +++ b/pkg/hints/hint_utils/hint_reference_test.go @@ -326,3 +326,17 @@ func TestParseHintDereferenceReferenceDoubleDerefBothOffOmitted(t *testing.T) { t.Errorf("Wrong parsed reference, %+v", ParseHintReference(reference)) } } + +func TestParseHintDereferenceValueMinusValPlusVal(t *testing.T) { + reference := parser.Reference{Value: "[cast(ap - 0 + (-1), felt*)]"} + expected := HintReference{ + Offset1: OffsetValue{ValueType: Reference, Value: 0, Dereference: false}, + Offset2: OffsetValue{ValueType: Value, Value: -1, Dereference: false}, + ValueType: "felt*", + Dereference: true, + } + + if ParseHintReference(reference) != expected { + t.Errorf("Wrong parsed reference, %+v", ParseHintReference(reference)) + } +} diff --git a/pkg/hints/hint_utils/secp_utils.go b/pkg/hints/hint_utils/secp_utils.go new file mode 100644 index 00000000..a125823a --- /dev/null +++ b/pkg/hints/hint_utils/secp_utils.go @@ -0,0 +1,56 @@ +package hint_utils + +import ( + "errors" + "math/big" +) + +func SECP_P() big.Int { + secpP, _ := new(big.Int).SetString("115792089237316195423570985008687907853269984665640564039457584007908834671663", 10) + return *secpP +} + +func SECP_P_V2() big.Int { + secpP, _ := new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10) + return *secpP +} + +func ALPHA() big.Int { + alpha := big.NewInt(0) + return *alpha +} + +func SECP256R1_ALPHA() big.Int { + secpPalpha, _ := new(big.Int).SetString("115792089210356248762697446949407573530086143415290314195533631308867097853948", 10) + return *secpPalpha +} + +func SECP256R1_N() big.Int { + secp256, _ := new(big.Int).SetString("115792089210356248762697446949407573529996955224135760342422259061068512044369", 10) + return *secp256 +} + +func SECP256R1_P() big.Int { + secp256r1, _ := new(big.Int).SetString("115792089210356248762697446949407573530086143415290314195533631308867097853951", 10) + return *secp256r1 +} + +func BASE_MINUS_ONE() *big.Int { + res, _ := new(big.Int).SetString("77371252455336267181195263", 10) + return res +} + +func Bigint3Split(integer big.Int) ([]big.Int, error) { + canonicalRepr := make([]big.Int, 3) + num := integer + + for i := 0; i < 3; i++ { + canonicalRepr[i] = *new(big.Int).And(&num, BASE_MINUS_ONE()) + num.Rsh(&num, 86) + } + if num.Cmp(big.NewInt(0)) != 0 { + return nil, errors.New("HintError SecpSplitOutOfRange") + } + + return canonicalRepr, nil +} diff --git a/pkg/hints/keccak_hints.go b/pkg/hints/keccak_hints.go index 969ccdba..e3b4382b 100644 --- a/pkg/hints/keccak_hints.go +++ b/pkg/hints/keccak_hints.go @@ -1,7 +1,10 @@ package hints import ( + "math" + "github.com/ebfe/keccak" + "github.com/lambdaclass/cairo-vm.go/pkg/builtins" . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" . "github.com/lambdaclass/cairo-vm.go/pkg/types" @@ -117,3 +120,138 @@ func unsafeKeccakFinalize(ids IdsManager, vm *VirtualMachine) error { } return ids.Insert("low", NewMaybeRelocatableFelt(low), vm) } + +func compareBytesInWordNondet(ids IdsManager, vm *VirtualMachine, constants *map[string]Felt) error { + nBytes, err := ids.GetFelt("n_bytes", vm) + if err != nil { + return err + } + bytesInWord, err := ids.GetConst("BYTES_IN_WORD", constants) + if nBytes.Cmp(bytesInWord) == -1 { + return vm.Segments.Memory.Insert(vm.RunContext.Ap, NewMaybeRelocatableFelt(FeltOne())) + } + return vm.Segments.Memory.Insert(vm.RunContext.Ap, NewMaybeRelocatableFelt(FeltZero())) +} + +func compareKeccakFullRateInBytesNondet(ids IdsManager, vm *VirtualMachine, constants *map[string]Felt) error { + nBytes, err := ids.GetFelt("n_bytes", vm) + if err != nil { + return err + } + bytesInWord, err := ids.GetConst("KECCAK_FULL_RATE_IN_BYTES", constants) + if nBytes.Cmp(bytesInWord) != -1 { + return vm.Segments.Memory.Insert(vm.RunContext.Ap, NewMaybeRelocatableFelt(FeltOne())) + } + return vm.Segments.Memory.Insert(vm.RunContext.Ap, NewMaybeRelocatableFelt(FeltZero())) +} + +func blockPermutation(ids IdsManager, vm *VirtualMachine, constants *map[string]Felt) error { + const KECCAK_SIZE = 25 + keccakStateSizeFeltsFelt, err := ids.GetConst("KECCAK_STATE_SIZE_FELTS", constants) + if err != nil { + return err + } + if keccakStateSizeFeltsFelt.Cmp(FeltFromUint64(KECCAK_SIZE)) != 0 { + return errors.New("Assertion failed: _keccak_state_size_felts == 25") + } + + keccakPtr, err := ids.GetRelocatable("keccak_ptr", vm) + if err != nil { + return err + } + startPtr, err := keccakPtr.SubUint(KECCAK_SIZE) + if err != nil { + return err + } + inputFelt, err := vm.Segments.GetFeltRange(startPtr, KECCAK_SIZE) + if err != nil { + return err + } + + var inputU64 [KECCAK_SIZE]uint64 + for i := 0; i < KECCAK_SIZE; i++ { + val, err := inputFelt[i].ToU64() + if err != nil { + return err + } + inputU64[i] = val + } + + builtins.KeccakF1600(&inputU64) + + output := make([]MaybeRelocatable, 0, KECCAK_SIZE) + for i := 0; i < KECCAK_SIZE; i++ { + output = append(output, *NewMaybeRelocatableFelt(FeltFromUint64(inputU64[i]))) + } + + _, err = vm.Segments.LoadData(keccakPtr, &output) + return err +} + +func cairoKeccakFinalize(ids IdsManager, vm *VirtualMachine, constants *map[string]Felt, blockSizeLimit uint64) error { + const KECCAK_SIZE = 25 + keccakStateSizeFeltsFelt, err := ids.GetConst("KECCAK_STATE_SIZE_FELTS", constants) + if err != nil { + return err + } + if keccakStateSizeFeltsFelt.Cmp(FeltFromUint64(KECCAK_SIZE)) != 0 { + return errors.New("Assertion failed: _keccak_state_size_felts == 25") + } + + blockSizeFelt, err := ids.GetConst("BLOCK_SIZE", constants) + if err != nil { + return err + } + if blockSizeFelt.Cmp(FeltFromUint64(blockSizeLimit)) != -1 { + return errors.Errorf("assert 0 <= _block_size < %d", blockSizeLimit) + } + blockSize, _ := blockSizeFelt.ToU64() + var input [KECCAK_SIZE]uint64 + builtins.KeccakF1600(&input) + padding := make([]MaybeRelocatable, 0, KECCAK_SIZE*2*blockSize) + for i := 0; i < KECCAK_SIZE; i++ { + padding = append(padding, *NewMaybeRelocatableFelt(FeltZero())) + } + for i := 0; i < KECCAK_SIZE; i++ { + padding = append(padding, *NewMaybeRelocatableFelt(FeltFromUint64(input[i]))) + } + for i := 1; i < int(blockSize); i++ { + padding = append(padding, padding[:50]...) + } + keccakEndPtr, err := ids.GetRelocatable("keccak_ptr_end", vm) + if err != nil { + return err + } + _, err = vm.Segments.LoadData(keccakEndPtr, &padding) + return err +} + +func keccakWriteArgs(ids IdsManager, vm *VirtualMachine) error { + inputs, err := ids.GetRelocatable("inputs", vm) + if err != nil { + return err + } + low, err := ids.GetFelt("low", vm) + if err != nil { + return err + } + high, err := ids.GetFelt("high", vm) + if err != nil { + return err + } + low_args := []MaybeRelocatable{ + *NewMaybeRelocatableFelt(low.And(FeltFromUint64(math.MaxUint64))), + *NewMaybeRelocatableFelt(low.Shr(64)), + } + high_args := []MaybeRelocatable{ + *NewMaybeRelocatableFelt(high.And(FeltFromUint64(math.MaxUint64))), + *NewMaybeRelocatableFelt(high.Shr(64)), + } + + inputs, err = vm.Segments.LoadData(inputs, &low_args) + if err != nil { + return err + } + _, err = vm.Segments.LoadData(inputs, &high_args) + return err +} diff --git a/pkg/hints/keccak_hints_test.go b/pkg/hints/keccak_hints_test.go index 6f20d8e4..3fe61996 100644 --- a/pkg/hints/keccak_hints_test.go +++ b/pkg/hints/keccak_hints_test.go @@ -166,3 +166,226 @@ func TestUnsafeKeccakFinalizeOk(t *testing.T) { t.Errorf("Wrong/No ids.low\n Expected %s, got %s.", expectedLow.ToHexString(), low.ToHexString()) } } + +func TestCompareBytesInWordHintEq(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + // Advance fp to avoid clashes with values inserted into ap + vm.RunContext.Fp.Offset += 1 + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n_bytes": {NewMaybeRelocatableFelt(FeltFromUint64(17))}, + }, + vm, + ) + constants := SetupConstantsForTest( + map[string]Felt{ + "BYTES_IN_WORD": FeltFromUint64(17), + }, + &idsManager) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: COMPARE_BYTES_IN_WORD_NONDET, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("COMPARE_BYTES_IN_WORD_NONDET hint test failed with error %s", err) + } + // Check the value of memory[ap] + val, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil || !val.IsZero() { + t.Error("Wrong/No value inserted into ap") + } +} + +func TestCompareBytesInWordHintGt(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + // Advance fp to avoid clashes with values inserted into ap + vm.RunContext.Fp.Offset += 1 + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n_bytes": {NewMaybeRelocatableFelt(FeltFromUint64(18))}, + }, + vm, + ) + constants := SetupConstantsForTest( + map[string]Felt{ + "BYTES_IN_WORD": FeltFromUint64(17), + }, + &idsManager) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: COMPARE_BYTES_IN_WORD_NONDET, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("COMPARE_BYTES_IN_WORD_NONDET hint test failed with error %s", err) + } + // Check the value of memory[ap] + val, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil || !val.IsZero() { + t.Error("Wrong/No value inserted into ap") + } +} + +func TestCompareBytesInWordHintLt(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + // Advance fp to avoid clashes with values inserted into ap + vm.RunContext.Fp.Offset += 1 + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n_bytes": {NewMaybeRelocatableFelt(FeltFromUint64(16))}, + }, + vm, + ) + constants := SetupConstantsForTest( + map[string]Felt{ + "BYTES_IN_WORD": FeltFromUint64(17), + }, + &idsManager) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: COMPARE_BYTES_IN_WORD_NONDET, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("COMPARE_BYTES_IN_WORD_NONDET hint test failed with error %s", err) + } + // Check the value of memory[ap] + val, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil || val != FeltOne() { + t.Error("Wrong/No value inserted into ap") + } +} + +func TestCompareKeccakFullRateInBytesHintEq(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + // Advance fp to avoid clashes with values inserted into ap + vm.RunContext.Fp.Offset += 1 + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n_bytes": {NewMaybeRelocatableFelt(FeltFromUint64(17))}, + }, + vm, + ) + constants := SetupConstantsForTest( + map[string]Felt{ + "KECCAK_FULL_RATE_IN_BYTES": FeltFromUint64(17), + }, + &idsManager) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET hint test failed with error %s", err) + } + // Check the value of memory[ap] + val, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil || val != FeltOne() { + t.Error("Wrong/No value inserted into ap") + } +} + +func TestCompareKeccakFullRateInBytesHintGt(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + // Advance fp to avoid clashes with values inserted into ap + vm.RunContext.Fp.Offset += 1 + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n_bytes": {NewMaybeRelocatableFelt(FeltFromUint64(18))}, + }, + vm, + ) + constants := SetupConstantsForTest( + map[string]Felt{ + "KECCAK_FULL_RATE_IN_BYTES": FeltFromUint64(17), + }, + &idsManager) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET hint test failed with error %s", err) + } + // Check the value of memory[ap] + val, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil || val != FeltOne() { + t.Error("Wrong/No value inserted into ap") + } +} + +func TestCompareKeccakFullRateInBytesHintLt(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + // Advance fp to avoid clashes with values inserted into ap + vm.RunContext.Fp.Offset += 1 + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "n_bytes": {NewMaybeRelocatableFelt(FeltFromUint64(16))}, + }, + vm, + ) + constants := SetupConstantsForTest( + map[string]Felt{ + "KECCAK_FULL_RATE_IN_BYTES": FeltFromUint64(17), + }, + &idsManager) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET hint test failed with error %s", err) + } + // Check the value of memory[ap] + val, err := vm.Segments.Memory.GetFelt(vm.RunContext.Ap) + if err != nil || !val.IsZero() { + t.Error("Wrong/No value inserted into ap") + } +} + +func TestBlockPermutationOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + keccak_ptr := vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "keccak_ptr": {NewMaybeRelocatableRelocatable(keccak_ptr.AddUint(25))}, + }, + vm, + ) + data := make([]MaybeRelocatable, 0, 25) + for i := 0; i < 25; i++ { + data = append(data, *NewMaybeRelocatableFelt(FeltZero())) + } + vm.Segments.LoadData(keccak_ptr, &data) + hintProcessor := CairoVmHintProcessor{} + constants := SetupConstantsForTest( + map[string]Felt{ + "KECCAK_STATE_SIZE_FELTS": FeltFromUint64(25), + }, + &idsManager) + hintData := any(HintData{ + Ids: idsManager, + Code: BLOCK_PERMUTATION, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, &constants, nil) + if err != nil { + t.Errorf("BLOCK_PERMUTATION hint test failed with error %s", err) + } +} diff --git a/pkg/hints/set_hints.go b/pkg/hints/set_hints.go new file mode 100644 index 00000000..2435aa02 --- /dev/null +++ b/pkg/hints/set_hints.go @@ -0,0 +1,81 @@ +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" + "github.com/pkg/errors" + "reflect" +) + +/* +Implements hint: + +assert ids.elm_size > 0 +assert ids.set_ptr <= ids.set_end_ptr +elm_list = memory.get_range(ids.elm_ptr, ids.elm_size) +for i in range(0, ids.set_end_ptr - ids.set_ptr, ids.elm_size): + + if memory.get_range(ids.set_ptr + i, ids.elm_size) == elm_list: + ids.index = i // ids.elm_size + ids.is_elm_in_set = 1 + break + +else: + + ids.is_elm_in_set = 0 +*/ +func setAdd(ids IdsManager, vm *VirtualMachine) error { + setPtr, err := ids.GetRelocatable("set_ptr", vm) + if err != nil { + return err + } + elmSizeFelt, err := ids.GetFelt("elm_size", vm) + if err != nil { + return err + } + elmPtr, err := ids.GetRelocatable("elm_ptr", vm) + if err != nil { + return err + } + setEndPtr, err := ids.GetRelocatable("set_end_ptr", vm) + if err != nil { + return err + } + + if elmSizeFelt.IsZero() { + return errors.Errorf("assert ids.elm_size > 0") + } + + elmSize, err := elmSizeFelt.ToUint() + + if err != nil { + return err + } + + if setPtr.Offset > setEndPtr.Offset { + return errors.Errorf("expected set_ptr: %v <= set_end_ptr: %v", setPtr, setEndPtr) + } + + elem, err := vm.Segments.Memory.GetRange(elmPtr, elmSize) + if err != nil { + return err + } + + for i := uint(0); i < setEndPtr.Offset-setPtr.Offset-elmSize; i++ { + otherElm, err := vm.Segments.Memory.GetRange(setPtr.AddUint(i*elmSize), elmSize) + if err != nil { + return err + } + if reflect.DeepEqual(elem, otherElm) { + err := ids.Insert("index", NewMaybeRelocatableFelt(FeltFromUint(i)), vm) + if err != nil { + return err + } + return ids.Insert("is_elm_in_set", NewMaybeRelocatableFelt(FeltOne()), vm) + } + } + + return ids.Insert("is_elm_in_set", NewMaybeRelocatableFelt(FeltZero()), vm) +} diff --git a/pkg/hints/set_hints_test.go b/pkg/hints/set_hints_test.go new file mode 100644 index 00000000..a7bbd9fa --- /dev/null +++ b/pkg/hints/set_hints_test.go @@ -0,0 +1,104 @@ +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 TestSetAddElmInSet(t *testing.T) { + vm := NewVirtualMachine() + // Initialize segments + vm.Segments.AddSegment() + vm.Segments.AddSegment() + // element to insert + vm.Segments.Memory.Insert(NewRelocatable(1, 0), NewMaybeRelocatableFelt(FeltFromUint64(2))) + // set + vm.Segments.Memory.Insert(NewRelocatable(1, 1), NewMaybeRelocatableFelt(FeltFromUint64(1))) + vm.Segments.Memory.Insert(NewRelocatable(1, 2), NewMaybeRelocatableFelt(FeltFromUint64(2))) + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "elm_ptr": {NewMaybeRelocatableRelocatable(NewRelocatable(1, 0))}, + "set_ptr": {NewMaybeRelocatableRelocatable(NewRelocatable(1, 1))}, + "set_end_ptr": {NewMaybeRelocatableRelocatable(NewRelocatable(1, 4))}, + "elm_size": {NewMaybeRelocatableFelt(FeltFromUint64(1))}, + "index": {nil}, + "is_elm_in_set": {nil}, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: SET_ADD, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("SET_ADD failed with error: %s", err) + } + + isElmInSet, err := idsManager.GetFelt("is_elm_in_set", vm) + if err != nil { + t.Errorf("SET_ADD couldn't get is_elm_in_set: %s", err) + } + + if !isElmInSet.IsOne() { + t.Errorf("Expected is_elm_in_set to be 1, got: %s", isElmInSet.ToSignedFeltString()) + } + + index, err := idsManager.GetFelt("index", vm) + if err != nil { + t.Errorf("SET_ADD couldn't get index: %s", err) + } + if !index.IsOne() { + t.Errorf("Expected element to be found at 1, got index: %s", index.ToSignedFeltString()) + } +} + +func TestSetAddElmNotInSet(t *testing.T) { + vm := NewVirtualMachine() + // Initialize segments + vm.Segments.AddSegment() + vm.Segments.AddSegment() + // element to insert + vm.Segments.Memory.Insert(NewRelocatable(1, 0), NewMaybeRelocatableFelt(FeltFromUint64(3))) + // set + vm.Segments.Memory.Insert(NewRelocatable(1, 1), NewMaybeRelocatableFelt(FeltFromUint64(1))) + vm.Segments.Memory.Insert(NewRelocatable(1, 2), NewMaybeRelocatableFelt(FeltFromUint64(2))) + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "elm_ptr": {NewMaybeRelocatableRelocatable(NewRelocatable(1, 0))}, + "set_ptr": {NewMaybeRelocatableRelocatable(NewRelocatable(1, 1))}, + "set_end_ptr": {NewMaybeRelocatableRelocatable(NewRelocatable(1, 3))}, + "elm_size": {NewMaybeRelocatableFelt(FeltFromUint64(1))}, + "index": {nil}, + "is_elm_in_set": {nil}, + }, + vm, + ) + + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: SET_ADD, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("SET_ADD failed with error: %s", err) + } + + isElmInSet, err := idsManager.GetFelt("is_elm_in_set", vm) + if err != nil { + t.Errorf("SET_ADD couldn't get is_elm_in_set: %s", err) + } + + if !isElmInSet.IsZero() { + t.Errorf("Expected is_elm_in_set to be 1, got: %s", isElmInSet.ToSignedFeltString()) + } +} diff --git a/pkg/lambdaworks/lambdaworks.go b/pkg/lambdaworks/lambdaworks.go index d6b27bdc..3cd29d21 100644 --- a/pkg/lambdaworks/lambdaworks.go +++ b/pkg/lambdaworks/lambdaworks.go @@ -1,7 +1,7 @@ package lambdaworks /* -#cgo LDFLAGS: pkg/lambdaworks/lib/liblambdaworks.a -ldl +#cgo LDFLAGS: pkg/lambdaworks/lib/liblambdaworks.a #include "lib/lambdaworks.h" #include */ @@ -59,6 +59,12 @@ func FeltFromUint64(value uint64) Felt { return fromC(result) } +func FeltFromUint(value uint) Felt { + var result C.felt_t + C.from_uint(&result[0], C.uint_t(value)) + return fromC(result) +} + func FeltFromHex(value string) Felt { cs := C.CString(value) defer C.free(unsafe.Pointer(cs)) @@ -77,7 +83,7 @@ func FeltFromDecString(value string) Felt { return fromC(result) } -// turns a felt to usize +// turns a felt to u64 func (felt Felt) ToU64() (uint64, error) { if felt.limbs[0] == 0 && felt.limbs[1] == 0 && felt.limbs[2] == 0 { return uint64(felt.limbs[3]), nil @@ -86,6 +92,15 @@ func (felt Felt) ToU64() (uint64, error) { } } +// turns a felt to usize +func (felt Felt) ToUint() (uint, error) { + felt_u64, err := felt.ToU64() + if err != nil { + return 0, ConversionError(felt, "uint") + } + return uint(felt_u64), nil +} + func (felt Felt) ToLeBytes() *[32]byte { var result_c [32]C.uint8_t var value C.felt_t = felt.toC() @@ -294,10 +309,9 @@ func FeltFromBigInt(n *big.Int) Felt { if n.Cmp(prime) != -1 { n = new(big.Int).Mod(n, prime) } - bytes := n.Bytes() - var bytes32 [32]byte - copy(bytes32[:], bytes) - return FeltFromLeBytes(&bytes32) + + value := n.Text(10) + return FeltFromDecString(value) } const CAIRO_PRIME_HEX = "0x800000000000011000000000000000000000000000000000000000000000001" diff --git a/pkg/lambdaworks/lambdaworks_test.go b/pkg/lambdaworks/lambdaworks_test.go index bffb7e7a..bf7c404d 100644 --- a/pkg/lambdaworks/lambdaworks_test.go +++ b/pkg/lambdaworks/lambdaworks_test.go @@ -426,6 +426,41 @@ func TestToU64Fail(t *testing.T) { t.Errorf("Conversion test should fail with error: %v", expected_err) } } + +func TestToUint1(t *testing.T) { + felt := lambdaworks.FeltOne() + result, err := felt.ToUint() + + var expected uint = 1 + + if expected != result { + t.Errorf("Error in conversion expected: %v, got %v with err: %v", expected, result, err) + } + +} + +func TestToUint10230(t *testing.T) { + felt := lambdaworks.FeltFromUint(10230) + result, err := felt.ToUint() + + var expected uint = 10230 + + if expected != result { + t.Errorf("Error in conversion expected: %v, got %v with err: %v", expected, result, err) + } +} + +func TestToUintFail(t *testing.T) { + felt := lambdaworks.FeltFromDecString("9999999999999999999999999") + + _, err := felt.ToUint() + expected_err := lambdaworks.ConversionError(felt, "uint") + + if err.Error() != expected_err.Error() { + t.Errorf("Conversion test should fail with error: %v", expected_err) + } +} + func TestFeltIsZero(t *testing.T) { f_zero := lambdaworks.FeltZero() diff --git a/pkg/lambdaworks/lib/lambdaworks.h b/pkg/lambdaworks/lib/lambdaworks.h index 850c9809..05764461 100644 --- a/pkg/lambdaworks/lib/lambdaworks.h +++ b/pkg/lambdaworks/lib/lambdaworks.h @@ -3,6 +3,7 @@ #include typedef uint64_t limb_t; +typedef unsigned int uint_t; /* A 256 bit prime field element (felt), represented as four limbs (integers). */ @@ -11,6 +12,9 @@ typedef limb_t felt_t[4]; /* Gets a felt_t representing the "value" number, in montgomery format. */ void from(felt_t result, uint64_t value); +/* Gets a felt_t representing the "value" number, in montgomery format. */ +void from_uint(felt_t result, uint_t value); + /*Gets a felt_t representing the "value" hexadecimal string, in montgomery * format. */ void from_hex(felt_t result, char *value); diff --git a/pkg/lambdaworks/lib/lambdaworks/src/lib.rs b/pkg/lambdaworks/lib/lambdaworks/src/lib.rs index b772d13d..14e27392 100644 --- a/pkg/lambdaworks/lib/lambdaworks/src/lib.rs +++ b/pkg/lambdaworks/lib/lambdaworks/src/lib.rs @@ -60,6 +60,11 @@ pub extern "C" fn from(result: Limbs, value: u64) { felt_to_limbs(Felt::from(value), result); } +#[no_mangle] +pub extern "C" fn from_uint(result: Limbs, value: usize) { + felt_to_limbs(Felt::from(value as u64), result); +} + #[no_mangle] pub extern "C" fn from_hex(result: Limbs, value: *const libc::c_char) { let val_cstr = unsafe { core::ffi::CStr::from_ptr(value) }; diff --git a/pkg/utils/math_utils.go b/pkg/utils/math_utils.go index 18adc42b..54000f2d 100644 --- a/pkg/utils/math_utils.go +++ b/pkg/utils/math_utils.go @@ -44,5 +44,9 @@ func MaxInt(x int, y int) int { } func DivCeil(x uint, y uint) uint { - return 1 + (x-1)/y + q := x / y + if x%y != 0 { + q++ + } + return q } diff --git a/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index b79cb921..5cae0a81 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -39,14 +39,26 @@ func TestPoseidonBuiltin(t *testing.T) { testProgram("poseidon_builtin", t) } +func TestPoseidonBuiltinProofMode(t *testing.T) { + testProgramProof("poseidon_builtin", t) +} + func TestPoseidonHash(t *testing.T) { testProgram("poseidon_hash", t) } +func TestPoseidonHashProofMode(t *testing.T) { + testProgramProof("poseidon_hash", t) +} + func TestSimplePrint(t *testing.T) { testProgram("simple_print", t) } +func TestSimplePrintProofMode(t *testing.T) { + testProgramProof("simple_print", t) +} + func TestWriteOutputProgram(t *testing.T) { cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false} runner, err := cairo_run.CairoRun("../../../cairo_programs/bitwise_output.json", cairoRunConfig) @@ -69,32 +81,66 @@ func TestPedersenBuiltin(t *testing.T) { testProgram("pedersen_test", t) } +func TestPedersenBuiltinProofMode(t *testing.T) { + testProgramProof("pedersen_test", t) +} + func TestPedersenAndBitwiseBuiltins(t *testing.T) { testProgram("pedersen_and_bitwise_builtins", t) } +func TestPedersenAndBitwiseBuiltinsProofMode(t *testing.T) { + testProgramProof("pedersen_and_bitwise_builtins", t) +} + func TestPedersenAndBitwiseBuiltinsWithAllocLocals(t *testing.T) { testProgram("pedersen_and_bitwise_builtins_with_alloc_locals", t) } +func TestPedersenAndBitwiseBuiltinsWithAllocLocalsProofMode(t *testing.T) { + testProgramProof("pedersen_and_bitwise_builtins_with_alloc_locals", t) +} + func TestAllocAddSegmentHint(t *testing.T) { testProgram("if_reloc_equal", t) } +func TestAllocAddSegmentHintProofMode(t *testing.T) { + testProgramProof("if_reloc_equal", t) +} + func TestAssertNNHint(t *testing.T) { testProgram("assert_nn", t) } +func TestAssertNNHintProofMode(t *testing.T) { + testProgramProof("assert_nn", t) +} + func TestAbsValue(t *testing.T) { testProgram("abs_value", t) } + +func TestAbsValueProofMode(t *testing.T) { + testProgramProof("abs_value", t) +} + func TestCommonSignature(t *testing.T) { testProgram("common_signature", t) } + +func TestCommonSignatureProofMode(t *testing.T) { + testProgramProof("common_signature", t) +} + func TestAssertNotZeroHint(t *testing.T) { testProgram("assert_not_zero", t) } +func TestAssertNotZeroHintProofMode(t *testing.T) { + testProgramProof("assert_not_zero", t) +} + func TestBitwiseRecursion(t *testing.T) { testProgram("bitwise_recursion", t) } @@ -115,87 +161,179 @@ func TestDict(t *testing.T) { testProgram("dict", t) } +func TestDictProofMode(t *testing.T) { + testProgramProof("dict", t) +} + func TestDictUpdate(t *testing.T) { testProgram("dict_update", t) } +func TestDictUpdateProofMode(t *testing.T) { + testProgramProof("dict_update", t) +} + func TestAssertNotEqualHint(t *testing.T) { testProgram("assert_not_equal", t) } +func TestAssertNotEqualHintProofMode(t *testing.T) { + testProgramProof("assert_not_equal", t) +} + func TestPowHint(t *testing.T) { testProgram("pow", t) } +func TestPowHintProofMode(t *testing.T) { + testProgramProof("pow", t) +} + func TestSqrtHint(t *testing.T) { testProgram("sqrt", t) } +func TestSqrtHintProofMode(t *testing.T) { + testProgramProof("sqrt", t) +} + func TestUnsafeKeccak(t *testing.T) { testProgram("unsafe_keccak", t) } +func TestUnsafeKeccakProofMode(t *testing.T) { + testProgramProof("unsafe_keccak", t) +} + func TestUnsafeKeccakFinalize(t *testing.T) { testProgram("unsafe_keccak_finalize", t) } +func TestUnsafeKeccakFinalizeProofMode(t *testing.T) { + testProgramProof("unsafe_keccak_finalize", t) +} + func TestUnsignedDivRemHint(t *testing.T) { - cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false} - _, err := cairo_run.CairoRun("../../../cairo_programs/unsigned_div_rem.json", cairoRunConfig) - if err != nil { - t.Errorf("Program execution failed with error: %s", err) - } + testProgram("unsigned_div_rem", t) +} + +func TestUnsignedDivRemHintProofMode(t *testing.T) { + testProgramProof("unsigned_div_rem", t) +} + +func TestSignedDivRemHint(t *testing.T) { + testProgram("signed_div_rem", t) +} + +func TestSignedDivRemHintProofMode(t *testing.T) { + testProgramProof("signed_div_rem", t) +} + +func TestSetAddHint(t *testing.T) { + testProgram("set_add", t) } func TestMemcpyHint(t *testing.T) { testProgram("memcpy_test", t) } +func TestMemcpyHintProofMode(t *testing.T) { + testProgramProof("memcpy_test", t) +} + func TestAssertLeFelt(t *testing.T) { testProgram("assert_le_felt", t) } +func TestAssertLeFeltProofMode(t *testing.T) { + testProgramProof("assert_le_felt", t) +} + func TestAssertLtFelt(t *testing.T) { testProgram("assert_lt_felt", t) } +func TestAssertLtFeltProofMode(t *testing.T) { + testProgramProof("assert_lt_felt", t) +} + func TestMemsetHint(t *testing.T) { testProgram("memset", t) } +func TestMemsetHintProofMode(t *testing.T) { + testProgramProof("memset", t) +} + func TestMathCmp(t *testing.T) { testProgram("math_cmp", t) } + +func TestMathCmpProofMode(t *testing.T) { + testProgramProof("math_cmp", t) +} + func TestSquashDict(t *testing.T) { testProgram("squash_dict", t) } -func TestSignedDivRemHint(t *testing.T) { - cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false} - _, err := cairo_run.CairoRun("../../../cairo_programs/signed_div_rem.json", cairoRunConfig) - if err != nil { - t.Errorf("Program execution failed with error: %s", err) - } +func TestSquashDictProofMode(t *testing.T) { + testProgramProof("squash_dict", t) +} + +func TestFindElementHint(t *testing.T) { + testProgram("find_element", t) +} + +func TestSearchSortedLowerHint(t *testing.T) { + testProgram("search_sorted_lower", t) } func TestAssert250BitHint(t *testing.T) { testProgram("assert_250_bit_element_array", t) } +func TestAssert250BitHintProofMode(t *testing.T) { + testProgramProof("assert_250_bit_element_array", t) +} + func TestDictSquash(t *testing.T) { testProgram("dict_squash", t) } +func TestDictSquashProofMode(t *testing.T) { + testProgramProof("dict_squash", t) +} + func TestSplitFeltHint(t *testing.T) { testProgram("split_felt", t) } +func TestSplitFeltHintProofMode(t *testing.T) { + testProgramProof("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) - } + testProgram("split_int", t) +} + +func TestSplitIntHintProofMode(t *testing.T) { + testProgramProof("split_int", t) +} + +func TestIntegrationEcDoubleSlope(t *testing.T) { + testProgram("ec_double_slope", t) +} +func TestKeccakIntegrationTests(t *testing.T) { + testProgram("keccak_integration_tests", t) +} + +func TestCairoKeccak(t *testing.T) { + testProgram("cairo_keccak", t) +} + +func TestKeccakAddUint256(t *testing.T) { + testProgram("keccak_add_uint256", t) } func TestEcMulInner(t *testing.T) { diff --git a/pkg/vm/memory/memory.go b/pkg/vm/memory/memory.go index 9985bc44..4939a8a0 100644 --- a/pkg/vm/memory/memory.go +++ b/pkg/vm/memory/memory.go @@ -150,6 +150,19 @@ func (m *Memory) GetFelt(addr Relocatable) (lambdaworks.Felt, error) { return lambdaworks.FeltZero(), err } +// Get a range of memory from the starting relocatable to the starting relocatable + size +func (m *Memory) GetRange(start Relocatable, size uint) ([]MaybeRelocatable, error) { + var res []MaybeRelocatable + for i := uint(0); i < size; i++ { + val, err := m.Get(start.AddUint(i)) + if err != nil { + return nil, err + } + res = append(res, *val) + } + return res, nil +} + // Adds a validation rule for a given segment func (m *Memory) AddValidationRule(SegmentIndex uint, rule ValidationRule) { m.validationRules[SegmentIndex] = rule diff --git a/pkg/vm/memory/relocatable.go b/pkg/vm/memory/relocatable.go index 756f5afb..3505f73b 100644 --- a/pkg/vm/memory/relocatable.go +++ b/pkg/vm/memory/relocatable.go @@ -132,7 +132,6 @@ func (m *MaybeRelocatable) IsZero() bool { // Turns a MaybeRelocatable into a Felt252 value. // If the inner value is an Int, it will extract the Felt252 value from it. // If the inner value is a Relocatable, it will relocate it according to the relocation_table -// TODO: Return value should be of type (felt, error) func (m *MaybeRelocatable) RelocateValue(relocationTable *[]uint) (lambdaworks.Felt, error) { inner_felt, ok := m.GetFelt() if ok { diff --git a/pkg/vm/vm_core.go b/pkg/vm/vm_core.go index 1c3804de..b8aa1e2f 100644 --- a/pkg/vm/vm_core.go +++ b/pkg/vm/vm_core.go @@ -247,7 +247,7 @@ func (vm *VirtualMachine) OpcodeAssertions(instruction Instruction, operands Ope return &VirtualMachineError{"UnconstrainedResAssertEq"} } if !operands.Res.IsEqual(&operands.Dst) { - return &VirtualMachineError{"DiffAssertValues"} + return &VirtualMachineError{fmt.Sprintf("An ASSERT_EQ instruction failed: %s != %s.", operands.Res.ToString(), operands.Dst.ToString())} } case Call: new_rel := vm.RunContext.Pc.AddUint(instruction.Size()) @@ -324,11 +324,13 @@ func (vm *VirtualMachine) DeduceOp1(instruction *Instruction, dst *memory.MaybeR return &dst_rel, dst, nil } case ResMul: - dst_felt, dst_is_felt := dst.GetFelt() - op0_felt, op0_is_felt := op0.GetFelt() - if dst_is_felt && op0_is_felt && !op0_felt.IsZero() { - res := memory.NewMaybeRelocatableFelt(dst_felt.Div(op0_felt)) - return res, dst, nil + if op0 != nil && dst != nil { + dst_felt, dst_is_felt := dst.GetFelt() + op0_felt, op0_is_felt := op0.GetFelt() + if dst_is_felt && op0_is_felt && !op0_felt.IsZero() { + res := memory.NewMaybeRelocatableFelt(dst_felt.Div(op0_felt)) + return res, dst, nil + } } } } diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 25ade31b..8f3ab08c 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -520,6 +520,14 @@ func TestDeduceMemoryCellNoBuiltins(t *testing.T) { } } +func TestRelocateTraceWithoutRelocatingTrace(t *testing.T) { + virtualMachine := vm.NewVirtualMachine() + _, err := virtualMachine.GetRelocatedTrace() + if err == nil { + t.Fatalf("GetRelocatedTrace should have failed") + } +} + func TestRelocateTraceOneEntry(t *testing.T) { virtualMachine := vm.NewVirtualMachine() buildTestProgramMemory(virtualMachine) @@ -615,7 +623,7 @@ func TestOpcodeAssertionsInstructionFailed(t *testing.T) { testVm := vm.NewVirtualMachine() err := testVm.OpcodeAssertions(instruction, operands) - if err.Error() != "DiffAssertValues" { + if err == nil { t.Error("Assertion should error out with DiffAssertValues") } @@ -645,7 +653,7 @@ func TestOpcodeAssertionsInstructionFailedRelocatables(t *testing.T) { testVm := vm.NewVirtualMachine() err := testVm.OpcodeAssertions(instruction, operands) - if err.Error() != "DiffAssertValues" { + if err == nil { t.Error("Assertion should error out with DiffAssertValues") } }