diff --git a/fhevm/errors.go b/fhevm/errors.go index c275c74..4a54da4 100644 --- a/fhevm/errors.go +++ b/fhevm/errors.go @@ -4,7 +4,8 @@ import ( "errors" ) -// List of EVM execution errors needed by the fhEVM +// List of EVM execution errors needed by the fhEVM. +// TODO: initialize errors from erros passed by users. That would make fhevm-go errors match the EVM environment's errors. var ( - ErrWriteProtection = errors.New("write protection") + ErrExecutionReverted = errors.New("execution reverted") ) diff --git a/fhevm/instructions.go b/fhevm/instructions.go index e68742c..68ab491 100644 --- a/fhevm/instructions.go +++ b/fhevm/instructions.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/hex" "errors" + "strings" "github.com/ethereum/go-ethereum/common" crypto "github.com/ethereum/go-ethereum/crypto" @@ -18,6 +19,10 @@ func newInt(buf []byte) *uint256.Int { return i.SetBytes(buf) } +func contains(haystack []byte, needle []byte) bool { + return strings.Contains(string(haystack), string(needle)) +} + // Ciphertext metadata is stored in protected storage, in a 32-byte slot. // Currently, we only utilize 17 bytes from the slot. type ciphertextMetadata struct { @@ -258,10 +263,7 @@ func persistIfVerifiedCiphertext(flagHandleLocation common.Hash, handle common.H env.SetState(protectedStorage, metadataKey, metadata.serialize()) } -func OpSstore(pc *uint64, env EVMEnvironment, scope ScopeContext) ([]byte, error) { - if env.IsReadOnly() { - return nil, ErrWriteProtection - } +func OpSstore(pc *uint64, env EVMEnvironment, scope ScopeContext) []byte { loc := scope.GetStack().Pop() locHash := common.BytesToHash(loc.Bytes()) newVal := scope.GetStack().Pop() @@ -286,5 +288,64 @@ func OpSstore(pc *uint64, env EVMEnvironment, scope ScopeContext) ([]byte, error } // Set the SSTORE's value in the actual contract. env.SetState(scope.GetContract().Address(), loc.Bytes32(), newValHash) - return nil, nil + return nil +} + +// If there are ciphertext handles in the arguments to a call, delegate them to the callee. +// Return a map from ciphertext hash -> depthSet before delegation. +func DelegateCiphertextHandlesInArgs(env EVMEnvironment, args []byte) (verified map[common.Hash]*depthSet) { + verified = make(map[common.Hash]*depthSet) + for key, verifiedCiphertext := range env.GetFhevmData().verifiedCiphertexts { + if contains(args, key.Bytes()) && isVerifiedAtCurrentDepth(env, verifiedCiphertext) { + if env.IsCommitting() { + env.GetLogger().Info("delegateCiphertextHandlesInArgs", + "handle", verifiedCiphertext.ciphertext.getHash().Hex(), + "fromDepth", env.GetDepth(), + "toDepth", env.GetDepth()+1) + } + verified[key] = verifiedCiphertext.verifiedDepths.clone() + verifiedCiphertext.verifiedDepths.add(env.GetDepth() + 1) + } + } + return +} + +func RestoreVerifiedDepths(env EVMEnvironment, verified map[common.Hash]*depthSet) { + for k, v := range verified { + env.GetFhevmData().verifiedCiphertexts[k].verifiedDepths = v + } +} + +func delegateCiphertextHandlesToCaller(env EVMEnvironment, ret []byte) { + for key, verifiedCiphertext := range env.GetFhevmData().verifiedCiphertexts { + if contains(ret, key.Bytes()) && isVerifiedAtCurrentDepth(env, verifiedCiphertext) { + if env.IsCommitting() { + env.GetLogger().Info("opReturn making ciphertext available to caller", + "handle", verifiedCiphertext.ciphertext.getHash().Hex(), + "fromDepth", env.GetDepth(), + "toDepth", env.GetDepth()-1) + } + // If a handle is returned, automatically make it available to the caller. + verifiedCiphertext.verifiedDepths.add(env.GetDepth() - 1) + } + } +} + +func RemoveVerifiedCipherextsAtCurrentDepth(env EVMEnvironment) { + for _, verifiedCiphertext := range env.GetFhevmData().verifiedCiphertexts { + if env.IsCommitting() { + env.GetLogger().Info("Run removing ciphertext from depth", + "handle", verifiedCiphertext.ciphertext.getHash().Hex(), + "depth", env.GetDepth()) + } + // Delete the current EVM depth from the set of verified depths. + verifiedCiphertext.verifiedDepths.del(env.GetDepth()) + } +} + +func OpReturn(pc *uint64, env EVMEnvironment, scope ScopeContext) []byte { + offset, size := scope.GetStack().Pop(), scope.GetStack().Pop() + ret := scope.GetMemory().GetPtr(int64(offset.Uint64()), int64(size.Uint64())) + delegateCiphertextHandlesToCaller(env, ret) + return ret } diff --git a/fhevm/interpreter.go b/fhevm/interpreter.go index d675047..6f5f7bb 100644 --- a/fhevm/interpreter.go +++ b/fhevm/interpreter.go @@ -3,6 +3,7 @@ package fhevm import "github.com/ethereum/go-ethereum/common" type ScopeContext interface { + GetMemory() Memory GetStack() Stack GetContract() Contract } diff --git a/fhevm/memory.go b/fhevm/memory.go new file mode 100644 index 0000000..a263e78 --- /dev/null +++ b/fhevm/memory.go @@ -0,0 +1,34 @@ +// BSD 3-Clause Clear License + +// Copyright © 2023 ZAMA. +// All rights reserved. + +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package fhevm + +import "github.com/holiman/uint256" + +type Memory interface { + Set(offset, size uint64, value []byte) + Set32(offset uint64, val *uint256.Int) + Resize(size uint64) + GetCopy(offset, size int64) (cpy []byte) + GetPtr(offset, size int64) []byte + Len() int + Data() []byte +} diff --git a/fhevm/precompiles.go b/fhevm/precompiles.go index 23ea4dd..d0abc0f 100644 --- a/fhevm/precompiles.go +++ b/fhevm/precompiles.go @@ -25,8 +25,6 @@ type PrecompiledContract interface { Run(environment *EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) (ret []byte, err error) } -var ErrExecutionReverted = errors.New("execution reverted") - var signatureFheAdd = makeKeccakSignature("fheAdd(uint256,uint256,bytes1)") var signatureCast = makeKeccakSignature("cast(uint256,bytes1)") var signatureDecrypt = makeKeccakSignature("decrypt(uint256)")