Skip to content

Commit

Permalink
Add VerifySecureRunner + SecureRun config flag (#303)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
fmoletta and pefontana authored Oct 23, 2023
1 parent 62f6a55 commit a629107
Show file tree
Hide file tree
Showing 17 changed files with 435 additions and 18 deletions.
14 changes: 13 additions & 1 deletion cmd/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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"},
Expand Down
11 changes: 11 additions & 0 deletions pkg/builtins/bitwise.go
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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
}
99 changes: 87 additions & 12 deletions pkg/builtins/builtin_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package builtins

import (
"fmt"
"sort"

"github.com/lambdaclass/cairo-vm.go/pkg/vm/memory"
"github.com/pkg/errors"
Expand All @@ -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
Expand All @@ -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
}
105 changes: 105 additions & 0 deletions pkg/builtins/builtin_runner_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
11 changes: 11 additions & 0 deletions pkg/builtins/ec_op.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
11 changes: 11 additions & 0 deletions pkg/builtins/keccak.go
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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
}
16 changes: 16 additions & 0 deletions pkg/builtins/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
)

const OUTPUT_BUILTIN_NAME = "output"
const OUTPUT_CELLS_PER_INSTANCE = 1

type OutputBuiltinRunner struct {
base memory.Relocatable
Expand Down Expand Up @@ -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
}
11 changes: 11 additions & 0 deletions pkg/builtins/pedersen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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
}
11 changes: 11 additions & 0 deletions pkg/builtins/poseidon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit a629107

Please sign in to comment.