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_FINALIZE hint #271

Merged
merged 17 commits into from
Sep 20, 2023
Merged
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 ();
}
28 changes: 28 additions & 0 deletions cairo_programs/unsafe_keccak_finalize.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_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 ();
}
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=
5 changes: 5 additions & 0 deletions pkg/hints/hint_codes/keccak_hint_codes.go
Original file line number Diff line number Diff line change
@@ -0,0 +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')"
4 changes: 4 additions & 0 deletions pkg/hints/hint_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ 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)
case UNSAFE_KECCAK_FINALIZE:
return unsafeKeccakFinalize(data.Ids, vm)
case IS_NN:
return isNN(data.Ids, vm)
case IS_NN_OUT_OF_RANGE:
Expand Down
29 changes: 29 additions & 0 deletions pkg/hints/hint_utils/ids_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
119 changes: 119 additions & 0 deletions pkg/hints/keccak_hints.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
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)
}

func unsafeKeccakFinalize(ids IdsManager, vm *VirtualMachine) 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
}

// Hint Logic
nElemsFelt, err := endPtr.Sub(startPtr)
if err != nil {
return err
}
nElems, err := nElemsFelt.ToU64()
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)
}
Loading