Skip to content

Commit

Permalink
Implement UNSAFE_KECCAK hint (#246)
Browse files Browse the repository at this point in the history
* Implement UnsafeKeccak

* Update dependencies

* Add unit test

* Fix hash

* Add unit tests

* Add integration test

* Add missing file

* Format files

---------

Co-authored-by: Mariano Nicolini <[email protected]>
  • Loading branch information
fmoletta and entropidelic authored Sep 20, 2023
1 parent 2fa2e5f commit 8455a89
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 12 deletions.
28 changes: 28 additions & 0 deletions cairo_programs/unsafe_keccak.cairo
Original file line number Diff line number Diff line change
@@ -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 ();
}
19 changes: 13 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
11 changes: 5 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
3 changes: 3 additions & 0 deletions pkg/hints/hint_codes/keccak_hint_codes.go
Original file line number Diff line number Diff line change
@@ -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')"
2 changes: 2 additions & 0 deletions pkg/hints/hint_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
73 changes: 73 additions & 0 deletions pkg/hints/keccak_hints.go
Original file line number Diff line number Diff line change
@@ -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)
}
125 changes: 125 additions & 0 deletions pkg/hints/keccak_hints_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
9 changes: 9 additions & 0 deletions pkg/vm/cairo_run/cairo_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit 8455a89

Please sign in to comment.