diff --git a/cairo_programs/unsafe_keccak.cairo b/cairo_programs/unsafe_keccak.cairo new file mode 100644 index 00000000..9f80a750 --- /dev/null +++ b/cairo_programs/unsafe_keccak.cairo @@ -0,0 +1,28 @@ +%builtins output + +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.serialize import serialize_word +from starkware.cairo.common.keccak import unsafe_keccak + +func main{output_ptr: felt*}() { + alloc_locals; + + let (data: felt*) = alloc(); + + assert data[0] = 500; + assert data[1] = 2; + assert data[2] = 3; + assert data[3] = 6; + assert data[4] = 1; + assert data[5] = 4444; + + let (low: felt, high: felt) = unsafe_keccak(data, 6); + + assert low = 182565855334575837944615807286777833262; + assert high = 90044356407795786957420814893241941221; + + serialize_word(low); + serialize_word(high); + + return (); +} diff --git a/go.mod b/go.mod index 5d10e288..8c74a96f 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,23 @@ go 1.20 require ( github.com/pkg/errors v0.9.1 - golang.org/x/crypto v0.12.0 - golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc + golang.org/x/crypto v0.13.0 +) + +require ( + github.com/ebfe/keccak v0.0.0-20150115210727-5cc570678d1b + github.com/urfave/cli/v2 v2.24.1 ) require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect - github.com/ethereum/go-ethereum v1.12.1 // indirect - github.com/miguelmota/go-solidity-sha3 v0.1.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/urfave/cli/v2 v2.25.7 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/sys v0.11.0 // indirect +) + +require ( + github.com/ethereum/go-ethereum v1.12.1 // indirect + github.com/miguelmota/go-solidity-sha3 v0.1.1 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 + golang.org/x/sys v0.12.0 // indirect ) diff --git a/go.sum b/go.sum index 2dd7019d..4989ccaa 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,18 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/ebfe/keccak v0.0.0-20150115210727-5cc570678d1b h1:BMyjwV6Fal/Ffphi4dJfulSxMeDl0xFS2vs5QLr6rsI= +github.com/ebfe/keccak v0.0.0-20150115210727-5cc570678d1b/go.mod h1:fnviDXB7GJWiSUI9thIXmk9QKM8Rhj1JV/LcMRzkiVA= github.com/ethereum/go-ethereum v1.12.1/go.mod h1:zKetLweqBR8ZS+1O9iJWI8DvmmD2NzD19apjEWDCsnw= github.com/miguelmota/go-solidity-sha3 v0.1.1/go.mod h1:sax1FvQF+f71j8W1uUHMZn8NxKyl5rYLks2nqj8RFEw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= -github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/urfave/cli/v2 v2.24.1 h1:/QYYr7g0EhwXEML8jO+8OYt5trPnLHS0p3mrgExJ5NU= +github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/hints/hint_codes/keccak_hint_codes.go b/pkg/hints/hint_codes/keccak_hint_codes.go new file mode 100644 index 00000000..2db053ca --- /dev/null +++ b/pkg/hints/hint_codes/keccak_hint_codes.go @@ -0,0 +1,3 @@ +package hint_codes + +const UNSAFE_KECCAK = "from eth_hash.auto import keccak\n\ndata, length = ids.data, ids.length\n\nif '__keccak_max_size' in globals():\n assert length <= __keccak_max_size, \\\n f'unsafe_keccak() can only be used with length<={__keccak_max_size}. ' \\\n f'Got: length={length}.'\n\nkeccak_input = bytearray()\nfor word_i, byte_i in enumerate(range(0, length, 16)):\n word = memory[data + word_i]\n n_bytes = min(16, length - byte_i)\n assert 0 <= word < 2 ** (8 * n_bytes)\n keccak_input += word.to_bytes(n_bytes, 'big')\n\nhashed = keccak(keccak_input)\nids.high = int.from_bytes(hashed[:16], 'big')\nids.low = int.from_bytes(hashed[16:32], 'big')" diff --git a/pkg/hints/hint_processor.go b/pkg/hints/hint_processor.go index b8d5fa6e..6695a14a 100644 --- a/pkg/hints/hint_processor.go +++ b/pkg/hints/hint_processor.go @@ -106,6 +106,8 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any, return memset_step_loop(data.Ids, vm, execScopes, "continue_loop") case VM_ENTER_SCOPE: return vm_enter_scope(execScopes) + case UNSAFE_KECCAK: + return unsafeKeccak(data.Ids, vm, *execScopes) case UNSIGNED_DIV_REM: return unsignedDivRem(data.Ids, vm) case SIGNED_DIV_REM: diff --git a/pkg/hints/keccak_hints.go b/pkg/hints/keccak_hints.go new file mode 100644 index 00000000..615e5d06 --- /dev/null +++ b/pkg/hints/keccak_hints.go @@ -0,0 +1,73 @@ +package hints + +import ( + "github.com/ebfe/keccak" + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" + "github.com/pkg/errors" +) + +func unsafeKeccak(ids IdsManager, vm *VirtualMachine, scopes ExecutionScopes) error { + // Fetch ids variable + lengthFelt, err := ids.GetFelt("length", vm) + if err != nil { + return err + } + length, err := lengthFelt.ToU64() + if err != nil { + return err + } + data, err := ids.GetRelocatable("data", vm) + if err != nil { + return err + } + // Check __keccak_max_size if available + keccakMaxSizeAny, err := scopes.Get("__keccak_max_size") + if err == nil { + keccakMaxSize, ok := keccakMaxSizeAny.(uint64) + if ok { + if length > keccakMaxSize { + return errors.Errorf("unsafe_keccak() can only be used with length<=%d. Got: length=%d", keccakMaxSize, length) + } + } + } + keccakInput := make([]byte, 0) + for byteIdx, wordIdx := 0, 0; byteIdx < int(length); byteIdx, wordIdx = byteIdx+16, wordIdx+1 { + wordAddr := data.AddUint(uint(wordIdx)) + word, err := vm.Segments.Memory.GetFelt(wordAddr) + if err != nil { + return err + } + nBytes := int(length) - byteIdx + if nBytes > 16 { + nBytes = 16 + } + + if int(word.Bits()) > 8*nBytes { + return errors.Errorf("Invalid word size: %s", word.ToHexString()) + } + + start := 32 - nBytes + keccakInput = append(keccakInput, word.ToBeBytes()[start:]...) + + } + + hasher := keccak.New256() + hasher.Write(keccakInput) + resBytes := hasher.Sum(nil) + + highBytes := append([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, resBytes[:16]...) + lowBytes := append([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, resBytes[16:32]...) + + high := FeltFromBeBytes((*[32]byte)(highBytes)) + low := FeltFromBeBytes((*[32]byte)(lowBytes)) + + err = ids.Insert("high", NewMaybeRelocatableFelt(high), vm) + if err != nil { + return err + } + return ids.Insert("low", NewMaybeRelocatableFelt(low), vm) +} diff --git a/pkg/hints/keccak_hints_test.go b/pkg/hints/keccak_hints_test.go new file mode 100644 index 00000000..020412b9 --- /dev/null +++ b/pkg/hints/keccak_hints_test.go @@ -0,0 +1,125 @@ +package hints_test + +import ( + "testing" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints" + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_codes" + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestUnsafeKeccakOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + data_ptr := vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "length": {NewMaybeRelocatableFelt(FeltFromUint64(3))}, + "data": {NewMaybeRelocatableRelocatable(data_ptr)}, + "high": {nil}, + "low": {nil}, + }, + vm, + ) + // Insert data into memory + data := []MaybeRelocatable{ + *NewMaybeRelocatableFelt(FeltOne()), + *NewMaybeRelocatableFelt(FeltOne()), + *NewMaybeRelocatableFelt(FeltOne()), + } + vm.Segments.LoadData(data_ptr, &data) + // Add __keccak_max_size + scopes := NewExecutionScopes() + scopes.AssignOrUpdateVariable("__keccak_max_size", uint64(500)) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: UNSAFE_KECCAK, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err != nil { + t.Errorf("UNSAFE_KECCAK hint test failed with error %s", err) + } + // Check ids values + high, err := idsManager.GetFelt("high", vm) + expectedHigh := FeltFromDecString("199195598804046335037364682505062700553") + if err != nil || high != expectedHigh { + t.Errorf("Wrong/No ids.high.\n Expected %s, got %s.", expectedHigh.ToHexString(), high.ToHexString()) + } + low, err := idsManager.GetFelt("low", vm) + expectedLow := FeltFromDecString("259413678945892999811634722593932702747") + if err != nil || low != expectedLow { + t.Errorf("Wrong/No ids.low\n Expected %s, got %s.", expectedLow.ToHexString(), low.ToHexString()) + } +} + +func TestUnsafeKeccakMaxSizeExceeded(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + data_ptr := vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "length": {NewMaybeRelocatableFelt(FeltFromUint64(3))}, + "data": {NewMaybeRelocatableRelocatable(data_ptr)}, + "high": {nil}, + "low": {nil}, + }, + vm, + ) + // Insert data into memory + data := []MaybeRelocatable{ + *NewMaybeRelocatableFelt(FeltOne()), + *NewMaybeRelocatableFelt(FeltOne()), + *NewMaybeRelocatableFelt(FeltOne()), + } + vm.Segments.LoadData(data_ptr, &data) + // Add __keccak_max_size + scopes := NewExecutionScopes() + scopes.AssignOrUpdateVariable("__keccak_max_size", uint64(2)) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: UNSAFE_KECCAK, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err == nil { + t.Errorf("UNSAFE_KECCAK hint test should have failed") + } +} + +func TestUnsafeKeccakInvalidWordSize(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + data_ptr := vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "length": {NewMaybeRelocatableFelt(FeltFromUint64(3))}, + "data": {NewMaybeRelocatableRelocatable(data_ptr)}, + "high": {nil}, + "low": {nil}, + }, + vm, + ) + // Insert data into memory + data := []MaybeRelocatable{ + *NewMaybeRelocatableFelt(FeltFromDecString("-1")), + *NewMaybeRelocatableFelt(FeltOne()), + *NewMaybeRelocatableFelt(FeltOne()), + } + vm.Segments.LoadData(data_ptr, &data) + // Add __keccak_max_size + scopes := NewExecutionScopes() + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: UNSAFE_KECCAK, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, scopes) + if err == nil { + t.Errorf("UNSAFE_KECCAK hint test should have failed") + } +} diff --git a/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index 55db8e9d..31d9c3b7 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -210,6 +210,14 @@ func TestSqrtHint(t *testing.T) { } } +func TestUnsafeKeccak(t *testing.T) { + cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false} + _, err := cairo_run.CairoRun("../../../cairo_programs/unsafe_keccak.json", cairoRunConfig) + if err != nil { + t.Errorf("Program execution failed with error: %s", err) + } +} + func TestUnsignedDivRemHint(t *testing.T) { cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false} _, err := cairo_run.CairoRun("../../../cairo_programs/unsigned_div_rem.json", cairoRunConfig) @@ -233,6 +241,7 @@ func TestAssertLeFelt(t *testing.T) { t.Errorf("Program execution failed with error: %s", err) } } + func TestMemsetHint(t *testing.T) { cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false} _, err := cairo_run.CairoRun("../../../cairo_programs/memset.json", cairoRunConfig)