From 66a5863a6d3c4bd1609766695b6081172855b139 Mon Sep 17 00:00:00 2001 From: greged93 <82421016+greged93@users.noreply.github.com> Date: Mon, 23 Oct 2023 20:39:10 +0200 Subject: [PATCH 1/5] fix instruction doc (#317) Co-authored-by: Pedro Fontana --- pkg/vm/instruction.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/vm/instruction.go b/pkg/vm/instruction.go index 12069d53..9b83d46d 100644 --- a/pkg/vm/instruction.go +++ b/pkg/vm/instruction.go @@ -12,10 +12,10 @@ import ( // │ off_op0 (biased representation) │ // ├─────────────────────────────────────────────────────────────────────────┤ // │ off_op1 (biased representation) │ -// ├─────┬─────┬───────┬───────┬───────────┬────────┬───────────────────┬────┤ -// │ dst │ op0 │ op1 │ res │ pc │ ap │ opcode │ 0 │ -// │ reg │ reg │ src │ logic │ update │ update │ │ │ -// ├─────┼─────┼───┬───┼───┬───┼───┬───┬───┼───┬────┼────┬────┬────┬────┼────┤ +// ├─────┬─────┬───────────┬───────┬───────────┬─────────┬──────────────┬────┤ +// │ dst │ op0 │ op1 │ res │ pc │ ap │ opcode │ 0 │ +// │ reg │ reg │ src │ logic │ update │ update │ │ │ +// ├─────┼─────┼───┬───┬───┼───┬───┼───┬───┬───┼────┬────┼────┬────┬────┼────┤ // │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │ // └─────┴─────┴───┴───┴───┴───┴───┴───┴───┴───┴────┴────┴────┴────┴────┴────┘ From 62f6a551a4db40da0a0b43b9eec9b58bbc8dc2a9 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Mon, 23 Oct 2023 21:52:42 +0300 Subject: [PATCH 2/5] Implement Blake2s hash (#310) * Begin implemneting blake2s * Finish blake2s impl * Add unit test * Add more unit tests --------- Co-authored-by: Pedro Fontana --- pkg/hints/hint_utils/bake2s_hash_test.go | 79 ++++++++++++++ pkg/hints/hint_utils/blake2s_hash.go | 133 +++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 pkg/hints/hint_utils/bake2s_hash_test.go create mode 100644 pkg/hints/hint_utils/blake2s_hash.go diff --git a/pkg/hints/hint_utils/bake2s_hash_test.go b/pkg/hints/hint_utils/bake2s_hash_test.go new file mode 100644 index 00000000..0d5a57a0 --- /dev/null +++ b/pkg/hints/hint_utils/bake2s_hash_test.go @@ -0,0 +1,79 @@ +package hint_utils_test + +import ( + "reflect" + "testing" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" +) + +func TestBlake2sCompressA(t *testing.T) { + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + expectedState := []uint32{412110711, 3234706100, 3894970767, 982912411, 937789635, 742982576, 3942558313, 1407547065} + newState := Blake2sCompress(h, message, 2, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} + +func TestBlake2sCompressB(t *testing.T) { + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{456710651, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + expectedState := []uint32{1061041453, 3663967611, 2158760218, 836165556, 3696892209, 3887053585, 2675134684, 2201582556} + newState := Blake2sCompress(h, message, 2, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} + +func TestBlake2sCompressC(t *testing.T) { + //Hashing "Hello World" + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{1819043144, 1870078063, 6581362, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + expectedState := []uint32{939893662, 3935214984, 1704819782, 3912812968, 4211807320, 3760278243, 674188535, 2642110762} + newState := Blake2sCompress(h, message, 9, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} + +func TestBlake2sCompressD(t *testing.T) { + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{1819043144, 1870078063, 6581362, 274628678, 715791845, 175498643, 871587583, 0, 0, 0, 0, 0, 0, 0, 0, 0} + expectedState := []uint32{3980510537, 3982966407, 1593299263, 2666882356, 3288094120, 2682988286, 1666615862, 378086837} + newState := Blake2sCompress(h, message, 28, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} + +func TestBlake2sCompressE(t *testing.T) { + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{1819043144, 1870078063, 6581362, 274628678, 715791845, 175498643, 871587583, 635963558, 557369694, 1576875962, 215769785, 0, 0, 0, 0, 0} + expectedState := []uint32{3251785223, 1946079609, 2665255093, 3508191500, 3630835628, 3067307230, 3623370123, 656151356} + newState := Blake2sCompress(h, message, 44, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} + +func TestBlake2sCompressF(t *testing.T) { + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{1819043144, 1870078063, 6581362, 274628678, 715791845, 175498643, 871587583, 635963558, 557369694, 1576875962, 215769785, 152379578, 585849303, 764739320, 437383930, 74833930} + expectedState := []uint32{2593218707, 3238077801, 914875393, 3462286058, 4028447058, 3174734057, 2001070146, 3741410512} + newState := Blake2sCompress(h, message, 64, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} + +func TestBlake2sCompressG(t *testing.T) { + h := [8]uint32{1795745351, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225} + message := [16]uint32{11563522, 43535528, 653255322, 274628678, 73471943, 17549868, 87158958, 635963558, 343656565, 1576875962, 215769785, 152379578, 585849303, 76473202, 437253230, 74833930} + expectedState := []uint32{3496615692, 3252241979, 3771521549, 2125493093, 3240605752, 2885407061, 3962009872, 3845288240} + newState := Blake2sCompress(h, message, 64, 0, 4294967295, 0) + if !reflect.DeepEqual(expectedState, newState) { + t.Error("Wrong state returned by Blake2sCompress") + } +} diff --git a/pkg/hints/hint_utils/blake2s_hash.go b/pkg/hints/hint_utils/blake2s_hash.go new file mode 100644 index 00000000..fd92b2ad --- /dev/null +++ b/pkg/hints/hint_utils/blake2s_hash.go @@ -0,0 +1,133 @@ +package hint_utils + +func IV() [8]uint32 { + return [8]uint32{ + 0x6A09E667, + 0xBB67AE85, + 0x3C6EF372, + 0xA54FF53A, + 0x510E527F, + 0x9B05688C, + 0x1F83D9AB, + 0x5BE0CD19} +} + +func SIGMA() [10][16]uint32 { + return [10][16]uint32{ + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, + } +} + +func rightRot(value uint32, n uint32) uint32 { + return (value >> n) | ((value & ((1 << n) - 1)) << (32 - n)) +} + +func mix(a uint32, b uint32, c uint32, d uint32, m0 uint32, m1 uint32) (uint32, uint32, uint32, uint32) { + a = a + b + m0 + d = rightRot(d^a, 16) + c = c + d + b = rightRot(b^c, 12) + a = a + b + m1 + d = rightRot(d^a, 8) + c = c + d + b = rightRot(b^c, 7) + return a, b, c, d +} + +func blakeRound(state []uint32, message [16]uint32, sigma [16]uint32) []uint32 { + state[0], state[4], state[8], state[12] = mix( + state[0], + state[4], + state[8], + state[12], + message[sigma[0]], + message[sigma[1]], + ) + state[1], state[5], state[9], state[13] = mix( + state[1], + state[5], + state[9], + state[13], + message[sigma[2]], + message[sigma[3]], + ) + state[2], state[6], state[10], state[14] = mix( + state[2], + state[6], + state[10], + state[14], + message[sigma[4]], + message[sigma[5]], + ) + state[3], state[7], state[11], state[15] = mix( + state[3], + state[7], + state[11], + state[15], + message[sigma[6]], + message[sigma[7]], + ) + state[0], state[5], state[10], state[15] = mix( + state[0], + state[5], + state[10], + state[15], + message[sigma[8]], + message[sigma[9]], + ) + state[1], state[6], state[11], state[12] = mix( + state[1], + state[6], + state[11], + state[12], + message[sigma[10]], + message[sigma[11]], + ) + state[2], state[7], state[8], state[13] = mix( + state[2], + state[7], + state[8], + state[13], + message[sigma[12]], + message[sigma[13]], + ) + state[3], state[4], state[9], state[14] = mix( + state[3], + state[4], + state[9], + state[14], + message[sigma[14]], + message[sigma[15]], + ) + return state +} + +func Blake2sCompress(h [8]uint32, message [16]uint32, t0 uint32, t1 uint32, f0 uint32, f1 uint32) []uint32 { + iv := IV() + + state := make([]uint32, 0, 16) + + state = append(state, h[:]...) + state = append(state, iv[:4]...) + state = append(state, iv[4]^t0, iv[5]^t1, iv[6]^f0, iv[7]^f1) + + for _, sigmaList := range SIGMA() { + state = blakeRound(state, message, sigmaList) + } + + newState := make([]uint32, 0, 8) + for i := 0; i < 8; i++ { + newState = append(newState, h[i]^state[i]^state[8+i]) + } + + return newState +} From a629107ce78e6a85781aad1b355b928ed4bd17cc Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Mon, 23 Oct 2023 22:45:43 +0300 Subject: [PATCH 3/5] Add `VerifySecureRunner` + `SecureRun` config flag (#303) * Add ExecutionResources struct * Fix: Remove vm argument from CairoRunner methods * Finish func + remove todos * Add unit test * Implement GenArg * Remove recursive processing * Add unit tests * Fix test values * Start fn * Add RunFromEntryPoint * Add test for RunFromEntryPoint * Add unit tests * Add comments * Add skeleton * Implement GetMemoryAccesses for BuiltinRunner * Start implementing RunSecurityChecks * Implement VerifyAutoDeductionsForAddr * Finish func + remove import cycle * Add fix + unit test * Fix logic, add test * Fix logic * Fix logic * Fix logic + add test * Add unit tests * Move RunSecurityChecks to external func in builtins package * Remove todos from BuiltinRunner interface * Finish verifySecureRunner * Add verifySecure to RunFromEntryPoint * Add unit tests * Add secure_run flag * Fix typo * Fix alias for new flag * Set secure_run to true in testProgram --------- Co-authored-by: Pedro Fontana --- cmd/cli/main.go | 14 +++- pkg/builtins/bitwise.go | 11 +++ pkg/builtins/builtin_runner.go | 99 ++++++++++++++++++++++---- pkg/builtins/builtin_runner_test.go | 105 ++++++++++++++++++++++++++++ pkg/builtins/ec_op.go | 11 +++ pkg/builtins/keccak.go | 11 +++ pkg/builtins/output.go | 16 +++++ pkg/builtins/pedersen.go | 11 +++ pkg/builtins/poseidon.go | 11 +++ pkg/builtins/range_check.go | 11 +++ pkg/builtins/signature.go | 11 +++ pkg/runners/cairo_runner.go | 7 +- pkg/runners/cairo_runner_test.go | 2 +- pkg/runners/security.go | 68 ++++++++++++++++++ pkg/runners/security_test.go | 55 +++++++++++++++ pkg/vm/cairo_run/cairo_run.go | 8 +++ pkg/vm/cairo_run/cairo_run_test.go | 2 +- 17 files changed, 435 insertions(+), 18 deletions(-) create mode 100644 pkg/builtins/builtin_runner_test.go create mode 100644 pkg/runners/security.go create mode 100644 pkg/runners/security_test.go diff --git a/cmd/cli/main.go b/cmd/cli/main.go index db77623b..094facea 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -17,7 +17,14 @@ func handleCommands(ctx *cli.Context) error { layout = "plain" } - cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, ProofMode: ctx.Bool("proof_mode"), Layout: layout} + proofMode := ctx.Bool("proof_mode") + + secureRun := !proofMode + if ctx.Bool("secure_run") { + secureRun = true + } + + cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, ProofMode: proofMode, Layout: layout, SecureRun: secureRun} cairoRunner, err := cairo_run.CairoRun(programPath, cairoRunConfig) if err != nil { @@ -51,6 +58,11 @@ func main() { Aliases: []string{"p"}, Usage: "Run in proof mode", }, + &cli.BoolFlag{ + Name: "secure_run", + Aliases: []string{"s"}, + Usage: "Run security checks. Default: true unless proof_mode is true", + }, &cli.StringFlag{ Name: "layout", Aliases: []string{"l"}, diff --git a/pkg/builtins/bitwise.go b/pkg/builtins/bitwise.go index b2e26a34..39a9a44c 100644 --- a/pkg/builtins/bitwise.go +++ b/pkg/builtins/bitwise.go @@ -111,6 +111,10 @@ func (b *BitwiseBuiltinRunner) CellsPerInstance() uint { return BITWISE_CELLS_PER_INSTANCE } +func (b *BitwiseBuiltinRunner) InputCellsPerInstance() uint { + return BIWISE_INPUT_CELLS_PER_INSTANCE +} + func (b *BitwiseBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -253,3 +257,10 @@ func (r *BitwiseBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentMa return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *BitwiseBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/builtin_runner.go b/pkg/builtins/builtin_runner.go index 64148d7f..c9e9c7ad 100644 --- a/pkg/builtins/builtin_runner.go +++ b/pkg/builtins/builtin_runner.go @@ -2,6 +2,7 @@ package builtins import ( "fmt" + "sort" "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" "github.com/pkg/errors" @@ -28,6 +29,10 @@ type BuiltinRunner interface { Base() memory.Relocatable // Returns the name of the builtin Name() string + // Cells per builtin instance + CellsPerInstance() uint + // Input cells per builtin instance + InputCellsPerInstance() uint // Creates a memory segment for the builtin and initializes its base InitializeSegments(*memory.MemorySegmentManager) // Returns the builtin's initial stack @@ -40,27 +45,97 @@ type BuiltinRunner interface { AddValidationRule(*memory.Memory) // Sets the inclusion of the Builtin Runner in the Cairo Runner Include(bool) - // TODO: Later additions -> Some of them could depend on a Default Implementation - // // Most of them depend on Layouts being implemented - // // Use cases: - // // I. PROOF_MODE // Returns the builtin's ratio, is zero if the layout is dynamic Ratio() uint // Returns the builtin's allocated memory units GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) - // // Returns the list of memory addresses used by the builtin + // Returns the list of memory addresses used by the builtin GetMemoryAccesses(*memory.MemorySegmentManager) ([]memory.Relocatable, error) GetRangeCheckUsage(*memory.Memory) (*uint, *uint) GetUsedPermRangeCheckLimits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) GetUsedDilutedCheckUnits(dilutedSpacing uint, dilutedNBits uint) uint GetUsedCellsAndAllocatedSizes(segments *memory.MemorySegmentManager, currentStep uint) (uint, uint, error) FinalStack(segments *memory.MemorySegmentManager, pointer memory.Relocatable) (memory.Relocatable, error) - // // II. SECURITY (secure-run flag cairo-run || verify-secure flag run_from_entrypoint) - // RunSecurityChecks(*vm.VirtualMachine) error // verify_secure_runner logic - // // Returns the base & stop_ptr, stop_ptr can be nil - // GetMemorySegmentAddresses() (memory.Relocatable, *memory.Relocatable) //verify_secure_runner logic - // // III. STARKNET-SPECIFIC + // Returns the base & stop_ptr + GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) + // Amount of builtin instances used GetUsedInstances(*memory.MemorySegmentManager) (uint, error) - // // IV. GENERAL CASE (but not critical) - // FinalStack(*memory.MemorySegmentManager, memory.Relocatable) (memory.Relocatable, error) // read_return_values +} + +func RunSecurityChecksForBuiltin(builtin BuiltinRunner, segments *memory.MemorySegmentManager) error { + if builtin.Name() == OUTPUT_BUILTIN_NAME { + return nil + } + + cellsPerInstance := builtin.CellsPerInstance() + nInputCells := builtin.InputCellsPerInstance() + builtinSegmentIndex := builtin.Base().SegmentIndex + + offsets := make([]int, 0) + // Collect the builtin segment's address' offsets + for addr := range segments.Memory.Data { + if addr.SegmentIndex == builtinSegmentIndex { + offsets = append(offsets, int(addr.Offset)) + } + } + + if len(offsets) == 0 { + // No checks to run for empty segment + return nil + } + // Sort offsets for easier comparison + sort.Ints(offsets) + // Obtain max offset + maxOffset := offsets[len(offsets)-1] + + n := (maxOffset / int(cellsPerInstance)) + 1 + //Verify that n is not too large to make sure the expectedOffsets list that is constructed below is not too large. + if n > len(offsets)/int(nInputCells) { + return errors.Errorf("Missing memory cells for %s", builtin.Name()) + } + + // Check that the two inputs (x and y) of each instance are set. + expectedOffsets := make([]int, 0) + for i := 0; i < n; i++ { + for j := 0; j < int(nInputCells); j++ { + expectedOffsets = append(expectedOffsets, int(cellsPerInstance)*i+j) + } + } + // Find the missing offsets (offsets in expectedOffsets but not in offsets) + missingOffsets := make([]int, 0) + j := 0 + i := 0 + for i < len(expectedOffsets) && j < len(offsets) { + if expectedOffsets[i] < offsets[j] { + missingOffsets = append(missingOffsets, expectedOffsets[i]) + } else { + j++ + } + i++ + } + for i < len(expectedOffsets) { + missingOffsets = append(missingOffsets, expectedOffsets[i]) + i++ + } + if len(missingOffsets) != 0 { + return errors.Errorf("Missing memory cells for builtin: %s: %v", builtin.Name(), missingOffsets) + } + + // Verify auto deduction rules for the unassigned output cells. + // Assigned output cells are checked as part of the call to VerifyAutoDeductions(). + for i := uint(0); i < uint(n); i++ { + for j := uint(nInputCells); j < cellsPerInstance; j++ { + addr := memory.NewRelocatable(builtinSegmentIndex, cellsPerInstance*i+j) + _, err := segments.Memory.Get(addr) + // Output cell not in memory + if err != nil { + _, err = builtin.DeduceMemoryCell(addr, &segments.Memory) + if err != nil { + return err + } + } + } + } + + return nil } diff --git a/pkg/builtins/builtin_runner_test.go b/pkg/builtins/builtin_runner_test.go new file mode 100644 index 00000000..210b25a2 --- /dev/null +++ b/pkg/builtins/builtin_runner_test.go @@ -0,0 +1,105 @@ +package builtins_test + +import ( + "testing" + + "github.com/lambdaclass/cairo-vm.go/pkg/builtins" + "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestRunSecurityChecksEmptyMemory(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err != nil { + t.Errorf("RunSecurityChecks failed with error: %s", err.Error()) + } +} + +func TestRunSecurityChecksMissingMemoryCells(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + + builtin.InitializeSegments(&segments) + builtinBase := builtin.Base() + // A bitwise cell consists of 5 elements: 2 input cells & 3 output cells + // In this test we insert cells 4-5 and leave the first input cell empty + // This will fail the security checks, as the memory cell with offset 0 will be missing + builtinSegment := []memory.MaybeRelocatable{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(1)), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(2)), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(3)), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(4)), + } + segments.LoadData(builtinBase.AddUint(1), &builtinSegment) + + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err == nil { + t.Errorf("RunSecurityChecks should have failed") + } +} + +func TestRunSecurityChecksMissingMemoryCellsNCheck(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + + builtin.InitializeSegments(&segments) + builtinBase := builtin.Base() + // n = max(offsets) // cellsPerInstance + 1 + // n = max[(0]) // 5 + 1 = 0 // 5 + 1 = 1 + // len(offsets) // inputCells = 1 // 2 + // This will fail the security checks, as n > len(offsets) // inputCells + builtinSegment := []memory.MaybeRelocatable{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(1)), + } + segments.LoadData(builtinBase, &builtinSegment) + + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err == nil { + t.Errorf("RunSecurityChecks should have failed") + } +} + +func TestRunSecurityChecksValidateOutputCellsNotDeducedOk(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + + builtin.InitializeSegments(&segments) + builtinBase := builtin.Base() + // A bitwise cell consists of 5 elements: 2 input cells & 3 output cells + // In this test we insert the input cells (1-2), but not the output cells (3-5) + // This will cause the security checks to run the auto-deductions for those output cells + builtinSegment := []memory.MaybeRelocatable{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(1)), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(2)), + } + segments.LoadData(builtinBase, &builtinSegment) + + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err != nil { + t.Errorf("RunSecurityChecks failed with error: %s", err.Error()) + } +} + +func TestRunSecurityChecksValidateOutputCellsNotDeducedErr(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + + builtin.InitializeSegments(&segments) + builtinBase := builtin.Base() + // A bitwise cell consists of 5 elements: 2 input cells & 3 output cells + // In this test we insert the input cells (1-2), but not the output cells (3-5) + // This will cause the security checks to run the auto-deductions for those output cells + // As we inserted an invalid value (PRIME -1) on the first input cell, this deduction will fail + builtinSegment := []memory.MaybeRelocatable{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromDecString("-1")), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(2)), + } + segments.LoadData(builtinBase, &builtinSegment) + + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err == nil { + t.Errorf("RunSecurityChecks should have failed") + } +} diff --git a/pkg/builtins/ec_op.go b/pkg/builtins/ec_op.go index 9053c25c..912445dd 100644 --- a/pkg/builtins/ec_op.go +++ b/pkg/builtins/ec_op.go @@ -147,6 +147,10 @@ func (r *EcOpBuiltinRunner) CellsPerInstance() uint { return CELLS_PER_EC_OP } +func (b *EcOpBuiltinRunner) InputCellsPerInstance() uint { + return INPUT_CELLS_PER_EC_OP +} + func (ec *EcOpBuiltinRunner) AddValidationRule(*memory.Memory) {} func (ec *EcOpBuiltinRunner) Base() memory.Relocatable { @@ -405,3 +409,10 @@ func (r *EcOpBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentManag return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *EcOpBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/keccak.go b/pkg/builtins/keccak.go index a5f9638e..ac11ae37 100644 --- a/pkg/builtins/keccak.go +++ b/pkg/builtins/keccak.go @@ -539,6 +539,10 @@ func (k *KeccakBuiltinRunner) CellsPerInstance() uint { return KECCAK_CELLS_PER_INSTANCE } +func (b *KeccakBuiltinRunner) InputCellsPerInstance() uint { + return KECCAK_INPUT_CELLS_PER_INSTANCE +} + func (k *KeccakBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -676,3 +680,10 @@ func (r *KeccakBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentMan return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *KeccakBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/output.go b/pkg/builtins/output.go index 945a772b..f445178b 100644 --- a/pkg/builtins/output.go +++ b/pkg/builtins/output.go @@ -5,6 +5,7 @@ import ( ) const OUTPUT_BUILTIN_NAME = "output" +const OUTPUT_CELLS_PER_INSTANCE = 1 type OutputBuiltinRunner struct { base memory.Relocatable @@ -133,3 +134,18 @@ func (r *OutputBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentMan return usedCells, nil } + +func (b *OutputBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} + +func (r *OutputBuiltinRunner) CellsPerInstance() uint { + return OUTPUT_CELLS_PER_INSTANCE +} + +func (b *OutputBuiltinRunner) InputCellsPerInstance() uint { + return OUTPUT_CELLS_PER_INSTANCE +} diff --git a/pkg/builtins/pedersen.go b/pkg/builtins/pedersen.go index baa1d14c..158ef68e 100644 --- a/pkg/builtins/pedersen.go +++ b/pkg/builtins/pedersen.go @@ -107,6 +107,10 @@ func (p *PedersenBuiltinRunner) CellsPerInstance() uint { return PEDERSEN_CELLS_PER_INSTANCE } +func (p *PedersenBuiltinRunner) InputCellsPerInstance() uint { + return PEDERSEN_INPUT_CELLS_PER_INSTANCE +} + func (p *PedersenBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -228,3 +232,10 @@ func (r *PedersenBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentM return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *PedersenBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/poseidon.go b/pkg/builtins/poseidon.go index 9bb09e7e..81a9a8e0 100644 --- a/pkg/builtins/poseidon.go +++ b/pkg/builtins/poseidon.go @@ -96,6 +96,10 @@ func (p *PoseidonBuiltinRunner) CellsPerInstance() uint { return POSEIDON_CELLS_PER_INSTANCE } +func (p *PoseidonBuiltinRunner) InputCellsPerInstance() uint { + return POSEIDON_INPUT_CELLS_PER_INSTANCE +} + func (p *PoseidonBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -217,3 +221,10 @@ func (r *PoseidonBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentM return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *PoseidonBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/range_check.go b/pkg/builtins/range_check.go index 8ee6ae65..ee913b94 100644 --- a/pkg/builtins/range_check.go +++ b/pkg/builtins/range_check.go @@ -110,6 +110,10 @@ func (r *RangeCheckBuiltinRunner) CellsPerInstance() uint { return CELLS_PER_RANGE_CHECK } +func (b *RangeCheckBuiltinRunner) InputCellsPerInstance() uint { + return CELLS_PER_RANGE_CHECK +} + func (r *RangeCheckBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -280,3 +284,10 @@ func (r *RangeCheckBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmen return usedCells, nil } + +func (b *RangeCheckBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/signature.go b/pkg/builtins/signature.go index 5e577b61..ce6865bd 100644 --- a/pkg/builtins/signature.go +++ b/pkg/builtins/signature.go @@ -131,6 +131,10 @@ func (r *SignatureBuiltinRunner) CellsPerInstance() uint { return SIGNATURE_CELLS_PER_INSTANCE } +func (r *SignatureBuiltinRunner) InputCellsPerInstance() uint { + return SIGNATURE_CELLS_PER_INSTANCE +} + func (r *SignatureBuiltinRunner) GetRangeCheckUsage(memory *memory.Memory) (*uint, *uint) { return nil, nil } @@ -235,3 +239,10 @@ func (r *SignatureBuiltinRunner) GetUsedInstances(segments *memory.MemorySegment return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *SignatureBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/runners/cairo_runner.go b/pkg/runners/cairo_runner.go index 32cb37d4..53354130 100644 --- a/pkg/runners/cairo_runner.go +++ b/pkg/runners/cairo_runner.go @@ -582,14 +582,13 @@ func (runner *CairoRunner) GetExecutionResources() (ExecutionResources, error) { }, nil } -// TODO: Add verifySecure once its implemented /* Runs a cairo program from a give entrypoint, indicated by its pc offset, with the given arguments. If `verifySecure` is set to true, [verifySecureRunner] will be called to run extra verifications. `programSegmentSize` is only used by the [verifySecureRunner] function and will be ignored if `verifySecure` is set to false. Each arg can be either MaybeRelocatable, []MaybeRelocatable or [][]MaybeRelocatable */ -func (runner *CairoRunner) RunFromEntrypoint(entrypoint uint, args []any, hintProcessor vm.HintProcessor) error { +func (runner *CairoRunner) RunFromEntrypoint(entrypoint uint, args []any, hintProcessor vm.HintProcessor, verifySecure bool, programSegmentSize *uint) error { stack := make([]memory.MaybeRelocatable, 0) for _, arg := range args { val, err := runner.Vm.Segments.GenArg(arg) @@ -615,6 +614,8 @@ func (runner *CairoRunner) RunFromEntrypoint(entrypoint uint, args []any, hintPr if err != nil { return err } - // TODO: verifySecureRunner + if verifySecure { + return VerifySecureRunner(runner, true, programSegmentSize) + } return nil } diff --git a/pkg/runners/cairo_runner_test.go b/pkg/runners/cairo_runner_test.go index 1c5b7130..0f5f34c6 100644 --- a/pkg/runners/cairo_runner_test.go +++ b/pkg/runners/cairo_runner_test.go @@ -743,7 +743,7 @@ func TestRunFromEntryPointFibonacci(t *testing.T) { runner.InitializeBuiltins() runner.InitializeSegments() - err := runner.RunFromEntrypoint(uint(entrypoint), args, &hintProcessor) + err := runner.RunFromEntrypoint(uint(entrypoint), args, &hintProcessor, true, nil) if err != nil { t.Errorf("Running fib entrypoint failed with error %s", err.Error()) diff --git a/pkg/runners/security.go b/pkg/runners/security.go new file mode 100644 index 00000000..c8c5d4c9 --- /dev/null +++ b/pkg/runners/security.go @@ -0,0 +1,68 @@ +package runners + +import ( + "github.com/lambdaclass/cairo-vm.go/pkg/builtins" + "github.com/pkg/errors" +) + +/* +Verify that the completed run in a runner is safe to be relocated and be +used by other Cairo programs. + +Checks include: + - (Only if `verifyBuiltins` is set to true) All accesses to the builtin segments must be within the range defined by + the builtins themselves. + - There must not be accesses to the program segment outside the program + data range. This check will use the `programSegmentSize` instead of the program data length if available. + - All addresses in memory must be real (not temporary) + +Note: Each builtin is responsible for checking its own segments' data. +*/ +func VerifySecureRunner(runner *CairoRunner, verifyBuiltins bool, programSegmentSize *uint) error { + programSize := uint(len(runner.Program.Data)) + if programSegmentSize != nil { + programSize = *programSegmentSize + } + // Get builtin segment info + builtinNames := make(map[int]string) + builtinSizes := make(map[int]uint) + if verifyBuiltins { + for i := 0; i < len(runner.Vm.BuiltinRunners); i++ { + base, stopPtr, err := runner.Vm.BuiltinRunners[i].GetMemorySegmentAddresses() + if err != nil { + return err + } + builtinNames[base.SegmentIndex] = runner.Vm.BuiltinRunners[i].Name() + builtinSizes[base.SegmentIndex] = stopPtr.Offset + } + } + // Run memory checks + for addr, val := range runner.Vm.Segments.Memory.Data { + // Check out of bound accesses to builtin segment + size, ok := builtinSizes[addr.SegmentIndex] + if ok && addr.Offset >= size { + return errors.Errorf("Out of bounds access to builtin segment %s at %s", builtinNames[addr.SegmentIndex], addr.ToString()) + } + // Check out of bound accesses to program segment + if addr.SegmentIndex == runner.ProgramBase.SegmentIndex && addr.SegmentIndex >= int(programSize) { + return errors.Errorf("Out of bounds access to program segment at %s", addr.ToString()) + } + // Check non-relocated temporary addresses + if addr.SegmentIndex < 0 { + return errors.Errorf("Security Error: Invalid Memory Value: temporary address not relocated: %s", addr.ToString()) + } + relVal, isRel := val.GetRelocatable() + if isRel && relVal.SegmentIndex < 0 { + return errors.Errorf("Security Error: Invalid Memory Value: temporary address not relocated: %s", relVal.ToString()) + } + } + // Run builtin-specific checks + for i := 0; i < len(runner.Vm.BuiltinRunners); i++ { + err := builtins.RunSecurityChecksForBuiltin(runner.Vm.BuiltinRunners[i], &runner.Vm.Segments) + if err != nil { + return err + } + } + + return nil +} diff --git a/pkg/runners/security_test.go b/pkg/runners/security_test.go new file mode 100644 index 00000000..dd808ac7 --- /dev/null +++ b/pkg/runners/security_test.go @@ -0,0 +1,55 @@ +package runners_test + +import ( + "testing" + + "github.com/lambdaclass/cairo-vm.go/pkg/builtins" + . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/runners" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestVerifySecureRunnerEmptyMemory(t *testing.T) { + runner, _ := NewCairoRunner(Program{}, "all_cairo", false) + runner.Initialize() + err := VerifySecureRunner(runner, true, nil) + if err != nil { + t.Errorf("VerifySecureRunner failed with error: %s", err.Error()) + } +} + +func TestVerifySecureRunnerOutOfBoundsAccessProgram(t *testing.T) { + runner, _ := NewCairoRunner(Program{}, "all_cairo", false) + runner.Initialize() + // Insert an element into the program segment to trigger out of bounds access to program segment error + runner.Vm.Segments.Memory.Insert(runner.ProgramBase, NewMaybeRelocatableFelt(FeltOne())) + err := VerifySecureRunner(runner, true, nil) + if err == nil { + t.Errorf("VerifySecureRunner should have failed") + } +} + +func TestVerifySecureRunnerOutOfBoundsAccessBuiltin(t *testing.T) { + runner, _ := NewCairoRunner(Program{Builtins: []string{builtins.OUTPUT_BUILTIN_NAME}}, "all_cairo", false) + runner.Initialize() + stopPtr := uint(0) + runner.Vm.BuiltinRunners[0].(*builtins.OutputBuiltinRunner).StopPtr = &stopPtr + // Insert an element into the output segment to trigger out of bounds access to builtin segment error + runner.Vm.Segments.Memory.Insert(runner.Vm.BuiltinRunners[0].Base(), NewMaybeRelocatableFelt(FeltOne())) + err := VerifySecureRunner(runner, true, nil) + if err == nil { + t.Errorf("VerifySecureRunner should have failed") + } +} + +func TestVerifySecureRunnerTemporaryVal(t *testing.T) { + runner, _ := NewCairoRunner(Program{}, "all_cairo", false) + runner.Initialize() + // Insert a temporary address into memory + runner.Vm.Segments.Memory.Insert(NewRelocatable(1, 7), NewMaybeRelocatableRelocatable(NewRelocatable(-1, 0))) + err := VerifySecureRunner(runner, true, nil) + if err == nil { + t.Errorf("VerifySecureRunner should have failed") + } +} diff --git a/pkg/vm/cairo_run/cairo_run.go b/pkg/vm/cairo_run/cairo_run.go index d54a1f99..db6b568a 100644 --- a/pkg/vm/cairo_run/cairo_run.go +++ b/pkg/vm/cairo_run/cairo_run.go @@ -24,6 +24,7 @@ type CairoRunConfig struct { DisableTracePadding bool ProofMode bool Layout string + SecureRun bool } func CairoRunError(err error) error { @@ -67,6 +68,13 @@ func CairoRun(programPath string, cairoRunConfig CairoRunConfig) (*runners.Cairo cairoRunner.FinalizeSegments() } + if cairoRunConfig.SecureRun { + err = runners.VerifySecureRunner(cairoRunner, true, nil) + if err != nil { + return nil, err + } + } + err = cairoRunner.Vm.Relocate() return cairoRunner, err } diff --git a/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index 4aa6c0f3..d04554d6 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -8,7 +8,7 @@ import ( ) func testProgram(programName string, t *testing.T) { - cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false} + cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false, SecureRun: true} _, err := cairo_run.CairoRun("../../../cairo_programs/"+programName+".json", cairoRunConfig) if err != nil { t.Errorf("Program execution failed with error: %s", err) From 5a2d812ab4973fe3a1900bc4d4e0e4a2a8b28d3e Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 24 Oct 2023 17:10:05 +0300 Subject: [PATCH 4/5] Implement Blake2s Hints (Part 1) (#311) * Begin implemneting blake2s * Finish blake2s impl * Add unit test * Add more unit tests * Add integration test * Implement BLAKE2S_COMPUTE * Add unit tests * Add unit tests * Add newline * Update cairo_run_test.go * fmt --------- Co-authored-by: Pedro Fontana Co-authored-by: Juan Bono --- cairo_programs/blake2s_hello_world_hash.cairo | 20 +++ pkg/hints/blake2s_hints.go | 84 ++++++++++ pkg/hints/blake2s_hints_test.go | 154 ++++++++++++++++++ pkg/hints/hint_codes/blake2s_hint_codes.go | 4 + pkg/hints/hint_processor.go | 2 + pkg/lambdaworks/lambdaworks.go | 10 ++ pkg/lambdaworks/lambdaworks_test.go | 34 ++++ pkg/vm/cairo_run/cairo_run_test.go | 4 + 8 files changed, 312 insertions(+) create mode 100644 cairo_programs/blake2s_hello_world_hash.cairo create mode 100644 pkg/hints/blake2s_hints.go create mode 100644 pkg/hints/blake2s_hints_test.go create mode 100644 pkg/hints/hint_codes/blake2s_hint_codes.go diff --git a/cairo_programs/blake2s_hello_world_hash.cairo b/cairo_programs/blake2s_hello_world_hash.cairo new file mode 100644 index 00000000..6d591cb9 --- /dev/null +++ b/cairo_programs/blake2s_hello_world_hash.cairo @@ -0,0 +1,20 @@ +%builtins range_check bitwise + +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.cairo_blake2s.blake2s import blake2s +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin + +// Computes the hash of "Hello World" +func main{range_check_ptr, bitwise_ptr: BitwiseBuiltin*}() { + alloc_locals; + let inputs: felt* = alloc(); + assert inputs[0] = 'Hell'; + assert inputs[1] = 'o Wo'; + assert inputs[2] = 'rld'; + let (local blake2s_ptr_start) = alloc(); + let blake2s_ptr = blake2s_ptr_start; + let (output) = blake2s{range_check_ptr=range_check_ptr, blake2s_ptr=blake2s_ptr}(inputs, 9); + assert output.low = 219917655069954262743903159041439073909; + assert output.high = 296157033687865319468534978667166017272; + return (); +} diff --git a/pkg/hints/blake2s_hints.go b/pkg/hints/blake2s_hints.go new file mode 100644 index 00000000..d66c4794 --- /dev/null +++ b/pkg/hints/blake2s_hints.go @@ -0,0 +1,84 @@ +package hints + +import ( + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func Uint32SliceToMRSlice(u32Slice []uint32) []MaybeRelocatable { + mRSlice := make([]MaybeRelocatable, 0, len(u32Slice)) + for _, u32 := range u32Slice { + mRSlice = append(mRSlice, *NewMaybeRelocatableFelt(FeltFromUint(uint(u32)))) + } + return mRSlice + +} + +func feltSliceToUint32Slice(feltSlice []Felt) ([]uint32, error) { + uint32Slice := make([]uint32, 0, len(feltSlice)) + for _, felt := range feltSlice { + val, err := felt.ToU32() + if err != nil { + return nil, err + } + uint32Slice = append(uint32Slice, val) + } + return uint32Slice, nil +} + +// Returns the range from (baseAddr - baseAddrOffset) to (baseAddr - baseAddrOffset + Size) +func getUint32MemoryRange(baseAddr Relocatable, baseAddrOffset uint, size uint, segments *MemorySegmentManager) ([]uint32, error) { + baseAddr, err := baseAddr.SubUint(baseAddrOffset) + if err != nil { + return nil, err + } + feltRange, err := segments.GetFeltRange(baseAddr, size) + if err != nil { + return nil, err + } + return feltSliceToUint32Slice(feltRange) +} + +// Returns the u32 value at memory[baseAddr - baseAddrOffset] +func getU32FromMemory(baseAddr Relocatable, baseAddrOffset uint, memory *Memory) (uint32, error) { + baseAddr, err := baseAddr.SubUint(baseAddrOffset) + if err != nil { + return 0, err + } + felt, err := memory.GetFelt(baseAddr) + if err != nil { + return 0, err + } + return felt.ToU32() +} + +func blake2sCompute(ids IdsManager, vm *VirtualMachine) error { + output, err := ids.GetRelocatable("output", vm) + if err != nil { + return err + } + h, err := getUint32MemoryRange(output, 26, 8, &vm.Segments) + if err != nil { + return err + } + message, err := getUint32MemoryRange(output, 18, 16, &vm.Segments) + if err != nil { + return err + } + t, err := getU32FromMemory(output, 2, &vm.Segments.Memory) + if err != nil { + return err + } + f, err := getU32FromMemory(output, 1, &vm.Segments.Memory) + if err != nil { + return err + } + + newState := Blake2sCompress([8]uint32(h), [16]uint32(message), t, 0, f, 0) + data := Uint32SliceToMRSlice(newState) + + _, err = vm.Segments.LoadData(output, &data) + return err +} diff --git a/pkg/hints/blake2s_hints_test.go b/pkg/hints/blake2s_hints_test.go new file mode 100644 index 00000000..42b88d26 --- /dev/null +++ b/pkg/hints/blake2s_hints_test.go @@ -0,0 +1,154 @@ +package hints_test + +import ( + "testing" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints" + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_codes" + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestBlake2sComputeOutputOffsetZero(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + output := vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "output": {NewMaybeRelocatableRelocatable(output)}, + }, + vm, + ) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: BLAKE2S_COMPUTE, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err == nil { + t.Errorf("BLAKE2S_COMPUTE hint test should have failed") + } +} + +func TestBlake2sComputeOutputSegmentEmpty(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + output := vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "output": {NewMaybeRelocatableRelocatable(output.AddUint(26))}, + }, + vm, + ) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: BLAKE2S_COMPUTE, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err == nil { + t.Errorf("BLAKE2S_COMPUTE hint test should have failed") + } +} + +func TestBlake2sComputeBigOutput(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + output := vm.Segments.AddSegment() + data := []MaybeRelocatable{ + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + *NewMaybeRelocatableFelt(FeltFromDecString("7842562439562793675803603603688959")), + } + output, _ = vm.Segments.LoadData(output, &data) + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "output": {NewMaybeRelocatableRelocatable(output)}, + }, + vm, + ) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: BLAKE2S_COMPUTE, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err == nil { + t.Errorf("BLAKE2S_COMPUTE hint test should have failed") + } +} + +func TestBlake2sComputeOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + output := vm.Segments.AddSegment() + data := []MaybeRelocatable{ + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + *NewMaybeRelocatableFelt(FeltFromDecString("17")), + } + output, _ = vm.Segments.LoadData(output, &data) + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "output": {NewMaybeRelocatableRelocatable(output)}, + }, + vm, + ) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: BLAKE2S_COMPUTE, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("BLAKE2S_COMPUTE hint test failed with error %s", err) + } +} diff --git a/pkg/hints/hint_codes/blake2s_hint_codes.go b/pkg/hints/hint_codes/blake2s_hint_codes.go new file mode 100644 index 00000000..fb26e1f5 --- /dev/null +++ b/pkg/hints/hint_codes/blake2s_hint_codes.go @@ -0,0 +1,4 @@ +package hint_codes + +const BLAKE2S_COMPUTE = `from starkware.cairo.common.cairo_blake2s.blake2s_utils import compute_blake2s_func +compute_blake2s_func(segments=segments, output_ptr=ids.output)` diff --git a/pkg/hints/hint_processor.go b/pkg/hints/hint_processor.go index 669cc1af..7abcc972 100644 --- a/pkg/hints/hint_processor.go +++ b/pkg/hints/hint_processor.go @@ -228,6 +228,8 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any, return fastEcAddAssignNewX(data.Ids, vm, execScopes, "pt0", "pt1", SECP_P()) case FAST_EC_ADD_ASSIGN_NEW_Y: return fastEcAddAssignNewY(execScopes) + case BLAKE2S_COMPUTE: + return blake2sCompute(data.Ids, vm) default: return errors.Errorf("Unknown Hint: %s", data.Code) } diff --git a/pkg/lambdaworks/lambdaworks.go b/pkg/lambdaworks/lambdaworks.go index fb175502..10bda142 100644 --- a/pkg/lambdaworks/lambdaworks.go +++ b/pkg/lambdaworks/lambdaworks.go @@ -8,6 +8,7 @@ package lambdaworks import "C" import ( + "math" "math/big" "reflect" "strings" @@ -101,6 +102,15 @@ func (felt Felt) ToUint() (uint, error) { return uint(felt_u64), nil } +// turns a felt to uint32 +func (felt Felt) ToU32() (uint32, error) { + feltU64, err := felt.ToU64() + if err != nil || feltU64 > math.MaxUint32 { + return 0, ConversionError(felt, "u32") + } + return uint32(feltU64), nil +} + func (felt Felt) ToLeBytes() *[32]byte { var result_c [32]C.uint8_t var value C.felt_t = felt.toC() diff --git a/pkg/lambdaworks/lambdaworks_test.go b/pkg/lambdaworks/lambdaworks_test.go index bf7c404d..fdd3cd89 100644 --- a/pkg/lambdaworks/lambdaworks_test.go +++ b/pkg/lambdaworks/lambdaworks_test.go @@ -427,6 +427,40 @@ func TestToU64Fail(t *testing.T) { } } +func TestToU321(t *testing.T) { + felt := lambdaworks.FeltOne() + result, err := felt.ToU32() + + var expected uint32 = 1 + + if expected != result { + t.Errorf("Error in conversion expected: %v, got %v with err: %v", expected, result, err) + } + +} + +func TestToU3210230(t *testing.T) { + felt := lambdaworks.FeltFromUint64(10230) + result, err := felt.ToU32() + + var expected uint32 = 10230 + + if expected != result { + t.Errorf("Error in conversion expected: %v, got %v with err: %v", expected, result, err) + } +} + +func TestTo32Fail(t *testing.T) { + felt := lambdaworks.FeltFromUint64(4294967297) + + _, err := felt.ToU32() + expected_err := lambdaworks.ConversionError(felt, "u32") + + if err.Error() != expected_err.Error() { + t.Errorf("Conversion test should fail with error: %v", expected_err) + } +} + func TestToUint1(t *testing.T) { felt := lambdaworks.FeltOne() result, err := felt.ToUint() diff --git a/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index d04554d6..4fc0fc75 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -353,6 +353,10 @@ func TestKeccakAddUint256(t *testing.T) { testProgram("keccak_add_uint256", t) } +func TestBlake2sHelloWorldHash(t *testing.T) { + testProgram("blake2s_hello_world_hash", t) +} + func TestUint256Integration(t *testing.T) { testProgram("uint256_integration_tests", t) } From 432dd89a7fbcf2bf254708a957f92ecfbdb3849e Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Wed, 25 Oct 2023 00:26:49 +0300 Subject: [PATCH 5/5] Add `RunResources` (#306) * Add ExecutionResources struct * Fix: Remove vm argument from CairoRunner methods * Finish func + remove todos * Add unit test * Implement GenArg * Remove recursive processing * Add unit tests * Fix test values * Start fn * Add RunFromEntryPoint * Add test for RunFromEntryPoint * Add unit tests * Add comments * Add skeleton * Implement GetMemoryAccesses for BuiltinRunner * Start implementing RunSecurityChecks * Implement VerifyAutoDeductionsForAddr * Finish func + remove import cycle * Add fix + unit test * Fix logic, add test * Fix logic * Fix logic * Fix logic + add test * Add unit tests * Move RunSecurityChecks to external func in builtins package * Remove todos from BuiltinRunner interface * Finish verifySecureRunner * Add verifySecure to RunFromEntryPoint * Add unit tests * Add secure_run flag * Fix typo * Add RunResources * Add RunResources * Fix bug * Use saturating subtraction --- pkg/runners/cairo_runner.go | 12 ++++++++++-- pkg/runners/cairo_runner_test.go | 26 +++++++++++++++++++++++++- pkg/vm/run_resources.go | 23 +++++++++++++++++++++++ pkg/vm/vm_core.go | 1 + 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 pkg/vm/run_resources.go diff --git a/pkg/runners/cairo_runner.go b/pkg/runners/cairo_runner.go index 53354130..5e3564fb 100644 --- a/pkg/runners/cairo_runner.go +++ b/pkg/runners/cairo_runner.go @@ -228,11 +228,18 @@ func (r *CairoRunner) RunUntilPC(end memory.Relocatable, hintProcessor vm.HintPr return err } constants := r.Program.ExtractConstants() - for r.Vm.RunContext.Pc != end { + for r.Vm.RunContext.Pc != end && + (r.Vm.RunResources == nil || !r.Vm.RunResources.Consumed()) { err := r.Vm.Step(hintProcessor, &hintDataMap, &constants, &r.execScopes) if err != nil { return err } + if r.Vm.RunResources != nil { + r.Vm.RunResources.ConsumeStep() + } + } + if r.Vm.RunContext.Pc != end { + return errors.New("Could not reach the end of the program. RunResources has no remaining steps.") } return nil } @@ -588,7 +595,8 @@ If `verifySecure` is set to true, [verifySecureRunner] will be called to run ext `programSegmentSize` is only used by the [verifySecureRunner] function and will be ignored if `verifySecure` is set to false. Each arg can be either MaybeRelocatable, []MaybeRelocatable or [][]MaybeRelocatable */ -func (runner *CairoRunner) RunFromEntrypoint(entrypoint uint, args []any, hintProcessor vm.HintProcessor, verifySecure bool, programSegmentSize *uint) error { +func (runner *CairoRunner) RunFromEntrypoint(entrypoint uint, args []any, hintProcessor vm.HintProcessor, runResources *vm.RunResources, verifySecure bool, programSegmentSize *uint) error { + runner.Vm.RunResources = runResources stack := make([]memory.MaybeRelocatable, 0) for _, arg := range args { val, err := runner.Vm.Segments.GenArg(arg) diff --git a/pkg/runners/cairo_runner_test.go b/pkg/runners/cairo_runner_test.go index 0f5f34c6..8ca5ae54 100644 --- a/pkg/runners/cairo_runner_test.go +++ b/pkg/runners/cairo_runner_test.go @@ -743,7 +743,7 @@ func TestRunFromEntryPointFibonacci(t *testing.T) { runner.InitializeBuiltins() runner.InitializeSegments() - err := runner.RunFromEntrypoint(uint(entrypoint), args, &hintProcessor, true, nil) + err := runner.RunFromEntrypoint(uint(entrypoint), args, &hintProcessor, nil, true, nil) if err != nil { t.Errorf("Running fib entrypoint failed with error %s", err.Error()) @@ -759,3 +759,27 @@ func TestRunFromEntryPointFibonacci(t *testing.T) { } } + +func TestRunFromEntryPointFibonacciNotEnoughSteps(t *testing.T) { + compiledProgram, _ := parser.Parse("../../cairo_programs/fibonacci.json") + programJson := vm.DeserializeProgramJson(compiledProgram) + + entrypoint := programJson.Identifiers["__main__.fib"].PC + args := []any{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltOne()), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltOne()), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(10)), + } + runner, _ := runners.NewCairoRunner(programJson, "all_cairo", false) + hintProcessor := hints.CairoVmHintProcessor{} + + runner.InitializeBuiltins() + runner.InitializeSegments() + runResources := vm.NewRunResources(2) + err := runner.RunFromEntrypoint(uint(entrypoint), args, &hintProcessor, &runResources, true, nil) + + if err == nil { + t.Errorf("Running fib entrypoint should have failed") + } + +} diff --git a/pkg/vm/run_resources.go b/pkg/vm/run_resources.go new file mode 100644 index 00000000..e9da4247 --- /dev/null +++ b/pkg/vm/run_resources.go @@ -0,0 +1,23 @@ +package vm + +type RunResources struct { + nSteps *uint +} + +func NewRunResources(nSteps uint) RunResources { + return RunResources{nSteps: &nSteps} +} + +func (r *RunResources) Consumed() bool { + return r.nSteps != nil && *r.nSteps == 0 +} + +func (r *RunResources) ConsumeStep() { + if r.nSteps != nil && *r.nSteps != 0 { + *r.nSteps-- + } +} + +func (r *RunResources) GetNSteps() *uint { + return r.nSteps +} diff --git a/pkg/vm/vm_core.go b/pkg/vm/vm_core.go index c5cb69b1..7bbe3547 100644 --- a/pkg/vm/vm_core.go +++ b/pkg/vm/vm_core.go @@ -35,6 +35,7 @@ type VirtualMachine struct { RunFinished bool RcLimitsMin *int RcLimitsMax *int + RunResources *RunResources } func NewVirtualMachine() *VirtualMachine {