From 8ac5b61c4c0f565e5ce626c79f6cf8ad7916950e Mon Sep 17 00:00:00 2001 From: youben11 Date: Mon, 23 Oct 2023 11:08:23 +0100 Subject: [PATCH] feat(fhelib): support all ops in fhelib precompile --- fhevm/crypto.go | 12 + fhevm/precompiles.go | 1902 ++++++++++++++++++++++++++++++++++++++-- go.mod | 2 +- params/fhevm_params.go | 9 +- 4 files changed, 1868 insertions(+), 57 deletions(-) create mode 100644 fhevm/crypto.go diff --git a/fhevm/crypto.go b/fhevm/crypto.go new file mode 100644 index 0000000..1975f07 --- /dev/null +++ b/fhevm/crypto.go @@ -0,0 +1,12 @@ +package fhevm + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// CreateProtectedStorageAddress creates an ethereum contract address for protected storage +// given the corresponding contract address +func CreateProtectedStorageContractAddress(b common.Address) common.Address { + return crypto.CreateAddress(b, 0) +} diff --git a/fhevm/precompiles.go b/fhevm/precompiles.go index 757ca2d..d3b2a90 100644 --- a/fhevm/precompiles.go +++ b/fhevm/precompiles.go @@ -2,13 +2,18 @@ package fhevm import ( "bytes" + "crypto/rand" "encoding/binary" "encoding/hex" "errors" "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/holiman/uint256" "github.com/zama-ai/fhevm-go/params" + "golang.org/x/crypto/chacha20" + "golang.org/x/crypto/nacl/box" ) // PrecompiledContract is the basic interface for native Go contracts. The implementation @@ -26,6 +31,29 @@ var signatureCast = makeKeccakSignature("cast(uint256,bytes1)") var signatureDecrypt = makeKeccakSignature("decrypt(uint256)") var signatureFhePubKey = makeKeccakSignature("fhePubKey(bytes1)") var signatureTrivialEncrypt = makeKeccakSignature("trivialEncrypt(uint256,bytes1)") +var signatureFheSub = makeKeccakSignature("fheSub(uint256,uint256,bytes1)") +var signatureFheMul = makeKeccakSignature("fheMul(uint256,uint256,bytes1)") +var signatureFheLe = makeKeccakSignature("fheLe(uint256,uint256,bytes1)") +var signatureFheLt = makeKeccakSignature("fheLt(uint256,uint256,bytes1)") +var signatureFheEq = makeKeccakSignature("fheEq(uint256,uint256,bytes1)") +var signatureFheGe = makeKeccakSignature("fheGe(uint256,uint256,bytes1)") +var signatureFheGt = makeKeccakSignature("fheGt(uint256,uint256,bytes1)") +var signatureFheShl = makeKeccakSignature("fheShl(uint256,uint256,bytes1)") +var signatureFheShr = makeKeccakSignature("fheShr(uint256,uint256,bytes1)") +var signatureFheNe = makeKeccakSignature("fheNe(uint256,uint256,bytes1)") +var signatureFheMin = makeKeccakSignature("fheMin(uint256,uint256,bytes1)") +var signatureFheMax = makeKeccakSignature("fheMax(uint256,uint256,bytes1)") +var signatureFheNeg = makeKeccakSignature("fheNeg(uint256)") +var signatureFheNot = makeKeccakSignature("fheNot(uint256)") +var signatureFheDiv = makeKeccakSignature("fheDiv(uint256,uint256,bytes1)") +var signatureFheRem = makeKeccakSignature("fheRem(uint256,uint256,bytes1)") +var signatureFheBitAnd = makeKeccakSignature("fheBitAnd(uint256,uint256,bytes1)") +var signatureFheBitOr = makeKeccakSignature("fheBitOr(uint256,uint256,bytes1)") +var signatureFheBitXor = makeKeccakSignature("fheBitXor(uint256,uint256,bytes1)") +var signatureFheRand = makeKeccakSignature("fheRand(bytes1)") +var signatureVerifyCiphertext = makeKeccakSignature("verifyCiphertext(bytes)") +var signatureReencrypt = makeKeccakSignature("reencrypt(uint256,uint256)") +var signatureOptimisticRequire = makeKeccakSignature("optimisticRequire(uint256)") func FheLibRequiredGas(environment EVMEnvironment, input []byte) uint64 { logger := environment.GetLogger() @@ -34,11 +62,12 @@ func FheLibRequiredGas(environment EVMEnvironment, input []byte) uint64 { logger.Error("fheLib precompile error", "err", err, "input", hex.EncodeToString(input)) return 0 } + // first 4 bytes are for the function signature signature := binary.BigEndian.Uint32(input[0:4]) switch signature { case signatureFheAdd: bwCompatBytes := input[4:minInt(69, len(input))] - return fheAddRequiredGas(environment, bwCompatBytes) + return fheAddSubRequiredGas(environment, bwCompatBytes) case signatureCast: bwCompatBytes := input[4:minInt(37, len(input))] return castRequiredGas(environment, bwCompatBytes) @@ -51,6 +80,75 @@ func FheLibRequiredGas(environment EVMEnvironment, input []byte) uint64 { case signatureTrivialEncrypt: bwCompatBytes := input[4:minInt(37, len(input))] return trivialEncryptRequiredGas(environment, bwCompatBytes) + case signatureFheSub: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheAddSubRequiredGas(environment, bwCompatBytes) + case signatureFheMul: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheMulRequiredGas(environment, bwCompatBytes) + case signatureFheLe: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheLeRequiredGas(environment, bwCompatBytes) + case signatureFheLt: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheLtRequiredGas(environment, bwCompatBytes) + case signatureFheEq: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheEqRequiredGas(environment, bwCompatBytes) + case signatureFheGe: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheGeRequiredGas(environment, bwCompatBytes) + case signatureFheGt: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheGtRequiredGas(environment, bwCompatBytes) + case signatureFheShl: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheShlRequiredGas(environment, bwCompatBytes) + case signatureFheShr: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheShrRequiredGas(environment, bwCompatBytes) + case signatureFheNe: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheNeRequiredGas(environment, bwCompatBytes) + case signatureFheMin: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheMinRequiredGas(environment, bwCompatBytes) + case signatureFheMax: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheMaxRequiredGas(environment, bwCompatBytes) + case signatureFheNeg: + bwCompatBytes := input[4:minInt(36, len(input))] + return fheNegRequiredGas(environment, bwCompatBytes) + case signatureFheNot: + bwCompatBytes := input[4:minInt(36, len(input))] + return fheNotRequiredGas(environment, bwCompatBytes) + case signatureFheDiv: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheDivRequiredGas(environment, bwCompatBytes) + case signatureFheRem: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheRemRequiredGas(environment, bwCompatBytes) + case signatureFheBitAnd: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheBitAndRequiredGas(environment, bwCompatBytes) + case signatureFheBitOr: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheBitOrRequiredGas(environment, bwCompatBytes) + case signatureFheBitXor: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheBitXorRequiredGas(environment, bwCompatBytes) + case signatureFheRand: + bwCompatBytes := input[4:minInt(5, len(input))] + return fheRandRequiredGas(environment, bwCompatBytes) + case signatureVerifyCiphertext: + bwCompatBytes := input[4:] + return verifyCiphertextRequiredGas(environment, bwCompatBytes) + case signatureReencrypt: + bwCompatBytes := input[4:minInt(68, len(input))] + return reencryptRequiredGas(environment, bwCompatBytes) + case signatureOptimisticRequire: + bwCompatBytes := input[4:minInt(36, len(input))] + return optimisticRequireRequiredGas(environment, bwCompatBytes) default: err := errors.New("precompile method not found") logger.Error("fheLib precompile error", "err", err, "input", hex.EncodeToString(input)) @@ -65,6 +163,7 @@ func FheLibRun(environment EVMEnvironment, caller common.Address, addr common.Ad logger.Error("fheLib precompile error", "err", err, "input", hex.EncodeToString(input)) return nil, err } + // first 4 bytes are for the function signature signature := binary.BigEndian.Uint32(input[0:4]) switch signature { case signatureFheAdd: @@ -90,6 +189,97 @@ func FheLibRun(environment EVMEnvironment, caller common.Address, addr common.Ad case signatureTrivialEncrypt: bwCompatBytes := input[4:minInt(37, len(input))] return trivialEncryptRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheSub: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheSubRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheMul: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheMulRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheLe: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheLeRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheLt: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheLtRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheEq: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheEqRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheGe: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheGeRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheGt: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheGtRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheShl: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheShlRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheShr: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheShrRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheNe: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheNeRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheMin: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheMinRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheMax: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheMaxRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheNeg: + bwCompatBytes := input[4:minInt(36, len(input))] + return fheNegRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheNot: + bwCompatBytes := input[4:minInt(36, len(input))] + return fheNotRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheDiv: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheDivRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheRem: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheRemRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheBitAnd: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheBitAndRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheBitOr: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheBitOrRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheBitXor: + bwCompatBytes := input[4:minInt(69, len(input))] + return fheBitXorRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureFheRand: + bwCompatBytes := input[4:minInt(5, len(input))] + return fheRandRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureVerifyCiphertext: + // first 32 bytes of the payload is offset, then 32 bytes are size of byte array + if len(input) <= 68 { + err := errors.New("verifyCiphertext(bytes) must contain at least 68 bytes for selector, byte offset and size") + logger.Error("fheLib precompile error", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + bytesPaddingSize := 32 + bytesSizeSlotSize := 32 + // read only last 4 bytes of padded number for byte array size + sizeStart := 4 + bytesPaddingSize + bytesSizeSlotSize - 4 + sizeEnd := sizeStart + 4 + bytesSize := binary.BigEndian.Uint32(input[sizeStart:sizeEnd]) + bytesStart := 4 + bytesPaddingSize + bytesSizeSlotSize + bytesEnd := bytesStart + int(bytesSize) + bwCompatBytes := input[bytesStart:minInt(bytesEnd, len(input))] + return verifyCiphertextRun(environment, caller, addr, bwCompatBytes, readOnly) + case signatureReencrypt: + bwCompatBytes := input[4:minInt(68, len(input))] + precompileBytes, err := reencryptRun(environment, caller, addr, bwCompatBytes, readOnly) + if err != nil { + return precompileBytes, err + } + // pad according to abi specification, first add offset to the dynamic bytes argument + outputBytes := make([]byte, 32, len(precompileBytes)+32) + outputBytes[31] = 0x20 + outputBytes = append(outputBytes, precompileBytes...) + return padArrayTo32Multiple(outputBytes), nil + case signatureOptimisticRequire: + bwCompatBytes := input[4:minInt(36, len(input))] + return optimisticRequireRun(environment, caller, addr, bwCompatBytes, readOnly) default: err := errors.New("precompile method not found") logger.Error("fheLib precompile error", "err", err, "input", hex.EncodeToString(input)) @@ -109,8 +299,80 @@ var fheDecryptGasCosts = map[fheUintType]uint64{ FheUint32: params.FheUint32DecryptGas, } +var fheBitwiseOpGasCosts = map[fheUintType]uint64{ + FheUint8: params.FheUint8BitwiseGas, + FheUint16: params.FheUint16BitwiseGas, + FheUint32: params.FheUint32BitwiseGas, +} + +var fheMulGasCosts = map[fheUintType]uint64{ + FheUint8: params.FheUint8MulGas, + FheUint16: params.FheUint16MulGas, + FheUint32: params.FheUint32MulGas, +} + +var fheDivGasCosts = map[fheUintType]uint64{ + FheUint8: params.FheUint8DivGas, + FheUint16: params.FheUint16DivGas, + FheUint32: params.FheUint32DivGas, +} + +var fheRemGasCosts = map[fheUintType]uint64{ + FheUint8: params.FheUint8RemGas, + FheUint16: params.FheUint16RemGas, + FheUint32: params.FheUint32RemGas, +} + +var fheShiftGasCosts = map[fheUintType]uint64{ + FheUint8: params.FheUint8ShiftGas, + FheUint16: params.FheUint16ShiftGas, + FheUint32: params.FheUint32ShiftGas, +} + +var fheLeGasCosts = map[fheUintType]uint64{ + FheUint8: params.FheUint8LeGas, + FheUint16: params.FheUint16LeGas, + FheUint32: params.FheUint32LeGas, +} + +var fheMinMaxGasCosts = map[fheUintType]uint64{ + FheUint8: params.FheUint8MinMaxGas, + FheUint16: params.FheUint16MinMaxGas, + FheUint32: params.FheUint32MinMaxGas, +} + +var fheNegNotGasCosts = map[fheUintType]uint64{ + FheUint8: params.FheUint8NegNotGas, + FheUint16: params.FheUint16NegNotGas, + FheUint32: params.FheUint32NegNotGas, +} + +var fheReencryptGasCosts = map[fheUintType]uint64{ + FheUint8: params.FheUint8ReencryptGas, + FheUint16: params.FheUint16ReencryptGas, + FheUint32: params.FheUint32ReencryptGas, +} + +var fheVerifyGasCosts = map[fheUintType]uint64{ + FheUint8: params.FheUint8VerifyGas, + FheUint16: params.FheUint16VerifyGas, + FheUint32: params.FheUint32VerifyGas, +} + +var fheTrivialEncryptGasCosts = map[fheUintType]uint64{ + FheUint8: params.FheUint8TrivialEncryptGas, + FheUint16: params.FheUint16TrivialEncryptGas, + FheUint32: params.FheUint32TrivialEncryptGas, +} + +var fheRandGasCosts = map[fheUintType]uint64{ + FheUint8: params.FheUint8RandGas, + FheUint16: params.FheUint16RandGas, + FheUint32: params.FheUint32RandGas, +} + // Gas costs -func fheAddRequiredGas(environment EVMEnvironment, input []byte) uint64 { +func fheAddSubRequiredGas(environment EVMEnvironment, input []byte) uint64 { logger := environment.GetLogger() isScalar, err := isScalarOp(input) if err != nil { @@ -139,92 +401,1278 @@ func fheAddRequiredGas(environment EVMEnvironment, input []byte) uint64 { return fheAddSubGasCosts[lhs.ciphertext.fheUintType] } -func castRequiredGas(environment EVMEnvironment, input []byte) uint64 { - if len(input) != 33 { +func fheMulRequiredGas(environment EVMEnvironment, input []byte) uint64 { + logger := environment.GetLogger() + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheMul RequiredGas() can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + var lhs, rhs *verifiedCiphertext + if !isScalar { + lhs, rhs, err = get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheMul RequiredGas() ciphertext inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + logger.Error("fheMul RequiredGas() operand type mismatch", "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return 0 + } + } else { + lhs, _, err = getScalarOperands(environment, input) + if err != nil { + logger.Error("fheMul RequiredGas() scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + } + return fheMulGasCosts[lhs.ciphertext.fheUintType] +} + +func fheLeRequiredGas(environment EVMEnvironment, input []byte) uint64 { + logger := environment.GetLogger() + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("comparison RequiredGas() can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + var lhs, rhs *verifiedCiphertext + if !isScalar { + lhs, rhs, err = get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("comparison RequiredGas() ciphertext inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + logger.Error("comparison RequiredGas() operand type mismatch", "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return 0 + } + } else { + lhs, _, err = getScalarOperands(environment, input) + if err != nil { + logger.Error("comparison RequiredGas() scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + } + return fheLeGasCosts[lhs.ciphertext.fheUintType] +} + +func fheLtRequiredGas(environment EVMEnvironment, input []byte) uint64 { + // Implement in terms of le, because le and lt costs are currently the same. + return fheLeRequiredGas(environment, input) +} + +func fheEqRequiredGas(environment EVMEnvironment, input []byte) uint64 { + // Implement in terms of le, because comparison costs are currently the same. + return fheLeRequiredGas(environment, input) +} + +func fheGeRequiredGas(environment EVMEnvironment, input []byte) uint64 { + // Implement in terms of le, because comparison costs are currently the same. + return fheLeRequiredGas(environment, input) +} + +func fheGtRequiredGas(environment EVMEnvironment, input []byte) uint64 { + // Implement in terms of le, because comparison costs are currently the same. + return fheLeRequiredGas(environment, input) +} + +func fheNeRequiredGas(environment EVMEnvironment, input []byte) uint64 { + // Implement in terms of le, because comparison costs are currently the same. + return fheLeRequiredGas(environment, input) +} + +func fheShlRequiredGas(environment EVMEnvironment, input []byte) uint64 { + logger := environment.GetLogger() + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheShift RequiredGas() can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + var lhs, rhs *verifiedCiphertext + if !isScalar { + lhs, rhs, err = get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheShift RequiredGas() ciphertext inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + logger.Error("fheShift RequiredGas() operand type mismatch", "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return 0 + } + } else { + lhs, _, err = getScalarOperands(environment, input) + if err != nil { + logger.Error("fheShift RequiredGas() scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + } + return fheShiftGasCosts[lhs.ciphertext.fheUintType] +} + +func fheShrRequiredGas(environment EVMEnvironment, input []byte) uint64 { + // Implement in terms of shl, because comparison costs are currently the same. + return fheShlRequiredGas(environment, input) +} + +func fheMinRequiredGas(environment EVMEnvironment, input []byte) uint64 { + logger := environment.GetLogger() + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheMin/Max RequiredGas() can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + var lhs, rhs *verifiedCiphertext + if !isScalar { + lhs, rhs, err = get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheMin/Max RequiredGas() ciphertext inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + logger.Error("fheMin/Max RequiredGas() operand type mismatch", "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return 0 + } + } else { + lhs, _, err = getScalarOperands(environment, input) + if err != nil { + logger.Error("fheMin/Max RequiredGas() scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + } + return fheMinMaxGasCosts[lhs.ciphertext.fheUintType] +} + +func fheMaxRequiredGas(environment EVMEnvironment, input []byte) uint64 { + // Implement in terms of min, because costs are currently the same. + return fheMinRequiredGas(environment, input) +} + +func fheNegRequiredGas(environment EVMEnvironment, input []byte) uint64 { + logger := environment.GetLogger() + if len(input) != 32 { + logger.Error("fheNeg input needs to contain one 256-bit sized value", "input", hex.EncodeToString(input)) + return 0 + } + ct := getVerifiedCiphertext(environment, common.BytesToHash(input[0:32])) + if ct == nil { + logger.Error("fheNeg input not verified", "input", hex.EncodeToString(input)) + return 0 + } + return fheNegNotGasCosts[ct.ciphertext.fheUintType] +} + +func fheNotRequiredGas(environment EVMEnvironment, input []byte) uint64 { + // Implement in terms of neg, because costs are currently the same. + return fheNegRequiredGas(environment, input) +} + +func fheDivRequiredGas(environment EVMEnvironment, input []byte) uint64 { + logger := environment.GetLogger() + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheDiv RequiredGas() cannot detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + var lhs *verifiedCiphertext + if !isScalar { + logger.Error("fheDiv RequiredGas() only scalar in division is supported, two ciphertexts received", "input", hex.EncodeToString(input)) + return 0 + } else { + lhs, _, err = getScalarOperands(environment, input) + if err != nil { + logger.Error("fheDiv RequiredGas() scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + } + return fheDivGasCosts[lhs.ciphertext.fheUintType] +} + +func fheRemRequiredGas(environment EVMEnvironment, input []byte) uint64 { + logger := environment.GetLogger() + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheRem RequiredGas() cannot detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + var lhs *verifiedCiphertext + if !isScalar { + logger.Error("fheRem RequiredGas() only scalar in division is supported, two ciphertexts received", "input", hex.EncodeToString(input)) + return 0 + } else { + lhs, _, err = getScalarOperands(environment, input) + if err != nil { + logger.Error("fheRem RequiredGas() scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + } + return fheRemGasCosts[lhs.ciphertext.fheUintType] +} + +func fheBitAndRequiredGas(environment EVMEnvironment, input []byte) uint64 { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("Bitwise op RequiredGas() can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + + if isScalar { + msg := "Bitwise op RequiredGas() scalar op not supported" + logger.Error(msg) + return 0 + } + + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("Bitwise op RequiredGas() inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return 0 + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + logger.Error("Bitwise op RequiredGas() operand type mismatch", "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return 0 + } + return fheBitwiseOpGasCosts[lhs.ciphertext.fheUintType] +} + +func fheBitOrRequiredGas(environment EVMEnvironment, input []byte) uint64 { + // Implement in terms of bitAnd, because bitwise op costs are currently the same. + return fheBitAndRequiredGas(environment, input) +} + +func fheBitXorRequiredGas(environment EVMEnvironment, input []byte) uint64 { + // Implement in terms of bitAnd, because bitwise op costs are currently the same. + return fheBitAndRequiredGas(environment, input) +} + +func fheRandRequiredGas(environment EVMEnvironment, input []byte) uint64 { + logger := environment.GetLogger() + if len(input) != 1 || !isValidType(input[0]) { + logger.Error("fheRand RequiredGas() input len must be at least 1 byte and be a valid FheUint type", "input", hex.EncodeToString(input), "len", len(input)) + return 0 + } + t := fheUintType(input[0]) + return fheRandGasCosts[t] +} + +func verifyCiphertextRequiredGas(environment EVMEnvironment, input []byte) uint64 { + if len(input) <= 1 { environment.GetLogger().Error( - "cast RequiredGas() input needs to contain a ciphertext and one byte for its type", + "verifyCiphertext RequiredGas() input needs to contain a ciphertext and one byte for its type", "len", len(input)) return 0 } - return params.FheCastGas + ctType := fheUintType(input[len(input)-1]) + return fheVerifyGasCosts[ctType] +} + +func reencryptRequiredGas(environment EVMEnvironment, input []byte) uint64 { + logger := environment.GetLogger() + if len(input) != 64 { + logger.Error("reencrypt RequiredGas() input len must be 64 bytes", "input", hex.EncodeToString(input), "len", len(input)) + return 0 + } + ct := getVerifiedCiphertext(environment, common.BytesToHash(input[0:32])) + if ct == nil { + logger.Error("reencrypt RequiredGas() input doesn't point to verified ciphertext", "input", hex.EncodeToString(input)) + return 0 + } + return fheReencryptGasCosts[ct.ciphertext.fheUintType] +} + +func optimisticRequireRequiredGas(environment EVMEnvironment, input []byte) uint64 { + 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.ciphertext.fheUintType != FheUint8 { + logger.Error("optimisticRequire RequiredGas() ciphertext type is not FheUint8", + "type", ct.ciphertext.fheUintType) + return 0 + } + if len(environment.GetFhevmData().optimisticRequires) == 0 { + return params.FheUint8OptimisticRequireGas + } + return params.FheUint8OptimisticRequireBitandGas +} + +func castRequiredGas(environment EVMEnvironment, input []byte) uint64 { + if len(input) != 33 { + environment.GetLogger().Error( + "cast RequiredGas() input needs to contain a ciphertext and one byte for its type", + "len", len(input)) + return 0 + } + return params.FheCastGas +} + +func decryptRequiredGas(environment EVMEnvironment, input []byte) uint64 { + logger := environment.GetLogger() + if len(input) != 32 { + logger.Error("decrypt 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("decrypt RequiredGas() input doesn't point to verified ciphertext", "input", hex.EncodeToString(input)) + return 0 + } + return fheDecryptGasCosts[ct.ciphertext.fheUintType] +} + +func fhePubKeyRequiredGas(environment EVMEnvironment, input []byte) uint64 { + return params.FhePubKeyGas +} + +func trivialEncryptRequiredGas(environment EVMEnvironment, input []byte) uint64 { + logger := environment.GetLogger() + if len(input) != 33 { + logger.Error("trivialEncrypt RequiredGas() input len must be 33 bytes", "input", hex.EncodeToString(input), "len", len(input)) + return 0 + } + encryptToType := fheUintType(input[32]) + return fheTrivialEncryptGasCosts[encryptToType] +} + +// Implementations +func fheAddRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheAdd can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if !isScalar { + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheAdd inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheAdd operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.add(rhs.ciphertext) + if err != nil { + logger.Error("fheAdd failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheAdd success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil + + } else { + lhs, rhs, err := getScalarOperands(environment, input) + if err != nil { + logger.Error("fheAdd scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.scalarAdd(rhs.Uint64()) + if err != nil { + logger.Error("fheAdd failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheAdd scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + return resultHash[:], nil + } +} + +func fheSubRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheSub can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if !isScalar { + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheSub inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheSub operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.sub(rhs.ciphertext) + if err != nil { + logger.Error("fheSub failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheSub success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil + + } else { + lhs, rhs, err := getScalarOperands(environment, input) + if err != nil { + logger.Error("fheSub scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.scalarSub(rhs.Uint64()) + if err != nil { + logger.Error("fheSub failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheSub scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + return resultHash[:], nil + } +} + +func fheMulRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheMul can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if !isScalar { + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheMul inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheMul operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.mul(rhs.ciphertext) + if err != nil { + logger.Error("fheMul failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheMul success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil + + } else { + lhs, rhs, err := getScalarOperands(environment, input) + if err != nil { + logger.Error("fheMul scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.scalarMul(rhs.Uint64()) + if err != nil { + logger.Error("fheMul failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheMul scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + return resultHash[:], nil + } +} + +func fheLeRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheLe can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if !isScalar { + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheLe inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheLe operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.le(rhs.ciphertext) + if err != nil { + logger.Error("fheLe failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheLe success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil + + } else { + lhs, rhs, err := getScalarOperands(environment, input) + if err != nil { + logger.Error("fheLe scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.scalarLe(rhs.Uint64()) + if err != nil { + logger.Error("fheLe failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheLe scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + return resultHash[:], nil + } +} + +func fheLtRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheLt can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if !isScalar { + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheLt inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheLt operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.lt(rhs.ciphertext) + if err != nil { + logger.Error("fheLt failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheLt success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil + + } else { + lhs, rhs, err := getScalarOperands(environment, input) + if err != nil { + logger.Error("fheLt scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.scalarLt(rhs.Uint64()) + if err != nil { + logger.Error("fheLt failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheLt scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + return resultHash[:], nil + } +} + +func fheEqRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheEq can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if !isScalar { + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheEq inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheEq operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.eq(rhs.ciphertext) + if err != nil { + logger.Error("fheEq failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheEq success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil + + } else { + lhs, rhs, err := getScalarOperands(environment, input) + if err != nil { + logger.Error("fheEq scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.scalarEq(rhs.Uint64()) + if err != nil { + logger.Error("fheEq failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheEq scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + return resultHash[:], nil + } +} + +func fheGeRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheGe can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if !isScalar { + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheGe inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheGe operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.ge(rhs.ciphertext) + if err != nil { + logger.Error("fheGe failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheGe success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil + + } else { + lhs, rhs, err := getScalarOperands(environment, input) + if err != nil { + logger.Error("fheGe scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.scalarGe(rhs.Uint64()) + if err != nil { + logger.Error("fheGe failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheGe scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + return resultHash[:], nil + } +} + +func fheGtRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheGt can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if !isScalar { + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheGt inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheGt operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.gt(rhs.ciphertext) + if err != nil { + logger.Error("fheGt failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheGt success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil + + } else { + lhs, rhs, err := getScalarOperands(environment, input) + if err != nil { + logger.Error("fheGt scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.scalarGt(rhs.Uint64()) + if err != nil { + logger.Error("fheGt failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheGt scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + return resultHash[:], nil + } +} + +func fheShlRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheShl can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if !isScalar { + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheShl inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheShl operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.shl(rhs.ciphertext) + if err != nil { + logger.Error("fheShl failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheShl success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil + + } else { + lhs, rhs, err := getScalarOperands(environment, input) + if err != nil { + logger.Error("fheShl scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.scalarShl(rhs.Uint64()) + if err != nil { + logger.Error("fheShl failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheShl scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + return resultHash[:], nil + } +} + +func fheShrRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheShr can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if !isScalar { + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheShr inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheShr operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.shr(rhs.ciphertext) + if err != nil { + logger.Error("fheShr failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheShr success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil + + } else { + lhs, rhs, err := getScalarOperands(environment, input) + if err != nil { + logger.Error("fheShr scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.scalarShr(rhs.Uint64()) + if err != nil { + logger.Error("fheShr failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheShr scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + return resultHash[:], nil + } +} + +func fheNeRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheNe can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if !isScalar { + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheNe inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheNe operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.ne(rhs.ciphertext) + if err != nil { + logger.Error("fheNe failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheNe success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil + + } else { + lhs, rhs, err := getScalarOperands(environment, input) + if err != nil { + logger.Error("fheNe scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.scalarNe(rhs.Uint64()) + if err != nil { + logger.Error("fheNe failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheNe scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + return resultHash[:], nil + } +} + +func fheMinRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheMin can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if !isScalar { + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheMin inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheMin operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.min(rhs.ciphertext) + if err != nil { + logger.Error("fheMin failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheMin success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil + + } else { + lhs, rhs, err := getScalarOperands(environment, input) + if err != nil { + logger.Error("fheMin scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.scalarMin(rhs.Uint64()) + if err != nil { + logger.Error("fheMin failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheMin scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + return resultHash[:], nil + } +} + +func fheMaxRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheMax can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if !isScalar { + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheMax inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheMax operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.max(rhs.ciphertext) + if err != nil { + logger.Error("fheMax failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheMax success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil + + } else { + lhs, rhs, err := getScalarOperands(environment, input) + if err != nil { + logger.Error("fheMax scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.scalarMax(rhs.Uint64()) + if err != nil { + logger.Error("fheMax failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheMax scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + return resultHash[:], nil + } +} + +func fheNegRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + if len(input) != 32 { + msg := "fheMax input needs to contain one 256-bit sized value" + logger.Error(msg, "input", hex.EncodeToString(input)) + return nil, errors.New(msg) + + } + + ct := getVerifiedCiphertext(environment, common.BytesToHash(input[0:32])) + if ct == nil { + msg := "fheNeg input not verified" + logger.Error(msg, msg, "input", hex.EncodeToString(input)) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, ct.ciphertext.fheUintType), nil + } + + result, err := ct.ciphertext.neg() + if err != nil { + logger.Error("fheNeg failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheNeg success", "ct", ct.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil } -func decryptRequiredGas(environment EVMEnvironment, input []byte) uint64 { +func fheNotRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { logger := environment.GetLogger() + if len(input) != 32 { - logger.Error("decrypt RequiredGas() input len must be 32 bytes", "input", hex.EncodeToString(input), "len", len(input)) - return 0 + msg := "fheMax input needs to contain one 256-bit sized value" + logger.Error(msg, "input", hex.EncodeToString(input)) + return nil, errors.New(msg) + } - ct := getVerifiedCiphertext(environment, common.BytesToHash(input)) + + ct := getVerifiedCiphertext(environment, common.BytesToHash(input[0:32])) if ct == nil { - logger.Error("decrypt RequiredGas() input doesn't point to verified ciphertext", "input", hex.EncodeToString(input)) - return 0 + msg := "fheNot input not verified" + logger.Error(msg, msg, "input", hex.EncodeToString(input)) + return nil, errors.New(msg) } - return fheDecryptGasCosts[ct.ciphertext.fheUintType] -} - -func fhePubKeyRequiredGas(accessibleState EVMEnvironment, input []byte) uint64 { - return params.FhePubKeyGas -} -var fheTrivialEncryptGasCosts = map[fheUintType]uint64{ - FheUint8: params.FheUint8TrivialEncryptGas, - FheUint16: params.FheUint16TrivialEncryptGas, - FheUint32: params.FheUint32TrivialEncryptGas, -} + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, ct.ciphertext.fheUintType), nil + } -func trivialEncryptRequiredGas(accessibleState EVMEnvironment, input []byte) uint64 { - logger := accessibleState.GetLogger() - if len(input) != 33 { - logger.Error("trivialEncrypt RequiredGas() input len must be 33 bytes", "input", hex.EncodeToString(input), "len", len(input)) - return 0 + result, err := ct.ciphertext.not() + if err != nil { + logger.Error("fheNot failed", "err", err) + return nil, err } - encryptToType := fheUintType(input[32]) - return fheTrivialEncryptGasCosts[encryptToType] + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheNot success", "ct", ct.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil } -// Implementations -func fheAddRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { +func fheDivRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { logger := environment.GetLogger() isScalar, err := isScalarOp(input) if err != nil { - logger.Error("fheAdd can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + logger.Error("fheDiv cannot detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) return nil, err } if !isScalar { - lhs, rhs, err := get2VerifiedOperands(environment, input) + err = errors.New("fheDiv supports only scalar input operation, two ciphertexts received") + logger.Error("fheDiv supports only scalar input operation, two ciphertexts received", "input", hex.EncodeToString(input)) + return nil, err + } else { + lhs, rhs, err := getScalarOperands(environment, input) if err != nil { - logger.Error("fheAdd inputs not verified", "err", err, "input", hex.EncodeToString(input)) + logger.Error("fheDiv scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) return nil, err } - if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { - msg := "fheAdd operand type mismatch" - logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) - return nil, errors.New(msg) - } // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. if !environment.IsCommitting() && !environment.IsEthCall() { return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil } - result, err := lhs.ciphertext.add(rhs.ciphertext) + result, err := lhs.ciphertext.scalarDiv(rhs.Uint64()) if err != nil { - logger.Error("fheAdd failed", "err", err) + logger.Error("fheDiv failed", "err", err) return nil, err } importCiphertext(environment, result) resultHash := result.getHash() - logger.Info("fheAdd success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + logger.Info("fheDiv scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) return resultHash[:], nil + } +} + +func fheRemRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheRem cannot detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + if !isScalar { + err = errors.New("fheRem supports only scalar input operation, two ciphertexts received") + logger.Error("fheRem supports only scalar input operation, two ciphertexts received", "input", hex.EncodeToString(input)) + return nil, err } else { lhs, rhs, err := getScalarOperands(environment, input) if err != nil { - logger.Error("fheAdd scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) + logger.Error("fheRem scalar inputs not verified", "err", err, "input", hex.EncodeToString(input)) return nil, err } @@ -233,19 +1681,365 @@ func fheAddRun(environment EVMEnvironment, caller common.Address, addr common.Ad return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil } - result, err := lhs.ciphertext.scalarAdd(rhs.Uint64()) + result, err := lhs.ciphertext.scalarRem(rhs.Uint64()) if err != nil { - logger.Error("fheAdd failed", "err", err) + logger.Error("fheRem failed", "err", err) return nil, err } importCiphertext(environment, result) resultHash := result.getHash() - logger.Info("fheAdd scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) + logger.Info("fheRem scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex()) return resultHash[:], nil } } +func fheBitAndRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheBitAnd can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if isScalar { + msg := "fheBitAnd scalar op not supported" + logger.Error(msg) + return nil, errors.New(msg) + } + + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheBitAnd inputs not verified", "err", err) + return nil, err + } + + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheBitAnd operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.bitand(rhs.ciphertext) + if err != nil { + logger.Error("fheBitAnd failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheBitAnd success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil +} + +func fheBitOrRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheBitOr can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if isScalar { + msg := "fheBitOr scalar op not supported" + logger.Error(msg) + return nil, errors.New(msg) + } + + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheBitOr inputs not verified", "err", err) + return nil, err + } + + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheBitOr operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.bitor(rhs.ciphertext) + if err != nil { + logger.Error("fheBitOr failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheBitOr success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil +} + +func fheBitXorRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + + isScalar, err := isScalarOp(input) + if err != nil { + logger.Error("fheBitXor can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input)) + return nil, err + } + + if isScalar { + msg := "fheBitXor scalar op not supported" + logger.Error(msg) + return nil, errors.New(msg) + } + + lhs, rhs, err := get2VerifiedOperands(environment, input) + if err != nil { + logger.Error("fheBitXor inputs not verified", "err", err) + return nil, err + } + + if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType { + msg := "fheBitXor operand type mismatch" + logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil + } + + result, err := lhs.ciphertext.bitxor(rhs.ciphertext) + if err != nil { + logger.Error("fheBitXor failed", "err", err) + return nil, err + } + importCiphertext(environment, result) + + resultHash := result.getHash() + logger.Info("fheBitXor success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex()) + return resultHash[:], nil +} + +var globalRngSeed []byte + +var rngNonceKey [32]byte = uint256.NewInt(0).Bytes32() + +func init() { + if chacha20.NonceSizeX != 24 { + panic("expected 24 bytes for NonceSizeX") + } + + // TODO: Since the current implementation is not FHE-based and, hence, not private, + // we just initialize the global seed with non-random public data. We will change + // that once the FHE version is available. + globalRngSeed = make([]byte, chacha20.KeySize) + for i := range globalRngSeed { + globalRngSeed[i] = byte(1 + i) + } +} + +func fheRandRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + if environment.IsEthCall() { + msg := "fheRand cannot be called via EthCall, because it needs to mutate internal state" + logger.Error(msg) + return nil, errors.New(msg) + } + if len(input) != 1 || !isValidType(input[0]) { + msg := "fheRand input len must be at least 1 byte and be a valid FheUint type" + logger.Error(msg, "input", hex.EncodeToString(input), "len", len(input)) + return nil, errors.New(msg) + } + + t := fheUintType(input[0]) + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() { + return importRandomCiphertext(environment, t), nil + } + + // Get the RNG nonce. + protectedStorage := CreateProtectedStorageContractAddress(caller) + currentRngNonceBytes := environment.GetState(protectedStorage, rngNonceKey).Bytes() + + // Increment the RNG nonce by 1. + nextRngNonce := newInt(currentRngNonceBytes) + nextRngNonce = nextRngNonce.AddUint64(nextRngNonce, 1) + environment.SetState(protectedStorage, rngNonceKey, nextRngNonce.Bytes32()) + + // Compute the seed and use it to create a new cipher. + hasher := crypto.NewKeccakState() + hasher.Write(globalRngSeed) + hasher.Write(caller.Bytes()) + seed := common.Hash{} + _, err := hasher.Read(seed[:]) + if err != nil { + return nil, err + } + // The RNG nonce bytes are of size chacha20.NonceSizeX, which is assumed to be 24 bytes (see init() above). + // Since uint256.Int.z[0] is the least significant byte and since uint256.Int.Bytes32() serializes + // in order of z[3], z[2], z[1], z[0], we want to essentially ignore the first byte, i.e. z[3], because + // it will always be 0 as the nonce size is 24. + cipher, err := chacha20.NewUnauthenticatedCipher(seed.Bytes(), currentRngNonceBytes[32-chacha20.NonceSizeX:32]) + if err != nil { + return nil, err + } + + // XOR a byte array of 0s with the stream from the cipher and receive the result in the same array. + randBytes := make([]byte, 8) + cipher.XORKeyStream(randBytes, randBytes) + + // Trivially encrypt the random integer. + randUint64 := binary.BigEndian.Uint64(randBytes) + randCt := new(tfheCiphertext) + randBigInt := big.NewInt(0) + randBigInt.SetUint64(randUint64) + randCt.trivialEncrypt(*randBigInt, t) + importCiphertext(environment, randCt) + + if err != nil { + return nil, err + } + ctHash := randCt.getHash() + return ctHash[:], nil +} + +func verifyCiphertextRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + if len(input) <= 1 { + msg := "verifyCiphertext Run() input needs to contain a ciphertext and one byte for its type" + logger.Error(msg, "len", len(input)) + return nil, errors.New(msg) + } + + ctBytes := input[:len(input)-1] + ctTypeByte := input[len(input)-1] + if !isValidType(ctTypeByte) { + msg := "verifyCiphertext Run() ciphertext type is invalid" + logger.Error(msg, "type", ctTypeByte) + return nil, errors.New(msg) + } + ctType := fheUintType(ctTypeByte) + + expectedSize, found := compactFheCiphertextSize[ctType] + if !found || expectedSize != uint(len(ctBytes)) { + msg := "verifyCiphertext Run() compact ciphertext size is invalid" + logger.Error(msg, "type", ctTypeByte, "size", len(ctBytes), "expectedSize", expectedSize) + return nil, errors.New(msg) + } + + // If we are doing gas estimation, skip execution and insert a random ciphertext as a result. + if !environment.IsCommitting() && !environment.IsEthCall() { + return importRandomCiphertext(environment, ctType), nil + } + + ct := new(tfheCiphertext) + err := ct.deserializeCompact(ctBytes, ctType) + if err != nil { + logger.Error("verifyCiphertext failed to deserialize input ciphertext", + "err", err, + "len", len(ctBytes), + "ctBytes64", hex.EncodeToString(ctBytes[:minInt(len(ctBytes), 64)])) + return nil, err + } + ctHash := ct.getHash() + importCiphertext(environment, ct) + if environment.IsCommitting() { + logger.Info("verifyCiphertext success", + "ctHash", ctHash.Hex(), + "ctBytes64", hex.EncodeToString(ctBytes[:minInt(len(ctBytes), 64)])) + } + return ctHash.Bytes(), nil +} + +func classicalPublicKeyEncrypt(value *big.Int, userPublicKey []byte) ([]byte, error) { + encrypted, err := box.SealAnonymous(nil, value.Bytes(), (*[32]byte)(userPublicKey), rand.Reader) + if err != nil { + return nil, err + } + return encrypted, nil +} + +func encryptToUserKey(value *big.Int, pubKey []byte) ([]byte, error) { + ct, err := classicalPublicKeyEncrypt(value, pubKey) + if err != nil { + return nil, err + } + + return ct, nil +} + +func reencryptRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() + if !environment.IsEthCall() { + msg := "reencrypt only supported on EthCall" + logger.Error(msg) + return nil, errors.New(msg) + } + if len(input) != 64 { + msg := "reencrypt input len must be 64 bytes" + logger.Error(msg, "input", hex.EncodeToString(input), "len", len(input)) + return nil, errors.New(msg) + } + ct := getVerifiedCiphertext(environment, common.BytesToHash(input[0:32])) + if ct != 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 + } + decryptedValue, err := ct.ciphertext.decrypt() + if err != nil { + logger.Error("reencrypt decryption failed", "err", err) + return nil, err + } + pubKey := input[32:64] + reencryptedValue, err := encryptToUserKey(&decryptedValue, pubKey) + if err != nil { + logger.Error("reencrypt failed to encrypt to user key", "err", err) + return nil, err + } + logger.Info("reencrypt success", "input", hex.EncodeToString(input), "callerAddr", caller) + return toEVMBytes(reencryptedValue), nil + } + msg := "reencrypt unverified ciphertext handle" + logger.Error(msg, "input", hex.EncodeToString(input)) + return nil, errors.New(msg) +} + +func optimisticRequireRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + 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) + } + // 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.ciphertext.fheUintType != FheUint8 { + msg := "optimisticRequire ciphertext type is not FheUint8" + logger.Error(msg, "type", ct.ciphertext.fheUintType) + return nil, errors.New(msg) + } + environment.GetFhevmData().optimisticRequires = append(environment.GetFhevmData().optimisticRequires, ct.ciphertext) + return nil, nil +} + func decryptRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { logger := environment.GetLogger() if len(input) != 32 { @@ -359,11 +2153,11 @@ func castRun(environment EVMEnvironment, caller common.Address, addr common.Addr var fhePubKeyHashPrecompile = common.BytesToAddress([]byte{93}) var fhePubKeyHashSlot = common.Hash{} -func fhePubKeyRun(accessibleState EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { - existing := accessibleState.GetState(fhePubKeyHashPrecompile, fhePubKeyHashSlot) +func fhePubKeyRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + existing := environment.GetState(fhePubKeyHashPrecompile, fhePubKeyHashSlot) if existing != pksHash { msg := "fhePubKey FHE public key hash doesn't match one stored in state" - accessibleState.GetLogger().Error(msg, "existing", existing.Hex(), "pksHash", pksHash.Hex()) + environment.GetLogger().Error(msg, "existing", existing.Hex(), "pksHash", pksHash.Hex()) return nil, errors.New(msg) } // If we have a single byte with the value of 1, return as an EVM array. Otherwise, returh the raw bytes. @@ -374,8 +2168,8 @@ func fhePubKeyRun(accessibleState EVMEnvironment, caller common.Address, addr co } } -func trivialEncryptRun(accessibleState EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { - logger := accessibleState.GetLogger() +func trivialEncryptRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) { + logger := environment.GetLogger() if len(input) != 33 { msg := "trivialEncrypt input len must be 33 bytes" logger.Error(msg, "input", hex.EncodeToString(input), "len", len(input)) @@ -388,8 +2182,8 @@ func trivialEncryptRun(accessibleState EVMEnvironment, caller common.Address, ad ct := new(tfheCiphertext).trivialEncrypt(valueToEncrypt, encryptToType) ctHash := ct.getHash() - importCiphertext(accessibleState, ct) - if accessibleState.IsCommitting() { + importCiphertext(environment, ct) + if environment.IsCommitting() { logger.Info("trivialEncrypt success", "ctHash", ctHash.Hex(), "valueToEncrypt", valueToEncrypt.Uint64()) diff --git a/go.mod b/go.mod index c9329ca..cc6cc6e 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.20 require ( github.com/ethereum/go-ethereum v1.12.0 github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c + golang.org/x/crypto v0.1.0 ) require ( github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect - golang.org/x/crypto v0.1.0 // indirect golang.org/x/sys v0.7.0 // indirect ) diff --git a/params/fhevm_params.go b/params/fhevm_params.go index 553be9d..a4aa3a6 100644 --- a/params/fhevm_params.go +++ b/params/fhevm_params.go @@ -13,6 +13,9 @@ var ( FheUint8DivGas uint64 = 1370000 FheUint16DivGas uint64 = 3500000 FheUint32DivGas uint64 = 9120000 + FheUint8RemGas uint64 = 1370000 // TODO: check again rem gas + FheUint16RemGas uint64 = 3500000 + FheUint32RemGas uint64 = 9120000 FheUint8BitwiseGas uint64 = 20000 FheUint16BitwiseGas uint64 = 21000 FheUint32BitwiseGas uint64 = 22000 @@ -55,8 +58,10 @@ var ( FheUint8OptimisticRequireGas uint64 = FheUint8RequireGas FheUint8OptimisticRequireBitandGas uint64 = FheUint8BitwiseGas - // TODO: This will change once we have an FHE-based random generaration with different types. - FheRandGas uint64 = evm.NetSstoreCleanGas + evm.ColdSloadCostEIP2929 + // TODO: These will change once we have an FHE-based random generaration. + FheUint8RandGas uint64 = evm.NetSstoreInitGas + 1000 + FheUint16RandGas uint64 = FheUint8RandGas + 1000 + FheUint32RandGas uint64 = FheUint16RandGas + 1000 // TODO: The values here are chosen somewhat arbitrarily (at least the 8 bit ones). Also, we don't // take into account whether a ciphertext existed (either "current" or "original") for the given handle.