Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement UNSAFE_KECCAK hint #246

Merged
merged 13 commits into from
Sep 20, 2023
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