From dd1d1fb21a4ca5631091686393c7ec0694557730 Mon Sep 17 00:00:00 2001 From: David Kazlauskas Date: Tue, 5 Nov 2024 08:34:12 +0200 Subject: [PATCH 1/3] feat: sketch for the initial native api of go ethereum --- .../.github/workflows/tests.yaml | 26 + fhevm-engine/fhevm-go-native/.gitignore | 1 + fhevm-engine/fhevm-go-native/.gitmodules | 3 + fhevm-engine/fhevm-go-native/Makefile | 21 + fhevm-engine/fhevm-go-native/README.md | 1 + fhevm-engine/fhevm-go-native/fhevm/api.go | 371 ++++ .../fhevm-go-native/fhevm/common.pb.go | 232 +++ fhevm-engine/fhevm-go-native/fhevm/fhelib.go | 205 ++ .../fhevm-go-native/fhevm/fhelib_ops.go | 1690 +++++++++++++++++ fhevm-engine/fhevm-go-native/go.mod | 21 + fhevm-engine/fhevm-go-native/go.sum | 32 + 11 files changed, 2603 insertions(+) create mode 100644 fhevm-engine/fhevm-go-native/.github/workflows/tests.yaml create mode 100644 fhevm-engine/fhevm-go-native/.gitignore create mode 100644 fhevm-engine/fhevm-go-native/.gitmodules create mode 100644 fhevm-engine/fhevm-go-native/Makefile create mode 100644 fhevm-engine/fhevm-go-native/README.md create mode 100644 fhevm-engine/fhevm-go-native/fhevm/api.go create mode 100644 fhevm-engine/fhevm-go-native/fhevm/common.pb.go create mode 100644 fhevm-engine/fhevm-go-native/fhevm/fhelib.go create mode 100644 fhevm-engine/fhevm-go-native/fhevm/fhelib_ops.go create mode 100644 fhevm-engine/fhevm-go-native/go.mod create mode 100644 fhevm-engine/fhevm-go-native/go.sum diff --git a/fhevm-engine/fhevm-go-native/.github/workflows/tests.yaml b/fhevm-engine/fhevm-go-native/.github/workflows/tests.yaml new file mode 100644 index 00000000..858561ad --- /dev/null +++ b/fhevm-engine/fhevm-go-native/.github/workflows/tests.yaml @@ -0,0 +1,26 @@ +name: Run Tests + +on: + push: + branches: + - 'main' + tags: + - '*' + pull_request: + +jobs: + build_and_test: + name: Build and test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.22.0' + + - name: Run tests + run: make test diff --git a/fhevm-engine/fhevm-go-native/.gitignore b/fhevm-engine/fhevm-go-native/.gitignore new file mode 100644 index 00000000..e43b0f98 --- /dev/null +++ b/fhevm-engine/fhevm-go-native/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/fhevm-engine/fhevm-go-native/.gitmodules b/fhevm-engine/fhevm-go-native/.gitmodules new file mode 100644 index 00000000..e1b5f337 --- /dev/null +++ b/fhevm-engine/fhevm-go-native/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tfhe-rs"] + path = tfhe-rs + url = https://github.com/zama-ai/tfhe-rs.git diff --git a/fhevm-engine/fhevm-go-native/Makefile b/fhevm-engine/fhevm-go-native/Makefile new file mode 100644 index 00000000..41e983b6 --- /dev/null +++ b/fhevm-engine/fhevm-go-native/Makefile @@ -0,0 +1,21 @@ +fhevm/executor.pb.go: ../../proto/executor.proto ../../proto/common.proto + protoc \ + --proto_path=../../proto/ \ + --go_opt=paths=source_relative \ + --go_opt=Mprotos/executor.proto=github.com/zama-ai/fhevm-backend/fhevm-engine/fhevm-go-native \ + --go_opt=Mprotos/common.proto=github.com/zama-ai/fhevm-backend/fhevm-engine/fhevm-go-native \ + --go-grpc_out=./fhevm/ --go-grpc_opt=paths=source_relative \ + --go_out=./fhevm/ \ + executor.proto common.proto + +.PHONY: build +build: fhevm/executor.pb.go + cd fhevm && go build . + +.PHONY: run +run: fhevm/executor.pb.go + cd fhevm && go run . + +.PHONY: test +test: fhevm/executor.pb.go + cd fhevm && go test ./... diff --git a/fhevm-engine/fhevm-go-native/README.md b/fhevm-engine/fhevm-go-native/README.md new file mode 100644 index 00000000..2f364642 --- /dev/null +++ b/fhevm-engine/fhevm-go-native/README.md @@ -0,0 +1 @@ +# fhevm-go-native diff --git a/fhevm-engine/fhevm-go-native/fhevm/api.go b/fhevm-engine/fhevm-go-native/fhevm/api.go new file mode 100644 index 00000000..f185da23 --- /dev/null +++ b/fhevm-engine/fhevm-go-native/fhevm/api.go @@ -0,0 +1,371 @@ +package fhevm + +import ( + "encoding/binary" + "errors" + "fmt" + "os" + + "github.com/ethereum/go-ethereum/common" + _ "github.com/mattn/go-sqlite3" +) + +type FheUintType uint8 + +const ( + FheBool FheUintType = 0 + FheUint4 FheUintType = 1 + FheUint8 FheUintType = 2 + FheUint16 FheUintType = 3 + FheUint32 FheUintType = 4 + FheUint64 FheUintType = 5 + FheUint128 FheUintType = 6 + FheUint160 FheUintType = 7 + FheUint256 FheUintType = 8 + FheEbytes64 FheUintType = 9 + FheEbytes128 FheUintType = 10 + FheEbytes256 FheUintType = 11 + FheUserBytes FheUintType = 255 +) + +type FheOp uint8 + +const ( + FheAdd FheOp = 0 + FheSub FheOp = 1 + FheMul FheOp = 2 + FheDiv FheOp = 3 + FheRem FheOp = 4 + FheBitAnd FheOp = 5 + FheBitOr FheOp = 6 + FheBitXor FheOp = 7 + FheShl FheOp = 8 + FheShr FheOp = 9 + FheRotl FheOp = 10 + FheRotr FheOp = 11 + FheEq FheOp = 12 + FheNe FheOp = 13 + FheGe FheOp = 14 + FheGt FheOp = 15 + FheLe FheOp = 16 + FheLt FheOp = 17 + FheMin FheOp = 18 + FheMax FheOp = 19 + FheNeg FheOp = 20 + FheNot FheOp = 21 + // unused + // VerifyCiphertext FheOp = 22 + FheCast FheOp = 23 + // unused + TrivialEncrypt FheOp = 24 + FheIfThenElse FheOp = 25 + FheRand FheOp = 26 + FheRandBounded FheOp = 27 +) + +func (t FheUintType) String() string { + switch t { + case FheBool: + return "fheBool" + case FheUint4: + return "fheUint4" + case FheUint8: + return "fheUint8" + case FheUint16: + return "fheUint16" + case FheUint32: + return "fheUint32" + case FheUint64: + return "fheUint64" + case FheUint128: + return "fheUint128" + case FheUint160: + return "fheUint160" + case FheUint256: + return "fheUint256" + case FheEbytes64: + return "fheEbytes64" + case FheEbytes128: + return "fheEbytes128" + case FheEbytes256: + return "fheEbytes256" + default: + return "unknownFheUintType" + } +} + +func IsValidFheType(t byte) bool { + if uint8(t) < uint8(FheBool) || uint8(t) > uint8(FheEbytes256) { + return false + } + return true +} + +// Api to the storage of the host chain, must be passed +// from the EVM to us +type ChainStorageApi interface { + GetState(common.Address, common.Hash) common.Hash + SetState(common.Address, common.Hash, common.Hash) +} + +type ExecutorApi interface { + // Create a session for a single transaction to capture all fhe + // operations inside the state. We also schedule asynchronous + // compute in background to have operations inside + // the cache prepared to be inserted when commit block comes. + // We pass current block number to know at which + // block ciphertext should be materialized inside blockchain state. + CreateSession(blockNumber int64, storage ChainStorageApi) ExecutorSession + // Insert existing fhe operations to the state from inside the state + // storage queue. This should be called at the end of every block. + FlushFheResultsToState(blockNumber int64, storage ChainStorageApi) ExecutorSession +} + +type SegmentId int + +type ExtraData struct { + FheRandSeed [32]byte +} + +type ExecutorSession interface { + Execute(input []byte, ed ExtraData, output []byte) error + ContractAddress() common.Address + AclContractAddress() common.Address + NextSegment() SegmentId + InvalidateSinceSegment(id SegmentId) SegmentId + // After commit fhe computations will be put inside the queue + // to the blockchain state + Commit() error + GetStore() ComputationStore +} + +type ComputationStore interface { + InsertComputation(computation ComputationToInsert) error + InsertComputationBatch(ciphertexts []ComputationToInsert) error +} + +type ApiImpl struct { + address common.Address + aclContractAddress common.Address +} + +type SessionImpl struct { + address common.Address + aclContractAddress common.Address + isCommitted bool + sessionStore *SessionComputationStore + storage ChainStorageApi +} + +type ComputationOperand struct { + IsScalar bool + Handle []byte + FheUintType FheUintType +} + +type ComputationToInsert struct { + segmentId SegmentId + Operation FheOp + OutputHandle []byte + Operands []ComputationOperand + CommitBlockId int64 +} + +type SessionComputationStore struct { + underlyingCiphertextStore ComputationStore + insertedHandles map[string]int + invalidatedSegments map[SegmentId]bool + inserts []ComputationToInsert + isCommitted bool + segmentCount int + blockNumber int64 +} + +type EvmStorageComputationStore struct { + evmStorage ChainStorageApi +} + +type handleOffset struct { + segment int + index int +} + +type ciphertextSegment struct { + inserts []ComputationToInsert + invalidated bool +} + +func (coprocApi *ApiImpl) CreateSession(blockNumber int64, api ChainStorageApi) ExecutorSession { + return &SessionImpl{ + address: coprocApi.address, + aclContractAddress: coprocApi.aclContractAddress, + isCommitted: false, + sessionStore: &SessionComputationStore{ + isCommitted: false, + inserts: make([]ComputationToInsert, 0), + insertedHandles: make(map[string]int), + invalidatedSegments: make(map[SegmentId]bool), + segmentCount: 0, + blockNumber: blockNumber, + underlyingCiphertextStore: &EvmStorageComputationStore{evmStorage: api}, + }, + } +} + +func (coprocApi *ApiImpl) FlushFheResultsToState(blockNumber int64, api ChainStorageApi) ExecutorSession { + panic("TODO: implement flushing to the blockchain state") +} + +func (sessionApi *SessionImpl) Commit() error { + if sessionApi.isCommitted { + return errors.New("session is already comitted") + } + + err := sessionApi.sessionStore.Commit() + if err != nil { + return err + } + + return nil +} + +func (sessionApi *SessionImpl) Execute(dataOrig []byte, ed ExtraData, outputOrig []byte) error { + if len(dataOrig) < 4 { + return fmt.Errorf("input data must be at least 4 bytes for signature, got %d", len(dataOrig)) + } + + // make copies so we could assume array is immutable later + data := make([]byte, len(dataOrig)) + output := make([]byte, len(outputOrig)) + copy(data, dataOrig) + copy(output, outputOrig) + + signature := binary.BigEndian.Uint32(data[0:4]) + callData := data[4:] + + method, exists := signatureToFheLibMethod[signature] + if exists { + fmt.Printf("Executing captured operation %s%s\n", method.Name, method.ArgTypes) + if len(output) >= 32 { + // where to get output handle from? + outputHandle := output[0:32] + return method.runFunction(sessionApi, callData, ed, outputHandle) + } else { + return errors.New("no output data provided") + } + } else { + return fmt.Errorf("signature %d not recognized", signature) + } +} + +func (sessionApi *SessionImpl) NextSegment() SegmentId { + sessionApi.sessionStore.segmentCount = sessionApi.sessionStore.segmentCount + 1 + return SegmentId(sessionApi.sessionStore.segmentCount) +} + +func (sessionApi *SessionImpl) InvalidateSinceSegment(id SegmentId) SegmentId { + for idx := int(id); idx <= sessionApi.sessionStore.segmentCount; idx++ { + sessionApi.sessionStore.invalidatedSegments[SegmentId(idx)] = true + } + + return sessionApi.NextSegment() +} + +func (sessionApi *SessionImpl) ContractAddress() common.Address { + return sessionApi.address +} + +func (sessionApi *SessionImpl) AclContractAddress() common.Address { + return sessionApi.aclContractAddress +} + +func (sessionApi *SessionImpl) GetStore() ComputationStore { + return sessionApi.sessionStore +} + +func (dbApi *SessionComputationStore) InsertComputationBatch(computations []ComputationToInsert) error { + for _, comp := range computations { + dbApi.InsertComputation(comp) + } + + return nil +} + +func (dbApi *SessionComputationStore) InsertComputation(computation ComputationToInsert) error { + _, found := dbApi.insertedHandles[string(computation.OutputHandle)] + if !found { + // preserve insertion order + dbApi.insertedHandles[string(computation.OutputHandle)] = len(dbApi.inserts) + computation.segmentId = SegmentId(dbApi.segmentCount) + // hardcode late commit for now to be 5 blocks from current block + // in future we can implement dynamic compute, if user pays more + // he can have faster commit + computation.CommitBlockId = dbApi.blockNumber + 5 + dbApi.inserts = append(dbApi.inserts, computation) + } + + return nil +} + +func (dbApi *SessionComputationStore) Commit() error { + if dbApi.isCommitted { + return errors.New("session computation store already committed") + } + + dbApi.isCommitted = true + + finalInserts := make([]ComputationToInsert, 0, len(dbApi.inserts)) + for _, ct := range dbApi.inserts { + if !dbApi.invalidatedSegments[ct.segmentId] { + finalInserts = append(finalInserts, ct) + } + } + + fmt.Printf("Inserting %d computations into database\n", len(finalInserts)) + + err := dbApi.underlyingCiphertextStore.InsertComputationBatch(finalInserts) + if err != nil { + return err + } + + return nil +} + +func (dbApi *EvmStorageComputationStore) InsertComputationBatch(computations []ComputationToInsert) error { + for _, comp := range computations { + dbApi.InsertComputation(comp) + } + + return nil +} + +func (dbApi *EvmStorageComputationStore) InsertComputation(computation ComputationToInsert) error { + panic("TODO: implement insert computation to EVM") +} + +func (dbApi *EvmStorageComputationStore) Commit() error { + // no commit inside EVM state store + return nil +} + +func InitExecutor() (ExecutorApi, error) { + contractAddr, hasAddr := os.LookupEnv("FHEVM_CONTRACT_ADDRESS") + if !hasAddr { + return nil, errors.New("FHEVM_CIPHERTEXTS_DB is set but FHEVM_CONTRACT_ADDRESS is not set") + } + fhevmContractAddress := common.HexToAddress(contractAddr) + fmt.Printf("Coprocessor contract address: %s\n", fhevmContractAddress) + + aclContractAddressHex := os.Getenv("ACL_CONTRACT_ADDRESS") + if !common.IsHexAddress(aclContractAddressHex) { + return nil, fmt.Errorf("bad or missing ACL_CONTRACT_ADDRESS: %s", aclContractAddressHex) + } + aclContractAddress := common.HexToAddress(aclContractAddressHex) + + apiImpl := ApiImpl{ + address: fhevmContractAddress, + aclContractAddress: aclContractAddress, + } + + return &apiImpl, nil +} diff --git a/fhevm-engine/fhevm-go-native/fhevm/common.pb.go b/fhevm-engine/fhevm-go-native/fhevm/common.pb.go new file mode 100644 index 00000000..af9d5076 --- /dev/null +++ b/fhevm-engine/fhevm-go-native/fhevm/common.pb.go @@ -0,0 +1,232 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.3 +// source: common.proto + +package fhevm + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type FheOperation int32 + +const ( + FheOperation_FHE_ADD FheOperation = 0 + FheOperation_FHE_SUB FheOperation = 1 + FheOperation_FHE_MUL FheOperation = 2 + FheOperation_FHE_DIV FheOperation = 3 + FheOperation_FHE_REM FheOperation = 4 + FheOperation_FHE_BIT_AND FheOperation = 5 + FheOperation_FHE_BIT_OR FheOperation = 6 + FheOperation_FHE_BIT_XOR FheOperation = 7 + FheOperation_FHE_SHL FheOperation = 8 + FheOperation_FHE_SHR FheOperation = 9 + FheOperation_FHE_ROTL FheOperation = 10 + FheOperation_FHE_ROTR FheOperation = 11 + FheOperation_FHE_EQ FheOperation = 12 + FheOperation_FHE_NE FheOperation = 13 + FheOperation_FHE_GE FheOperation = 14 + FheOperation_FHE_GT FheOperation = 15 + FheOperation_FHE_LE FheOperation = 16 + FheOperation_FHE_LT FheOperation = 17 + FheOperation_FHE_MIN FheOperation = 18 + FheOperation_FHE_MAX FheOperation = 19 + FheOperation_FHE_NEG FheOperation = 20 + FheOperation_FHE_NOT FheOperation = 21 + FheOperation_FHE_CAST FheOperation = 23 + FheOperation_FHE_TRIVIAL_ENCRYPT FheOperation = 24 + FheOperation_FHE_IF_THEN_ELSE FheOperation = 25 + FheOperation_FHE_RAND FheOperation = 26 + FheOperation_FHE_RAND_BOUNDED FheOperation = 27 + FheOperation_FHE_GET_CIPHERTEXT FheOperation = 32 +) + +// Enum value maps for FheOperation. +var ( + FheOperation_name = map[int32]string{ + 0: "FHE_ADD", + 1: "FHE_SUB", + 2: "FHE_MUL", + 3: "FHE_DIV", + 4: "FHE_REM", + 5: "FHE_BIT_AND", + 6: "FHE_BIT_OR", + 7: "FHE_BIT_XOR", + 8: "FHE_SHL", + 9: "FHE_SHR", + 10: "FHE_ROTL", + 11: "FHE_ROTR", + 12: "FHE_EQ", + 13: "FHE_NE", + 14: "FHE_GE", + 15: "FHE_GT", + 16: "FHE_LE", + 17: "FHE_LT", + 18: "FHE_MIN", + 19: "FHE_MAX", + 20: "FHE_NEG", + 21: "FHE_NOT", + 23: "FHE_CAST", + 24: "FHE_TRIVIAL_ENCRYPT", + 25: "FHE_IF_THEN_ELSE", + 26: "FHE_RAND", + 27: "FHE_RAND_BOUNDED", + 32: "FHE_GET_CIPHERTEXT", + } + FheOperation_value = map[string]int32{ + "FHE_ADD": 0, + "FHE_SUB": 1, + "FHE_MUL": 2, + "FHE_DIV": 3, + "FHE_REM": 4, + "FHE_BIT_AND": 5, + "FHE_BIT_OR": 6, + "FHE_BIT_XOR": 7, + "FHE_SHL": 8, + "FHE_SHR": 9, + "FHE_ROTL": 10, + "FHE_ROTR": 11, + "FHE_EQ": 12, + "FHE_NE": 13, + "FHE_GE": 14, + "FHE_GT": 15, + "FHE_LE": 16, + "FHE_LT": 17, + "FHE_MIN": 18, + "FHE_MAX": 19, + "FHE_NEG": 20, + "FHE_NOT": 21, + "FHE_CAST": 23, + "FHE_TRIVIAL_ENCRYPT": 24, + "FHE_IF_THEN_ELSE": 25, + "FHE_RAND": 26, + "FHE_RAND_BOUNDED": 27, + "FHE_GET_CIPHERTEXT": 32, + } +) + +func (x FheOperation) Enum() *FheOperation { + p := new(FheOperation) + *p = x + return p +} + +func (x FheOperation) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (FheOperation) Descriptor() protoreflect.EnumDescriptor { + return file_common_proto_enumTypes[0].Descriptor() +} + +func (FheOperation) Type() protoreflect.EnumType { + return &file_common_proto_enumTypes[0] +} + +func (x FheOperation) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use FheOperation.Descriptor instead. +func (FheOperation) EnumDescriptor() ([]byte, []int) { + return file_common_proto_rawDescGZIP(), []int{0} +} + +var File_common_proto protoreflect.FileDescriptor + +var file_common_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, + 0x66, 0x68, 0x65, 0x76, 0x6d, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2a, 0xac, 0x03, 0x0a, + 0x0c, 0x46, 0x68, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, + 0x07, 0x46, 0x48, 0x45, 0x5f, 0x41, 0x44, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x48, + 0x45, 0x5f, 0x53, 0x55, 0x42, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x48, 0x45, 0x5f, 0x4d, + 0x55, 0x4c, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x48, 0x45, 0x5f, 0x44, 0x49, 0x56, 0x10, + 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x48, 0x45, 0x5f, 0x52, 0x45, 0x4d, 0x10, 0x04, 0x12, 0x0f, + 0x0a, 0x0b, 0x46, 0x48, 0x45, 0x5f, 0x42, 0x49, 0x54, 0x5f, 0x41, 0x4e, 0x44, 0x10, 0x05, 0x12, + 0x0e, 0x0a, 0x0a, 0x46, 0x48, 0x45, 0x5f, 0x42, 0x49, 0x54, 0x5f, 0x4f, 0x52, 0x10, 0x06, 0x12, + 0x0f, 0x0a, 0x0b, 0x46, 0x48, 0x45, 0x5f, 0x42, 0x49, 0x54, 0x5f, 0x58, 0x4f, 0x52, 0x10, 0x07, + 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x48, 0x45, 0x5f, 0x53, 0x48, 0x4c, 0x10, 0x08, 0x12, 0x0b, 0x0a, + 0x07, 0x46, 0x48, 0x45, 0x5f, 0x53, 0x48, 0x52, 0x10, 0x09, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x48, + 0x45, 0x5f, 0x52, 0x4f, 0x54, 0x4c, 0x10, 0x0a, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x48, 0x45, 0x5f, + 0x52, 0x4f, 0x54, 0x52, 0x10, 0x0b, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x48, 0x45, 0x5f, 0x45, 0x51, + 0x10, 0x0c, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x48, 0x45, 0x5f, 0x4e, 0x45, 0x10, 0x0d, 0x12, 0x0a, + 0x0a, 0x06, 0x46, 0x48, 0x45, 0x5f, 0x47, 0x45, 0x10, 0x0e, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x48, + 0x45, 0x5f, 0x47, 0x54, 0x10, 0x0f, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x48, 0x45, 0x5f, 0x4c, 0x45, + 0x10, 0x10, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x48, 0x45, 0x5f, 0x4c, 0x54, 0x10, 0x11, 0x12, 0x0b, + 0x0a, 0x07, 0x46, 0x48, 0x45, 0x5f, 0x4d, 0x49, 0x4e, 0x10, 0x12, 0x12, 0x0b, 0x0a, 0x07, 0x46, + 0x48, 0x45, 0x5f, 0x4d, 0x41, 0x58, 0x10, 0x13, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x48, 0x45, 0x5f, + 0x4e, 0x45, 0x47, 0x10, 0x14, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x48, 0x45, 0x5f, 0x4e, 0x4f, 0x54, + 0x10, 0x15, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x48, 0x45, 0x5f, 0x43, 0x41, 0x53, 0x54, 0x10, 0x17, + 0x12, 0x17, 0x0a, 0x13, 0x46, 0x48, 0x45, 0x5f, 0x54, 0x52, 0x49, 0x56, 0x49, 0x41, 0x4c, 0x5f, + 0x45, 0x4e, 0x43, 0x52, 0x59, 0x50, 0x54, 0x10, 0x18, 0x12, 0x14, 0x0a, 0x10, 0x46, 0x48, 0x45, + 0x5f, 0x49, 0x46, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x45, 0x4c, 0x53, 0x45, 0x10, 0x19, 0x12, + 0x0c, 0x0a, 0x08, 0x46, 0x48, 0x45, 0x5f, 0x52, 0x41, 0x4e, 0x44, 0x10, 0x1a, 0x12, 0x14, 0x0a, + 0x10, 0x46, 0x48, 0x45, 0x5f, 0x52, 0x41, 0x4e, 0x44, 0x5f, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x45, + 0x44, 0x10, 0x1b, 0x12, 0x16, 0x0a, 0x12, 0x46, 0x48, 0x45, 0x5f, 0x47, 0x45, 0x54, 0x5f, 0x43, + 0x49, 0x50, 0x48, 0x45, 0x52, 0x54, 0x45, 0x58, 0x54, 0x10, 0x20, 0x42, 0x2d, 0x0a, 0x13, 0x69, + 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x66, 0x68, 0x65, 0x76, 0x6d, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x42, 0x0b, 0x46, 0x68, 0x65, 0x76, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x50, + 0x01, 0x5a, 0x07, 0x2e, 0x2f, 0x66, 0x68, 0x65, 0x76, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_common_proto_rawDescOnce sync.Once + file_common_proto_rawDescData = file_common_proto_rawDesc +) + +func file_common_proto_rawDescGZIP() []byte { + file_common_proto_rawDescOnce.Do(func() { + file_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_proto_rawDescData) + }) + return file_common_proto_rawDescData +} + +var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_common_proto_goTypes = []any{ + (FheOperation)(0), // 0: fhevm.common.FheOperation +} +var file_common_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_common_proto_init() } +func file_common_proto_init() { + if File_common_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_common_proto_rawDesc, + NumEnums: 1, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_common_proto_goTypes, + DependencyIndexes: file_common_proto_depIdxs, + EnumInfos: file_common_proto_enumTypes, + }.Build() + File_common_proto = out.File + file_common_proto_rawDesc = nil + file_common_proto_goTypes = nil + file_common_proto_depIdxs = nil +} diff --git a/fhevm-engine/fhevm-go-native/fhevm/fhelib.go b/fhevm-engine/fhevm-go-native/fhevm/fhelib.go new file mode 100644 index 00000000..fd523742 --- /dev/null +++ b/fhevm-engine/fhevm-go-native/fhevm/fhelib.go @@ -0,0 +1,205 @@ +package fhevm + +import ( + "encoding/binary" + "fmt" + + "github.com/ethereum/go-ethereum/crypto" +) + +type FheLibMethod struct { + // Name of the fhelib function + Name string + // types of the arguments that the fhelib function take. format is "(type1,type2...)" (e.g "(uint256,bytes1)") + ArgTypes string + runFunction func(sess ExecutorSession, input []byte, ed ExtraData, outputHandle []byte) error + ScalarSupport bool + NonScalarDisabled bool +} + +var signatureToFheLibMethod = map[uint32]*FheLibMethod{} + +func FheLibMethods() []*FheLibMethod { + return []*FheLibMethod{ + { + Name: "fheAdd", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheAddRun, + ScalarSupport: true, + }, + { + Name: "fheSub", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheSubRun, + ScalarSupport: true, + }, + { + Name: "fheMul", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheMulRun, + ScalarSupport: true, + }, + { + Name: "fheRem", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheRemRun, + ScalarSupport: true, + NonScalarDisabled: true, + }, + { + Name: "fheBitAnd", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheBitAndRun, + ScalarSupport: false, + }, + { + Name: "fheBitOr", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheBitOrRun, + ScalarSupport: false, + }, + { + Name: "fheBitXor", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheBitXorRun, + ScalarSupport: false, + }, + { + Name: "fheShl", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheShlRun, + ScalarSupport: true, + }, + { + Name: "fheShr", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheShrRun, + ScalarSupport: true, + }, + { + Name: "fheRotl", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheRotlRun, + ScalarSupport: true, + }, + { + Name: "fheRotr", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheRotrRun, + ScalarSupport: true, + }, + { + Name: "fheEq", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheEqRun, + ScalarSupport: true, + }, + { + Name: "fheNe", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheNeRun, + ScalarSupport: true, + }, + { + Name: "fheEq", + ArgTypes: "(uint256,bytes,bytes1)", + runFunction: fheEqBytesRun, + ScalarSupport: true, + }, + { + Name: "fheNe", + ArgTypes: "(uint256,bytes,bytes1)", + runFunction: fheNeBytesRun, + ScalarSupport: true, + }, + { + Name: "fheGe", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheGeRun, + ScalarSupport: true, + }, + { + Name: "fheGt", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheGtRun, + ScalarSupport: true, + }, + { + Name: "fheLe", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheLeRun, + ScalarSupport: true, + }, + { + Name: "fheLt", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheLtRun, + ScalarSupport: true, + }, + { + Name: "fheMin", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheMinRun, + ScalarSupport: true, + }, + { + Name: "fheMax", + ArgTypes: "(uint256,uint256,bytes1)", + runFunction: fheMaxRun, + ScalarSupport: true, + }, + { + Name: "fheNeg", + ArgTypes: "(uint256)", + runFunction: fheNegRun, + }, + { + Name: "fheNot", + ArgTypes: "(uint256)", + runFunction: fheNotRun, + }, + { + Name: "fheIfThenElse", + ArgTypes: "(uint256,uint256,uint256)", + runFunction: fheIfThenElseRun, + }, + { + Name: "cast", + ArgTypes: "(uint256,bytes1)", + runFunction: castRun, + }, + { + Name: "fheRand", + ArgTypes: "(bytes1)", + runFunction: fheRandRun, + }, + { + Name: "fheRandBounded", + ArgTypes: "(uint256,bytes1)", + runFunction: fheRandBoundedRun, + }, + { + Name: "trivialEncrypt", + ArgTypes: "(uint256,bytes1)", + runFunction: trivialEncryptRun, + }, + { + Name: "trivialEncrypt", + ArgTypes: "(bytes,bytes1)", + runFunction: trivialEncryptBytesRun, + }, + } +} + +func MakeKeccakSignature(input string) uint32 { + return binary.BigEndian.Uint32(crypto.Keccak256([]byte(input))[0:4]) +} + +func init() { + // create the mapping for every available fhelib method + for _, method := range FheLibMethods() { + signature := fmt.Sprintf("%s%s", method.Name, method.ArgTypes) + signatureNum := MakeKeccakSignature(signature) + signatureToFheLibMethod[signatureNum] = method + } +} diff --git a/fhevm-engine/fhevm-go-native/fhevm/fhelib_ops.go b/fhevm-engine/fhevm-go-native/fhevm/fhelib_ops.go new file mode 100644 index 00000000..8261ff75 --- /dev/null +++ b/fhevm-engine/fhevm-go-native/fhevm/fhelib_ops.go @@ -0,0 +1,1690 @@ +package fhevm + +import ( + "errors" + "fmt" + "math/big" +) + +func handleType(handle []byte) FheUintType { + return FheUintType(handle[30]) +} + +func fheAddRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheAdd + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + + if err != nil { + return err + } + return nil + } +} + +func fheSubRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheSub + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + + if err != nil { + return err + } + return nil + } +} + +func fheMulRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheMul + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + + if err != nil { + return err + } + return nil + } +} + +func fheRemRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheRem + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } +} + +func fheBitAndRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheBitAnd + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + return errors.New("scalar fheBitAnd is not supported") + } +} + +func fheBitOrRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheBitOr + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + return errors.New("scalar fheBitOr is not supported") + } +} + +func fheBitXorRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheBitXor + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + return errors.New("scalar fheBitXor is not supported") + } +} + +func fheShlRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheShl + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } +} + +func fheShrRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheShr + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } +} + +func fheRotlRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheRotl + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } +} + +func fheRotrRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheRotr + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } +} + +func fheEqRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheEq + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } +} + +func fheNeRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheNe + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } +} + +func fheEqBytesRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + // uint256,bytes,bytes1 + if len(unslicedInput) < 128 { + return fmt.Errorf("expected at least 128 bytes as input, got %d", len(unslicedInput)) + } + lhs := unslicedInput[0:32] + lhsByteOffset := unslicedInput[32:64] + isScalar := unslicedInput[64] > 0 + + lhsByteOffsetNum := big.NewInt(0) + lhsByteOffsetNum.SetBytes(lhsByteOffset) + + offsetEnd := lhsByteOffsetNum.Uint64() + 32 + if offsetEnd > uint64(len(unslicedInput)) { + return fmt.Errorf("byte array offset out of bounds, got %d, input length %d", offsetEnd, len(unslicedInput)) + } + + scalarOperandLengthBytes := unslicedInput[lhsByteOffsetNum.Uint64() : lhsByteOffsetNum.Uint64()+32] + scalarOperandLength := big.NewInt(0) + scalarOperandLength.SetBytes(scalarOperandLengthBytes) + + byteArrayEnd := offsetEnd + scalarOperandLength.Uint64() + if byteArrayEnd > uint64(len(unslicedInput)) { + return fmt.Errorf("byte array offset out of bounds, got %d, input length %d", byteArrayEnd, len(unslicedInput)) + } + + rhs := unslicedInput[offsetEnd : offsetEnd+scalarOperandLength.Uint64()] + + operation := FheEq + if isScalar { + err := sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } else { + return errors.New("only scalar is operand supported for fheEq(uint256,bytes,bytes1) overload") + } +} + +func fheNeBytesRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + // uint256,bytes,bytes1 + if len(unslicedInput) < 128 { + return fmt.Errorf("expected at least 128 bytes as input, got %d", len(unslicedInput)) + } + lhs := unslicedInput[0:32] + lhsByteOffset := unslicedInput[32:64] + isScalar := unslicedInput[64] > 0 + + lhsByteOffsetNum := big.NewInt(0) + lhsByteOffsetNum.SetBytes(lhsByteOffset) + + offsetEnd := lhsByteOffsetNum.Uint64() + 32 + if offsetEnd > uint64(len(unslicedInput)) { + return fmt.Errorf("byte array offset out of bounds, got %d, input length %d", offsetEnd, len(unslicedInput)) + } + + scalarOperandLengthBytes := unslicedInput[lhsByteOffsetNum.Uint64() : lhsByteOffsetNum.Uint64()+32] + scalarOperandLength := big.NewInt(0) + scalarOperandLength.SetBytes(scalarOperandLengthBytes) + + byteArrayEnd := offsetEnd + scalarOperandLength.Uint64() + if byteArrayEnd > uint64(len(unslicedInput)) { + return fmt.Errorf("byte array offset out of bounds, got %d, input length %d", byteArrayEnd, len(unslicedInput)) + } + + rhs := unslicedInput[offsetEnd : offsetEnd+scalarOperandLength.Uint64()] + + operation := FheNe + if isScalar { + err := sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } else { + return errors.New("only scalar is operand supported for fheNe(uint256,bytes,bytes1) overload") + } +} + +func fheGeRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheGe + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } +} + +func fheGtRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheGt + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } +} + +func fheLeRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheLe + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } +} + +func fheLtRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheLt + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } +} + +func fheMinRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheMin + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } +} + +func fheMaxRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 65 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:65] + + isScalar, err := isScalarOp(input) + if err != nil { + return err + } + + operation := FheMax + if !isScalar { + lhs, rhs, err := get2FheOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + + return nil + } else { + lhs, rhs, err := getScalarOperands(sess, input) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: lhs, + FheUintType: handleType(lhs), + IsScalar: false, + }, + { + Handle: rhs, + FheUintType: handleType(rhs), + IsScalar: isScalar, + }, + }, + }) + if err != nil { + return err + } + return nil + } +} + +func fheNegRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 32 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:32] + + ct, err := getSingleFheOperand(sess, input) + if err != nil { + return err + } + + operation := FheNeg + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: ct, + FheUintType: handleType(ct), + IsScalar: false, + }, + }, + }) + if err != nil { + return err + } + + return nil +} + +func fheNotRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 32 { + return fmt.Errorf("expected at least 65 bytes as input, got %d", len(unslicedInput)) + } + input := unslicedInput[0:32] + + ct, err := getSingleFheOperand(sess, input) + if err != nil { + return err + } + + operation := FheNot + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: ct, + FheUintType: handleType(ct), + IsScalar: false, + }, + }, + }) + if err != nil { + return err + } + + return nil +} + +func fheIfThenElseRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 96 { + return fmt.Errorf("expected at least 96 bytes as input, got %d", len(unslicedInput)) + } + inputs := unslicedInput[0:96] + + first, second, third, err := getThreeFheOperands(sess, inputs) + if err != nil { + return err + } + + if handleType(second) != handleType(third) { + return errors.New("fheIfThenElse second argument type doesn't match third argument type") + } + + operation := FheIfThenElse + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: first, + FheUintType: handleType(first), + IsScalar: false, + }, + { + Handle: second, + FheUintType: handleType(second), + IsScalar: false, + }, + { + Handle: third, + FheUintType: handleType(third), + IsScalar: false, + }, + }, + }) + if err != nil { + return err + } + + return nil +} + +func castRun(sess ExecutorSession, unslicedInput []byte, _ ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 33 { + return fmt.Errorf("expected at least 33 bytes as input, got %d", len(unslicedInput)) + } + + inputCt := unslicedInput[0:32] + toType := unslicedInput[32] + + operation := FheCast + if !IsValidFheType(toType) { + return fmt.Errorf("invalid fhe type byte: %d", toType) + } + + sourceCt, err := getSingleFheOperand(sess, inputCt) + if err != nil { + return err + } + + err = sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: operation, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: sourceCt, + FheUintType: handleType(sourceCt), + IsScalar: false, + }, + { + Handle: []byte{toType}, + FheUintType: FheUint8, + IsScalar: true, + }, + }, + }) + if err != nil { + return err + } + + return nil +} + +func fheRandRun(sess ExecutorSession, unslicedInput []byte, ed ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 1 { + return fmt.Errorf("expected at least 1 bytes as input, got %d", len(unslicedInput)) + } + + resultTypeByte := unslicedInput[0] + if !IsValidFheType(resultTypeByte) { + return fmt.Errorf("invalid fhe type byte: %d", resultTypeByte) + } + + err := sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: FheRand, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: ed.FheRandSeed[:], + FheUintType: FheUint256, + IsScalar: true, + }, + { + Handle: []byte{resultTypeByte}, + FheUintType: FheUint8, + IsScalar: true, + }, + }, + }) + if err != nil { + return err + } + + return nil +} + +func fheRandBoundedRun(sess ExecutorSession, unslicedInput []byte, ed ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 33 { + return fmt.Errorf("expected at least 1 bytes as input, got %d", len(unslicedInput)) + } + + resultTypeByte := unslicedInput[32] + if !IsValidFheType(resultTypeByte) { + return fmt.Errorf("invalid fhe type byte: %d", resultTypeByte) + } + + upperBound := big.NewInt(0) + upperBound.SetBytes(unslicedInput[0:32]) + + err := sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: FheRandBounded, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: ed.FheRandSeed[:], + FheUintType: FheUint256, + IsScalar: true, + }, + { + Handle: unslicedInput[0:32], + FheUintType: FheUint256, + IsScalar: true, + }, + { + Handle: []byte{resultTypeByte}, + FheUintType: FheUint8, + IsScalar: true, + }, + }, + }) + if err != nil { + return err + } + + return nil +} + +func trivialEncryptRun(sess ExecutorSession, unslicedInput []byte, ed ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 33 { + return fmt.Errorf("expected at least 1 bytes as input, got %d", len(unslicedInput)) + } + + resultTypeByte := unslicedInput[32] + if !IsValidFheType(resultTypeByte) { + return fmt.Errorf("invalid fhe type byte: %d", resultTypeByte) + } + + err := sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: TrivialEncrypt, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: unslicedInput[0:32], + FheUintType: FheUint256, + IsScalar: true, + }, + { + Handle: []byte{resultTypeByte}, + FheUintType: FheUint8, + IsScalar: true, + }, + }, + }) + if err != nil { + return err + } + + return nil +} + +func trivialEncryptBytesRun(sess ExecutorSession, unslicedInput []byte, ed ExtraData, outputHandle []byte) error { + if len(unslicedInput) < 96 { + return fmt.Errorf("expected at least 96 bytes as input, got %d", len(unslicedInput)) + } + + resultTypeByte := unslicedInput[32] + + offsetBigNum := big.NewInt(0) + offsetBigNum.SetBytes(unslicedInput[0:32]) + startOfByteArray := offsetBigNum.Uint64() + if startOfByteArray+32 > uint64(len(unslicedInput)) { + return fmt.Errorf("byte array offset out of bounds, got %d, input length %d", startOfByteArray+32, len(unslicedInput)) + } + byteArrayLength := big.NewInt(0) + byteArrayLength.SetBytes(unslicedInput[startOfByteArray : startOfByteArray+32]) + + if startOfByteArray+32+byteArrayLength.Uint64() > uint64(len(unslicedInput)) { + return fmt.Errorf("byte array offset out of bounds, got %d, input length %d", startOfByteArray+32+byteArrayLength.Uint64(), len(unslicedInput)) + } + + // array could be empty + rawCiphertextByteSlice := []byte{} + if byteArrayLength.Uint64() > 0 { + rawCiphertextByteSlice = unslicedInput[startOfByteArray+32 : startOfByteArray+32+byteArrayLength.Uint64()] + } + + if !IsValidFheType(resultTypeByte) { + return fmt.Errorf("invalid fhe type byte: %d", resultTypeByte) + } + + err := sess.GetStore().InsertComputation(ComputationToInsert{ + Operation: TrivialEncrypt, + OutputHandle: outputHandle, + Operands: []ComputationOperand{ + { + Handle: rawCiphertextByteSlice, + FheUintType: FheUint256, + IsScalar: true, + }, + { + Handle: []byte{resultTypeByte}, + FheUintType: FheUint8, + IsScalar: true, + }, + }, + }) + if err != nil { + return err + } + + return nil +} + +func isScalarOp(input []byte) (bool, error) { + if len(input) != 65 { + return false, errors.New("input needs to contain two 256-bit sized values and 1 8-bit value") + } + isScalar := (input[64] == 1) + return isScalar, nil +} + +func get2FheOperands(sess ExecutorSession, input []byte) (lhs []byte, rhs []byte, err error) { + if len(input) != 65 { + return nil, nil, errors.New("input needs to contain two 256-bit sized values and 1 8-bit value") + } + return input[0:32], input[32:64], nil +} + +func getSingleFheOperand(sess ExecutorSession, input []byte) (operand []byte, err error) { + if len(input) != 32 { + return nil, errors.New("input needs to contain one 256-bit sized value") + } + return input[0:32], nil +} + +func getScalarOperands(sess ExecutorSession, input []byte) (lhs []byte, rhs []byte, err error) { + if len(input) != 65 { + return nil, nil, errors.New("input needs to contain two 256-bit sized values and 1 8-bit value") + } + return input[0:32], input[32:64], nil +} + +func getThreeFheOperands(sess ExecutorSession, input []byte) (first []byte, second []byte, third []byte, err error) { + if len(input) != 96 { + return nil, nil, nil, errors.New("input needs to contain three 256-bit sized values") + } + + return input[0:32], input[32:64], input[64:96], nil +} diff --git a/fhevm-engine/fhevm-go-native/go.mod b/fhevm-engine/fhevm-go-native/go.mod new file mode 100644 index 00000000..40a9469e --- /dev/null +++ b/fhevm-engine/fhevm-go-native/go.mod @@ -0,0 +1,21 @@ +module github.com/zama-ai/fhevm-backend/fhevm-engine/fhevm-go-native + +go 1.21 + +require ( + github.com/ethereum/go-ethereum v1.13.15 + github.com/mattn/go-sqlite3 v1.14.22 + google.golang.org/grpc v1.66.0 + google.golang.org/protobuf v1.34.1 +) + +require ( + github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/holiman/uint256 v1.2.4 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect +) diff --git a/fhevm-engine/fhevm-go-native/go.sum b/fhevm-engine/fhevm-go-native/go.sum new file mode 100644 index 00000000..96245181 --- /dev/null +++ b/fhevm-engine/fhevm-go-native/go.sum @@ -0,0 +1,32 @@ +github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/ethereum/go-ethereum v1.13.15 h1:U7sSGYGo4SPjP6iNIifNoyIAiNjrmQkz6EwQG+/EZWo= +github.com/ethereum/go-ethereum v1.13.15/go.mod h1:TN8ZiHrdJwSe8Cb6x+p0hs5CxhJZPbqB7hHkaUXcmIU= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= From 64c150eac6b924c72fc84d423d417c152a07126a Mon Sep 17 00:00:00 2001 From: David Kazlauskas Date: Wed, 6 Nov 2024 08:52:20 +0200 Subject: [PATCH 2/3] feat: implement ciphertext storage queue --- fhevm-engine/fhevm-go-native/fhevm/api.go | 132 ++++++++++++++++++---- 1 file changed, 111 insertions(+), 21 deletions(-) diff --git a/fhevm-engine/fhevm-go-native/fhevm/api.go b/fhevm-engine/fhevm-go-native/fhevm/api.go index f185da23..e3014d7f 100644 --- a/fhevm-engine/fhevm-go-native/fhevm/api.go +++ b/fhevm-engine/fhevm-go-native/fhevm/api.go @@ -4,9 +4,12 @@ import ( "encoding/binary" "errors" "fmt" + "math/big" "os" + "sort" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" _ "github.com/mattn/go-sqlite3" ) @@ -145,8 +148,9 @@ type ComputationStore interface { } type ApiImpl struct { - address common.Address - aclContractAddress common.Address + address common.Address + aclContractAddress common.Address + contractStorageAddress common.Address } type SessionImpl struct { @@ -182,7 +186,9 @@ type SessionComputationStore struct { } type EvmStorageComputationStore struct { - evmStorage ChainStorageApi + evmStorage ChainStorageApi + currentBlockNumber int64 + contractStorageAddress common.Address } type handleOffset struct { @@ -195,27 +201,27 @@ type ciphertextSegment struct { invalidated bool } -func (coprocApi *ApiImpl) CreateSession(blockNumber int64, api ChainStorageApi) ExecutorSession { +func (executorApi *ApiImpl) CreateSession(blockNumber int64, api ChainStorageApi) ExecutorSession { return &SessionImpl{ - address: coprocApi.address, - aclContractAddress: coprocApi.aclContractAddress, + address: executorApi.address, + aclContractAddress: executorApi.aclContractAddress, isCommitted: false, sessionStore: &SessionComputationStore{ - isCommitted: false, - inserts: make([]ComputationToInsert, 0), - insertedHandles: make(map[string]int), - invalidatedSegments: make(map[SegmentId]bool), - segmentCount: 0, - blockNumber: blockNumber, - underlyingCiphertextStore: &EvmStorageComputationStore{evmStorage: api}, + isCommitted: false, + inserts: make([]ComputationToInsert, 0), + insertedHandles: make(map[string]int), + invalidatedSegments: make(map[SegmentId]bool), + segmentCount: 0, + blockNumber: blockNumber, + underlyingCiphertextStore: &EvmStorageComputationStore{ + evmStorage: api, + contractStorageAddress: executorApi.contractStorageAddress, + currentBlockNumber: blockNumber, + }, }, } } -func (coprocApi *ApiImpl) FlushFheResultsToState(blockNumber int64, api ChainStorageApi) ExecutorSession { - panic("TODO: implement flushing to the blockchain state") -} - func (sessionApi *SessionImpl) Commit() error { if sessionApi.isCommitted { return errors.New("session is already comitted") @@ -331,16 +337,97 @@ func (dbApi *SessionComputationStore) Commit() error { return nil } +func blockNumberToQueueItemCountAddress(blockNumber int64) common.Hash { + return common.BigToHash(big.NewInt(blockNumber)) +} + +func blockQueueStorageAddress(blockNumber int64, ctNumber *big.Int) common.Hash { + toHash := common.BigToHash(big.NewInt(blockNumber)) + initialOffsetHash := crypto.Keccak256(toHash[:]) + res := big.NewInt(0) + res.SetBytes(initialOffsetHash) + return common.BytesToHash(res.Add(res, ctNumber).Bytes()) +} + func (dbApi *EvmStorageComputationStore) InsertComputationBatch(computations []ComputationToInsert) error { + // storage layout for the late commit queue: + // + // blockNumber address - stores the amount of ciphertexts in the queue in the block, + // block number is directly converted to storage address which has count for the queue + // blockNumber represents when ciphertexts are to be commited to the storage + // and queue should be cleaned up after the block passes + // + // queue address - hash block number converted to 32 big endian bytes + // this address contains all the handles to be computed in this block + // example: + // keccak256(blockNumber) + 0 - 1st ciphertext handle in the block + // keccak256(blockNumber) + 1 - 2nd ciphertext handle in the block + // keccak256(blockNumber) + 2 - 3rd ciphertext handle in the block + + // prepare for dynamic evaluation. Say, users want to evaluate ciphertext + // in 5 or 10 blocks from current block, depending on how much they pay. + // We create buckets, how many blocks in the future user wants + // his ciphertexts to be evaluated + buckets := make(map[int64][]*ComputationToInsert) + // index the buckets for _, comp := range computations { - dbApi.InsertComputation(comp) + if buckets[comp.CommitBlockId] == nil { + buckets[comp.CommitBlockId] = make([]*ComputationToInsert, 0) + } + buckets[comp.CommitBlockId] = append(buckets[comp.CommitBlockId], &comp) + } + // collect all their keys and sort because golang doesn't traverse map + // in deterministic order + allKeys := make([]int, 0) + for k, _ := range buckets { + allKeys = append(allKeys, int(k)) + } + sort.Ints(allKeys) + + // iterate all buckets and put items to their appropriate block queues + for _, key := range allKeys { + queueBlockNumber := int64(key) + bucket := buckets[queueBlockNumber] + + countAddress := blockNumberToQueueItemCountAddress(queueBlockNumber) + ciphertextsInBlock := dbApi.evmStorage.GetState(dbApi.contractStorageAddress, countAddress).Big() + one := big.NewInt(1) + + for _, comp := range bucket { + handleOutputAddress := blockQueueStorageAddress(queueBlockNumber, ciphertextsInBlock) + ciphertextsInBlock = ciphertextsInBlock.Add(ciphertextsInBlock, one) + dbApi.evmStorage.SetState(dbApi.contractStorageAddress, handleOutputAddress, common.Hash(comp.OutputHandle)) + } + + // set updated count back + dbApi.evmStorage.SetState(dbApi.contractStorageAddress, countAddress, common.BigToHash(ciphertextsInBlock)) } return nil } +func (executorApi *ApiImpl) FlushFheResultsToState(blockNumber int64, api ChainStorageApi) ExecutorSession { + // cleanup the queue for the block number + countAddress := blockNumberToQueueItemCountAddress(blockNumber) + ciphertextsInBlock := api.GetState(executorApi.contractStorageAddress, countAddress).Big() + ctCount := ciphertextsInBlock.Int64() + zero := common.BigToHash(big.NewInt(0)) + + // zero out queue ciphertexts + for i := 0; i < int(ctCount); i++ { + ctNumber := big.NewInt(int64(i)) + ctAddr := blockQueueStorageAddress(blockNumber, ctNumber) + api.SetState(executorApi.contractStorageAddress, ctAddr, zero) + } + + // set 0 as count + api.SetState(executorApi.contractStorageAddress, countAddress, zero) + + panic("TODO: implement flushing of ciphertext data to the blockchain state") +} + func (dbApi *EvmStorageComputationStore) InsertComputation(computation ComputationToInsert) error { - panic("TODO: implement insert computation to EVM") + return dbApi.InsertComputationBatch([]ComputationToInsert{computation}) } func (dbApi *EvmStorageComputationStore) Commit() error { @@ -362,9 +449,12 @@ func InitExecutor() (ExecutorApi, error) { } aclContractAddress := common.HexToAddress(aclContractAddressHex) + // pick hardcoded value in the beginning, we can change later + storageAddress := common.HexToAddress("0x0000000000000000000000000000000000000070") apiImpl := ApiImpl{ - address: fhevmContractAddress, - aclContractAddress: aclContractAddress, + address: fhevmContractAddress, + aclContractAddress: aclContractAddress, + contractStorageAddress: storageAddress, } return &apiImpl, nil From 66f3eba9e9b58263cfb91535379fce01617810e0 Mon Sep 17 00:00:00 2001 From: David Kazlauskas Date: Mon, 11 Nov 2024 08:51:19 +0200 Subject: [PATCH 3/3] feat: implement computations storage for native --- fhevm-engine/fhevm-go-native/fhevm/api.go | 129 ++++++++++++++++++++-- 1 file changed, 117 insertions(+), 12 deletions(-) diff --git a/fhevm-engine/fhevm-go-native/fhevm/api.go b/fhevm-engine/fhevm-go-native/fhevm/api.go index e3014d7f..8aa097a2 100644 --- a/fhevm-engine/fhevm-go-native/fhevm/api.go +++ b/fhevm-engine/fhevm-go-native/fhevm/api.go @@ -341,12 +341,91 @@ func blockNumberToQueueItemCountAddress(blockNumber int64) common.Hash { return common.BigToHash(big.NewInt(blockNumber)) } -func blockQueueStorageAddress(blockNumber int64, ctNumber *big.Int) common.Hash { +func blockQueueStorageLayout(blockNumber int64, ctNumber int64) NativeQueueAddressLayout { toHash := common.BigToHash(big.NewInt(blockNumber)) + // main storage prefix + // number is on the right bitwise, should never overwrite storage prefix + // because block numbers are much less than 256 bit numbers + copy(toHash[:], "main") initialOffsetHash := crypto.Keccak256(toHash[:]) - res := big.NewInt(0) + copy(toHash[:], "bigscalar") + bigScalarOffsetHash := crypto.Keccak256(toHash[:]) + bigScalarNum := new(big.Int) + bigScalarNum.SetBytes(bigScalarOffsetHash) + // 2048 bit is maximum supported number + // one 2048 bit contains 8 256 bit words + bigScalarNum.Add(bigScalarNum, big.NewInt(ctNumber*8)) + + one := big.NewInt(1) + res := new(big.Int) res.SetBytes(initialOffsetHash) - return common.BytesToHash(res.Add(res, ctNumber).Bytes()) + // four 256 bit words, calculate offset + // according to ciphertext number + res.Add(res, big.NewInt(ctNumber*4)) + metadata := common.BytesToHash(res.Bytes()) + res.Add(res, one) + outputHandle := common.BytesToHash(res.Bytes()) + res.Add(res, one) + firstOperand := common.BytesToHash(res.Bytes()) + res.Add(res, one) + secondOperand := common.BytesToHash(res.Bytes()) + res.Add(res, one) + return NativeQueueAddressLayout{ + metadata: metadata, + outputHandle: outputHandle, + firstOperand: firstOperand, + secondOperand: secondOperand, + bigScalarOperand: common.Hash(bigScalarOffsetHash), + } +} + +func computationMetadata(comp ComputationToInsert) common.Hash { + var res common.Hash + + // operation type + res[0] = byte(comp.Operation) + for _, op := range comp.Operands { + if op.IsScalar { + // set scalar byte + res[1] = 1 + if op.FheUintType > FheUint256 { + // set big scalar byte, we'll need big scalar register + // for this computation + res[2] = 1 + } + } + } + + return res +} + +func bytesToMetadata(input common.Hash) ComputationMetadata { + return ComputationMetadata{ + Operation: FheOp(input[0]), + IsScalar: input[1] > 0, + IsBigScalar: input[2] > 0, + } +} + +type ComputationMetadata struct { + Operation FheOp + IsScalar bool + IsBigScalar bool +} + +type NativeQueueAddressLayout struct { + // metadata about the computation + // like operation type, is scalar etc + metadata common.Hash + // output handle of the computation + outputHandle common.Hash + // first operand to the computation + firstOperand common.Hash + // second operand to the computation + secondOperand common.Hash + // if operand size is more than 256 bits + // it is stored in special place here + bigScalarOperand common.Hash } func (dbApi *EvmStorageComputationStore) InsertComputationBatch(computations []ComputationToInsert) error { @@ -357,12 +436,16 @@ func (dbApi *EvmStorageComputationStore) InsertComputationBatch(computations []C // blockNumber represents when ciphertexts are to be commited to the storage // and queue should be cleaned up after the block passes // - // queue address - hash block number converted to 32 big endian bytes + // queue address - hash 'main' prefix and block number converted to 32 big endian bytes // this address contains all the handles to be computed in this block // example: - // keccak256(blockNumber) + 0 - 1st ciphertext handle in the block - // keccak256(blockNumber) + 1 - 2nd ciphertext handle in the block - // keccak256(blockNumber) + 2 - 3rd ciphertext handle in the block + // keccak256('main' .. blockNumber) + 0 - operation metadata, is extended scalar operand needed + // keccak256('main' .. blockNumber) + 1 - output ciphertext handle + // keccak256('main' .. blockNumber) + 2 - first ciphertext argument + // keccak256('main' .. blockNumber) + 3 - second ciphertext argument + // + // if scalar operand is bigger than 256 bit number, we use special + // bigscalar address // prepare for dynamic evaluation. Say, users want to evaluate ciphertext // in 5 or 10 blocks from current block, depending on how much they pay. @@ -393,10 +476,18 @@ func (dbApi *EvmStorageComputationStore) InsertComputationBatch(computations []C ciphertextsInBlock := dbApi.evmStorage.GetState(dbApi.contractStorageAddress, countAddress).Big() one := big.NewInt(1) - for _, comp := range bucket { - handleOutputAddress := blockQueueStorageAddress(queueBlockNumber, ciphertextsInBlock) + for idx, comp := range bucket { + layout := blockQueueStorageLayout(queueBlockNumber, idx) ciphertextsInBlock = ciphertextsInBlock.Add(ciphertextsInBlock, one) - dbApi.evmStorage.SetState(dbApi.contractStorageAddress, handleOutputAddress, common.Hash(comp.OutputHandle)) + metadata := computationMetadata(*comp) + dbApi.evmStorage.SetState(dbApi.contractStorageAddress, layout.metadata, metadata) + dbApi.evmStorage.SetState(dbApi.contractStorageAddress, layout.outputHandle, common.Hash(comp.OutputHandle)) + if len(comp.Operands) > 0 { + dbApi.evmStorage.SetState(dbApi.contractStorageAddress, layout.firstOperand, common.Hash(comp.Operands[0].Handle)) + } + if len(comp.Operands) > 1 { + dbApi.evmStorage.SetState(dbApi.contractStorageAddress, layout.secondOperand, common.Hash(comp.Operands[1].Handle)) + } } // set updated count back @@ -412,12 +503,26 @@ func (executorApi *ApiImpl) FlushFheResultsToState(blockNumber int64, api ChainS ciphertextsInBlock := api.GetState(executorApi.contractStorageAddress, countAddress).Big() ctCount := ciphertextsInBlock.Int64() zero := common.BigToHash(big.NewInt(0)) + one := big.NewInt(1) // zero out queue ciphertexts for i := 0; i < int(ctCount); i++ { ctNumber := big.NewInt(int64(i)) - ctAddr := blockQueueStorageAddress(blockNumber, ctNumber) - api.SetState(executorApi.contractStorageAddress, ctAddr, zero) + ctAddr := blockQueueStorageLayout(blockNumber, ctNumber) + metadata := bytesToMetadata(api.GetState(executorApi.contractStorageAddress, ctAddr.metadata)) + api.SetState(executorApi.contractStorageAddress, ctAddr.metadata, zero) + api.SetState(executorApi.contractStorageAddress, ctAddr.outputHandle, zero) + api.SetState(executorApi.contractStorageAddress, ctAddr.firstOperand, zero) + api.SetState(executorApi.contractStorageAddress, ctAddr.secondOperand, zero) + if metadata.IsBigScalar { + counter := new(big.Int) + counter.SetBytes(ctAddr.bigScalarOperand[:]) + // max supporter number 2048 is 2048 + for i := 0; i < 2048/256; i++ { + api.SetState(executorApi.contractStorageAddress, common.BigToHash(counter), zero) + counter.Add(counter, one) + } + } } // set 0 as count