Skip to content
This repository has been archived by the owner on Dec 23, 2024. It is now read-only.

Commit

Permalink
Merge pull request #114 from zama-ai/petar/fhe-array-eq-dev
Browse files Browse the repository at this point in the history
Petar/fhe array eq dev
  • Loading branch information
immortal-tofu authored May 29, 2024
2 parents 9c0269e + 06e9cdf commit 64e86cd
Show file tree
Hide file tree
Showing 13 changed files with 1,482 additions and 84 deletions.
769 changes: 769 additions & 0 deletions fhevm/contracts_test.go

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions fhevm/fhelib.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ var fhelibMethods = []*FheLibMethod{
requiredGasFunction: fheIfThenElseRequiredGas,
runFunction: fheIfThenElseRun,
},
{
name: "fheArrayEq",
argTypes: "(uint256[],uint256[])",
requiredGasFunction: fheArrayEqRequiredGas,
runFunction: fheArrayEqRun,
},
{
name: "fhePubKey",
argTypes: "(bytes1)",
Expand Down
10 changes: 10 additions & 0 deletions fhevm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ func getVerifiedCiphertextFromEVM(environment EVMEnvironment, ciphertextHash com
return nil
}

// Returns the type of the verified ciphertext for `ciphertextHash` or nil if it doesn't exist or not verified at current depth.
func GetTypeOfVerifiedCiphertext(env EVMEnvironment, ciphertextHash common.Hash) *tfhe.FheUintType {
ct := getVerifiedCiphertextFromEVM(env, ciphertextHash)
if ct == nil {
return nil
}
t := ct.ciphertext.Type()
return &t
}

func verifyIfCiphertextHandle(handle common.Hash, env EVMEnvironment, contractAddress common.Address) error {
ct, ok := env.FhevmData().verifiedCiphertexts[handle]
if ok {
Expand Down
10 changes: 0 additions & 10 deletions fhevm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,3 @@ func (vc *verifiedCiphertext) serialization() []byte {
func (vc *verifiedCiphertext) hash() common.Hash {
return vc.ciphertext.GetHash()
}

type PrivilegedMemory struct {
// A map from a ciphertext hash to itself and stack depths at which it is verified
VerifiedCiphertexts map[common.Hash]*verifiedCiphertext
}

var PrivilegedMempory *PrivilegedMemory = &PrivilegedMemory{
make(map[common.Hash]*verifiedCiphertext),
}

109 changes: 109 additions & 0 deletions fhevm/operators_comparison.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package fhevm
import (
"encoding/hex"
"errors"
"fmt"
"math/big"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/zama-ai/fhevm-go/fhevm/tfhe"
"go.opentelemetry.io/otel/trace"
Expand Down Expand Up @@ -570,3 +574,108 @@ func fheIfThenElseRun(environment EVMEnvironment, caller common.Address, addr co
logger.Info("fheIfThenElse success", "first", first.hash().Hex(), "second", second.hash().Hex(), "third", third.hash().Hex(), "result", resultHash.Hex())
return resultHash[:], nil
}

// TODO: implement as part of fhelibMethods.
const fheArrayEqAbiJson = `
[
{
"name": "fheArrayEq",
"type": "function",
"inputs": [
{
"name": "lhs",
"type": "uint256[]"
},
{
"name": "rhs",
"type": "uint256[]"
}
],
"outputs": [
{
"name": "",
"type": "uint256"
}
]
}
]
`

var arrayEqMethod abi.Method

func init() {
reader := strings.NewReader(fheArrayEqAbiJson)
arrayEqAbi, err := abi.JSON(reader)
if err != nil {
panic(err)
}

var ok bool
arrayEqMethod, ok = arrayEqAbi.Methods["fheArrayEq"]
if !ok {
panic("couldn't find the fheArrayEq method")
}
}

func getVerifiedCiphertexts(environment EVMEnvironment, unpacked interface{}) ([]*tfhe.TfheCiphertext, error) {
big, ok := unpacked.([]*big.Int)
if !ok {
return nil, fmt.Errorf("fheArrayEq failed to cast to []*big.Int")
}
ret := make([]*tfhe.TfheCiphertext, 0, len(big))
for _, b := range big {
ct := getVerifiedCiphertext(environment, common.BigToHash(b))
if ct == nil {
return nil, fmt.Errorf("fheArrayEq unverified ciphertext")
}
ret = append(ret, ct.ciphertext)
}
return ret, nil
}

func fheArrayEqRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool, runSpan trace.Span) ([]byte, error) {
logger := environment.GetLogger()

unpacked, err := arrayEqMethod.Inputs.UnpackValues(input)
if err != nil {
msg := "fheArrayEqRun failed to unpack input"
logger.Error(msg, "err", err)
return nil, err
}

if len(unpacked) != 2 {
err := fmt.Errorf("fheArrayEqRun unexpected unpacked len: %d", len(unpacked))
logger.Error(err.Error())
return nil, err
}

lhs, err := getVerifiedCiphertexts(environment, unpacked[0])
if err != nil {
msg := "fheArrayEqRun failed to get lhs to verified ciphertexts"
logger.Error(msg, "err", err)
return nil, err
}

rhs, err := getVerifiedCiphertexts(environment, unpacked[1])
if err != nil {
msg := "fheArrayEqRun failed to get rhs to verified ciphertexts"
logger.Error(msg, "err", err)
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, tfhe.FheBool), nil
}

result, err := tfhe.EqArray(lhs, rhs)
if err != nil {
msg := "fheArrayEqRun failed to execute"
logger.Error(msg, "err", err)
return nil, err
}
importCiphertext(environment, result)
resultHash := result.GetHash()
logger.Info("fheArrayEqRun success", "result", resultHash.Hex())
return resultHash[:], nil
}
65 changes: 65 additions & 0 deletions fhevm/operators_comparison_gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fhevm

import (
"encoding/hex"
"fmt"

"github.com/zama-ai/fhevm-go/fhevm/tfhe"
)
Expand Down Expand Up @@ -141,3 +142,67 @@ func fheIfThenElseRequiredGas(environment EVMEnvironment, input []byte) uint64 {
}
return environment.FhevmParams().GasCosts.FheIfThenElse[second.fheUintType()]
}

func fheArrayEqRequiredGas(environment EVMEnvironment, input []byte) uint64 {
logger := environment.GetLogger()

unpacked, err := arrayEqMethod.Inputs.UnpackValues(input)
if err != nil {
msg := "fheArrayEqRun RequiredGas() failed to unpack input"
logger.Error(msg, "err", err)
return 0
}

if len(unpacked) != 2 {
err := fmt.Errorf("fheArrayEqRun RequiredGas() unexpected unpacked len: %d", len(unpacked))
logger.Error(err.Error())
return 0
}

lhs, err := getVerifiedCiphertexts(environment, unpacked[0])
if err != nil {
msg := "fheArrayEqRun RequiredGas() failed to get lhs to verified ciphertexts"
logger.Error(msg, "err", err)
return 0
}

rhs, err := getVerifiedCiphertexts(environment, unpacked[1])
if err != nil {
msg := "fheArrayEqRun RequiredGas() failed to get rhs to verified ciphertexts"
logger.Error(msg, "err", err)
return 0
}

if len(lhs) != len(rhs) || (len(lhs) == 0 && len(rhs) == 0) {
return environment.FhevmParams().GasCosts.FheTrivialEncrypt[tfhe.FheBool]
}

numElements := len(lhs)
elementType := lhs[0].Type()
// TODO: tie to supported types in tfhe.TfheCiphertext.EqArray()
if elementType != tfhe.FheUint4 && elementType != tfhe.FheUint8 && elementType != tfhe.FheUint16 && elementType != tfhe.FheUint32 && elementType != tfhe.FheUint64 {
return 0
}
for i := range lhs {
if lhs[i].Type() != elementType || rhs[i].Type() != elementType {
return 0
}
}

numBits := elementType.NumBits() * uint(numElements)
if numBits <= 4 {
return environment.FhevmParams().GasCosts.FheEq[tfhe.FheUint4]
} else if numBits <= 8 {
return environment.FhevmParams().GasCosts.FheEq[tfhe.FheUint8]
} else if numBits <= 16 {
return environment.FhevmParams().GasCosts.FheEq[tfhe.FheUint16]
} else if numBits <= 32 {
return environment.FhevmParams().GasCosts.FheEq[tfhe.FheUint32]
} else if numBits <= 64 {
return environment.FhevmParams().GasCosts.FheEq[tfhe.FheUint64]
} else if numBits <= 160 {
return environment.FhevmParams().GasCosts.FheEq[tfhe.FheUint160]
} else {
return (environment.FhevmParams().GasCosts.FheEq[tfhe.FheUint160] + environment.FhevmParams().GasCosts.FheArrayEqBigArrayFactor) * (uint64(numBits) / 160)
}
}
100 changes: 59 additions & 41 deletions fhevm/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,10 @@ const ColdSloadCostEIP2929 uint64 = 2100

const GetNonExistentCiphertextGas uint64 = 1000

var (
// 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.
// Finally, costs are likely to change in the future.
FheUint8ProtectedStorageSstoreGas uint64 = EvmNetSstoreInitGas + 2000
FheUint16ProtectedStorageSstoreGas uint64 = FheUint8ProtectedStorageSstoreGas * 2
FheUint32ProtectedStorageSstoreGas uint64 = FheUint16ProtectedStorageSstoreGas * 2

// TODO: We don't take whether the slot is cold or warm into consideration.
FheUint8ProtectedStorageSloadGas uint64 = ColdSloadCostEIP2929 + 200
FheUint16ProtectedStorageSloadGas uint64 = FheUint8ProtectedStorageSloadGas * 2
FheUint32ProtectedStorageSloadGas uint64 = FheUint16ProtectedStorageSloadGas * 2
)
// Base costs of fhEVM SSTORE and SLOAD operations.
// TODO: We don't take whether the slot is cold or warm into consideration.
const SstoreFheUint4Gas = EvmNetSstoreInitGas + 1000
const SloadFheUint4Gas = ColdSloadCostEIP2929 + 100

func DefaultFhevmParams() FhevmParams {
return FhevmParams{
Expand All @@ -40,33 +31,38 @@ type FhevmParams struct {
}

type GasCosts struct {
FheCast uint64
FhePubKey uint64
FheAddSub map[tfhe.FheUintType]uint64
FheDecrypt map[tfhe.FheUintType]uint64
FheBitwiseOp map[tfhe.FheUintType]uint64
FheMul map[tfhe.FheUintType]uint64
FheScalarMul map[tfhe.FheUintType]uint64
FheScalarDiv map[tfhe.FheUintType]uint64
FheScalarRem map[tfhe.FheUintType]uint64
FheShift map[tfhe.FheUintType]uint64
FheScalarShift map[tfhe.FheUintType]uint64
FheEq map[tfhe.FheUintType]uint64
FheLe map[tfhe.FheUintType]uint64
FheMinMax map[tfhe.FheUintType]uint64
FheScalarMinMax map[tfhe.FheUintType]uint64
FheNot map[tfhe.FheUintType]uint64
FheNeg map[tfhe.FheUintType]uint64
FheReencrypt map[tfhe.FheUintType]uint64
FheTrivialEncrypt map[tfhe.FheUintType]uint64
FheRand map[tfhe.FheUintType]uint64
FheIfThenElse map[tfhe.FheUintType]uint64
FheVerify map[tfhe.FheUintType]uint64
FheGetCiphertext map[tfhe.FheUintType]uint64
FheCast uint64
FhePubKey uint64
FheAddSub map[tfhe.FheUintType]uint64
FheDecrypt map[tfhe.FheUintType]uint64
FheBitwiseOp map[tfhe.FheUintType]uint64
FheMul map[tfhe.FheUintType]uint64
FheScalarMul map[tfhe.FheUintType]uint64
FheScalarDiv map[tfhe.FheUintType]uint64
FheScalarRem map[tfhe.FheUintType]uint64
FheShift map[tfhe.FheUintType]uint64
FheScalarShift map[tfhe.FheUintType]uint64
FheEq map[tfhe.FheUintType]uint64
FheArrayEqBigArrayFactor uint64 // TODO: either rename or come up with a better solution
FheLe map[tfhe.FheUintType]uint64
FheMinMax map[tfhe.FheUintType]uint64
FheScalarMinMax map[tfhe.FheUintType]uint64
FheNot map[tfhe.FheUintType]uint64
FheNeg map[tfhe.FheUintType]uint64
FheReencrypt map[tfhe.FheUintType]uint64
FheTrivialEncrypt map[tfhe.FheUintType]uint64
FheRand map[tfhe.FheUintType]uint64
FheIfThenElse map[tfhe.FheUintType]uint64
FheVerify map[tfhe.FheUintType]uint64
FheGetCiphertext map[tfhe.FheUintType]uint64
ProtectedStorageSstoreGas map[tfhe.FheUintType]uint64
ProtectedStorageSloadGas map[tfhe.FheUintType]uint64
}

func DefaultGasCosts() GasCosts {
return GasCosts{
FheCast: 200,
FhePubKey: 50,
FheAddSub: map[tfhe.FheUintType]uint64{
tfhe.FheUint4: 55000 + AdjustFHEGas,
tfhe.FheUint8: 84000 + AdjustFHEGas,
Expand Down Expand Up @@ -132,13 +128,14 @@ func DefaultGasCosts() GasCosts {
tfhe.FheUint64: 28000 + AdjustFHEGas,
},
FheEq: map[tfhe.FheUintType]uint64{
tfhe.FheUint4: 41000 + AdjustFHEGas,
tfhe.FheUint8: 43000 + AdjustFHEGas,
tfhe.FheUint16: 44000 + AdjustFHEGas,
tfhe.FheUint32: 72000 + AdjustFHEGas,
tfhe.FheUint64: 76000 + AdjustFHEGas,
tfhe.FheUint4: 41000 + AdjustFHEGas,
tfhe.FheUint8: 43000 + AdjustFHEGas,
tfhe.FheUint16: 44000 + AdjustFHEGas,
tfhe.FheUint32: 72000 + AdjustFHEGas,
tfhe.FheUint64: 76000 + AdjustFHEGas,
tfhe.FheUint160: 80000 + AdjustFHEGas,
},
FheArrayEqBigArrayFactor: 1000,
FheLe: map[tfhe.FheUintType]uint64{
tfhe.FheUint4: 60000 + AdjustFHEGas,
tfhe.FheUint8: 72000 + AdjustFHEGas,
Expand Down Expand Up @@ -220,6 +217,27 @@ func DefaultGasCosts() GasCosts {
tfhe.FheUint32: 18000,
tfhe.FheUint64: 28000,
},
// TODO: The values here are chosen somewhat arbitrarily.
// Also, we don't take into account whether a ciphertext existed (either "current" or "original") for the given handle.
// Finally, costs are likely to change in the future.
ProtectedStorageSstoreGas: map[tfhe.FheUintType]uint64{
tfhe.FheUint4: SstoreFheUint4Gas,
tfhe.FheUint8: SstoreFheUint4Gas * 2,
tfhe.FheUint16: SstoreFheUint4Gas * 4,
tfhe.FheUint32: SstoreFheUint4Gas * 8,
tfhe.FheUint64: SstoreFheUint4Gas * 16,
tfhe.FheUint128: SstoreFheUint4Gas * 32,
tfhe.FheUint160: SstoreFheUint4Gas * 40,
},
ProtectedStorageSloadGas: map[tfhe.FheUintType]uint64{
tfhe.FheUint4: SloadFheUint4Gas,
tfhe.FheUint8: SloadFheUint4Gas * 2,
tfhe.FheUint16: SloadFheUint4Gas * 4,
tfhe.FheUint32: SloadFheUint4Gas * 8,
tfhe.FheUint64: SloadFheUint4Gas * 16,
tfhe.FheUint128: SloadFheUint4Gas * 32,
tfhe.FheUint160: SloadFheUint4Gas * 40,
},
}
}

Expand Down
10 changes: 10 additions & 0 deletions fhevm/protected_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ type ciphertextData struct {
bytes []byte
}

// Returns the type of the persisted ciphertext for `handle` in protected storage or nil if `handle`doesn't point to a persisted ciphertext.
func GetTypeOfPersistedCiphertext(env EVMEnvironment, contractAddress common.Address, handle common.Hash) *tfhe.FheUintType {
metadata := getCiphertextMetadataFromProtectedStorage(env, contractAddress, handle)
if metadata == nil {
return nil
}
t := metadata.fheUintType
return &t
}

func getCiphertextMetadataKey(handle common.Hash) common.Hash {
return crypto.Keccak256Hash(handle.Bytes())
}
Expand Down
Loading

0 comments on commit 64e86cd

Please sign in to comment.