Skip to content

Commit

Permalink
Add all available FHE ops (ethereum#120)
Browse files Browse the repository at this point in the history
* feat(tfhe): add support for casting

* feat: add `cast` precompile

* feat(tfhe): add `&`, `|`, `^`, `==`, `>`, `>=`

* feat(precompiles): add missing ops

* feat(precompiles): add tests

* fix: add precompile contract addresses

* fit(precompiles): change precompile order
  • Loading branch information
tremblaythibaultl authored Jun 26, 2023
1 parent 1038691 commit e1a6156
Show file tree
Hide file tree
Showing 5 changed files with 1,268 additions and 51 deletions.
324 changes: 322 additions & 2 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{75}): &optimisticRequire{},
common.BytesToAddress([]byte{76}): &cast{},
common.BytesToAddress([]byte{77}): &trivialEncrypt{},
common.BytesToAddress([]byte{78}): &fheBitAnd{},
common.BytesToAddress([]byte{79}): &fheBitOr{},
common.BytesToAddress([]byte{80}): &fheBitXor{},
common.BytesToAddress([]byte{81}): &fheEq{},
common.BytesToAddress([]byte{82}): &fheGe{},
common.BytesToAddress([]byte{83}): &fheGt{},
common.BytesToAddress([]byte{99}): &faucet{},
}

Expand Down Expand Up @@ -108,6 +114,12 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{75}): &optimisticRequire{},
common.BytesToAddress([]byte{76}): &cast{},
common.BytesToAddress([]byte{77}): &trivialEncrypt{},
common.BytesToAddress([]byte{78}): &fheBitAnd{},
common.BytesToAddress([]byte{79}): &fheBitOr{},
common.BytesToAddress([]byte{80}): &fheBitXor{},
common.BytesToAddress([]byte{81}): &fheEq{},
common.BytesToAddress([]byte{82}): &fheGe{},
common.BytesToAddress([]byte{83}): &fheGt{},
common.BytesToAddress([]byte{99}): &faucet{},
}

Expand Down Expand Up @@ -138,6 +150,12 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{75}): &optimisticRequire{},
common.BytesToAddress([]byte{76}): &cast{},
common.BytesToAddress([]byte{77}): &trivialEncrypt{},
common.BytesToAddress([]byte{78}): &fheBitAnd{},
common.BytesToAddress([]byte{79}): &fheBitOr{},
common.BytesToAddress([]byte{80}): &fheBitXor{},
common.BytesToAddress([]byte{81}): &fheEq{},
common.BytesToAddress([]byte{82}): &fheGe{},
common.BytesToAddress([]byte{83}): &fheGt{},
common.BytesToAddress([]byte{99}): &faucet{},
}

Expand Down Expand Up @@ -168,6 +186,12 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{75}): &optimisticRequire{},
common.BytesToAddress([]byte{76}): &cast{},
common.BytesToAddress([]byte{77}): &trivialEncrypt{},
common.BytesToAddress([]byte{78}): &fheBitAnd{},
common.BytesToAddress([]byte{79}): &fheBitOr{},
common.BytesToAddress([]byte{80}): &fheBitXor{},
common.BytesToAddress([]byte{81}): &fheEq{},
common.BytesToAddress([]byte{82}): &fheGe{},
common.BytesToAddress([]byte{83}): &fheGt{},
common.BytesToAddress([]byte{99}): &faucet{},
}

Expand Down Expand Up @@ -198,6 +222,12 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{75}): &optimisticRequire{},
common.BytesToAddress([]byte{76}): &cast{},
common.BytesToAddress([]byte{77}): &trivialEncrypt{},
common.BytesToAddress([]byte{78}): &fheBitAnd{},
common.BytesToAddress([]byte{79}): &fheBitOr{},
common.BytesToAddress([]byte{80}): &fheBitXor{},
common.BytesToAddress([]byte{81}): &fheEq{},
common.BytesToAddress([]byte{82}): &fheGe{},
common.BytesToAddress([]byte{83}): &fheGt{},
common.BytesToAddress([]byte{99}): &faucet{},
}

Expand Down Expand Up @@ -1305,6 +1335,12 @@ var fheAddSubGasCosts = map[fheUintType]uint64{
FheUint32: params.FheUint32AddSubGas,
}

var fheBitwiseOpGasCosts = map[fheUintType]uint64{
FheUint8: params.FheUint8BitwiseGas,
FheUint16: params.FheUint16BitwiseGas,
FheUint32: params.FheUint32BitwiseGas,
}

var fheMulGasCosts = map[fheUintType]uint64{
FheUint8: params.FheUint8MulGas,
FheUint16: params.FheUint16MulGas,
Expand Down Expand Up @@ -1754,11 +1790,11 @@ type fheLte struct{}
func (e *fheLte) RequiredGas(accessibleState PrecompileAccessibleState, input []byte) uint64 {
lhs, rhs, err := get2VerifiedOperands(accessibleState, input)
if err != nil {
accessibleState.Interpreter().evm.Logger.Error("fheLt/Lte RequiredGas() inputs not verified", "err", err)
accessibleState.Interpreter().evm.Logger.Error("fheLte (comparison) RequiredGas() inputs not verified", "err", err)
return 0
}
if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType {
accessibleState.Interpreter().evm.Logger.Error("fheLt/Lte RequiredGas() operand type mismatch", "lhs",
accessibleState.Interpreter().evm.Logger.Error("fheLte (comparison) RequiredGas() operand type mismatch", "lhs",
lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType)
return 0
}
Expand Down Expand Up @@ -1902,6 +1938,290 @@ func (e *fheMul) Run(accessibleState PrecompileAccessibleState, caller common.Ad
return ctHash[:], nil
}

type fheBitAnd struct{}

func (e *fheBitAnd) RequiredGas(accessibleState PrecompileAccessibleState, input []byte) uint64 {
logger := accessibleState.Interpreter().evm.Logger
lhs, rhs, err := get2VerifiedOperands(accessibleState, input)
if err != nil {
logger.Error("Bitwise op RequiredGas() inputs not verified", "err", err, "input", hex.EncodeToString(input))
return 0
}
if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType {
logger.Error("Bitwise op RequiredGas() operand type mismatch", "lhs", lhs.ciphertext.fheUintType, "rhs", rhs.ciphertext.fheUintType)
return 0
}
return fheBitwiseOpGasCosts[lhs.ciphertext.fheUintType]
}

func (e *fheBitAnd) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) {
logger := accessibleState.Interpreter().evm.Logger
lhs, rhs, err := get2VerifiedOperands(accessibleState, input)
if err != nil {
logger.Error("fheBitAnd inputs not verified", "err", err)
return nil, err
}

if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType {
msg := "fheBitAnd 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 !accessibleState.Interpreter().evm.Commit && !accessibleState.Interpreter().evm.EthCall {
return importRandomCiphertext(accessibleState, lhs.ciphertext.fheUintType), nil
}

result, err := lhs.ciphertext.bitand(rhs.ciphertext)
if err != nil {
logger.Error("fheBitAnd failed", "err", err)
return nil, err
}
importCiphertext(accessibleState, result)

// TODO: for testing
err = os.WriteFile("/tmp/bitand_result", result.serialize(), 0644)
if err != nil {
logger.Error("fheBitAnd failed to write /tmp/bitand_result", "err", err)
return nil, err
}

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

type fheBitOr struct{}

func (e *fheBitOr) RequiredGas(accessibleState PrecompileAccessibleState, input []byte) uint64 {
// Implement in terms of bitAnd, because bitwise op costs are currently the same.
and := fheBitAnd{}
return and.RequiredGas(accessibleState, input)
}

func (e *fheBitOr) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) {
logger := accessibleState.Interpreter().evm.Logger
lhs, rhs, err := get2VerifiedOperands(accessibleState, input)
if err != nil {
logger.Error("fheBitOr inputs not verified", "err", err)
return nil, err
}

if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType {
msg := "fheBitOr 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 !accessibleState.Interpreter().evm.Commit && !accessibleState.Interpreter().evm.EthCall {
return importRandomCiphertext(accessibleState, lhs.ciphertext.fheUintType), nil
}

result, err := lhs.ciphertext.bitor(rhs.ciphertext)
if err != nil {
logger.Error("fheBitOr failed", "err", err)
return nil, err
}
importCiphertext(accessibleState, result)

// TODO: for testing
err = os.WriteFile("/tmp/bitor_result", result.serialize(), 0644)
if err != nil {
logger.Error("fheBitOr failed to write /tmp/bitor_result", "err", err)
return nil, err
}

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

type fheBitXor struct{}

func (e *fheBitXor) RequiredGas(accessibleState PrecompileAccessibleState, input []byte) uint64 {
// Implement in terms of bitAnd, because bitwise op costs are currently the same.
and := fheBitAnd{}
return and.RequiredGas(accessibleState, input)
}

func (e *fheBitXor) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) {
logger := accessibleState.Interpreter().evm.Logger
lhs, rhs, err := get2VerifiedOperands(accessibleState, input)
if err != nil {
logger.Error("fheBitXor inputs not verified", "err", err)
return nil, err
}

if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType {
msg := "fheBitXor 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 !accessibleState.Interpreter().evm.Commit && !accessibleState.Interpreter().evm.EthCall {
return importRandomCiphertext(accessibleState, lhs.ciphertext.fheUintType), nil
}

result, err := lhs.ciphertext.bitxor(rhs.ciphertext)
if err != nil {
logger.Error("fheBitXor failed", "err", err)
return nil, err
}
importCiphertext(accessibleState, result)

// TODO: for testing
err = os.WriteFile("/tmp/bitxor_result", result.serialize(), 0644)
if err != nil {
logger.Error("fheBitXor failed to write /tmp/bitxor_result", "err", err)
return nil, err
}

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

type fheEq struct{}

func (e *fheEq) RequiredGas(accessibleState PrecompileAccessibleState, input []byte) uint64 {
// Implement in terms of lte, because comparison costs are currently the same.
lte := fheLte{}
return lte.RequiredGas(accessibleState, input)
}

func (e *fheEq) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) {
logger := accessibleState.Interpreter().evm.Logger
lhs, rhs, err := get2VerifiedOperands(accessibleState, input)
if err != nil {
logger.Error("fheEq inputs not verified", "err", err)
return nil, err
}

if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType {
msg := "fheEq 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 !accessibleState.Interpreter().evm.Commit && !accessibleState.Interpreter().evm.EthCall {
return importRandomCiphertext(accessibleState, lhs.ciphertext.fheUintType), nil
}

result, err := lhs.ciphertext.eq(rhs.ciphertext)
if err != nil {
logger.Error("fheEq failed", "err", err)
return nil, err
}
importCiphertext(accessibleState, result)

// TODO: for testing
err = os.WriteFile("/tmp/eq_result", result.serialize(), 0644)
if err != nil {
logger.Error("fheEq failed to write /tmp/eq_result", "err", err)
return nil, err
}

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

type fheGe struct{}

func (e *fheGe) RequiredGas(accessibleState PrecompileAccessibleState, input []byte) uint64 {
// Implement in terms of lte, because comparison costs are currently the same.
lte := fheLte{}
return lte.RequiredGas(accessibleState, input)
}

func (e *fheGe) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) {
logger := accessibleState.Interpreter().evm.Logger
lhs, rhs, err := get2VerifiedOperands(accessibleState, input)
if err != nil {
logger.Error("fheGe inputs not verified", "err", err)
return nil, err
}

if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType {
msg := "fheGe 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 !accessibleState.Interpreter().evm.Commit && !accessibleState.Interpreter().evm.EthCall {
return importRandomCiphertext(accessibleState, lhs.ciphertext.fheUintType), nil
}

result, err := lhs.ciphertext.ge(rhs.ciphertext)
if err != nil {
logger.Error("fheGe failed", "err", err)
return nil, err
}
importCiphertext(accessibleState, result)

// TODO: for testing
err = os.WriteFile("/tmp/ge_result", result.serialize(), 0644)
if err != nil {
logger.Error("fheGe failed to write /tmp/ge_result", "err", err)
return nil, err
}

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

type fheGt struct{}

func (e *fheGt) RequiredGas(accessibleState PrecompileAccessibleState, input []byte) uint64 {
// Implement in terms of lte, because comparison costs are currently the same.
lte := fheLte{}
return lte.RequiredGas(accessibleState, input)
}

func (e *fheGt) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) {
logger := accessibleState.Interpreter().evm.Logger
lhs, rhs, err := get2VerifiedOperands(accessibleState, input)
if err != nil {
logger.Error("fheGt inputs not verified", "err", err)
return nil, err
}

if lhs.ciphertext.fheUintType != rhs.ciphertext.fheUintType {
msg := "fheGt 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 !accessibleState.Interpreter().evm.Commit && !accessibleState.Interpreter().evm.EthCall {
return importRandomCiphertext(accessibleState, lhs.ciphertext.fheUintType), nil
}

result, err := lhs.ciphertext.gt(rhs.ciphertext)
if err != nil {
logger.Error("fheGt failed", "err", err)
return nil, err
}
importCiphertext(accessibleState, result)

// TODO: for testing
err = os.WriteFile("/tmp/gt_result", result.serialize(), 0644)
if err != nil {
logger.Error("fheGt failed to write /tmp/gt_result", "err", err)
return nil, err
}

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

type fheLt struct{}

func (e *fheLt) RequiredGas(accessibleState PrecompileAccessibleState, input []byte) uint64 {
Expand Down
Loading

0 comments on commit e1a6156

Please sign in to comment.