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

V0.5.6 zama #6

Merged
merged 4 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: build
build:
cd fhevm && go build .

.PHONY: test
test:
cd fhevm && go test -v .
101 changes: 100 additions & 1 deletion fhevm/evm.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
package fhevm

import "fmt"
import (
"encoding/binary"
"errors"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/holiman/uint256"
)

// A Logger interface for the EVM.
type Logger interface {
Expand Down Expand Up @@ -30,3 +39,93 @@ func (*DefaultLogger) Info(msg string, keyvals ...interface{}) {
func (*DefaultLogger) Error(msg string, keyvals ...interface{}) {
fmt.Println("Error: "+msg, toString(keyvals...))
}

func makeKeccakSignature(input string) uint32 {
return binary.BigEndian.Uint32(crypto.Keccak256([]byte(input))[0:4])
}

func isScalarOp(environment *EVMEnvironment, 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 getVerifiedCiphertext(environment *EVMEnvironment, ciphertextHash common.Hash) *verifiedCiphertext {
return getVerifiedCiphertextFromEVM(*environment, ciphertextHash)
}

func get2VerifiedOperands(environment *EVMEnvironment, input []byte) (lhs *verifiedCiphertext, rhs *verifiedCiphertext, 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")
}
lhs = getVerifiedCiphertext(environment, common.BytesToHash(input[0:32]))
if lhs == nil {
return nil, nil, errors.New("unverified ciphertext handle")
}
rhs = getVerifiedCiphertext(environment, common.BytesToHash(input[32:64]))
if rhs == nil {
return nil, nil, errors.New("unverified ciphertext handle")
}
err = nil
return
}

func getScalarOperands(environment *EVMEnvironment, input []byte) (lhs *verifiedCiphertext, rhs *big.Int, 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")
}
lhs = getVerifiedCiphertext(environment, common.BytesToHash(input[0:32]))
if lhs == nil {
return nil, nil, errors.New("unverified ciphertext handle")
}
rhs = &big.Int{}
rhs.SetBytes(input[32:64])
return
}

func importCiphertextToEVMAtDepth(environment *EVMEnvironment, ct *tfheCiphertext, depth int) *verifiedCiphertext {
existing, ok := (*environment).GetFhevmData().verifiedCiphertexts[ct.getHash()]
if ok {
existing.verifiedDepths.add(depth)
return existing
} else {
verifiedDepths := newDepthSet()
verifiedDepths.add(depth)
new := &verifiedCiphertext{
verifiedDepths,
ct,
}
(*environment).GetFhevmData().verifiedCiphertexts[ct.getHash()] = new
return new
}
}

func importCiphertextToEVM(environment *EVMEnvironment, ct *tfheCiphertext) *verifiedCiphertext {
return importCiphertextToEVMAtDepth(environment, ct, (*environment).GetDepth())
}

func importCiphertext(environment *EVMEnvironment, ct *tfheCiphertext) *verifiedCiphertext {
return importCiphertextToEVM(environment, ct)
}

func importRandomCiphertext(environment *EVMEnvironment, t fheUintType) []byte {
nextCtHash := &(*environment).GetFhevmData().nextCiphertextHashOnGasEst
ctHashBytes := crypto.Keccak256(nextCtHash.Bytes())
handle := common.BytesToHash(ctHashBytes)
ct := new(tfheCiphertext)
ct.fheUintType = t
ct.hash = &handle
importCiphertext(environment, ct)
temp := nextCtHash.Clone()
nextCtHash.Add(temp, uint256.NewInt(1))
return ct.getHash().Bytes()
}

func minInt(a int, b int) int {
if a < b {
return a
}
return b
}
3 changes: 3 additions & 0 deletions fhevm/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fhevm

import (
"github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256"
)

type EVMEnvironment interface {
Expand Down Expand Up @@ -29,4 +30,6 @@ type FhevmData struct {

// All optimistic requires encountered up to that point in the txn execution
optimisticRequires []*tfheCiphertext

nextCiphertextHashOnGasEst uint256.Int
}
161 changes: 161 additions & 0 deletions fhevm/precompiles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package fhevm

import (
"encoding/binary"
"encoding/hex"
"errors"

"github.com/ethereum/go-ethereum/common"
"github.com/zama-ai/fhevm-go/params"
)

// PrecompiledContract is the basic interface for native Go contracts. The implementation
// requires a deterministic gas count based on the input size of the Run method of the
// contract.
type PrecompiledContract interface {
RequiredGas(environment *EVMEnvironment, input []byte) uint64 // RequiredGas calculates the contract gas use
Run(environment *EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) (ret []byte, err error)
}

var PrecompiledContracts = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{93}): &fheLib{},
}

var signatureFheAdd = makeKeccakSignature("fheAdd(uint256,uint256,bytes1)")

type fheLib struct{}

func (e *fheLib) RequiredGas(environment *EVMEnvironment, input []byte) uint64 {
logger := (*environment).GetLogger()
if len(input) < 4 {
err := errors.New("input must contain at least 4 bytes for method signature")
logger.Error("fheLib precompile error", "err", err, "input", hex.EncodeToString(input))
return 0
}
signature := binary.BigEndian.Uint32(input[0:4])
switch signature {
case signatureFheAdd:
bwCompatBytes := input[4:minInt(69, len(input))]
return fheAddRequiredGas(environment, bwCompatBytes)
default:
err := errors.New("precompile method not found")
logger.Error("fheLib precompile error", "err", err, "input", hex.EncodeToString(input))
return 0
}
}

func (e *fheLib) Run(environment *EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) {
logger := (*environment).GetLogger()
if len(input) < 4 {
err := errors.New("input must contain at least 4 bytes for method signature")
logger.Error("fheLib precompile error", "err", err, "input", hex.EncodeToString(input))
return nil, err
}
signature := binary.BigEndian.Uint32(input[0:4])
switch signature {
case signatureFheAdd:
bwCompatBytes := input[4:minInt(69, len(input))]
return fheAddRun(environment, caller, addr, bwCompatBytes, readOnly)
default:
err := errors.New("precompile method not found")
logger.Error("fheLib precompile error", "err", err, "input", hex.EncodeToString(input))
return nil, err
}
}

var fheAddSubGasCosts = map[fheUintType]uint64{
FheUint8: params.FheUint8AddSubGas,
FheUint16: params.FheUint16AddSubGas,
FheUint32: params.FheUint32AddSubGas,
}

func fheAddRequiredGas(environment *EVMEnvironment, input []byte) uint64 {
logger := (*environment).GetLogger()
isScalar, err := isScalarOp(environment, input)
if err != nil {
logger.Error("fheAdd/Sub RequiredGas() can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input))
return 0
}
var lhs, rhs *verifiedCiphertext
if !isScalar {
lhs, rhs, err = get2VerifiedOperands(environment, input)
if err != nil {
logger.Error("fheAdd/Sub RequiredGas() ciphertext inputs not verified", "err", err, "input", hex.EncodeToString(input))
return 0
}
if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType {
logger.Error("fheAdd/Sub RequiredGas() operand type mismatch", "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType)
return 0
}
} else {
lhs, _, err = getScalarOperands(environment, input)
if err != nil {
logger.Error("fheAdd/Sub RequiredGas() scalar inputs not verified", "err", err, "input", hex.EncodeToString(input))
return 0
}
}

return fheAddSubGasCosts[lhs.ciphertext.fheUintType]
}

func fheAddRun(environment *EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) {
logger := (*environment).GetLogger()

isScalar, err := isScalarOp(environment, input)
if err != nil {
logger.Error("fheAdd can not detect if operator is meant to be scalar", "err", err, "input", hex.EncodeToString(input))
return nil, err
}

if !isScalar {
lhs, rhs, err := get2VerifiedOperands(environment, input)
if err != nil {
logger.Error("fheAdd inputs not verified", "err", err, "input", hex.EncodeToString(input))
return nil, err
}
if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType {
msg := "fheAdd operand type mismatch"
logger.Error(msg, "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType)
return nil, errors.New(msg)
}

// If we are doing gas estimation, skip execution and insert a random ciphertext as a result.
if !(*environment).IsCommitting() && !(*environment).IsEthCall() {
return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil
}

result, err := lhs.ciphertext.add(rhs.ciphertext)
if err != nil {
logger.Error("fheAdd failed", "err", err)
return nil, err
}
importCiphertext(environment, result)

resultHash := result.getHash()
logger.Info("fheAdd success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.ciphertext.getHash().Hex(), "result", resultHash.Hex())
return resultHash[:], nil

} else {
lhs, rhs, err := getScalarOperands(environment, input)
if err != nil {
logger.Error("fheAdd scalar inputs not verified", "err", err, "input", hex.EncodeToString(input))
return nil, err
}

// If we are doing gas estimation, skip execution and insert a random ciphertext as a result.
if !(*environment).IsCommitting() && !(*environment).IsEthCall() {
return importRandomCiphertext(environment, lhs.ciphertext.fheUintType), nil
}

result, err := lhs.ciphertext.scalarAdd(rhs.Uint64())
if err != nil {
logger.Error("fheAdd failed", "err", err)
return nil, err
}
importCiphertext(environment, result)

resultHash := result.getHash()
logger.Info("fheAdd scalar success", "lhs", lhs.ciphertext.getHash().Hex(), "rhs", rhs.Uint64(), "result", resultHash.Hex())
return resultHash[:], nil
}
}
2 changes: 1 addition & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=