Skip to content

Commit

Permalink
Merge pull request zama-ai#6 from zama-ai/v0.5.6-zama
Browse files Browse the repository at this point in the history
V0.5.6 zama
  • Loading branch information
david-zk authored Oct 10, 2023
2 parents c707cdc + 9f71d85 commit 71fd302
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 2 deletions.
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=

0 comments on commit 71fd302

Please sign in to comment.