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

feat: verify ciphertext #32

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
33 changes: 21 additions & 12 deletions fhevm/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,19 @@ type GasCosts struct {
FheGetCiphertext map[tfhe.FheUintType]uint64

// TEE Operations
TeeAddSub map[tfhe.FheUintType]uint64
TeeMul map[tfhe.FheUintType]uint64
TeeDiv map[tfhe.FheUintType]uint64
TeeRem map[tfhe.FheUintType]uint64
TeeEncrypt map[tfhe.FheUintType]uint64
TeeDecrypt map[tfhe.FheUintType]uint64
TeeComparison map[tfhe.FheUintType]uint64
TeeShift map[tfhe.FheUintType]uint64
TeeNot map[tfhe.FheUintType]uint64
TeeNeg map[tfhe.FheUintType]uint64
TeeBitwiseOp map[tfhe.FheUintType]uint64
TeeCast uint64
TeeAddSub map[tfhe.FheUintType]uint64
TeeMul map[tfhe.FheUintType]uint64
TeeDiv map[tfhe.FheUintType]uint64
TeeRem map[tfhe.FheUintType]uint64
TeeEncrypt map[tfhe.FheUintType]uint64
TeeDecrypt map[tfhe.FheUintType]uint64
TeeComparison map[tfhe.FheUintType]uint64
TeeShift map[tfhe.FheUintType]uint64
TeeNot map[tfhe.FheUintType]uint64
TeeNeg map[tfhe.FheUintType]uint64
TeeBitwiseOp map[tfhe.FheUintType]uint64
TeeVerifyCiphertext map[tfhe.FheUintType]uint64
TeeCast uint64
}

func DefaultGasCosts() GasCosts {
Expand Down Expand Up @@ -308,6 +309,14 @@ func DefaultGasCosts() GasCosts {
tfhe.FheUint32: 150,
tfhe.FheUint64: 189,
},
TeeVerifyCiphertext: map[tfhe.FheUintType]uint64{
tfhe.FheBool: 60,
tfhe.FheUint4: 60,
tfhe.FheUint8: 60,
tfhe.FheUint16: 70,
tfhe.FheUint32: 90,
tfhe.FheUint64: 120,
},
}
}

Expand Down
4 changes: 2 additions & 2 deletions fhevm/tee_cast.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ func teeCastTo(ciphertext *tfhe.TfheCiphertext, castToType tfhe.FheUintType) (*t

value := big.NewInt(0).SetBytes(result.Value).Uint64()

resultBz, err := marshalTfheType(value, castToType)

resultBz, err := tee.MarshalTfheType(value, castToType)
if err != nil {
return nil, errors.New("marshalling failed")
}

teePlaintext := tee.NewTeePlaintext(resultBz, castToType, common.Address{})

resultCt, err := tee.Encrypt(teePlaintext)
Expand Down
2 changes: 1 addition & 1 deletion fhevm/tee_comparison.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func teeSelectRun(environment EVMEnvironment, caller common.Address, addr common
} else {
result.Set(t)
}
resultBz, err := marshalTfheType(&result, p2.FheUintType)
resultBz, err := tee.MarshalTfheType(&result, p2.FheUintType)
if err != nil {
return nil, err
}
Expand Down
73 changes: 73 additions & 0 deletions fhevm/tee_crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fhevm

import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"math/big"
Expand Down Expand Up @@ -88,3 +89,75 @@ func teeDecryptRun(environment EVMEnvironment, caller common.Address, addr commo
copy(ret[32-len(plaintext):], plaintext)
return ret, nil
}

func teeVerifyCiphertextRun(environment EVMEnvironment, caller common.Address, addr common.Address, input []byte, readOnly bool, runSpan trace.Span) ([]byte, error) {
logger := environment.GetLogger()
// first 32 bytes of the payload is offset, then 32 bytes are size of byte array
if len(input) <= 68 {
err := errors.New("verifyCiphertext(bytes) must contain at least 68 bytes for selector, byte offset and size")
logger.Error("fheLib precompile error", "err", err, "input", hex.EncodeToString(input))
return nil, err
}
bytesPaddingSize := 32
bytesSizeSlotSize := 32
// read only last 4 bytes of padded number for byte array size
sizeStart := bytesPaddingSize + bytesSizeSlotSize - 4
sizeEnd := sizeStart + 4
bytesSize := binary.BigEndian.Uint32(input[sizeStart:sizeEnd])
bytesStart := bytesPaddingSize + bytesSizeSlotSize
bytesEnd := bytesStart + int(bytesSize)
input = input[bytesStart:minInt(bytesEnd, len(input))]

if len(input) <= 1 {
msg := "verifyCiphertext Run() input needs to contain a ciphertext and one byte for its type"
logger.Error(msg, "len", len(input))
return nil, errors.New(msg)
}

ctBytes := input[:len(input)-1]
ctTypeByte := input[len(input)-1]
if !tfhe.IsValidFheType(ctTypeByte) {
msg := "verifyCiphertext Run() ciphertext type is invalid"
logger.Error(msg, "type", ctTypeByte)
return nil, errors.New(msg)
}
ctType := tfhe.FheUintType(ctTypeByte)
otelDescribeOperandsFheTypes(runSpan, ctType)

expectedSize, found := tee.GetTeeCiphertextSize(ctType)
if !found || expectedSize != uint(len(ctBytes)) {
msg := "verifyCiphertext Run() compact ciphertext size is invalid"
logger.Error(msg, "type", ctTypeByte, "size", len(ctBytes), "expectedSize", expectedSize)
return nil, errors.New(msg)
}

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

ct := new(tfhe.TfheCiphertext)
ct.Serialization = ctBytes

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lovenoble this make sense. but we should try deserialize the ciphertext in order to ensure it is valid format like tfhe does? I guess we need to add TODO comments.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I check the format after decrypting it in the following lines of codes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I check the format after decrypting it in the following lines of codes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lovenoble Got it. But i see that the tfhe uses deserializecompact function which produces non-compact ciphertext serialization from compacted one. Guess like the original meaning of verifyCipherTextRun function gets changed in this implementation. I think its original purpose was to deserialize the compacted cipher text and returns hash of it.

Copy link
Author

@lovenoble lovenoble Jun 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to implement compact serialization. Do you have any idea about it? And do we need a compact serialization for TEE case, @amaury1093 , @kenta-mori3322 ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lovenoble yeah actually was trying to decode rust code to golang.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @lovenoble we have actually agreed on the fact that we won't use compacted ciphertext for TEE as it won't be so long. I think I can approve your PR.

ct.FheUintType = ctType

plaintext, err := tee.Decrypt(ct)
if err != nil {
msg := "verifyCiphertext Run() compact ciphertext is invalid"
return nil, errors.New(msg)
}

if plaintext.FheUintType != ctType {
msg := "verifyCiphertext Run() compact type mismatch"
logger.Error(msg, "type", plaintext.FheUintType, "expectedType", ctType)
return nil, errors.New(msg)
}

ctHash := ct.GetHash()
importCiphertext(environment, ct)
if environment.IsCommitting() {
logger.Info("verifyCiphertext success",
"ctHash", ctHash.Hex(),
"ctBytes64", hex.EncodeToString(ctBytes[:minInt(len(ctBytes), 64)]))
}
return ctHash.Bytes(), nil
}
18 changes: 18 additions & 0 deletions fhevm/tee_crypto_gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,21 @@ func teeDecryptRequiredGas(environment EVMEnvironment, input []byte) uint64 {
}
return environment.FhevmParams().GasCosts.TeeDecrypt[ct.fheUintType()]
}

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

if len(input) <= 68 {
logger.Error("verifyCiphertext(bytes) must contain at least 68 bytes for selector, byte offset and size")
return 0
}
ctTypeByte := input[len(input)-1]
if !tfhe.IsValidFheType(ctTypeByte) {
msg := "verifyCiphertext Run() ciphertext type is invalid"
logger.Error(msg, "type", ctTypeByte)
return 0
}

ctType := tfhe.FheUintType(ctTypeByte)
return environment.FhevmParams().GasCosts.TeeVerifyCiphertext[ctType]
}
146 changes: 146 additions & 0 deletions fhevm/tee_crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/zama-ai/fhevm-go/fhevm/tfhe"
"github.com/zama-ai/fhevm-go/tee"
"pgregory.net/rapid"
)

Expand Down Expand Up @@ -46,3 +47,148 @@ func TestTeeDecryptRun(t *testing.T) {
}
})
}

func TestTeeVerifyCiphertext4(t *testing.T) {
TeeVerifyCiphertext(t, tfhe.FheUint4)
}

func TestTeeVerifyCiphertext8(t *testing.T) {
TeeVerifyCiphertext(t, tfhe.FheUint8)
}

func TestTeeVerifyCiphertext16(t *testing.T) {
TeeVerifyCiphertext(t, tfhe.FheUint16)
}

func TestTeeVerifyCiphertext32(t *testing.T) {
TeeVerifyCiphertext(t, tfhe.FheUint32)
}

func TestTeeVerifyCiphertext64(t *testing.T) {
TeeVerifyCiphertext(t, tfhe.FheUint64)
}

func TestTeeVerifyCiphertext4BadType(t *testing.T) {
TeeVerifyCiphertextBadType(t, tfhe.FheUint4, tfhe.FheUint8)
TeeVerifyCiphertextBadType(t, tfhe.FheUint4, tfhe.FheUint16)
TeeVerifyCiphertextBadType(t, tfhe.FheUint4, tfhe.FheUint32)
TeeVerifyCiphertextBadType(t, tfhe.FheUint4, tfhe.FheUint64)
}

func TestTeeVerifyCiphertext8BadType(t *testing.T) {
TeeVerifyCiphertextBadType(t, tfhe.FheUint8, tfhe.FheUint4)
TeeVerifyCiphertextBadType(t, tfhe.FheUint8, tfhe.FheUint16)
TeeVerifyCiphertextBadType(t, tfhe.FheUint8, tfhe.FheUint32)
TeeVerifyCiphertextBadType(t, tfhe.FheUint8, tfhe.FheUint64)
}

func TestTeeVerifyCiphertext16BadType(t *testing.T) {
TeeVerifyCiphertextBadType(t, tfhe.FheUint16, tfhe.FheUint4)
TeeVerifyCiphertextBadType(t, tfhe.FheUint16, tfhe.FheUint8)
TeeVerifyCiphertextBadType(t, tfhe.FheUint16, tfhe.FheUint32)
TeeVerifyCiphertextBadType(t, tfhe.FheUint16, tfhe.FheUint64)
}

func TestTeeVerifyCiphertext32BadType(t *testing.T) {
TeeVerifyCiphertextBadType(t, tfhe.FheUint32, tfhe.FheUint4)
TeeVerifyCiphertextBadType(t, tfhe.FheUint32, tfhe.FheUint8)
TeeVerifyCiphertextBadType(t, tfhe.FheUint32, tfhe.FheUint16)
TeeVerifyCiphertextBadType(t, tfhe.FheUint32, tfhe.FheUint64)
}

func TestTeeVerifyCiphertext64BadType(t *testing.T) {
TeeVerifyCiphertextBadType(t, tfhe.FheUint64, tfhe.FheUint4)
TeeVerifyCiphertextBadType(t, tfhe.FheUint64, tfhe.FheUint8)
TeeVerifyCiphertextBadType(t, tfhe.FheUint64, tfhe.FheUint16)
TeeVerifyCiphertextBadType(t, tfhe.FheUint64, tfhe.FheUint32)
}

func TeeVerifyCiphertext(t *testing.T, fheUintType tfhe.FheUintType) {
var value uint64
switch fheUintType {
case tfhe.FheBool:
value = 1
case tfhe.FheUint4:
value = 2
case tfhe.FheUint8:
value = 234
case tfhe.FheUint16:
value = 4283
case tfhe.FheUint32:
value = 1333337
case tfhe.FheUint64:
value = 13333377777777777
}
depth := 1
environment := newTestEVMEnvironment()
environment.depth = depth
addr := common.Address{}
readOnly := false

resultBz, err := tee.MarshalTfheType(value, fheUintType)
if err != nil {
t.Fatalf(err.Error())
}

plaintext := tee.NewTeePlaintext(resultBz, fheUintType, addr)
ct, err := tee.Encrypt(plaintext)
if err != nil {
t.Fatalf(err.Error())
}

input := prepareInputForVerifyCiphertext(append(ct.Serialization, byte(fheUintType)))
out, err := teeVerifyCiphertextRun(environment, addr, addr, input, readOnly, nil)
if err != nil {
t.Fatalf(err.Error())
}

if common.BytesToHash(out) != ct.GetHash() {
t.Fatalf("output hash in verifyCipertext is incorrect")
}
res := getVerifiedCiphertextFromEVM(environment, ct.GetHash())
if res == nil {
t.Fatalf("verifyCiphertext must have verified given ciphertext")
}
}

func TeeVerifyCiphertextBadType(t *testing.T, actualType tfhe.FheUintType, metadataType tfhe.FheUintType) {
var value uint64
switch actualType {
case tfhe.FheUint4:
value = 2
case tfhe.FheUint8:
value = 2
case tfhe.FheUint16:
value = 4283
case tfhe.FheUint32:
value = 1333337
case tfhe.FheUint64:
value = 13333377777777777
}
depth := 1
environment := newTestEVMEnvironment()
environment.depth = depth
addr := common.Address{}
readOnly := false

resultBz, err := tee.MarshalTfheType(value, actualType)
if err != nil {
t.Fatalf(err.Error())
}

plaintext := tee.NewTeePlaintext(resultBz, actualType, addr)
ct, err := tee.Encrypt(plaintext)
if err != nil {
t.Fatalf(err.Error())
}

input := prepareInputForVerifyCiphertext(append(ct.Serialization, byte(metadataType)))
_, err = teeVerifyCiphertextRun(environment, addr, addr, input, readOnly, nil)
if err == nil {
t.Fatalf("verifyCiphertext must have failed on type mismatch")
}

if len(environment.FhevmData().verifiedCiphertexts) != 0 {
t.Fatalf("verifyCiphertext mustn't have verified given ciphertext")
}
}
Loading
Loading