diff --git a/.gitignore b/.gitignore index 4eb9c9ba8..4fb222339 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ bin/ +.idea/ +.python-version + .DS_Store vendor/ -.idea/ \ No newline at end of file diff --git a/pkg/vm/memory/memory_manager.go b/pkg/vm/memory/memory_manager.go index 57fd52d39..cda7e4de1 100644 --- a/pkg/vm/memory/memory_manager.go +++ b/pkg/vm/memory/memory_manager.go @@ -8,14 +8,45 @@ type MemoryManager struct { Memory *Memory } -func CreateMemoryManager() (*MemoryManager, error) { +// Creates a new memory manager +func CreateMemoryManager() *MemoryManager { memory := InitializeEmptyMemory() return &MemoryManager{ Memory: memory, - }, nil + } } -func (mm *MemoryManager) GetByteCodeAt(segmentIndex uint64, offset uint64) *f.Element { - return nil +// It returns all segments in memory but relocated as a single segment +// Each element is a pointer to a field element, if the cell was not accessed, +// nil is stored instead +func (mm *MemoryManager) RelocateMemory() []*f.Element { + maxMemoryUsed := 0 + // segmentsOffsets[0] = 0 + // segmentsOffsets[1] = len(segment[0]) + // segmentsOffsets[N] = len(segment[n - 1]) + sum of segmentsOffsets[n - i] for i in [0, n-1] + segmentsOffsets := make([]uint64, uint64(len(mm.Memory.Segments))+1) + for i, segment := range mm.Memory.Segments { + maxMemoryUsed += len(segment.Data) + segmentsOffsets[i+1] = segmentsOffsets[i] + uint64(len(segment.Data)) + } + + relocatedMemory := make([]*f.Element, maxMemoryUsed) + for i, segment := range mm.Memory.Segments { + for j, cell := range segment.Data { + var felt *f.Element + if !cell.Accessed { + continue + } + if cell.Value.IsAddress() { + felt = cell.Value.address.Relocate(segmentsOffsets) + } else { + felt = cell.Value.felt + } + + relocatedMemory[segmentsOffsets[i]+uint64(j)] = felt + } + } + + return relocatedMemory } diff --git a/pkg/vm/memory/memory_manager_test.go b/pkg/vm/memory/memory_manager_test.go new file mode 100644 index 000000000..3b2d500da --- /dev/null +++ b/pkg/vm/memory/memory_manager_test.go @@ -0,0 +1,149 @@ +package memory + +import ( + "fmt" + "testing" + + f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" + "github.com/stretchr/testify/require" +) + +func TestMemoryRelocationWithFelt(t *testing.T) { + // segment 0: [2, -, -, 3] + // segment 3: [5, -, 7, -, 11, 13] + // relocated: [2, -, -, 3, 5, -, 7, -, 11, 13] + + manager := CreateMemoryManager() + updateMemoryWithValues( + manager.Memory, + []memoryWrite{ + // segment zero + {0, 0, uint64(2)}, + {0, 3, uint64(3)}, + // segment three + {3, 0, uint64(5)}, + {3, 2, uint64(7)}, + {3, 4, uint64(11)}, + {3, 5, uint64(13)}, + }, + ) + + res := manager.RelocateMemory() + + expected := []*f.Element{ + // segment zero + new(f.Element).SetUint64(2), + nil, + nil, + new(f.Element).SetUint64(3), + // segment three + new(f.Element).SetUint64(5), + nil, + new(f.Element).SetUint64(7), + nil, + new(f.Element).SetUint64(11), + new(f.Element).SetUint64(13), + } + + require.Equal(t, len(expected), len(res)) + require.Equal(t, expected, res) +} + +func TestMemoryRelocationWithAddress(t *testing.T) { + // segment 0: [-, 1, -, 1:5] (4) + // segment 1: [1, 4:3, 7, -, -, 13] (10) + // segment 2: [0:1] (11) + // segment 3: [2:0] (12) + // segment 4: [0:0, 1:1, 1:5, 15] (16) + // relocated: [ + // zero: -, 1, -, 9, + // one: 1, 15, 7, -, -, 13, + // two: 1, + // three: 10, + // four: 0, 5, 9, 15, + // ] + + manager := CreateMemoryManager() + updateMemoryWithValues( + manager.Memory, + []memoryWrite{ + // segment zero + {0, 1, uint64(1)}, + {0, 3, NewMemoryAddress(1, 5)}, + // segment one + {1, 0, uint64(1)}, + {1, 1, NewMemoryAddress(4, 3)}, + {1, 2, uint64(7)}, + {1, 5, uint64(13)}, + // segment two + {2, 0, NewMemoryAddress(0, 1)}, + // segment three + {3, 0, NewMemoryAddress(2, 0)}, + // segment four + {4, 0, NewMemoryAddress(0, 0)}, + {4, 1, NewMemoryAddress(1, 1)}, + {4, 2, NewMemoryAddress(1, 5)}, + {4, 3, uint64(15)}, + }, + ) + + res := manager.RelocateMemory() + + expected := []*f.Element{ + // segment zero + nil, + new(f.Element).SetUint64(1), + nil, + new(f.Element).SetUint64(9), + // segment one + new(f.Element).SetUint64(1), + new(f.Element).SetUint64(15), + new(f.Element).SetUint64(7), + nil, + nil, + new(f.Element).SetUint64(13), + // segment two + new(f.Element).SetUint64(1), + // segment three + new(f.Element).SetUint64(10), + // segment 4 + new(f.Element).SetUint64(0), + new(f.Element).SetUint64(5), + new(f.Element).SetUint64(9), + new(f.Element).SetUint64(15), + } + + require.Equal(t, len(expected), len(res)) + require.Equal(t, expected, res) +} + +type memoryWrite struct { + SegmentIndex uint64 + Offset uint64 + Value any +} + +func updateMemoryWithValues(memory *Memory, valuesToWrite []memoryWrite) { + var max_segment uint64 = 0 + for _, toWrite := range valuesToWrite { + // wrap any inside a memory value + val, err := MemoryValueFromAny(toWrite.Value) + if err != nil { + panic(err) + } + + // if the destination segment does not exist, create it + for toWrite.SegmentIndex >= max_segment { + max_segment += 1 + memory.AllocateEmptySegment() + } + + fmt.Println("c") + // write the memory val + err = memory.Write(toWrite.SegmentIndex, toWrite.Offset, val) + if err != nil { + panic(err) + } + + } +} diff --git a/pkg/vm/memory/memory_value.go b/pkg/vm/memory/memory_value.go index 1649c690b..0923f79cc 100644 --- a/pkg/vm/memory/memory_value.go +++ b/pkg/vm/memory/memory_value.go @@ -93,6 +93,13 @@ func (address *MemoryAddress) Sub(lhs *MemoryAddress, rhs any) (*MemoryAddress, } } +func (address *MemoryAddress) Relocate(segmentsOffset []uint64) *f.Element { + // no risk overflow because this sizes exists in actual Memory + // so if by chance the uint64 addition overflowed, then we have + // a machine with more than 2**64 bytes of memory (quite a lot!) + return new(f.Element).SetUint64(segmentsOffset[address.SegmentIndex] + address.Offset) +} + func (address MemoryAddress) String() string { return fmt.Sprintf( "Memory Address: segment: %d, offset: %d", address.SegmentIndex, address.Offset, @@ -137,6 +144,8 @@ func MemoryValueFromSegmentAndOffset[T constraints.Integer](segmentIndex, offset func MemoryValueFromAny(anyType any) (*MemoryValue, error) { switch t := anyType.(type) { + case uint64: + return MemoryValueFromInt(anyType.(uint64)), nil case *f.Element: return MemoryValueFromFieldElement(anyType.(*f.Element)), nil case *MemoryAddress: @@ -279,16 +288,3 @@ func (mv *MemoryValue) Uint64() (uint64, error) { return mv.felt.Uint64(), nil } - -// Note: Commenting this function since relocation is possibly going to look -// different. -// Given a map of segment relocation, update a memory address location -//func (r *MemoryAddress) Relocate(r1 *MemoryAddress, segmentsOffsets *map[uint64]*MemoryAddress) (*MemoryAddress, error) { -// if (*segmentsOffsets)[r1.SegmentIndex] == nil { -// return nil, fmt.Errorf("missing segment %d relocation rule", r.SegmentIndex) -// } -// -// r, err := r.Add((*segmentsOffsets)[r1.SegmentIndex], &MemoryAddress{0, r1.Offset}) -// -// return r, err -//} diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 6eb4b5fd3..d9463d311 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -28,48 +28,58 @@ type Context struct { Pc uint64 } +// relocates pc, ap and fp to be their real address value +// that is, pc + 0, ap + programSegmentOffset, fp + programSegmentOffset +func (ctx *Context) Relocate(executionSegmentOffset uint64) { + ctx.Ap += executionSegmentOffset + ctx.Fp += executionSegmentOffset +} + // This type represents the current execution context of the vm type VirtualMachineConfig struct { - Trace bool - // Todo(rodro): Update this property to include all builtins - Builtins bool + // If true, the vm outputs the trace and the relocated memory at the end of execution + ProofMode bool } type VirtualMachine struct { Context Context MemoryManager *mem.MemoryManager Step uint64 - Config VirtualMachineConfig + Trace []Context + config VirtualMachineConfig } // NewVirtualMachine creates a VM from the program bytecode using a specified config. func NewVirtualMachine(programBytecode []*f.Element, config VirtualMachineConfig) (*VirtualMachine, error) { - manager, err := mem.CreateMemoryManager() - if err != nil { - return nil, fmt.Errorf("error creating new virtual machine: %w", err) - } - + // Initialize memory with to initial segments: + // the first one for the program segment and + // the second one to keep track of the execution + manager := mem.CreateMemoryManager() // 0 (programSegment) <- segment where the bytecode is stored - _, err = manager.Memory.AllocateSegment(programBytecode) + _, err := manager.Memory.AllocateSegment(programBytecode) if err != nil { return nil, fmt.Errorf("error loading bytecode: %w", err) } - // 1 (executionSegment) <- segment where ap and fp move around - manager.Memory.Segments = append(manager.Memory.Segments, mem.EmptySegmentWithCapacity(10)) + manager.Memory.AllocateEmptySegment() + + // Initialize the trace if necesary + var trace []Context + if config.ProofMode { + trace = make([]Context, 0) + } return &VirtualMachine{ Context: Context{Fp: 0, Ap: 0, Pc: 0}, Step: 0, MemoryManager: manager, - Config: config, + Trace: trace, + config: config, }, nil } // todo(rodro): add a cache mechanism for not decoding the same instruction twice -// todo(rodro): how to know when te execute a hint or normal instruction - func (vm *VirtualMachine) RunStep(hintRunner HintRunner) error { // Run hint err := hintRunner.RunHint(vm) @@ -93,6 +103,11 @@ func (vm *VirtualMachine) RunStep(hintRunner HintRunner) error { return fmt.Errorf("cannot decode step at %d: %w", vm.Context.Pc, err) } + // store the trace before state change + if vm.config.ProofMode { + vm.Trace = append(vm.Trace, vm.Context) + } + err = vm.RunInstruction(instruction) if err != nil { return fmt.Errorf("cannot run step at %d: %w", vm.Context.Pc, err) @@ -107,8 +122,6 @@ func (vm *VirtualMachine) RunStepAt(hinter HintRunner, pc uint64) error { } func (vm *VirtualMachine) RunInstruction(instruction *Instruction) error { - // todo(rodro): any OffOpX can be negative, a better math system is required due to - // substraction. Also it will need to handle overflows and underflows dstCell, err := vm.getCellDst(instruction) if err != nil { return err @@ -162,6 +175,23 @@ func (vm *VirtualMachine) RunInstruction(instruction *Instruction) error { return nil } +// It returns the current trace entry, the public memory, and the occurrence of an error +func (vm *VirtualMachine) Proof() ([]Context, []*f.Element, error) { + if !vm.config.ProofMode { + return nil, nil, fmt.Errorf("cannot get proof if proof mode is off") + } + + totalBytecode := vm.MemoryManager.Memory.Segments[ProgramSegment].Len() + for i := range vm.Trace { + vm.Trace[i].Relocate(totalBytecode) + } + + // after that, get the relocated memory + relocatedMemory := vm.MemoryManager.RelocateMemory() + + return vm.Trace, relocatedMemory, nil +} + func (vm *VirtualMachine) getCellDst(instruction *Instruction) (*mem.Cell, error) { var dstRegister uint64 if instruction.DstRegister == Ap { diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 3ee37cfd6..fd4717607 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -16,8 +16,7 @@ func TestVMCreation(t *testing.T) { newElementPtr(5), newElementPtr(7), } - - vm, err := NewVirtualMachine(dummyBytecode, VirtualMachineConfig{false, false}) + vm, err := NewVirtualMachine(dummyBytecode, VirtualMachineConfig{false}) require.NoError(t, err) assert.Len(t, vm.MemoryManager.Memory.Segments, 2) @@ -35,8 +34,7 @@ func TestVMCreation(t *testing.T) { // - update FP: verify all posible cases, and when Res is a negative value func TestGetCellApDst(t *testing.T) { - vm, err := NewVirtualMachine(make([]*f.Element, 0), VirtualMachineConfig{false, false}) - require.NoError(t, err) + vm := defaultVirtualMachine() // Prepare vm with dummy values const offDest = 15 @@ -58,8 +56,7 @@ func TestGetCellApDst(t *testing.T) { } func TestGetCellFpDst(t *testing.T) { - vm, err := NewVirtualMachine(make([]*f.Element, 0), VirtualMachineConfig{false, false}) - require.NoError(t, err) + vm := defaultVirtualMachine() // Prepare vm with dummy values const ( @@ -84,8 +81,7 @@ func TestGetCellFpDst(t *testing.T) { } func TestGetApCellOp0(t *testing.T) { - vm, err := NewVirtualMachine(make([]*f.Element, 0), VirtualMachineConfig{false, false}) - require.NoError(t, err) + vm := defaultVirtualMachine() // Prepare vm with dummy values const ( @@ -108,15 +104,13 @@ func TestGetApCellOp0(t *testing.T) { } func TestGetImmCellOp1(t *testing.T) { - vm, err := NewVirtualMachine( + vm := defaultVirtualMachineWithBytecode( []*f.Element{ newElementPtr(0), // dummy newElementPtr(0), // dummy newElementPtr(1234), // imm }, - VirtualMachineConfig{false, false}, ) - require.NoError(t, err) // Prepare vm with dummy values const offOp1 = 1 // target imm @@ -136,9 +130,7 @@ func TestGetImmCellOp1(t *testing.T) { } func TestInferOperandSub(t *testing.T) { - vm, err := NewVirtualMachine(make([]*f.Element, 0), VirtualMachineConfig{false, false}) - require.NoError(t, err) - + vm := defaultVirtualMachine() instruction := Instruction{ Opcode: AssertEq, Res: AddOperands, @@ -168,8 +160,7 @@ func TestInferOperandSub(t *testing.T) { } func TestComputeAddRes(t *testing.T) { - vm, err := NewVirtualMachine(make([]*f.Element, 0), VirtualMachineConfig{false, false}) - require.NoError(t, err) + vm := defaultVirtualMachine() instruction := Instruction{ Res: AddOperands, @@ -197,9 +188,8 @@ func TestComputeAddRes(t *testing.T) { assert.Equal(t, expected, res) } -func (vm *VirtualMachine) TestOpcodeAssertionAssertEq(t *testing.T) { - vm, err := NewVirtualMachine(make([]*f.Element, 0), VirtualMachineConfig{false, false}) - require.NoError(t, err) +func TestOpcodeAssertionAssertEq(t *testing.T) { + vm := defaultVirtualMachine() instruction := Instruction{ Opcode: AssertEq, @@ -208,7 +198,7 @@ func (vm *VirtualMachine) TestOpcodeAssertionAssertEq(t *testing.T) { dstCell := mem.Cell{} res := mem.MemoryValueFromMemoryAddress(mem.NewMemoryAddress(2, 10)) - err = vm.opcodeAssertions(&instruction, &dstCell, nil, res) + err := vm.opcodeAssertions(&instruction, &dstCell, nil, res) require.NoError(t, err) assert.Equal( t, @@ -219,13 +209,13 @@ func (vm *VirtualMachine) TestOpcodeAssertionAssertEq(t *testing.T) { ) } -func (vm *VirtualMachine) TestUpdatePcNextInstr(t *testing.T) { - vm, err := NewVirtualMachine(make([]*f.Element, 0), VirtualMachineConfig{false, false}) - require.NoError(t, err) +func TestUpdatePcNextInstr(t *testing.T) { + vm := defaultVirtualMachine() vm.Context.Pc = 3 instruction := Instruction{ - PcUpdate: NextInstr, + PcUpdate: NextInstr, + Op1Source: Op0, // anything but imm } nextPc, err := vm.updatePc(&instruction, nil, nil, nil) @@ -233,9 +223,8 @@ func (vm *VirtualMachine) TestUpdatePcNextInstr(t *testing.T) { assert.Equal(t, vm.Context.Pc+1, nextPc) } -func (vm *VirtualMachine) TestUpdatePcNextInstrImm(t *testing.T) { - vm, err := NewVirtualMachine(make([]*f.Element, 0), VirtualMachineConfig{false, false}) - require.NoError(t, err) +func TestUpdatePcNextInstrImm(t *testing.T) { + vm := defaultVirtualMachine() vm.Context.Pc = 3 instruction := Instruction{ @@ -248,9 +237,8 @@ func (vm *VirtualMachine) TestUpdatePcNextInstrImm(t *testing.T) { assert.Equal(t, vm.Context.Pc+2, nextPc) } -func (vm *VirtualMachine) TestUpdateApAddOne(t *testing.T) { - vm, err := NewVirtualMachine(make([]*f.Element, 0), VirtualMachineConfig{false, false}) - require.NoError(t, err) +func TestUpdateApAddOne(t *testing.T) { + vm := defaultVirtualMachine() vm.Context.Ap = 5 instruction := Instruction{ @@ -263,9 +251,8 @@ func (vm *VirtualMachine) TestUpdateApAddOne(t *testing.T) { assert.Equal(t, vm.Context.Ap+1, nextAp) } -func (vm *VirtualMachine) TestUpdateFp(t *testing.T) { - vm, err := NewVirtualMachine(make([]*f.Element, 0), VirtualMachineConfig{false, false}) - require.NoError(t, err) +func TestUpdateFp(t *testing.T) { + vm := defaultVirtualMachine() vm.Context.Fp = 5 instruction := Instruction{ @@ -284,6 +271,16 @@ func writeToDataSegment(vm *VirtualMachine, index uint64, value *mem.MemoryValue } } +func defaultVirtualMachine() *VirtualMachine { + vm, _ := NewVirtualMachine(make([]*f.Element, 0), VirtualMachineConfig{false}) + return vm +} + +func defaultVirtualMachineWithBytecode(bytecode []*f.Element) *VirtualMachine { + vm, _ := NewVirtualMachine(bytecode, VirtualMachineConfig{false}) + return vm +} + // create a pointer to an Element func newElementPtr(val uint64) *f.Element { element := f.NewElement(val)