diff --git a/fhevm/contracts_test.go b/fhevm/contracts_test.go index 58203df..38f866b 100644 --- a/fhevm/contracts_test.go +++ b/fhevm/contracts_test.go @@ -77,26 +77,6 @@ func toPrecompileInputNoScalar(isScalar bool, hashes ...common.Hash) []byte { return ret } -func evaluateRemainingOptimisticRequiresWithoutKms(environment EVMEnvironment) (bool, error) { - requires := environment.FhevmData().optimisticRequires - len := len(requires) - defer func() { environment.FhevmData().resetOptimisticRequires() }() - if len != 0 { - var cumulative *tfhe.TfheCiphertext = requires[0] - var err error - for i := 1; i < len; i++ { - cumulative, err = cumulative.Bitand(requires[i]) - if err != nil { - environment.GetLogger().Error("evaluateRemainingOptimisticRequires bitand failed", "err", err) - return false, err - } - } - result, err := cumulative.Decrypt() - return result.Uint64() != 0, err - } - return true, nil -} - func decryptRunWithoutKms(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { logger := environment.GetLogger() // if not gas estimation and not view function fail if decryptions are disabled in transactions @@ -122,13 +102,6 @@ func decryptRunWithoutKms(environment EVMEnvironment, caller common.Address, add if !environment.IsCommitting() && !environment.IsEthCall() { return bytes.Repeat([]byte{0xFF}, 32), nil } - // Make sure we don't decrypt before any optimistic requires are checked. - optReqResult, optReqErr := evaluateRemainingOptimisticRequiresWithoutKms(environment) - if optReqErr != nil { - return nil, optReqErr - } else if !optReqResult { - return nil, ErrExecutionReverted - } plaintext, err := ct.ciphertext.Decrypt() if err != nil { @@ -4170,27 +4143,11 @@ func TestFheRandBoundedEthCall(t *testing.T) { } } -func EvalRemOptReqWhenStopTokenWithoutKms(env EVMEnvironment) (err error) { - err = nil - // If we are finishing execution (about to go to from depth 1 to depth 0), evaluate - // any remaining optimistic requires. - if env.GetDepth() == 1 { - result, evalErr := evaluateRemainingOptimisticRequiresWithoutKms(env) - if evalErr != nil { - err = evalErr - } else if !result { - err = ErrExecutionReverted - } - } - return err -} - func interpreterRunWithStopContract(environment *MockEVMEnvironment, interpreter *vm.EVMInterpreter, contract *vm.Contract, input []byte, readOnly bool) (ret []byte, err error) { ret, _ = interpreter.Run(contract, input, readOnly) // the following functions are meant to be ran from within interpreter.run so we increment depth to emulate that environment.depth++ RemoveVerifiedCipherextsAtCurrentDepth(environment) - err = EvalRemOptReqWhenStopTokenWithoutKms(environment) environment.depth-- return ret, err } @@ -4215,205 +4172,6 @@ func newStopOpcodeContract() *vm.Contract { return c } -func TestLibOneTrueOptimisticRequire(t *testing.T) { - var value uint64 = 1 - signature := "optimisticRequire(uint256)" - hashRes := crypto.Keccak256([]byte(signature)) - signatureBytes := hashRes[0:4] - depth := 1 - environment := newTestEVMEnvironment() - environment.depth = depth - addr := common.Address{} - readOnly := false - input := make([]byte, 0) - hash := verifyCiphertextInTestMemory(environment, value, depth, tfhe.FheUint8).GetHash() - input = append(input, signatureBytes...) - input = append(input, hash.Bytes()...) - out, err := FheLibRun(environment, addr, addr, input, readOnly) - if err != nil { - t.Fatalf(err.Error()) - } else if len(out) != 0 { - t.Fatalf("require expected output len of 0, got %v", len(out)) - } - - interpreter := newInterpreterFromEnvironment(environment) - // Call the interpreter with a single STOP opcode and expect that the optimistic require doesn't revert. - out, err = interpreterRunWithStopContract(environment, interpreter, newStopOpcodeContract(), make([]byte, 0), readOnly) - if err != nil { - t.Fatalf(err.Error()) - } else if out != nil { - t.Fatalf("expected empty response") - } -} - -func TestOneFalseOptimisticRequire(t *testing.T) { - var value uint64 = 0 - depth := 0 - environment := newTestEVMEnvironment() - environment.depth = depth - addr := common.Address{} - readOnly := false - hash := verifyCiphertextInTestMemory(environment, value, depth, tfhe.FheUint8).GetHash() - out, err := optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly, nil) - if err != nil { - t.Fatalf(err.Error()) - } else if len(out) != 0 { - t.Fatalf("require expected output len of 0, got %v", len(out)) - } - interpreter := newInterpreterFromEnvironment(environment) - // Call the interpreter with a single STOP opcode and expect that the optimistic require reverts. - out, err = interpreterRunWithStopContract(environment, interpreter, newStopOpcodeContract(), make([]byte, 0), readOnly) - if err == nil || err != ErrExecutionReverted { - t.Fatalf("require expected reversal on value 0") - } else if out != nil { - t.Fatalf("expected empty response") - } -} - -func TestTwoTrueOptimisticRequires(t *testing.T) { - var value uint64 = 1 - depth := 1 - environment := newTestEVMEnvironment() - environment.depth = depth - addr := common.Address{} - readOnly := false - hash := verifyCiphertextInTestMemory(environment, value, depth, tfhe.FheUint8).GetHash() - out, err := optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly, nil) - if err != nil { - t.Fatalf(err.Error()) - } else if len(out) != 0 { - t.Fatalf("require expected output len of 0, got %v", len(out)) - } - hash = verifyCiphertextInTestMemory(environment, value, depth, tfhe.FheUint8).GetHash() - out, err = optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly, nil) - if err != nil { - t.Fatalf(err.Error()) - } else if len(out) != 0 { - t.Fatalf("require expected output len of 0, got %v", len(out)) - } - interpreter := newInterpreterFromEnvironment(environment) - // Call the interpreter with a single STOP opcode and expect that the optimistic require doesn't revert. - out, err = interpreterRunWithStopContract(environment, interpreter, newStopOpcodeContract(), make([]byte, 0), readOnly) - if err != nil { - t.Fatalf(err.Error()) - } else if out != nil { - t.Fatalf("expected empty response") - } -} - -func TestOptimisticRequireTwiceOnSameCiphertext(t *testing.T) { - var value uint64 = 1 - depth := 1 - environment := newTestEVMEnvironment() - environment.depth = depth - addr := common.Address{} - readOnly := false - ct := verifyCiphertextInTestMemory(environment, value, depth, tfhe.FheUint8) - hash := ct.GetHash() - out, err := optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly, nil) - if err != nil { - t.Fatalf(err.Error()) - } else if len(out) != 0 { - t.Fatalf("require expected output len of 0, got %v", len(out)) - } - out, err = optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly, nil) - if err != nil { - t.Fatalf(err.Error()) - } else if len(out) != 0 { - t.Fatalf("require expected output len of 0, got %v", len(out)) - } - interpreter := newInterpreterFromEnvironment(environment) - // Call the interpreter with a single STOP opcode and expect that the optimistic require doesn't revert. - out, err = interpreterRunWithStopContract(environment, interpreter, newStopOpcodeContract(), make([]byte, 0), readOnly) - if err != nil { - t.Fatalf(err.Error()) - } else if out != nil { - t.Fatalf("expected empty response") - } -} - -func TestOneFalseAndOneTrueOptimisticRequire(t *testing.T) { - depth := 0 - environment := newTestEVMEnvironment() - environment.depth = depth - addr := common.Address{} - readOnly := false - hash := verifyCiphertextInTestMemory(environment, 0, depth, tfhe.FheUint8).GetHash() - out, err := optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly, nil) - if err != nil { - t.Fatalf(err.Error()) - } else if len(out) != 0 { - t.Fatalf("require expected output len of 0, got %v", len(out)) - } - hash = verifyCiphertextInTestMemory(environment, 1, depth, tfhe.FheUint8).GetHash() - out, err = optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly, nil) - if err != nil { - t.Fatalf(err.Error()) - } else if len(out) != 0 { - t.Fatalf("require expected output len of 0, got %v", len(out)) - } - interpreter := newInterpreterFromEnvironment(environment) - // Call the interpreter with a single STOP opcode and expect that the optimistic require reverts. - out, err = interpreterRunWithStopContract(environment, interpreter, newStopOpcodeContract(), make([]byte, 0), readOnly) - if err == nil || err != ErrExecutionReverted { - t.Fatalf("require expected reversal on value 0") - } else if out != nil { - t.Fatalf("expected empty response") - } -} - -func TestDecryptWithFalseOptimisticRequire(t *testing.T) { - depth := 0 - environment := newTestEVMEnvironment() - environment.depth = depth - addr := common.Address{} - readOnly := false - // Call optimistic require with a false value and expect it succeeds. - hash := verifyCiphertextInTestMemory(environment, 0, depth, tfhe.FheUint8).GetHash() - out, err := optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly, nil) - if err != nil { - t.Fatalf(err.Error()) - } else if len(out) != 0 { - t.Fatalf("require expected output len of 0, got %v", len(out)) - } - // Call decrypt and expect it to fail due to the optimistic require being false. - _, err = decryptRunWithoutKms(environment, addr, addr, hash.Bytes(), readOnly) - if err == nil { - t.Fatalf("expected decrypt fails due to false optimistic require") - } - // Make sure there are no more optimistic requires after the decrypt call. - if len(environment.FhevmData().optimisticRequires) != 0 { - t.Fatalf("expected that there are no optimistic requires after decrypt") - } -} - -func TestDecryptWithTrueOptimisticRequire(t *testing.T) { - depth := 0 - environment := newTestEVMEnvironment() - environment.depth = depth - addr := common.Address{} - readOnly := false - // Call optimistic require with a false value and expect it succeeds. - hash := verifyCiphertextInTestMemory(environment, 1, depth, tfhe.FheUint8).GetHash() - out, err := optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly, nil) - if err != nil { - t.Fatalf(err.Error()) - } else if len(out) != 0 { - t.Fatalf("require expected output len of 0, got %v", len(out)) - } - // Call decrypt and expect it to succeed due to the optimistic require being true. - out, err = decryptRunWithoutKms(environment, addr, addr, hash.Bytes(), readOnly) - if err != nil { - t.Fatalf(err.Error()) - } else if len(out) != 32 { - t.Fatalf("decrypt expected output len of 32, got %v", len(out)) - } - // Make sure there are no more optimistic requires after the decrypt call. - if len(environment.FhevmData().optimisticRequires) != 0 { - t.Fatalf("expected that there are no optimistic requires after decrypt") - } -} - func TestDecryptInTransactionDisabled(t *testing.T) { depth := 0 environment := newTestEVMEnvironment() diff --git a/fhevm/fhelib.go b/fhevm/fhelib.go index cab695c..44f7c6b 100644 --- a/fhevm/fhelib.go +++ b/fhevm/fhelib.go @@ -226,12 +226,6 @@ var fhelibMethods = []*FheLibMethod{ requiredGasFunction: verifyCiphertextRequiredGas, runFunction: verifyCiphertextRun, }, - { - name: "optimisticRequire", - argTypes: "(uint256)", - requiredGasFunction: optimisticRequireRequiredGas, - runFunction: optimisticRequireRun, - }, { name: "getCiphertext", argTypes: "(address,uint256)", diff --git a/fhevm/interface.go b/fhevm/interface.go index bc8941c..79c4ca6 100644 --- a/fhevm/interface.go +++ b/fhevm/interface.go @@ -6,7 +6,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" - "github.com/zama-ai/fhevm-go/fhevm/tfhe" ) type EVMEnvironment interface { @@ -47,25 +46,11 @@ type FhevmData struct { // A map from a ciphertext hash to itself and stack depth at which it is verified verifiedCiphertexts map[common.Hash]*verifiedCiphertext - // All optimistic requires encountered up to that point in the txn execution - optimisticRequires []*tfhe.TfheCiphertext - nextCiphertextHashOnGasEst uint256.Int } -// Set the optimisticRequires array to an empty array -func (data *FhevmData) resetOptimisticRequires() { - data.optimisticRequires = make([]*tfhe.TfheCiphertext, 0) -} - -// Append one ciphertext to the optimisticRequires array -func (data *FhevmData) appendOptimisticRequires(ct *tfhe.TfheCiphertext) { - data.optimisticRequires = append(data.optimisticRequires, ct) -} - func NewFhevmData() FhevmData { return FhevmData{ verifiedCiphertexts: make(map[common.Hash]*verifiedCiphertext), - optimisticRequires: make([]*tfhe.TfheCiphertext, 0), } } diff --git a/fhevm/interpreter.go b/fhevm/interpreter.go index d3a1155..5d62cfb 100644 --- a/fhevm/interpreter.go +++ b/fhevm/interpreter.go @@ -69,31 +69,9 @@ func (vc *verifiedCiphertext) hash() common.Hash { type PrivilegedMemory struct { // A map from a ciphertext hash to itself and stack depths at which it is verified VerifiedCiphertexts map[common.Hash]*verifiedCiphertext - - // All optimistic requires encountered up to that point in the txn execution - optimisticRequires []*tfhe.TfheCiphertext } var PrivilegedMempory *PrivilegedMemory = &PrivilegedMemory{ make(map[common.Hash]*verifiedCiphertext), - make([]*tfhe.TfheCiphertext, 0), } -// Evaluate remaining optimistic requires when Interpreter.Run get an errStopToken -// -// This function is meant to be integrated as part of vm.EVMInterpreter.Run in the case -// there was an errStopToken -func EvalRemOptReqWhenStopToken(env EVMEnvironment) (err error) { - err = nil - // If we are finishing execution (about to go to from depth 1 to depth 0), evaluate - // any remaining optimistic requires. - if env.GetDepth() == 1 { - result, evalErr := evaluateRemainingOptimisticRequires(env) - if evalErr != nil { - err = evalErr - } else if !result { - err = ErrExecutionReverted - } - } - return err -} diff --git a/fhevm/operators_crypto.go b/fhevm/operators_crypto.go index 1f14a03..e3db9b7 100644 --- a/fhevm/operators_crypto.go +++ b/fhevm/operators_crypto.go @@ -100,13 +100,6 @@ func reencryptRun(environment EVMEnvironment, caller common.Address, addr common ct := getVerifiedCiphertext(environment, common.BytesToHash(input[0:32])) if ct != nil { otelDescribeOperandsFheTypes(runSpan, ct.fheUintType()) - // Make sure we don't decrypt before any optimistic requires are checked. - // optReqResult, optReqErr := evaluateRemainingOptimisticRequires(environment) - // if optReqErr != nil { - // return nil, optReqErr - // } else if !optReqResult { - // return nil, ErrExecutionReverted - // } var fheType kms.FheType switch ct.fheUintType() { @@ -172,34 +165,6 @@ func reencryptRun(environment EVMEnvironment, caller common.Address, addr common return nil, errors.New(msg) } -func optimisticRequireRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool, runSpan trace.Span) ([]byte, error) { - input = input[:minInt(32, len(input))] - - logger := environment.GetLogger() - if len(input) != 32 { - msg := "optimisticRequire input len must be 32 bytes" - logger.Error(msg, "input", hex.EncodeToString(input), "len", len(input)) - return nil, errors.New(msg) - } - ct := getVerifiedCiphertext(environment, common.BytesToHash(input)) - if ct == nil { - msg := "optimisticRequire unverified handle" - logger.Error(msg, "input", hex.EncodeToString(input)) - return nil, errors.New(msg) - } - otelDescribeOperandsFheTypes(runSpan, ct.fheUintType()) - // If we are doing gas estimation, don't do anything as we would assume all requires are true. - if !environment.IsCommitting() && !environment.IsEthCall() { - return nil, nil - } - if ct.fheUintType() != tfhe.FheUint8 { - msg := "optimisticRequire ciphertext type is not FheUint8" - logger.Error(msg, "type", ct.fheUintType()) - return nil, errors.New(msg) - } - environment.FhevmData().appendOptimisticRequires(ct.ciphertext) - return nil, nil -} func decryptRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool, runSpan trace.Span) ([]byte, error) { input = input[:minInt(32, len(input))] @@ -229,13 +194,6 @@ func decryptRun(environment EVMEnvironment, caller common.Address, addr common.A if !environment.IsCommitting() && !environment.IsEthCall() { return bytes.Repeat([]byte{0xFF}, 32), nil } - // Make sure we don't decrypt before any optimistic requires are checked. - optReqResult, optReqErr := evaluateRemainingOptimisticRequires(environment) - if optReqErr != nil { - return nil, optReqErr - } else if !optReqResult { - return nil, ErrExecutionReverted - } plaintext, err := decryptValue(environment, ct.ciphertext) if err != nil { @@ -331,29 +289,6 @@ func decryptValue(environment EVMEnvironment, ct *tfhe.TfheCiphertext) (uint64, return uint64(res.Plaintext), err } -// If there are optimistic requires, check them by doing bitwise AND on all of them. -// That works, because we assume their values are either 0 or 1. If there is at least -// one 0, the result will be 0 (false). -func evaluateRemainingOptimisticRequires(environment EVMEnvironment) (bool, error) { - requires := environment.FhevmData().optimisticRequires - len := len(requires) - defer func() { environment.FhevmData().resetOptimisticRequires() }() - if len != 0 { - var cumulative *tfhe.TfheCiphertext = requires[0] - var err error - for i := 1; i < len; i++ { - cumulative, err = cumulative.Bitand(requires[i]) - if err != nil { - environment.GetLogger().Error("evaluateRemainingOptimisticRequires bitand failed", "err", err) - return false, err - } - } - result, err := decryptValue(environment, cumulative) - return result != 0, err - } - return true, nil -} - func castRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool, runSpan trace.Span) ([]byte, error) { input = input[:minInt(33, len(input))] diff --git a/fhevm/operators_crypto_gas.go b/fhevm/operators_crypto_gas.go index 676a549..71bab04 100644 --- a/fhevm/operators_crypto_gas.go +++ b/fhevm/operators_crypto_gas.go @@ -34,32 +34,6 @@ func reencryptRequiredGas(environment EVMEnvironment, input []byte) uint64 { return environment.FhevmParams().GasCosts.FheReencrypt[ct.fheUintType()] } -func optimisticRequireRequiredGas(environment EVMEnvironment, input []byte) uint64 { - input = input[:minInt(32, len(input))] - - logger := environment.GetLogger() - if len(input) != 32 { - logger.Error("optimisticRequire RequiredGas() input len must be 32 bytes", - "input", hex.EncodeToString(input), "len", len(input)) - return 0 - } - ct := getVerifiedCiphertext(environment, common.BytesToHash(input)) - if ct == nil { - logger.Error("optimisticRequire RequiredGas() input doesn't point to verified ciphertext", - "input", hex.EncodeToString(input)) - return 0 - } - if ct.fheUintType() != tfhe.FheUint8 { - logger.Error("optimisticRequire RequiredGas() ciphertext type is not FheUint8", - "type", ct.fheUintType()) - return 0 - } - if len(environment.FhevmData().optimisticRequires) == 0 { - return environment.FhevmParams().GasCosts.FheOptRequire[tfhe.FheUint8] - } - return environment.FhevmParams().GasCosts.FheOptRequireBitAnd[tfhe.FheUint8] -} - func getCiphertextRequiredGas(environment EVMEnvironment, input []byte) uint64 { input = input[:minInt(64, len(input))] diff --git a/fhevm/params.go b/fhevm/params.go index 0386797..e98d0ab 100644 --- a/fhevm/params.go +++ b/fhevm/params.go @@ -62,8 +62,6 @@ type GasCosts struct { FheRand map[tfhe.FheUintType]uint64 FheIfThenElse map[tfhe.FheUintType]uint64 FheVerify map[tfhe.FheUintType]uint64 - FheOptRequire map[tfhe.FheUintType]uint64 - FheOptRequireBitAnd map[tfhe.FheUintType]uint64 FheGetCiphertext map[tfhe.FheUintType]uint64 } @@ -215,23 +213,6 @@ func DefaultGasCosts() GasCosts { tfhe.FheUint32: 40000 + AdjustFHEGas, tfhe.FheUint64: 43000 + AdjustFHEGas, }, - // TODO: As of now, only support FheUint8. All optimistic require predicates are - // downcast to FheUint8 at the solidity level. Eventually move to ebool. - // If there is at least one optimistic require, we need to decrypt it as it was a normal FHE require. - // For every subsequent optimistic require, we need to bitand it with the current require value - that - // works, because we assume requires have a value of 0 or 1. - FheOptRequire: map[tfhe.FheUintType]uint64{ - tfhe.FheUint4: 170000, - tfhe.FheUint8: 170000, - tfhe.FheUint16: 180000, - tfhe.FheUint32: 190000, - }, - FheOptRequireBitAnd: map[tfhe.FheUintType]uint64{ - tfhe.FheUint4: 20000, - tfhe.FheUint8: 20000, - tfhe.FheUint16: 20000, - tfhe.FheUint32: 20000, - }, FheGetCiphertext: map[tfhe.FheUintType]uint64{ tfhe.FheUint8: 12000, tfhe.FheUint16: 14000,