diff --git a/op-geth/core/types/hashing.go b/op-geth/core/types/hashing.go index fbdeaf0d0793c..9dd0f849b15e4 100644 --- a/op-geth/core/types/hashing.go +++ b/op-geth/core/types/hashing.go @@ -1,3 +1,5 @@ +//go:build !(js || wasm || wasip1) +// +build !wasm,!wasip1, !js // Copyright 2021 The go-ethereum Authors // This file is part of the go-ethereum library. // diff --git a/op-geth/core/types/hashing_wasm.go b/op-geth/core/types/hashing_wasm.go new file mode 100644 index 0000000000000..0d012a405c7c8 --- /dev/null +++ b/op-geth/core/types/hashing_wasm.go @@ -0,0 +1,144 @@ +//go:build js || wasm || wasip1 +// +build js wasm wasip1 +package types + +import ( + "bytes" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" +) + +// hasherPool holds LegacyKeccak256 hashers for rlpHash. +var hasherPool = sync.Pool{ + New: func() interface{} { return sha3.NewLegacyKeccak256() }, +} + +// encodeBufferPool holds temporary encoder buffers for DeriveSha and TX encoding. +var encodeBufferPool = sync.Pool{ + New: func() interface{} { return new(bytes.Buffer) }, +} + +// rlpHash encodes x and hashes the encoded bytes. +func oldrlpHash(x interface{}) (h common.Hash) { + sha := hasherPool.Get().(crypto.KeccakState) + defer hasherPool.Put(sha) + sha.Reset() + rlp.Encode(sha, x) + sha.Read(h[:]) + return h +} + +func checkrlpHash(x interface{}) (h common.Hash) { + sha := hasherPool.Get().(crypto.KeccakState) + defer hasherPool.Put(sha) + sha.Reset() + hash := NewHashHelper() + rlp.Encode(hash, x) + hash.WriteTo(sha) + sha.Read(h[:]) + n := hash.Hash() + for i := 0; i < 32; i++ { + if h[i] != n[i] { + } + require_bool(h[i] == n[i]) + } + return n +} + +func rlpHash(x interface{}) (h common.Hash) { + hash := NewHashHelper() + rlp.Encode(hash, x) + n := hash.Hash() + return n +} + +// prefixedRlpHash writes the prefix into the hasher before rlp-encoding x. +// It's used for typed transactions. +func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) { + sha := hasherPool.Get().(crypto.KeccakState) + defer hasherPool.Put(sha) + sha.Reset() + sha.Write([]byte{prefix}) + rlp.Encode(sha, x) + sha.Read(h[:]) + wasm_dbg(222) + return h +} + +// TrieHasher is the tool used to calculate the hash of derivable list. +// This is internal, do not use. +type TrieHasher interface { + Reset() + Update([]byte, []byte) error + Hash() common.Hash +} + +// DerivableList is the input to DeriveSha. +// It is implemented by the 'Transactions' and 'Receipts' types. +// This is internal, do not use these methods. +type DerivableList interface { + Len() int + EncodeIndex(int, *bytes.Buffer) +} + +func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte { + buf.Reset() + list.EncodeIndex(i, buf) + // It's really unfortunate that we need to do perform this copy. + // StackTrie holds onto the values until Hash is called, so the values + // written to it must not alias. + return common.CopyBytes(buf.Bytes()) +} + +// DeriveSha creates the tree hashes of transactions, receipts, and withdrawals in a block header. +func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash { + hasher.Reset() + + valueBuf := encodeBufferPool.Get().(*bytes.Buffer) + defer encodeBufferPool.Put(valueBuf) + + // StackTrie requires values to be inserted in increasing hash order, which is not the + // order that `list` provides hashes in. This insertion sequence ensures that the + // order is correct. + // + // The error returned by hasher is omitted because hasher will produce an incorrect + // hash in case any error occurs. + var indexBuf []byte + for i := 1; i < list.Len() && i <= 0x7f; i++ { + indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) + value := encodeForDerive(list, i, valueBuf) + hasher.Update(indexBuf, value) + } + if list.Len() > 0 { + indexBuf = rlp.AppendUint64(indexBuf[:0], 0) + value := encodeForDerive(list, 0, valueBuf) + hasher.Update(indexBuf, value) + } + for i := 0x80; i < list.Len(); i++ { + indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) + value := encodeForDerive(list, i, valueBuf) + hasher.Update(indexBuf, value) + } + wasm_dbg(111) + return hasher.Hash() +} + +//go:wasmimport env wasm_dbg +//go:noescape +func wasm_dbg(uint64) + +//go:wasmimport env require +//go:noescape +func require(uint32) + +func require_bool(cond bool) { + if cond { + require(1) + } else { + require(0) + } +} diff --git a/op-geth/core/types/keccak256_wasm.go b/op-geth/core/types/keccak256_wasm.go new file mode 100644 index 0000000000000..cad9a1f86c9bb --- /dev/null +++ b/op-geth/core/types/keccak256_wasm.go @@ -0,0 +1,164 @@ +//go:build js || wasm || wasip1 +// +build js wasm wasip1 +package types + +import ( + "encoding/binary" + "io" +) + +//go:wasmimport env keccak_new +//go:noescape +func keccak_new(uint64) + +//go:wasmimport env keccak_push +//go:noescape +func keccak_push(uint64) + +//go:wasmimport env keccak_finalize +//go:noescape +func keccak_finalize() uint64 + +func NewHashHelper() *HashHelper { + return &HashHelper{} +} + +type HashHelper struct { + data []byte +} + +func (b *HashHelper) Write(p []byte) (n int, err error) { + b.data = append(b.data, p...) + return len(p), nil +} + +func (b *HashHelper) WriteTo(w io.Writer) (err error) { + w.Write(b.data) + return nil +} + +func (b *HashHelper) Hash() [32]byte { + size := uint64(len(b.data)) + padding := size % 136 + if padding != 0 { + padding = 136 - padding + } else { + padding = 136 + } + buf := make([]byte, size+padding) + //wasm_dbg(size) + wasm_dbg(size + padding) + copy(buf, b.data) + hash := Keccak256Hash(buf, size, padding) + return hash +} + +/* + *func (b HashHelper) PrintData() { + * for _, v := range b.data { + * wasm_dbg(v) + * } + * for i := 0; i < 136; i++ { + * wasm_dbg(v) + * } + *} + */ + +var hash [32]byte + +func Keccak256Hash(data []byte, size uint64, padding uint64) [32]byte { + total_len := len(data) + if padding == 1 { + data[total_len-1] = 0x81 + } else { + data[size] = 0x01 + data[total_len-1] = 0x80 + } + + var hash_0 uint64 + var hash_1 uint64 + var hash_2 uint64 + var hash_3 uint64 + + var val uint64 + + round := total_len / 136 + keccak_new(1) + for i := 0; i < round; i++ { + for j := 0; j < 17; j++ { + start := i*136 + j*8 + val = binary.LittleEndian.Uint64(data[start : start+8]) + keccak_push(val) + } + hash_0 = keccak_finalize() + hash_1 = keccak_finalize() + hash_2 = keccak_finalize() + hash_3 = keccak_finalize() + keccak_new(0) + } + + binary.LittleEndian.PutUint64(hash[:], hash_0) + binary.LittleEndian.PutUint64(hash[8:], hash_1) + binary.LittleEndian.PutUint64(hash[16:], hash_2) + binary.LittleEndian.PutUint64(hash[24:], hash_3) + + return hash +} + +func Keccak256HashUint64(data []uint64) [32]byte { + var hash [32]byte + var hash_0 uint64 + var hash_1 uint64 + var hash_2 uint64 + var hash_3 uint64 + + keccak_new(1) + round := len(data) / 17 + for i := 0; i < round; i++ { + for j := 0; j < 17; j++ { + keccak_push(data[i*17+j]) + } + hash_0 = keccak_finalize() + hash_1 = keccak_finalize() + hash_2 = keccak_finalize() + hash_3 = keccak_finalize() + keccak_new(0) + } + + binary.LittleEndian.PutUint64(hash[:], hash_0) + binary.LittleEndian.PutUint64(hash[8:], hash_1) + binary.LittleEndian.PutUint64(hash[16:], hash_2) + binary.LittleEndian.PutUint64(hash[24:], hash_3) + + return hash +} + +/* +// for test + +func keccak256check(input []byte, output []byte) { + result := Keccak256Hash(input) + for i := 0; i < len(result); i++ { + if result[i] != output[i] { + require(1) + require(0) + } + } +} + +func main() { + input := make([]byte, 0) + emtpy_output := []byte{ + 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, + 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112, + } + keccak256check(input, emtpy_output) + + input = []byte{102, 111, 111, 98, 97, 114, 97, 97} + short_output := []byte{ + 172, 132, 33, 155, 248, 181, 178, 245, 199, 105, 157, 164, 188, 53, 193, 25, 7, 35, 159, + 188, 30, 123, 91, 143, 30, 100, 188, 128, 172, 248, 137, 202, + } + keccak256check(input, short_output) +} +*/ diff --git a/op-preimage/oracle.go b/op-preimage/oracle.go index 07b479f97ba26..be38079e07fc2 100644 --- a/op-preimage/oracle.go +++ b/op-preimage/oracle.go @@ -1,3 +1,5 @@ +//go:build !(js || wasm || wasip1) +// +build !wasm,!wasip1, !js package preimage import ( diff --git a/op-preimage/oracle_wasm.go b/op-preimage/oracle_wasm.go new file mode 100644 index 0000000000000..7111d043c8a3e --- /dev/null +++ b/op-preimage/oracle_wasm.go @@ -0,0 +1,102 @@ +//go:build js || wasm || wasip1 +// +build js wasm wasip1 +package preimage + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + "io" + "os" +) + +// OracleClient implements the Oracle by writing the pre-image key to the given stream, +// and reading back a length-prefixed value. +type OracleClient struct { + rw io.ReadWriter +} + +func NewOracleClient(rw io.ReadWriter) *OracleClient { + return &OracleClient{rw: rw} +} + +var _ Oracle = (*OracleClient)(nil) + +func min(x, y int) int { + if x < y { + return x + } + return y +} + +func (o *OracleClient) Get(key Key) []byte { + h := key.PreimageKey() + if _, err := o.rw.Write(h[:]); err != nil { + panic(fmt.Errorf("failed to write key %s (%T) to pre-image oracle: %w", key, key, err)) + } + + var length uint64 + if err := binary.Read(o.rw, binary.LittleEndian, &length); err != nil { + panic(fmt.Errorf("failed to read pre-image length of key %s (%T) from pre-image oracle: %w", key, key, err)) + } + payload := make([]byte, length) + if _, err := io.ReadFull(o.rw, payload); err != nil { + panic(fmt.Errorf("failed to read pre-image payload (length %d) of key %s (%T) from pre-image oracle: %w", length, key, key, err)) + } + return payload +} + +// OracleServer serves the pre-image requests of the OracleClient, implementing the same protocol as the onchain VM. +type OracleServer struct { + rw io.ReadWriter +} + +func NewOracleServer(rw io.ReadWriter) *OracleServer { + return &OracleServer{rw: rw} +} + +type PreimageGetter func(key [32]byte) ([]byte, error) + +var PreimageFile *os.File + +func (o *OracleServer) NextPreimageRequest(getPreimage PreimageGetter) error { + var key [32]byte + if _, err := io.ReadFull(o.rw, key[:]); err != nil { + if err == io.EOF { + return io.EOF + } + return fmt.Errorf("failed to read requested pre-image key: %w", err) + } + value, err := getPreimage(key) + if err != nil { + return fmt.Errorf("failed to serve pre-image %s request: %w", hex.EncodeToString(key[:]), err) + } + + if PreimageFile != nil { + // write length & data to preimages.bin + binary.Write(PreimageFile, binary.LittleEndian, uint64(len(value))) + _, err := PreimageFile.Write(value) + if err != nil { + return fmt.Errorf("failed to dump pre-image binary file: %w", err) + } + + // padding some zeros to make preimages length can be divided by 8 + if len(value)%8 != 0 { + _, err := PreimageFile.Write(make([]byte, 8-len(value)%8)) + if err != nil { + return fmt.Errorf("failed to dump pre-image binary file: %w", err) + } + } + } + + if err := binary.Write(o.rw, binary.LittleEndian, uint64(len(value))); err != nil { + return fmt.Errorf("failed to write length-prefix %d: %w", len(value), err) + } + if len(value) == 0 { + return nil + } + if _, err := o.rw.Write(value); err != nil { + return fmt.Errorf("failed to write pre-image value (%d long): %w", len(value), err) + } + return nil +} diff --git a/op-program/client/io_wasm.go b/op-program/client/io_wasm.go index 1179715db750a..77a258f8ba5d6 100644 --- a/op-program/client/io_wasm.go +++ b/op-program/client/io_wasm.go @@ -44,14 +44,13 @@ func (o wasmHostIO) OldGet(key preimage.Key) []byte { // TODO: can use customized circuit to optimize if !_isPublic { hash := crypto.Keccak256Hash(buf) - //hash := Keccak256Hash(buf) hash[0] = _key[0] require_bool(hash == _key) } return buf } -func (o wasmHostIO) Get(key preimage.Key) []byte { +func (o wasmHostIO) Uint8Get(key preimage.Key) []byte { _key := key.PreimageKey() _, _isPublic := key.(preimage.LocalIndexKey) @@ -90,6 +89,184 @@ func (o wasmHostIO) Get(key preimage.Key) []byte { return buf[:size] } +func (o wasmHostIO) Uint64Get(key preimage.Key) []byte { + _key := key.PreimageKey() + _, _isPublic := key.(preimage.LocalIndexKey) + + size := wasm_input(0) + padding := size % 136 + if padding != 0 { + padding = 136 - padding + } else { + padding = 136 + } + totalLen := size + padding + totalUint64Len := totalLen / 8 + buf := make([]byte, totalLen) + bufUint64 := make([]uint64, totalUint64Len) + ssize := size / 8 + for i := uint64(0); i < ssize; i++ { + bufUint64[i] = wasm_input(0) + binary.LittleEndian.PutUint64(buf[i*8:], bufUint64[i]) + } + if ssize*8 < size { + data := wasm_input(0) + var sv uint64 = 0 + for i := uint64(ssize * 8); i < size; i++ { + buf[i] = byte(data >> sv) + sv = sv + 8 + } + } + if padding == 1 { + buf[totalLen-1] = 0x81 + } else { + buf[size] = 0x01 + buf[totalLen-1] = 0x80 + } + for i := ssize; i < totalUint64Len; i++ { + bufUint64[i] = binary.LittleEndian.Uint64(buf[i*8:]) + } + // Integrity check + // TODO: can use customized circuit to optimize + if !_isPublic { + // hash := crypto.Keccak256Hash(buf) + hash := Keccak256HashUint64(bufUint64) + hash[0] = _key[0] + require_bool(hash == _key) + } + return buf[:size] +} + +func (o wasmHostIO) Keccak256Get(key preimage.Key) []byte { + _key := key.PreimageKey() + _, _isPublic := key.(preimage.LocalIndexKey) + + size := wasm_input(0) + padding := size % 136 + if padding != 0 { + padding = 136 - padding + } else { + padding = 136 + } + totalLen := size + padding + totalUint64Len := totalLen / 8 + buf := make([]byte, totalLen) + + keccak_new(1) + ssize := size / 8 + for i := uint64(0); i < ssize; i++ { + data := wasm_input(0) + binary.LittleEndian.PutUint64(buf[i*8:], data) + keccak_push(data) + if (i+1)%17 == 0 { + keccak_finalize() + keccak_finalize() + keccak_finalize() + keccak_finalize() + keccak_new(0) + } + } + if ssize*8 < size { + data := wasm_input(0) + var sv uint64 = 0 + for i := uint64(ssize * 8); i < size; i++ { + buf[i] = byte(data >> sv) + sv = sv + 8 + } + } + if padding == 1 { + buf[totalLen-1] = 0x81 + } else { + buf[size] = 0x01 + buf[totalLen-1] = 0x80 + } + for i := ssize; i < totalUint64Len; i++ { + keccak_push(binary.LittleEndian.Uint64(buf[i*8:])) + } + // Integrity check + // TODO: can use customized circuit to optimize + if !_isPublic { + // hash := crypto.Keccak256Hash(buf) + var hash [32]byte + hash_0 := keccak_finalize() + hash_1 := keccak_finalize() + hash_2 := keccak_finalize() + hash_3 := keccak_finalize() + binary.LittleEndian.PutUint64(hash[:], hash_0) + binary.LittleEndian.PutUint64(hash[8:], hash_1) + binary.LittleEndian.PutUint64(hash[16:], hash_2) + binary.LittleEndian.PutUint64(hash[24:], hash_3) + hash[0] = _key[0] + require_bool(hash == _key) + } + return buf[:size] +} + +func (o wasmHostIO) Get(key preimage.Key) []byte { + _key := key.PreimageKey() + _, _isPublic := key.(preimage.LocalIndexKey) + + size := wasm_input(0) + padding := size % 136 + if padding != 0 { + padding = 136 - padding + } else { + padding = 136 + } + totalLen := size + padding + totalUint64Len := totalLen / 8 + buf := make([]byte, totalLen) + + keccak_new(1) + ssize := size / 8 + for i := uint64(0); i < ssize; i++ { + data := wasm_input(0) + binary.LittleEndian.PutUint64(buf[i*8:], data) + keccak_push(data) + if (i+1)%17 == 0 { + keccak_finalize() + keccak_finalize() + keccak_finalize() + keccak_finalize() + keccak_new(0) + } + } + if ssize*8 < size { + data := wasm_input(0) + var sv uint64 = 0 + for i := uint64(ssize * 8); i < size; i++ { + buf[i] = byte(data >> sv) + sv = sv + 8 + } + } + if padding == 1 { + buf[totalLen-1] = 0x81 + } else { + buf[size] = 0x01 + buf[totalLen-1] = 0x80 + } + for i := ssize; i < totalUint64Len; i++ { + keccak_push(binary.LittleEndian.Uint64(buf[i*8:])) + } + // Integrity check + // TODO: can use customized circuit to optimize + if !_isPublic { + // hash := crypto.Keccak256Hash(buf) + var hash [32]byte + hash_0 := keccak_finalize() + hash_1 := keccak_finalize() + hash_2 := keccak_finalize() + hash_3 := keccak_finalize() + binary.LittleEndian.PutUint64(hash[:], hash_0) + binary.LittleEndian.PutUint64(hash[8:], hash_1) + binary.LittleEndian.PutUint64(hash[16:], hash_2) + binary.LittleEndian.PutUint64(hash[24:], hash_3) + hash[0] = _key[0] + require_bool(hash == _key) + } + return buf[:size] +} + func (o wasmHostIO) Hint(v preimage.Hint) { // do nothing return diff --git a/op-program/client/keccak256_wasm.go b/op-program/client/keccak256_wasm.go index a8c7e5e6b1a5d..189ed2b496f81 100644 --- a/op-program/client/keccak256_wasm.go +++ b/op-program/client/keccak256_wasm.go @@ -58,6 +58,34 @@ func Keccak256Hash(data []byte, size uint64, padding uint64) [32]byte { return hash } +func Keccak256HashUint64(data []uint64) [32]byte { + var hash [32]byte + var hash_0 uint64 + var hash_1 uint64 + var hash_2 uint64 + var hash_3 uint64 + + keccak_new(1) + round := len(data) / 17 + for i := 0; i < round; i++ { + for j := 0; j < 17; j++ { + keccak_push(data[i*17+j]) + } + hash_0 = keccak_finalize() + hash_1 = keccak_finalize() + hash_2 = keccak_finalize() + hash_3 = keccak_finalize() + keccak_new(0) + } + + binary.LittleEndian.PutUint64(hash[:], hash_0) + binary.LittleEndian.PutUint64(hash[8:], hash_1) + binary.LittleEndian.PutUint64(hash[16:], hash_2) + binary.LittleEndian.PutUint64(hash[24:], hash_3) + + return hash +} + /* // for test diff --git a/op-program/zkWasm-emulator/wasi/wasi_exec_node.js b/op-program/zkWasm-emulator/wasi/wasi_exec_node.js index bbbae38d23a15..23651142bd6d2 100644 --- a/op-program/zkWasm-emulator/wasi/wasi_exec_node.js +++ b/op-program/zkWasm-emulator/wasi/wasi_exec_node.js @@ -20,7 +20,8 @@ import fs from "fs" const hostio = { env: { wasm_input: (ispulic) => { - let data = preimages.readBigInt64BE(cur) + //let data = preimages.readBigInt64BE(cur) + let data = preimages.readBigInt64LE(cur) cur += 8 return data },