Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] Support rotate left and right operations #141

Merged
Merged
Show file tree
Hide file tree
Changes from 3 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
66 changes: 64 additions & 2 deletions chains/arbitrum/contractsgen/contractsgen.go

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions nitro-overrides/precompiles/FheOps.go
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,78 @@ func (con FheOps) Req(c ctx, evm mech, utype byte, input []byte) ([]byte, error)
return ret, err
}

func (con FheOps) Rol(c ctx, evm mech, utype byte, lhsHash []byte, rhsHash []byte) ([]byte, error) {
tp := fheos.TxParamsFromEVM(evm, c.caller)
if metrics.Enabled {
h := fmt.Sprintf("%s/%s/%s", "fheos", "Rol", fheos.UtypeToString(utype))
defer func(start time.Time) {
sampler := func() metrics.Sample {
return metrics.NewBoundedHistogramSample()
}
metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds())
}(time.Now())
}

ret, gas, err := fheos.Rol(utype, lhsHash, rhsHash, &tp)

if err != nil {
if metrics.Enabled {
c := fmt.Sprintf("%s/%s/%s/%s", "fheos", "Rol", fheos.UtypeToString(utype), "error/fhe_failure")
metrics.GetOrRegisterCounter(c, nil).Inc(1)
}
return ret, err
}

err = c.Burn(gas)

if metrics.Enabled {
metricPath := fmt.Sprintf("%s/%s/%s/%s", "fheos", "Rol", fheos.UtypeToString(utype), "success/total")
if err != nil {
metricPath = fmt.Sprintf("%s/%s/%s/%s", "fheos", "Rol", fheos.UtypeToString(utype), "error/fhe_gas_failure")
}

metrics.GetOrRegisterCounter(metricPath, nil).Inc(1)
}

return ret, err
}

func (con FheOps) Ror(c ctx, evm mech, utype byte, lhsHash []byte, rhsHash []byte) ([]byte, error) {
tp := fheos.TxParamsFromEVM(evm, c.caller)
if metrics.Enabled {
h := fmt.Sprintf("%s/%s/%s", "fheos", "Ror", fheos.UtypeToString(utype))
defer func(start time.Time) {
sampler := func() metrics.Sample {
return metrics.NewBoundedHistogramSample()
}
metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds())
}(time.Now())
}

ret, gas, err := fheos.Ror(utype, lhsHash, rhsHash, &tp)

if err != nil {
if metrics.Enabled {
c := fmt.Sprintf("%s/%s/%s/%s", "fheos", "Ror", fheos.UtypeToString(utype), "error/fhe_failure")
metrics.GetOrRegisterCounter(c, nil).Inc(1)
}
return ret, err
}

err = c.Burn(gas)

if metrics.Enabled {
metricPath := fmt.Sprintf("%s/%s/%s/%s", "fheos", "Ror", fheos.UtypeToString(utype), "success/total")
if err != nil {
metricPath = fmt.Sprintf("%s/%s/%s/%s", "fheos", "Ror", fheos.UtypeToString(utype), "error/fhe_gas_failure")
}

metrics.GetOrRegisterCounter(metricPath, nil).Inc(1)
}

return ret, err
}

func (con FheOps) SealOutput(c ctx, evm mech, utype byte, ctHash []byte, pk []byte) (string, error) {
tp := fheos.TxParamsFromEVM(evm, c.caller)
if metrics.Enabled {
Expand Down
105 changes: 102 additions & 3 deletions precompiles/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package precompiles
import (
"encoding/hex"
"fmt"
"math/big"
"os"
"strings"

"github.com/ethereum/go-ethereum/common"
"github.com/fhenixprotocol/fheos/precompiles/types"
storage2 "github.com/fhenixprotocol/fheos/storage"
"github.com/fhenixprotocol/warp-drive/fhe-driver"
"math/big"
"os"
"strings"

"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
Expand Down Expand Up @@ -1354,6 +1355,104 @@ func Shr(utype byte, lhsHash []byte, rhsHash []byte, tp *TxParams) ([]byte, uint
return ctHash[:], gas, nil
}

func Rol(utype byte, lhsHash []byte, rhsHash []byte, tp *TxParams) ([]byte, uint64, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Rol and Ror functions are and always will be the same, therefore I would probably create a helper function somewhere for "rotate" that does everything for which the only differences (based on function input or something?) are the "functionName" and the direction (for which you can choose calling ror or rol)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've noticed that it might corrupt the solgen generator due to the way thecontracts_parser.ts works, cuz it's relying on function calls like get2VerifiedOperands to understand that it's 2 operands, and having if lhs.UintType != rhs.UintType to make sure they are the same types and etc..
BUT, i'll add support and lmk if you think it's better

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it can be said for every function that

  1. receives two euints
  2. returns 1 euint

I mean we can create a 2OperandMathImpl()™ bc I think all these functions of the same type have basically the same implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Following the conversations with Tom and Itzik, i'm reverting this refactor for now as it's out of scope here and can be handled better as a standalone separated task

functionName := types.Rol

storage := storage2.NewMultiStore(tp.CiphertextDb, &State.Storage)
uintType := fhe.EncryptionType(utype)
if !types.IsValidType(uintType) {
logger.Error("invalid ciphertext", "type", utype)
return nil, 0, vm.ErrExecutionReverted
}

gas := getGasForPrecompile(functionName, uintType)
if tp.GasEstimation {
randomHash := State.GetRandomForGasEstimation()
return randomHash[:], gas, nil
}

if shouldPrintPrecompileInfo(tp) {
logger.Info("Starting new precompiled contract function: " + functionName.String())
}

lhs, rhs, err := get2VerifiedOperands(storage, lhsHash, rhsHash, tp.ContractAddress)
if err != nil {
logger.Error(functionName.String()+" inputs not verified", "err", err)
return nil, 0, vm.ErrExecutionReverted
}

if lhs.UintType != rhs.UintType {
msg := functionName.String() + " operand type mismatch"
logger.Error(msg, "lhs", lhs.UintType, "rhs", rhs.UintType)
return nil, 0, vm.ErrExecutionReverted
}

result, err := lhs.Rol(rhs)
if err != nil {
logger.Error(functionName.String()+" failed", "err", err)
return nil, 0, vm.ErrExecutionReverted
}
err = storeCipherText(storage, result, tp.ContractAddress)
if err != nil {
logger.Error(functionName.String()+" failed", "err", err)
return nil, 0, vm.ErrExecutionReverted
}

ctHash := result.Hash()

logger.Debug(functionName.String()+" success", "contractAddress", tp.ContractAddress, "lhs", lhs.Hash().Hex(), "rhs", rhs.Hash().Hex(), "result", ctHash.Hex())
return ctHash[:], gas, nil
}

func Ror(utype byte, lhsHash []byte, rhsHash []byte, tp *TxParams) ([]byte, uint64, error) {
functionName := types.Ror

storage := storage2.NewMultiStore(tp.CiphertextDb, &State.Storage)
uintType := fhe.EncryptionType(utype)
if !types.IsValidType(uintType) {
logger.Error("invalid ciphertext", "type", utype)
return nil, 0, vm.ErrExecutionReverted
}

gas := getGasForPrecompile(functionName, uintType)
if tp.GasEstimation {
randomHash := State.GetRandomForGasEstimation()
return randomHash[:], gas, nil
}

if shouldPrintPrecompileInfo(tp) {
logger.Info("Starting new precompiled contract function: " + functionName.String())
}

lhs, rhs, err := get2VerifiedOperands(storage, lhsHash, rhsHash, tp.ContractAddress)
if err != nil {
logger.Error(functionName.String()+" inputs not verified", "err", err)
return nil, 0, vm.ErrExecutionReverted
}

if lhs.UintType != rhs.UintType {
msg := functionName.String() + " operand type mismatch"
logger.Error(msg, "lhs", lhs.UintType, "rhs", rhs.UintType)
return nil, 0, vm.ErrExecutionReverted
}

result, err := lhs.Ror(rhs)
if err != nil {
logger.Error(functionName.String()+" failed", "err", err)
return nil, 0, vm.ErrExecutionReverted
}
err = storeCipherText(storage, result, tp.ContractAddress)
if err != nil {
logger.Error(functionName.String()+" failed", "err", err)
return nil, 0, vm.ErrExecutionReverted
}

ctHash := result.Hash()

logger.Debug(functionName.String()+" success", "contractAddress", tp.ContractAddress, "lhs", lhs.Hash().Hex(), "rhs", rhs.Hash().Hex(), "result", ctHash.Hex())
return ctHash[:], gas, nil
}

func Not(utype byte, value []byte, tp *TxParams) ([]byte, uint64, error) {
functionName := types.Not

Expand Down
2 changes: 1 addition & 1 deletion precompiles/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func getRawPrecompileGas(precompileName types.PrecompileName, uintType fhe.Encry
case fhe.Uint128:
return 250000
}
case types.Shl, types.Shr:
case types.Shl, types.Shr, types.Rol, types.Ror:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TEMP

Copy link
Contributor Author

@roeezolantz roeezolantz Sep 18, 2024

Choose a reason for hiding this comment

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

Benchmark for mean time consumption on 10 runs each -
(running bench of 100 each now as well)
image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After 100 runs each :
image

switch uintType {
case fhe.Uint8:
return 65000
Expand Down
12 changes: 6 additions & 6 deletions precompiles/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ const (
Ne
TrivialEncrypt
Random
// Rol // Commented out if not used
// Ror // Commented out if not used
Rol
Ror
)

var precompileNameToString = map[PrecompileName]string{
Expand Down Expand Up @@ -102,8 +102,8 @@ var precompileNameToString = map[PrecompileName]string{
Ne: "ne",
Random: "random",
TrivialEncrypt: "trivialEncrypt",
// Rol: "rol",
// Ror: "ror",
Rol: "rol",
Ror: "ror",
}

var stringToPrecompileName = map[string]PrecompileName{
Expand Down Expand Up @@ -135,8 +135,8 @@ var stringToPrecompileName = map[string]PrecompileName{
"ne": Ne,
"random": Random,
"trivialEncrypt": TrivialEncrypt,
// "rol": Rol,
// "ror": Ror,
"rol": Rol,
"ror": Ror,
}

func (pn PrecompileName) String() string {
Expand Down
16 changes: 15 additions & 1 deletion solgen/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const EInputType = [
// FYI: Operations ["sealoutput", "seal", "decrypt", "ne"] are the minimum required for
// non failing generated code.

const patternAllowedOperationsEbool = ["ne|eq|^and$|or|xor|sealoutput|select|seal|decrypt|not"];
const patternAllowedOperationsEbool = ["ne|eq|^and$|^or$|xor|sealoutput|select|seal|decrypt|not"];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fix for conflict with ror

const patternAllowedOperationsEuint8 = [".*"];
const patternAllowedOperationsEuint16 = [".*"];
const patternAllowedOperationsEuint32 = [".*"];
Expand Down Expand Up @@ -230,6 +230,18 @@ export const ShorthandOperations: OperatorMap[] = [
unary: false,
returnsBool: false,
},
{
func: "rol",
operator: null,
unary: false,
returnsBool: false,
},
{
func: "ror",
operator: null,
unary: false,
returnsBool: false,
},
];

export const BindMathOperators = [
Expand All @@ -251,6 +263,8 @@ export const BindMathOperators = [
"min",
"shl",
"shr",
"rol",
"ror"
];
export const bitwiseAndLogicalOperators = ["and", "or", "xor", "not"];

Expand Down
Loading
Loading