From d7a7693205e931310c4c573abc93e5820914fbf7 Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 12:37:01 -0300 Subject: [PATCH 01/14] Implement UnsafeKeccak --- pkg/hints/keccak_hint_codes.go | 3 ++ pkg/hints/keccak_hints.go | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 pkg/hints/keccak_hint_codes.go create mode 100644 pkg/hints/keccak_hints.go diff --git a/pkg/hints/keccak_hint_codes.go b/pkg/hints/keccak_hint_codes.go new file mode 100644 index 00000000..f05875b8 --- /dev/null +++ b/pkg/hints/keccak_hint_codes.go @@ -0,0 +1,3 @@ +package hints + +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/keccak_hints.go b/pkg/hints/keccak_hints.go new file mode 100644 index 00000000..b9bb4527 --- /dev/null +++ b/pkg/hints/keccak_hints.go @@ -0,0 +1,69 @@ +package hints + +import ( + . "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils" + . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/types" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" + "github.com/pkg/errors" + "golang.org/x/crypto/sha3" +) + +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 := sha3.New256() + resBytes := hasher.Sum(keccakInput) + + high := FeltFromBeBytes((*[32]byte)(resBytes[:16])) + low := FeltFromBeBytes((*[32]byte)(resBytes[16:32])) + + err = ids.Insert("high", NewMaybeRelocatableFelt(high), vm) + if err != nil { + return err + } + return ids.Insert("low", NewMaybeRelocatableFelt(low), vm) +} From 4ee4a97fddbf059cac31796ec218eeb9769b63a2 Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 12:37:48 -0300 Subject: [PATCH 02/14] Update dependencies --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 0a509fa9..6fea8f82 100644 --- a/go.sum +++ b/go.sum @@ -10,5 +10,7 @@ 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/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 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 73acd4746f2eda0fe5b82fb94ac351d7fba719d9 Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 13:20:04 -0300 Subject: [PATCH 03/14] Add unit test --- pkg/hints/hint_processor.go | 2 ++ pkg/hints/keccak_hints.go | 7 +++-- pkg/hints/keccak_hints_test.go | 57 ++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 pkg/hints/keccak_hints_test.go diff --git a/pkg/hints/hint_processor.go b/pkg/hints/hint_processor.go index cf8181a6..cdc33536 100644 --- a/pkg/hints/hint_processor.go +++ b/pkg/hints/hint_processor.go @@ -69,6 +69,8 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any, return memcpy_enter_scope(data.Ids, vm, execScopes) case VM_ENTER_SCOPE: return vm_enter_scope(execScopes) + case UNSAFE_KECCAK: + return unsafeKeccak(data.Ids, vm, *execScopes) default: return errors.Errorf("Unknown Hint: %s", data.Code) } diff --git a/pkg/hints/keccak_hints.go b/pkg/hints/keccak_hints.go index b9bb4527..22e32edd 100644 --- a/pkg/hints/keccak_hints.go +++ b/pkg/hints/keccak_hints.go @@ -58,8 +58,11 @@ func unsafeKeccak(ids IdsManager, vm *VirtualMachine, scopes ExecutionScopes) er hasher := sha3.New256() resBytes := hasher.Sum(keccakInput) - high := FeltFromBeBytes((*[32]byte)(resBytes[:16])) - low := FeltFromBeBytes((*[32]byte)(resBytes[16:32])) + 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 { diff --git a/pkg/hints/keccak_hints_test.go b/pkg/hints/keccak_hints_test.go new file mode 100644 index 00000000..1ebd4ae1 --- /dev/null +++ b/pkg/hints/keccak_hints_test.go @@ -0,0 +1,57 @@ +package hints_test + +import ( + "testing" + + . "github.com/lambdaclass/cairo-vm.go/pkg/hints" + . "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", uint16(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()) + } +} From 79ad5d4837bd2893f2cec8aaf1db762492601bd1 Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 15:55:36 -0300 Subject: [PATCH 04/14] Fix hash --- go.mod | 5 +++-- go.sum | 9 +++++++-- pkg/hints/keccak_hints.go | 7 ++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 463c710c..919dbd1b 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,16 @@ go 1.20 require ( github.com/pkg/errors v0.9.1 - golang.org/x/crypto v0.12.0 + golang.org/x/crypto v0.13.0 ) require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/ebfe/keccak v0.0.0-20150115210727-5cc570678d1b // 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 + golang.org/x/sys v0.12.0 // indirect ) diff --git a/go.sum b/go.sum index 6fea8f82..03f416b0 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ 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= @@ -10,7 +12,10 @@ 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/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 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +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 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/hints/keccak_hints.go b/pkg/hints/keccak_hints.go index 22e32edd..615e5d06 100644 --- a/pkg/hints/keccak_hints.go +++ b/pkg/hints/keccak_hints.go @@ -1,13 +1,13 @@ 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" - "golang.org/x/crypto/sha3" ) func unsafeKeccak(ids IdsManager, vm *VirtualMachine, scopes ExecutionScopes) error { @@ -55,8 +55,9 @@ func unsafeKeccak(ids IdsManager, vm *VirtualMachine, scopes ExecutionScopes) er } - hasher := sha3.New256() - resBytes := hasher.Sum(keccakInput) + 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]...) From e969f48fc98b27b9374705d2e10b15e2d5054d8b Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 16:51:10 -0300 Subject: [PATCH 05/14] Add unit tests --- pkg/hints/keccak_hints_test.go | 69 +++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/pkg/hints/keccak_hints_test.go b/pkg/hints/keccak_hints_test.go index 1ebd4ae1..18c02b8a 100644 --- a/pkg/hints/keccak_hints_test.go +++ b/pkg/hints/keccak_hints_test.go @@ -33,7 +33,7 @@ func TestUnsafeKeccakOk(t *testing.T) { vm.Segments.LoadData(data_ptr, &data) // Add __keccak_max_size scopes := NewExecutionScopes() - scopes.AssignOrUpdateVariable("__keccak_max_size", uint16(500)) + scopes.AssignOrUpdateVariable("__keccak_max_size", uint64(500)) hintProcessor := CairoVmHintProcessor{} hintData := any(HintData{ Ids: idsManager, @@ -55,3 +55,70 @@ func TestUnsafeKeccakOk(t *testing.T) { 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") + } +} From 7f5dbfbb557894f66aee8346d258f9244bc9eaef Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 16:57:55 -0300 Subject: [PATCH 06/14] Add integration test --- cairo_programs/unsafe_keccak.cairo | 28 ++++++++++++++++++++++++++++ pkg/vm/cairo_run/cairo_run_test.go | 8 ++++++++ 2 files changed, 36 insertions(+) create mode 100644 cairo_programs/unsafe_keccak.cairo 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/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index 3182e252..c6cf2dc0 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -202,3 +202,11 @@ func TestSqrtHint(t *testing.T) { t.Errorf("Program execution failed with error: %s", err) } } + +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) + } +} From 1d569c89a34e3d2ebe7f4b92ead36dd8e0590c11 Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 17:35:06 -0300 Subject: [PATCH 07/14] Add missing file --- pkg/hints/hint_codes/keccak_hint_codes.go | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pkg/hints/hint_codes/keccak_hint_codes.go 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')" From 2da342e2bdb324308a4f34e0007d4c172862a4a6 Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 17:48:04 -0300 Subject: [PATCH 08/14] Add constant + GetStructFieldRelocatable + start hint --- pkg/hints/hint_codes/keccak_hint_codes.go | 2 ++ pkg/hints/hint_utils/ids_manager.go | 29 +++++++++++++++++++++++ pkg/hints/keccak_hints.go | 16 +++++++++++++ 3 files changed, 47 insertions(+) diff --git a/pkg/hints/hint_codes/keccak_hint_codes.go b/pkg/hints/hint_codes/keccak_hint_codes.go index 2db053ca..c9cc1ea1 100644 --- a/pkg/hints/hint_codes/keccak_hint_codes.go +++ b/pkg/hints/hint_codes/keccak_hint_codes.go @@ -1,3 +1,5 @@ package hint_codes const UNSAFE_KECCAK = "from eth_hash.auto import keccak\n\ndata, length = ids.data, ids.length\n\nif '__keccak_max_size' in globals():\n assert length <= __keccak_max_size, \\\n f'unsafe_keccak() can only be used with length<={__keccak_max_size}. ' \\\n f'Got: length={length}.'\n\nkeccak_input = bytearray()\nfor word_i, byte_i in enumerate(range(0, length, 16)):\n word = memory[data + word_i]\n n_bytes = min(16, length - byte_i)\n assert 0 <= word < 2 ** (8 * n_bytes)\n keccak_input += word.to_bytes(n_bytes, 'big')\n\nhashed = keccak(keccak_input)\nids.high = int.from_bytes(hashed[:16], 'big')\nids.low = int.from_bytes(hashed[16:32], 'big')" + +const UNSAFE_KECCAK_FINALIZE = "from eth_hash.auto import keccak\nkeccak_input = bytearray()\nn_elms = ids.keccak_state.end_ptr - ids.keccak_state.start_ptr\nfor word in memory.get_range(ids.keccak_state.start_ptr, n_elms):\n keccak_input += word.to_bytes(16, 'big')\nhashed = keccak(keccak_input)\nids.high = int.from_bytes(hashed[:16], 'big')\nids.low = int.from_bytes(hashed[16:32], 'big')" diff --git a/pkg/hints/hint_utils/ids_manager.go b/pkg/hints/hint_utils/ids_manager.go index f883ac5c..fadacfeb 100644 --- a/pkg/hints/hint_utils/ids_manager.go +++ b/pkg/hints/hint_utils/ids_manager.go @@ -162,6 +162,35 @@ func (ids *IdsManager) GetStructFieldFelt(name string, field_off uint, vm *Virtu return lambdaworks.Felt{}, ErrUnknownIdentifier(name) } +/* + Returns the value of an ids' field (given that the identifier is a sruct) as a Relocatable + For example: + + struct shelter { + cats cat* + dogs dog* + } + + to access each struct field, cats will be field 0 and dogs will be field 1, so to access them we can use: + ids_cats := ids.GetStructFieldFelt("shelter", 0, vm) or ids_cats := ids.Get("shelter", vm) + ids_dogs := ids.GetStructFieldFelt("shelter", 1, vm) +*/ +func (ids *IdsManager) GetStructFieldRelocatable(name string, field_off uint, vm *VirtualMachine) (Relocatable, error) { + reference, ok := ids.References[name] + if ok { + val, ok := getStructFieldFromReference(&reference, field_off, ids.HintApTracking, vm) + if ok { + rel, is_rel := val.GetRelocatable() + if !is_rel { + return Relocatable{}, errors.Errorf("Identifier %s is not a Relocatable", name) + } + return rel, nil + } + } + + return Relocatable{}, ErrUnknownIdentifier(name) +} + /* Inserts value into an ids' field (given that the identifier is a sruct) For example: diff --git a/pkg/hints/keccak_hints.go b/pkg/hints/keccak_hints.go index 615e5d06..1ec95b77 100644 --- a/pkg/hints/keccak_hints.go +++ b/pkg/hints/keccak_hints.go @@ -71,3 +71,19 @@ func unsafeKeccak(ids IdsManager, vm *VirtualMachine, scopes ExecutionScopes) er } return ids.Insert("low", NewMaybeRelocatableFelt(low), vm) } + +func unsafeKeccakFinalize(ids IdsManager, vm *VirtualMachine, scopes ExecutionScopes) error { + // Fetch ids variables + startPtr, err := ids.GetStructFieldRelocatable("keccak_state", 0, vm) + if err != nil { + return err + } + endPtr, err := ids.GetStructFieldRelocatable("keccak_state", 1, vm) + if err != nil { + return err + } + n_elems, err := endPtr.Sub(startPtr) + if err != nil { + return err + } +} From 96137fb021953c14793c7898d1f4c40d74fa1933 Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 17:48:30 -0300 Subject: [PATCH 09/14] Add test file --- cairo_programs/unsafe_keccak_finalize.cairo | 28 +++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 cairo_programs/unsafe_keccak_finalize.cairo diff --git a/cairo_programs/unsafe_keccak_finalize.cairo b/cairo_programs/unsafe_keccak_finalize.cairo new file mode 100644 index 00000000..6cf89f88 --- /dev/null +++ b/cairo_programs/unsafe_keccak_finalize.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_finalize, KeccakState +from starkware.cairo.common.uint256 import Uint256 + +func main{output_ptr: felt*}() { + alloc_locals; + + let (data: felt*) = alloc(); + + assert data[0] = 0; + assert data[1] = 1; + assert data[2] = 2; + + let keccak_state = KeccakState(start_ptr=data, end_ptr=data + 2); + + let res: Uint256 = unsafe_keccak_finalize(keccak_state); + + assert res.low = 17219183504112405672555532996650339574; + assert res.high = 235346966651632113557018504892503714354; + + serialize_word(res.low); + serialize_word(res.high); + + return (); +} From bdbe7b458096709cac287647972a186ea662c26e Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 17:55:55 -0300 Subject: [PATCH 10/14] Add MemorySegmentManager.GetFeltRange --- pkg/vm/memory/segments.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/vm/memory/segments.go b/pkg/vm/memory/segments.go index d22d62f7..659304f8 100644 --- a/pkg/vm/memory/segments.go +++ b/pkg/vm/memory/segments.go @@ -172,3 +172,15 @@ func (m *MemorySegmentManager) Finalize(size *uint, segmentIndex uint, publicMem m.PublicMemoryOffsets[segmentIndex] = emptyList } } + +func (m *MemorySegmentManager) GetFeltRange(start Relocatable, size uint) ([]lambdaworks.Felt, error) { + feltRange := make([]lambdaworks.Felt, 0, size) + for i := uint(0); i < size; i++ { + val, err := m.Memory.GetFelt(start.AddUint(i)) + if err != nil { + return nil, err + } + feltRange = append(feltRange, val) + } + return feltRange, nil +} From 7cb6ecaca17e7f929478e80fad50a03916c997ca Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 17:56:16 -0300 Subject: [PATCH 11/14] Progress --- pkg/hints/keccak_hints.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/hints/keccak_hints.go b/pkg/hints/keccak_hints.go index 1ec95b77..a79ffbff 100644 --- a/pkg/hints/keccak_hints.go +++ b/pkg/hints/keccak_hints.go @@ -82,8 +82,15 @@ func unsafeKeccakFinalize(ids IdsManager, vm *VirtualMachine, scopes ExecutionSc if err != nil { return err } - n_elems, err := endPtr.Sub(startPtr) + + // Hint Logic + nElemsFelt, err := endPtr.Sub(startPtr) if err != nil { return err } + nElems, err := nElemsFelt.ToU64() + if err != nil { + return err + } + } From ce7c084bdf7748a58559eff05220025d5ae3be40 Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 18:14:23 -0300 Subject: [PATCH 12/14] Finish hint --- pkg/hints/hint_processor.go | 2 ++ pkg/hints/keccak_hints.go | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/pkg/hints/hint_processor.go b/pkg/hints/hint_processor.go index f9b1174b..9a25fb77 100644 --- a/pkg/hints/hint_processor.go +++ b/pkg/hints/hint_processor.go @@ -102,6 +102,8 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any, return vm_enter_scope(execScopes) case UNSAFE_KECCAK: return unsafeKeccak(data.Ids, vm, *execScopes) + case UNSAFE_KECCAK_FINALIZE: + return unsafeKeccakFinalize(data.Ids, vm, *execScopes) case IS_NN: return isNN(data.Ids, vm) case IS_NN_OUT_OF_RANGE: diff --git a/pkg/hints/keccak_hints.go b/pkg/hints/keccak_hints.go index a79ffbff..6f639991 100644 --- a/pkg/hints/keccak_hints.go +++ b/pkg/hints/keccak_hints.go @@ -92,5 +92,28 @@ func unsafeKeccakFinalize(ids IdsManager, vm *VirtualMachine, scopes ExecutionSc if err != nil { return err } + inputFelts, err := vm.Segments.GetFeltRange(startPtr, uint(nElems)) + if err != nil { + return err + } + inputBytes := make([]byte, 0, 16*nElems) + for i := 0; i < int(nElems); i++ { + inputBytes = append(inputBytes, inputFelts[i].ToBeBytes()[16:]...) + } + + hasher := keccak.New256() + hasher.Write(inputBytes) + 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) } From 85ebe5064fe01357529069ce5d5976f07a31b253 Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 18:23:25 -0300 Subject: [PATCH 13/14] Add unit test --- pkg/hints/hint_processor.go | 2 +- pkg/hints/keccak_hints.go | 2 +- pkg/hints/keccak_hints_test.go | 43 ++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/pkg/hints/hint_processor.go b/pkg/hints/hint_processor.go index 9a25fb77..55ce7a89 100644 --- a/pkg/hints/hint_processor.go +++ b/pkg/hints/hint_processor.go @@ -103,7 +103,7 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any, case UNSAFE_KECCAK: return unsafeKeccak(data.Ids, vm, *execScopes) case UNSAFE_KECCAK_FINALIZE: - return unsafeKeccakFinalize(data.Ids, vm, *execScopes) + return unsafeKeccakFinalize(data.Ids, vm) case IS_NN: return isNN(data.Ids, vm) case IS_NN_OUT_OF_RANGE: diff --git a/pkg/hints/keccak_hints.go b/pkg/hints/keccak_hints.go index 6f639991..969ccdba 100644 --- a/pkg/hints/keccak_hints.go +++ b/pkg/hints/keccak_hints.go @@ -72,7 +72,7 @@ func unsafeKeccak(ids IdsManager, vm *VirtualMachine, scopes ExecutionScopes) er return ids.Insert("low", NewMaybeRelocatableFelt(low), vm) } -func unsafeKeccakFinalize(ids IdsManager, vm *VirtualMachine, scopes ExecutionScopes) error { +func unsafeKeccakFinalize(ids IdsManager, vm *VirtualMachine) error { // Fetch ids variables startPtr, err := ids.GetStructFieldRelocatable("keccak_state", 0, vm) if err != nil { diff --git a/pkg/hints/keccak_hints_test.go b/pkg/hints/keccak_hints_test.go index 020412b9..6f20d8e4 100644 --- a/pkg/hints/keccak_hints_test.go +++ b/pkg/hints/keccak_hints_test.go @@ -123,3 +123,46 @@ func TestUnsafeKeccakInvalidWordSize(t *testing.T) { t.Errorf("UNSAFE_KECCAK hint test should have failed") } } + +func TestUnsafeKeccakFinalizeOk(t *testing.T) { + vm := NewVirtualMachine() + vm.Segments.AddSegment() + inputStart := vm.Segments.AddSegment() + idsManager := SetupIdsForTest( + map[string][]*MaybeRelocatable{ + "keccak_state": { + NewMaybeRelocatableRelocatable(inputStart), + NewMaybeRelocatableRelocatable(inputStart.AddUint(2)), + }, + "high": {nil}, + "low": {nil}, + }, + vm, + ) + // Insert keccak input into memory + input := []MaybeRelocatable{ + *NewMaybeRelocatableFelt(FeltZero()), + *NewMaybeRelocatableFelt(FeltOne()), + } + vm.Segments.LoadData(inputStart, &input) + hintProcessor := CairoVmHintProcessor{} + hintData := any(HintData{ + Ids: idsManager, + Code: UNSAFE_KECCAK_FINALIZE, + }) + err := hintProcessor.ExecuteHint(vm, &hintData, nil, nil) + if err != nil { + t.Errorf("UNSAFE_KECCAK_FINALIZE hint test failed with error %s", err) + } + // Check ids values + high, err := idsManager.GetFelt("high", vm) + expectedHigh := FeltFromDecString("235346966651632113557018504892503714354") + 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("17219183504112405672555532996650339574") + if err != nil || low != expectedLow { + t.Errorf("Wrong/No ids.low\n Expected %s, got %s.", expectedLow.ToHexString(), low.ToHexString()) + } +} From 18d1137d7791a7547fc23ec1500606a9e6e94f7f Mon Sep 17 00:00:00 2001 From: Federica Date: Tue, 19 Sep 2023 18:24:28 -0300 Subject: [PATCH 14/14] Add integration test --- pkg/vm/cairo_run/cairo_run_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index ea6d889f..f3587222 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -218,6 +218,14 @@ func TestUnsafeKeccak(t *testing.T) { } } +func TestUnsafeKeccakFinalize(t *testing.T) { + cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false} + _, err := cairo_run.CairoRun("../../../cairo_programs/unsafe_keccak_finalize.json", cairoRunConfig) + if err != nil { + t.Errorf("Program execution failed with error: %s", err) + } +} + func TestMathCmp(t *testing.T) { cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false} _, err := cairo_run.CairoRun("../../../cairo_programs/math_cmp.json", cairoRunConfig)