Files
fhevm-solidity/codegen/templates.ts
jat 367290f93d feat: use latest core contracts, updated lib API, renamed to fhevm an… (#745)
* feat: use latest core contracts, updated lib API, renamed to fhevm and fhe

chore: add dummy addresses for CI

test: add tests for makePubliclyDecryptable method

* chore: update package-lock.json for linux

* chore: update package-lock.json

* fix: solidity comments (#746)

* chore: update oracle version

* chore: update fhevm core-contracts version

* feat: update fhevm sdk and mocked userDecrypt

* chore: fix coverage
2025-04-19 14:45:30 +02:00

1566 lines
56 KiB
TypeScript

import { assert } from 'console';
import { AdjustedFheType, AliasFheType, FheType, Operator, OperatorArguments, ReturnType } from './common';
import { getUint } from './utils';
/**
* Generates Solidity type aliases from an array of FHE types.
*
* This function filters the provided FHE types to include only those that are supported for
* binary or unary operations. It then maps these types to Solidity type aliases, where each
* type is represented as a `bytes32`. Additionally, it includes predefined aliases for
* `externalEXXX`, which is represented as `bytes32`.
*
* @param fheTypes - An array of FHE types to generate Solidity type aliases from.
* @returns A string containing the Solidity type aliases, each on a new line.
*/
export function createSolidityTypeAliasesFromFheTypes(fheTypes: FheType[]): string {
let res = fheTypes
.filter((fheType: FheType) => fheType.supportedOperators.length > 0)
.map((fheType: FheType) => `type e${fheType.type.toLowerCase()} is bytes32;`);
res = res.concat(
fheTypes
.map((fheType: FheType) =>
(fheType.aliases?.filter((fheTypeAlias: AliasFheType) => fheTypeAlias.supportedOperators.length > 0) ?? []).map(
(fheTypeAlias: AliasFheType) => `type e${fheTypeAlias.type.toLowerCase()} is bytes32;`,
),
)
.flat(),
);
res = res.concat(
fheTypes
.filter((fheType: FheType) => fheType.supportedOperators.length > 0)
.map((fheType: FheType) => `type externalE${fheType.type.toLowerCase()} is bytes32;`),
);
res = res.concat(
fheTypes
.map((fheType: FheType) =>
(fheType.aliases?.filter((fheTypeAlias: AliasFheType) => fheTypeAlias.supportedOperators.length > 0) ?? []).map(
(fheTypeAlias: AliasFheType) => `type externalE${fheTypeAlias.type.toLowerCase()} is bytes32;`,
),
)
.flat(),
);
return res.join('\n');
}
/**
* Generates a Solidity enum definition from an array of FheType objects.
*
* @param {FheType[]} fheTypes - An array of FheType objects to be converted into a Solidity enum.
* @returns {string} A string representing the Solidity enum definition.
*/
export function createSolidityEnumFromFheTypes(fheTypes: FheType[]): string {
return `enum FheType {
${fheTypes
.map((fheType: FheType, index: number) => `${fheType.type}${index < fheTypes.length - 1 ? ',' : ''}`)
.join('\n')}
}`;
}
export function generateSolidityFheType(fheTypes: FheType[]): string {
return `
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;
${createSolidityEnumFromFheTypes(fheTypes)}
`;
}
/**
* Generates an array of adjusted FHE (Fully Homomorphic Encryption) types based on the provided FHE types.
*
* This function processes an array of `FheType` objects and creates a new array of `AdjustedFheType` objects.
* It includes the original FHE types that have supported operators and also processes their aliases, if any,
* to include them in the adjusted array with additional alias-specific properties.
*
* @param fheTypes - An array of `FheType` objects to be adjusted.
*
* @returns An array of `AdjustedFheType` objects containing the adjusted FHE types and their aliases.
*
* @remarks
* - Only FHE types with supported operators are included in the result.
* - Aliases are processed separately and marked with the `isAlias` property.
* - The `aliasType` property indicates the original type for an alias.
* - The `clearMatchingTypeAlias` property is included for aliases to reference the original clear matching type.
*/
function generateAdjustedFheTypeArray(fheTypes: FheType[]): AdjustedFheType[] {
let adjustedFheTypes: AdjustedFheType[] = [];
for (let i = 0; i < fheTypes.length; i++) {
const fheType = fheTypes[i];
if (fheType.supportedOperators.length > 0) {
adjustedFheTypes.push({
type: fheType.type,
bitLength: fheType.bitLength,
supportedOperators: fheType.supportedOperators,
clearMatchingType: fheType.clearMatchingType,
value: fheType.value,
});
}
if (fheType.aliases !== undefined && fheType.aliases.length > 0) {
for (let i = 0; i < fheType.aliases.length; i++) {
if (fheType.aliases[i].supportedOperators.length > 0) {
adjustedFheTypes.push({
type: fheType.aliases[i].type,
bitLength: fheType.bitLength,
supportedOperators: fheType.aliases[i].supportedOperators,
clearMatchingType: fheType.aliases[i].clearMatchingType,
value: fheType.value,
isAlias: true,
aliasType: fheType.type,
clearMatchingTypeAlias: fheType.clearMatchingType,
});
}
}
}
}
return adjustedFheTypes;
}
/**
* Generates the implementation of a binary operator function for Impl.sol.
*
* @param op - The operator for which the implementation is generated.
* @returns The string representation of the binary operator function.
*/
function handleSolidityBinaryOperatorForImpl(op: Operator): string {
const scalarArg = op.hasScalar && op.hasEncrypted ? ', bool scalar' : '';
const scalarByte = op.hasScalar ? '0x01' : '0x00';
const scalarSection =
op.hasScalar && op.hasEncrypted
? `bytes1 scalarByte;
if (scalar) {
scalarByte = 0x01;
} else {
scalarByte = 0x00;
}`
: `bytes1 scalarByte = ${scalarByte};`;
return (
`
function ${op.name}(bytes32 lhs, bytes32 rhs${scalarArg}) internal returns (bytes32 result) {
${scalarSection}
FHEVMConfigStruct storage $ = getFHEVMConfig();
result = IFHEVMExecutor($.FHEVMExecutorAddress).${op.fheLibName}(lhs, rhs, scalarByte);
}` + '\n'
);
}
/**
* Generates the Solidity implementation (Impl.sol) library for FHE operations.
*
* @param operators - An array of Operator objects representing the supported operations.
* @returns A string containing the Solidity implementation library code.
*/
export function generateSolidityImplLib(operators: Operator[]): string {
const res: string[] = [];
res.push(`
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;
import {FheType} from "./FheType.sol";
${generateImplCoprocessorInterface(operators)}
${generateACLInterface()}
${generateInputVerifierInterface()}
/**
* @title Impl
* @notice This library is the core implementation for computing FHE operations (e.g. add, sub, xor).
*/
library Impl {
/// keccak256(abi.encode(uint256(keccak256("fhevm.storage.FHEVMConfig")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant FHEVMConfigLocation = 0xed8d60e34876f751cc8b014c560745351147d9de11b9347c854e881b128ea600;
/// keccak256(abi.encode(uint256(keccak256("fhevm.storage.DecryptionRequests")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant DecryptionRequestsStorageLocation = 0x5ea69329017273582817d320489fbd94f775580e90c092699ca6f3d12fdf7d00;
/**
* @dev Returns the FHEVM config.
*/
function getFHEVMConfig() internal pure returns (FHEVMConfigStruct storage $) {
assembly {
$.slot := FHEVMConfigLocation
}
}
/**
* @dev Returns the DecryptionRequestsStruct storage struct.
*/
function getDecryptionRequests() internal pure returns (DecryptionRequestsStruct storage $) {
assembly {
$.slot := DecryptionRequestsStorageLocation
}
}
/**
* @notice Sets the coprocessor addresses.
* @param fhevmConfig FHEVM config struct that contains contract addresses.
*/
function setCoprocessor(FHEVMConfigStruct memory fhevmConfig) internal {
FHEVMConfigStruct storage $ = getFHEVMConfig();
$.ACLAddress = fhevmConfig.ACLAddress;
$.FHEVMExecutorAddress = fhevmConfig.FHEVMExecutorAddress;
$.KMSVerifierAddress = fhevmConfig.KMSVerifierAddress;
$.InputVerifierAddress = fhevmConfig.InputVerifierAddress;
}
/**
* @notice Sets the decryption oracle address.
* @param decryptionOracle The decryption oracle address.
*/
function setDecryptionOracle(address decryptionOracle) internal {
DecryptionRequestsStruct storage $ = getDecryptionRequests();
$.DecryptionOracleAddress = decryptionOracle;
}
`);
operators.forEach((op) => {
switch (op.arguments) {
case OperatorArguments.Binary:
res.push(handleSolidityBinaryOperatorForImpl(op));
break;
case OperatorArguments.Unary:
res.push(handleUnaryOperatorForImpl(op));
break;
}
});
res.push(generateCustomMethodsForImpl());
res.push('}\n');
return res.join('');
}
function generateImplCoprocessorInterface(operators: Operator[]): string {
const res: string[] = [];
res.push(`
/**
* @title FHEVMConfigStruct
* @notice This struct contains all addresses of core contracts, which are needed in a typical dApp.
*/
struct FHEVMConfigStruct {
address ACLAddress;
address FHEVMExecutorAddress;
address KMSVerifierAddress;
address InputVerifierAddress;
}
/**
* @title DecryptionRequestsStruct
* @notice This struct contains the address of the decryption oracle contract,
* the internal counter for requestIDs generated by the dapp,
* and the mapping from internal requestIDs to list of handles requested for decryption.
*/
struct DecryptionRequestsStruct {
address DecryptionOracleAddress;
uint256 counterRequest;
mapping(uint256 => bytes32[]) requestedHandles;
}
/**
* @title IFHEVMExecutor
* @notice This interface contains all functions to conduct FHE operations.
*/
interface IFHEVMExecutor {`);
operators.forEach((op) => {
const tail = 'external returns (bytes32 result);';
let functionArguments: string;
switch (op.arguments) {
case OperatorArguments.Binary:
functionArguments = '(bytes32 lhs, bytes32 rhs, bytes1 scalarByte)';
res.push(`
/**
* @notice Computes ${op.fheLibName} operation.
* @param lhs LHS.
* @param rhs RHS.
* @param scalarByte Scalar byte.
* @return result Result.
*/
function ${op.fheLibName}${functionArguments} ${tail}`);
break;
case OperatorArguments.Unary:
functionArguments = '(bytes32 ct)';
res.push(`
/**
* @notice Computes ${op.fheLibName} operation.
* @param ct Ct
* @return result Result.
*/
function ${op.fheLibName}${functionArguments} ${tail}`);
break;
}
});
res.push(coprocessorInterfaceCustomFunctions());
res.push('}');
return res.join('');
}
function coprocessorInterfaceCustomFunctions(): string {
return `
/**
* @notice Verifies the ciphertext.
* @param inputHandle Input handle.
* @param callerAddress Address of the caller.
* @param inputProof Input proof.
* @param inputType Input type.
* @return result Result.
*/
function verifyCiphertext(bytes32 inputHandle, address callerAddress, bytes memory inputProof, FheType inputType) external returns (bytes32 result);
/**
* @notice Performs the casting to a target type.
* @param ct Value to cast.
* @param toType Target type.
* @return result Result value of the target type.
*/
function cast(bytes32 ct, FheType toType) external returns (bytes32 result);
/**
* @notice Does trivial encryption.
* @param ct Value to encrypt.
* @param toType Target type.
* @return result Result value of the target type.
*/
function trivialEncrypt(uint256 ct, FheType toType) external returns (bytes32 result);
/**
* @notice Does trivial encryption.
* @param ct Value to encrypt.
* @param toType Target type.
* @return result Result value of the target type.
*/
function trivialEncrypt(bytes memory ct, FheType toType) external returns (bytes32 result);
/**
* @notice Computes FHEEq operation.
* @param lhs LHS.
* @param rhs RHS.
* @param scalarByte Scalar byte.
* @return result Result.
*/
function fheEq(bytes32 lhs, bytes memory rhs, bytes1 scalarByte) external returns (bytes32 result);
/**
* @notice Computes FHENe operation.
* @param lhs LHS.
* @param rhs RHS.
* @param scalarByte Scalar byte.
* @return result Result.
*/
function fheNe(bytes32 lhs, bytes memory rhs, bytes1 scalarByte) external returns (bytes32 result);
/**
* @notice Computes FHEIfThenElse operation.
* @param control Control value.
* @param ifTrue If true.
* @param ifFalse If false.
* @return result Result.
*/
function fheIfThenElse(bytes32 control, bytes32 ifTrue, bytes32 ifFalse) external returns (bytes32 result);
/**
* @notice Computes FHERand operation.
* @param randType Type for the random result.
* @return result Result.
*/
function fheRand(FheType randType) external returns (bytes32 result);
/**
* @notice Computes FHERandBounded operation.
* @param upperBound Upper bound value.
* @param randType Type for the random result.
* @return result Result.
*/
function fheRandBounded(uint256 upperBound, FheType randType) external returns (bytes32 result);
`;
}
function generateACLInterface(): string {
return `
/**
* @title IACL.
* @notice This interface contains all functions that are used to conduct operations
* with the ACL contract.
*/
interface IACL {
/**
* @notice Allows the use of handle by address account for this transaction.
* @dev The caller must be allowed to use handle for allowTransient() to succeed.
* If not, allowTransient() reverts.
* The Coprocessor contract can always allowTransient(), contrarily to allow().
* @param ciphertext Ciphertext.
* @param account Address of the account.
*/
function allowTransient(bytes32 ciphertext, address account) external;
/**
* @notice Allows the use of handle for the address account.
* @dev The caller must be allowed to use handle for allow() to succeed. If not, allow() reverts.
* @param handle Handle.
* @param account Address of the account.
*/
function allow(bytes32 handle, address account) external;
/**
* @dev This function removes the transient allowances, which could be useful for integration with
* Account Abstraction when bundling several UserOps calling the FHEVMExecutor Coprocessor.
*/
function cleanTransientStorage() external;
/**
* @notice Returns whether the account is allowed to use the handle, either due to
* allowTransient() or allow().
* @param handle Handle.
* @param account Address of the account.
* @return isAllowed Whether the account can access the handle.
*/
function isAllowed(bytes32 handle, address account) external view returns(bool);
/**
* @notice Allows a list of handles to be decrypted.
* @param handlesList List of handles.
*/
function allowForDecryption(bytes32[] memory handlesList) external;
/**
* @notice Returns wether a handle is allowed to be publicly decrypted.
* @param handle Handle.
* @return isDecryptable Whether the handle can be publicly decrypted.
*/
function isAllowedForDecryption(bytes32 handle) external view returns(bool);
}
`;
}
function generateInputVerifierInterface(): string {
return `
/**
* @title IInputVerifier
* @notice This interface contains the only function required from InputVerifier.
*/
interface IInputVerifier {
/**
* @dev This function removes the transient allowances, which could be useful for integration with
* Account Abstraction when bundling several UserOps calling the FHEVMExecutor Coprocessor.
*/
function cleanTransientStorage() external;
}
`;
}
function generateKMSVerifierInterface(): string {
return `
/**
* @title IKMSVerifier
* @notice This interface contains the only function required from KMSVerifier.
*/
interface IKMSVerifier {
function verifyDecryptionEIP712KMSSignatures(
bytes32[] memory handlesList,
bytes memory decryptedResult,
bytes[] memory signatures
) external returns (bool);
}
`;
}
function generateDecryptionOracleInterface(): string {
return `
/**
* @title IDecryptionOracle
* @notice This interface contains the only function required from DecryptionOracle.
*/
interface IDecryptionOracle {
function requestDecryption(uint256 requestID, bytes32[] calldata ctsHandles, bytes4 callbackSelector) external;
}
`;
}
export function generateSolidityFHELib(operators: Operator[], fheTypes: FheType[]): string {
const res: string[] = [];
res.push(`// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;
import "./Impl.sol";
import {FheType} from "./FheType.sol";
${createSolidityTypeAliasesFromFheTypes(fheTypes)}
${generateKMSVerifierInterface()}
${generateDecryptionOracleInterface()}
/**
* @title FHE
* @notice This library is the interaction point for all smart contract developers
* that interact with the FHEVM protocol.
*/
library FHE {
/// @notice Returned if the input's length is greater than 64 bytes.
error InputLengthAbove64Bytes(uint256 inputLength);
/// @notice Returned if the input's length is greater than 128 bytes.
error InputLengthAbove128Bytes(uint256 inputLength);
/// @notice Returned if the input's length is greater than 256 bytes.
error InputLengthAbove256Bytes(uint256 inputLength);
/// @notice Returned if some handles were already saved for corresponding ID.
error HandlesAlreadySavedForRequestID();
/// @notice Returned if there was not handle found for the requested ID.
error NoHandleFoundForRequestID();
/// @notice Returned if the returned KMS signatures are not valid.
error InvalidKMSSignatures();
/// @notice Returned if the requested handle to be decrypted is not of a supported type.
error UnsupportedHandleType();
/// @notice This event is emitted when requested decryption has been fulfilled.
event DecryptionFulfilled(uint256 indexed requestID);
/**
* @notice Sets the coprocessor addresses.
* @param fhevmConfig FHEVM config struct that contains contract addresses.
*/
function setCoprocessor(FHEVMConfigStruct memory fhevmConfig) internal {
Impl.setCoprocessor(fhevmConfig);
}
/**
* @notice Sets the decryption oracle address.
* @param decryptionOracle The decryption oracle address.
*/
function setDecryptionOracle(address decryptionOracle) internal {
Impl.setDecryptionOracle(decryptionOracle);
}
`);
// 1. Exclude types that do not support any operators.
const adjustedFheTypes = generateAdjustedFheTypeArray(fheTypes);
// 2. Generate isInitialized function for all supported types
adjustedFheTypes.forEach((fheType: AdjustedFheType) => {
res.push(handleSolidityTFHEIsInitialized(fheType));
});
// 3. Handle encrypted operators for two encrypted types
adjustedFheTypes.forEach((lhsFheType: AdjustedFheType) => {
adjustedFheTypes.forEach((rhsFheType: AdjustedFheType) => {
operators.forEach((operator) => {
res.push(handleSolidityTFHEEncryptedOperatorForTwoEncryptedTypes(lhsFheType, rhsFheType, operator));
});
});
});
// 4. Handle scalar operators for all supported types
adjustedFheTypes.forEach((fheType: AdjustedFheType) => {
operators.forEach((operator) => {
res.push(generateSolidityTFHEScalarOperator(fheType, operator));
});
});
// 5. Handle shift & rotate operators for all supported types
adjustedFheTypes.forEach((fheType: AdjustedFheType) => {
operators.forEach((operator) => {
res.push(handleSolidityTFHEShiftOperator(fheType, operator));
});
});
// 6. Handle ternary operator (i.e., select) for all supported types
adjustedFheTypes.forEach((fheType: AdjustedFheType) => res.push(handleSolidityTFHESelect(fheType)));
// 7. Handle custom casting (1) between euint types and (2) between an euint type and ebool.
adjustedFheTypes.forEach((outputFheType: AdjustedFheType) => {
adjustedFheTypes.forEach((inputFheType: AdjustedFheType) => {
res.push(handleSolidityTFHECustomCastBetweenTwoEuint(inputFheType, outputFheType));
});
res.push(handleSolidityTFHECustomCastBetweenEboolAndEuint(outputFheType));
});
// 8. Handle unary operators for all supported types.
adjustedFheTypes.forEach((fheType: AdjustedFheType) =>
res.push(handleSolidityTFHEUnaryOperators(fheType, operators)),
);
// 9. Handle conversion from plaintext and externalEXXX to all supported types (e.g., externalEbool --> ebool, bytes memory --> ebytes64, uint32 --> euint32)
adjustedFheTypes.forEach((fheType: AdjustedFheType) =>
res.push(handleSolidityTFHEConvertPlaintextAndEinputToRespectiveType(fheType)),
);
// 10. Handle rand/randBounded for all supported types
adjustedFheTypes.forEach((fheType: AdjustedFheType) => res.push(handleSolidityTFHERand(fheType)));
// 11. Add padding to bytes for all ebytes types
adjustedFheTypes.forEach((fheType: AdjustedFheType) => res.push(handleTFHEPadToBytesForEbytes(fheType)));
// 12. Push ACL Solidity methods
res.push(generateSolidityACLMethods(adjustedFheTypes));
// 12. Push DecryptionOracle Solidity methods
res.push(generateSolidityDecryptionOracleMethods(adjustedFheTypes));
res.push('}\n');
return res.join('');
}
function handleSolidityTFHEEncryptedOperatorForTwoEncryptedTypes(
lhsFheType: AdjustedFheType,
rhsFheType: AdjustedFheType,
operator: Operator,
): string {
const res: string[] = [];
if (operator.shiftOperator || operator.rotateOperator) {
return '';
}
if (!operator.hasEncrypted || operator.arguments != OperatorArguments.Binary) {
return '';
}
if (
!lhsFheType.supportedOperators.includes(operator.name) ||
!rhsFheType.supportedOperators.includes(operator.name)
) {
return '';
}
if (lhsFheType.type.startsWith('Uint') && rhsFheType.type.startsWith('Uint')) {
// Determine the maximum number of bits between lhsBits and rhsBits
const outputBits = Math.max(lhsFheType.bitLength, rhsFheType.bitLength);
const castLeftToRight = lhsFheType.bitLength < rhsFheType.bitLength;
const castRightToLeft = lhsFheType.bitLength > rhsFheType.bitLength;
const returnType =
operator.returnType == ReturnType.Euint
? `euint${outputBits}`
: operator.returnType == ReturnType.Ebool
? `ebool`
: assert(false, 'Unknown return type');
const scalarFlag = operator.hasEncrypted && operator.hasScalar ? ', false' : '';
const leftExpr = castLeftToRight ? `asEuint${outputBits}(a)` : 'a';
const rightExpr = castRightToLeft ? `asEuint${outputBits}(b)` : 'b';
let implExpression = `Impl.${operator.name}(euint${outputBits}.unwrap(${leftExpr}), euint${outputBits}.unwrap(${rightExpr})${scalarFlag})`;
res.push(`
/**
* @dev Evaluates ${operator.name}(e${lhsFheType.type.toLowerCase()} a, e${rhsFheType.type.toLowerCase()} b) and returns the result.
*/
function ${operator.name}(e${lhsFheType.type.toLowerCase()} a, e${rhsFheType.type.toLowerCase()} b) internal returns (${returnType}) {
if (!isInitialized(a)) {
a = asE${lhsFheType.type.toLowerCase()}(0);
}
if (!isInitialized(b)) {
b = asE${rhsFheType.type.toLowerCase()}(0);
}
return ${returnType}.wrap(${implExpression});
}
`);
} else if (lhsFheType.type === 'Bool' && rhsFheType.type === 'Bool') {
res.push(`
/**
* @dev Evaluates ${operator.name}(ebool a, ebool b) and returns the result.
*/
function ${operator.name}(ebool a, ebool b) internal returns (ebool) {
if (!isInitialized(a)) {
a = asEbool(false);
}
if (!isInitialized(b)) {
b = asEbool(false);
}
return ebool.wrap(Impl.${operator.name}(ebool.unwrap(a), ebool.unwrap(b), false));
}
`);
} else if (lhsFheType.type == rhsFheType.type && rhsFheType.type.startsWith('Bytes')) {
const bytesLength = lhsFheType.bitLength / 8;
res.push(`
/**
* @dev Evaluates ${operator.name}(e${lhsFheType.type.toLowerCase()} a, e${rhsFheType.type.toLowerCase()} b) and returns the result.
*/
function ${operator.name}(e${lhsFheType.type.toLowerCase()} a, e${rhsFheType.type.toLowerCase()} b) internal returns (ebool) {
if (!isInitialized(a)) {
a = asEbytes${bytesLength}(padToBytes${bytesLength}(hex""));
}
if (!isInitialized(b)) {
b = asEbytes${bytesLength}(padToBytes${bytesLength}(hex""));
}
return ebool.wrap(Impl.${operator.name}(e${lhsFheType.type.toLowerCase()}.unwrap(a), e${rhsFheType.type.toLowerCase()}.unwrap(b), false));
}
`);
} else if (lhsFheType.type.startsWith('Address') && rhsFheType.type.startsWith('Address')) {
res.push(`
/**
* @dev Evaluates ${operator.name}(eaddress a, eaddress b) and returns the result.
*/
function ${operator.name}(eaddress a, eaddress b) internal returns (ebool) {
if (!isInitialized(a)) {
a = asEaddress(address(0));
}
if (!isInitialized(b)) {
b = asEaddress(address(0));
}
return ebool.wrap(Impl.${operator.name}(eaddress.unwrap(a), eaddress.unwrap(b), false));
}
`);
} else if (lhsFheType.type.startsWith('Int') && rhsFheType.type.startsWith('Int')) {
throw new Error('Int types are not supported!');
}
return res.join('');
}
function generateSolidityTFHEScalarOperator(fheType: AdjustedFheType, operator: Operator): string {
const res: string[] = [];
if (operator.shiftOperator || operator.rotateOperator) {
return '';
}
if (operator.arguments != OperatorArguments.Binary) {
return '';
}
if (!operator.hasScalar) {
return '';
}
if (!fheType.supportedOperators.includes(operator.name)) {
return '';
}
const returnType =
operator.returnType == ReturnType.Euint
? `e${fheType.type.toLowerCase()} `
: operator.returnType == ReturnType.Ebool
? `ebool`
: assert(false, 'Unknown return type');
let scalarFlag = operator.hasEncrypted && operator.hasScalar ? ', true' : '';
const leftOpName = operator.leftScalarInvertOp ?? operator.name;
let implExpressionA;
if (fheType.type == 'Bool') {
implExpressionA = `Impl.${operator.name}(e${fheType.type.toLowerCase()}.unwrap(a), bytes32(uint256(b?1:0))${scalarFlag})`;
} else if (fheType.type.startsWith('Bytes')) {
implExpressionA = `Impl.${operator.name}(e${fheType.type.toLowerCase()}.unwrap(a), b${scalarFlag})`;
} else if (fheType.type.startsWith('Int')) {
throw new Error('Int types are not supported!');
} else {
implExpressionA = `Impl.${operator.name}(e${fheType.type.toLowerCase()}.unwrap(a), bytes32(uint256(${
fheType.isAlias && fheType.clearMatchingTypeAlias !== undefined
? `${fheType.clearMatchingTypeAlias.toLowerCase()}(b)`
: 'b'
}))${scalarFlag})`;
}
let implExpressionB;
if (fheType.type == 'Bool') {
implExpressionB = `Impl.${leftOpName}(e${fheType.type.toLowerCase()}.unwrap(b), bytes32(uint256(a?1:0))${scalarFlag})`;
} else if (fheType.type.startsWith('Bytes')) {
implExpressionB = `Impl.${leftOpName}(e${fheType.type.toLowerCase()}.unwrap(b), a${scalarFlag})`;
} else if (fheType.type.startsWith('Int')) {
throw new Error('Int types are not supported yet!');
} else {
implExpressionB = `Impl.${leftOpName}(e${fheType.type.toLowerCase()}.unwrap(b), bytes32(uint256(${
fheType.isAlias && fheType.clearMatchingTypeAlias !== undefined
? `${fheType.clearMatchingTypeAlias.toLowerCase()}(a)`
: 'a'
}))${scalarFlag})`;
}
let maybeEncryptLeft = '';
if (operator.leftScalarEncrypt) {
// workaround until tfhe-rs left scalar support:
// do the trivial encryption and preserve order of operations
scalarFlag = ', false';
maybeEncryptLeft = `e${fheType.type.toLowerCase()} aEnc = asE${fheType.type.toLowerCase()}(a);`;
implExpressionB = `Impl.${leftOpName}(e${fheType.type.toLowerCase()}.unwrap(aEnc), e${fheType.type.toLowerCase()}.unwrap(b)${scalarFlag})`;
}
const clearMatchingType =
fheType.type === 'Address'
? fheType.clearMatchingType
: fheType.isAlias && fheType.clearMatchingTypeAlias !== undefined
? fheType.clearMatchingTypeAlias
: fheType.clearMatchingType;
// rhs scalar
res.push(`
/**
* @dev Evaluates ${operator.name}(e${fheType.type.toLowerCase()} a, ${clearMatchingType.toLowerCase()} b) and returns the result.
*/
function ${operator.name}(e${fheType.type.toLowerCase()} a, ${clearMatchingType.toLowerCase()} b) internal returns (${returnType}) {
if (!isInitialized(a)) {
a = asE${fheType.type.toLowerCase()}(${
fheType.type == 'Bool'
? 'false'
: fheType.type.startsWith('Bytes')
? `padToBytes${fheType.bitLength / 8}(hex"")`
: fheType.type == 'Address'
? `${clearMatchingType.toLowerCase()}(0)`
: 0
});
}
return ${returnType}.wrap(${implExpressionA});
}
`);
// lhs scalar
if (!operator.leftScalarDisable) {
res.push(`
/**
* @dev Evaluates ${operator.name}(${clearMatchingType.toLowerCase()} a, e${fheType.type.toLowerCase()} b) and returns the result.
*/
function ${operator.name}(${clearMatchingType.toLowerCase()} a, e${fheType.type.toLowerCase()} b) internal returns (${returnType}) {
${maybeEncryptLeft}
if (!isInitialized(b)) {
b = asE${fheType.type.toLowerCase()}(${
fheType.type == 'Bool'
? 'false'
: fheType.type.startsWith('Bytes')
? `padToBytes${fheType.bitLength / 8}(hex"")`
: fheType.type == 'Address'
? `${clearMatchingType.toLowerCase()}(0)`
: 0
});
}
return ${returnType}.wrap(${implExpressionB});
}
`);
}
return res.join('');
}
function handleSolidityTFHEIsInitialized(fheType: AdjustedFheType): string {
return `
/**
* @dev Returns true if the encrypted integer is initialized and false otherwise.
*/
function isInitialized(e${fheType.type.toLowerCase()} v) internal pure returns (bool) {
return e${fheType.type.toLowerCase()}.unwrap(v) != 0;
}
`;
}
function handleSolidityTFHEShiftOperator(fheType: AdjustedFheType, operator: Operator): string {
const res: string[] = [];
if (!operator.shiftOperator && !operator.rotateOperator) {
return res.join();
}
if (fheType.supportedOperators.includes(operator.name)) {
const lhsBits = fheType.bitLength;
const rhsBits = 8;
const castRightToLeft = lhsBits > rhsBits;
let scalarFlag = ', false';
const leftExpr = 'a';
const rightExpr = castRightToLeft ? `asE${fheType.type.toLowerCase()}(b)` : 'b';
let implExpression: string = `Impl.${operator.name}(e${fheType.type.toLowerCase()}.unwrap(${leftExpr}), e${fheType.type.toLowerCase()}.unwrap(${rightExpr})${scalarFlag})`;
res.push(`
/**
* @dev Evaluates ${operator.name}(euint${lhsBits} a, euint${rhsBits} b) and returns the result.
*/
function ${operator.name}(euint${lhsBits} a, euint${rhsBits} b) internal returns (e${fheType.type.toLowerCase()}) {
if (!isInitialized(a)) {
a = asEuint${lhsBits}(0);
}
if (!isInitialized(b)) {
b = asEuint${rhsBits}(0);
}
return e${fheType.type.toLowerCase()}.wrap(${implExpression});
}
`);
// Code and test for shift(euint{inputBits},uint8}
scalarFlag = ', true';
implExpression = `Impl.${operator.name}(e${fheType.type.toLowerCase()}.unwrap(a), bytes32(uint256(b))${scalarFlag})`;
res.push(`
/**
* @dev Evaluates ${operator.name}(e${fheType.type.toLowerCase()} a, ${getUint(rhsBits)}) and returns the result.
*/
function ${operator.name}(e${fheType.type.toLowerCase()} a, ${getUint(rhsBits)} b) internal returns (e${fheType.type.toLowerCase()}) {
if (!isInitialized(a)) {
a = asE${fheType.type.toLowerCase()}(0);
}
return e${fheType.type.toLowerCase()}.wrap(${implExpression});
}
`);
}
return res.join('');
}
function handleSolidityTFHESelect(fheType: AdjustedFheType): string {
let res = '';
if (fheType.supportedOperators.includes('select')) {
res += `
/**
* @dev If 'control's value is 'true', the result has the same value as 'ifTrue'.
* If 'control's value is 'false', the result has the same value as 'ifFalse'.
*/
function select(ebool control, e${fheType.type.toLowerCase()} a, e${fheType.type.toLowerCase()} b) internal returns (e${fheType.type.toLowerCase()}) {
return e${fheType.type.toLowerCase()}.wrap(Impl.select(ebool.unwrap(control), e${fheType.type.toLowerCase()}.unwrap(a), e${fheType.type.toLowerCase()}.unwrap(b)));
}`;
}
return res;
}
function handleSolidityTFHECustomCastBetweenTwoEuint(
inputFheType: AdjustedFheType,
outputFheType: AdjustedFheType,
): string {
if (
inputFheType.type == outputFheType.type ||
!inputFheType.type.startsWith('Uint') ||
!outputFheType.type.startsWith('Uint')
) {
return '';
}
return `
/**
* @dev Casts an encrypted integer from 'e${inputFheType.type.toLowerCase()}' to 'e${outputFheType.type.toLowerCase()}'.
*/
function asE${outputFheType.type.toLowerCase()}(e${inputFheType.type.toLowerCase()} value) internal returns (e${outputFheType.type.toLowerCase()}) {
return e${outputFheType.type.toLowerCase()}.wrap(Impl.cast(e${inputFheType.type.toLowerCase()}.unwrap(value), FheType.${outputFheType.type}));
}
`;
}
function handleSolidityTFHECustomCastBetweenEboolAndEuint(fheType: AdjustedFheType): string {
const res: string[] = [];
if (fheType.type.startsWith('Uint')) {
res.push(`
/**
/**
* @dev Converts an 'ebool' to an 'e${fheType.type.toLowerCase()}'.
*/
function asE${fheType.type.toLowerCase()}(ebool b) internal returns (e${fheType.type.toLowerCase()}) {
return e${fheType.type.toLowerCase()}.wrap(Impl.cast(ebool.unwrap(b), FheType.${fheType.type}));
}
`);
if (fheType.supportedOperators.includes('ne')) {
res.push(`
/**
* @dev Casts an encrypted integer from 'e${fheType.type.toLowerCase()}' to 'ebool'.
*/
function asEbool(e${fheType.type.toLowerCase()} value) internal returns (ebool) {
return ne(value, 0);
}
`);
}
}
return res.join('');
}
function handleSolidityTFHEUnaryOperators(fheType: AdjustedFheType, operators: Operator[]): string {
const res: string[] = [];
operators.forEach((op) => {
if (op.arguments == OperatorArguments.Unary && fheType.supportedOperators.includes(op.name)) {
res.push(`
/**
* @dev Evaluates ${op.name}(e${fheType.type.toLowerCase()} value) and returns the result.
*/
function ${op.name}(e${fheType.type.toLowerCase()} value) internal returns (e${fheType.type.toLowerCase()}) {
return e${fheType.type.toLowerCase()}.wrap(Impl.${op.name}(e${fheType.type.toLowerCase()}.unwrap(value)));
}
`);
}
});
return res.join('\n');
}
/**
* Generates Solidity functions to convert plaintext and encrypted input handles to their respective encrypted types.
*
* @param {AdjustedFheType} fheType - The Fully Homomorphic Encryption (FHE) type information.
* @returns {string} - The Solidity code for the conversion functions.
*
* The generated functions include:
* - A function to convert an `einput` handle and its proof to an encrypted type.
* - If the type is `Bool`, an additional function to convert a plaintext boolean to an encrypted boolean.
* - If the type is `Bytes`, an additional function to convert plaintext bytes to the respective encrypted type.
* - For other types, a function to convert a plaintext value to the respective encrypted type.
*/
function handleSolidityTFHEConvertPlaintextAndEinputToRespectiveType(fheType: AdjustedFheType): string {
let result = `
/**
* @dev Convert an inputHandle with corresponding inputProof to an encrypted e${fheType.type.toLowerCase()} integer.
*/
function fromExternal(externalE${fheType.type.toLowerCase()} inputHandle, bytes memory inputProof) internal returns (e${fheType.type.toLowerCase()}) {
return e${fheType.type.toLowerCase()}.wrap(Impl.verify(externalE${fheType.type.toLowerCase()}.unwrap(inputHandle), inputProof, FheType.${fheType.isAlias ? fheType.aliasType : fheType.type}));
}
`;
/// If boolean, add also the asEbool function that allows casting bool
if (fheType.type.startsWith('Bool')) {
result += `
/**
* @dev Converts a plaintext boolean to an encrypted boolean.
*/
function asEbool(bool value) internal returns (ebool) {
return ebool.wrap(Impl.trivialEncrypt(value? 1 : 0, FheType.Bool));
}
`;
} else if (fheType.type.startsWith('Bytes')) {
result += `
/**
* @dev Convert the plaintext bytes to a e${fheType.type.toLowerCase()} value.
*/
function asE${fheType.type.toLowerCase()}(${fheType.clearMatchingTypeAlias} value) internal returns (e${fheType.type.toLowerCase()}) {
return e${fheType.type.toLowerCase()}.wrap(Impl.trivialEncrypt(value, FheType.${fheType.isAlias ? fheType.aliasType : fheType.type}));
}
`;
} else {
const value =
fheType.isAlias && fheType.clearMatchingTypeAlias !== undefined
? `${fheType.clearMatchingTypeAlias.toLowerCase()}(value)`
: 'value';
result += `
/**
* @dev Convert a plaintext value to an encrypted e${fheType.type.toLowerCase()} integer.
*/
function asE${fheType.type.toLowerCase()}(${fheType.clearMatchingType} value) internal returns (e${fheType.type.toLowerCase()}) {
return e${fheType.type.toLowerCase()}.wrap(Impl.trivialEncrypt(uint256(${value}), FheType.${fheType.isAlias ? fheType.aliasType : fheType.type}));
}
`;
}
return result;
}
/**
* Generates the implementation of a unary operator function.
*
* @param op - The operator for which the implementation is generated.
* @returns The string representation of the unary operator function.
*/
function handleUnaryOperatorForImpl(op: Operator): string {
return `
function ${op.name}(bytes32 ct) internal returns (bytes32 result) {
FHEVMConfigStruct storage $ = getFHEVMConfig();
result = IFHEVMExecutor($.FHEVMExecutorAddress).${op.fheLibName}(ct);
}
`;
}
/**
* Generates Solidity ACL (Access Control List) methods for the provided FHE types.
*
* @param {AdjustedFheType[]} fheTypes - An array of FHE types for which to generate the ACL methods.
* @returns {string} A string containing the generated Solidity code for the ACL methods.
*/
function generateSolidityACLMethods(fheTypes: AdjustedFheType[]): string {
const res: string[] = [];
res.push(
`
/**
* @dev This function cleans the transient storage for the ACL (accounts) and the InputVerifier
* (input proofs).
* This could be useful for integration with Account Abstraction when bundling several
* UserOps calling the FHEVMExecutor.
*/
function cleanTransientStorage() internal {
Impl.cleanTransientStorageACL();
Impl.cleanTransientStorageInputVerifier();
}
`,
);
fheTypes.forEach((fheType: AdjustedFheType) =>
res.push(`
/**
* @dev Returns whether the account is allowed to use the value.
*/
function isAllowed(e${fheType.type.toLowerCase()} value, address account) internal view returns (bool) {
return Impl.isAllowed(e${fheType.type.toLowerCase()}.unwrap(value), account);
}
/**
* @dev Returns whether the sender is allowed to use the value.
*/
function isSenderAllowed(e${fheType.type.toLowerCase()} value) internal view returns (bool) {
return Impl.isAllowed(e${fheType.type.toLowerCase()}.unwrap(value), msg.sender);
}
/**
* @dev Allows the use of value for the address account.
*/
function allow(e${fheType.type.toLowerCase()} value, address account) internal returns(e${fheType.type.toLowerCase()}) {
Impl.allow(e${fheType.type.toLowerCase()}.unwrap(value), account);
return value;
}
/**
* @dev Allows the use of value for this address (address(this)).
*/
function allowThis(e${fheType.type.toLowerCase()} value) internal returns(e${fheType.type.toLowerCase()}) {
Impl.allow(e${fheType.type.toLowerCase()}.unwrap(value), address(this));
return value;
}
/**
* @dev Allows the use of value by address account for this transaction.
*/
function allowTransient(e${fheType.type.toLowerCase()} value, address account) internal returns(e${fheType.type.toLowerCase()}) {
Impl.allowTransient(e${fheType.type.toLowerCase()}.unwrap(value), account);
return value;
}
/**
* @dev Makes the value publicly decryptable.
*/
function makePubliclyDecryptable(e${fheType.type.toLowerCase()} value) internal returns(e${fheType.type.toLowerCase()}) {
Impl.makePubliclyDecryptable(e${fheType.type.toLowerCase()}.unwrap(value));
return value;
}
/**
* @dev Returns whether the the value is publicly decryptable.
*/
function isPubliclyDecryptable(e${fheType.type.toLowerCase()} value) internal view returns (bool) {
return Impl.isPubliclyDecryptable(e${fheType.type.toLowerCase()}.unwrap(value));
}
`),
);
return res.join('');
}
/**
* Generates Solidity DecryptionOracle methods for the provided FHE types.
*
* @param {AdjustedFheType[]} fheTypes - An array of FHE types for which to generate the ACL methods.
* @returns {string} A string containing the generated Solidity code for the ACL methods.
*/
function generateSolidityDecryptionOracleMethods(fheTypes: AdjustedFheType[]): string {
const res: string[] = [];
res.push(
`
/**
* @dev Recovers the stored array of handles corresponding to requestID.
*/
function loadRequestedHandles(uint256 requestID) internal view returns (bytes32[] memory) {
DecryptionRequestsStruct storage $ = Impl.getDecryptionRequests();
if ($.requestedHandles[requestID].length == 0) {
revert NoHandleFoundForRequestID();
}
return $.requestedHandles[requestID];
}
/**
* @dev Calls the DecryptionOracle contract to request the decryption of a list of handles.
* @notice Also does the needed call to ACL::allowForDecryption with requested handles.
*/
function requestDecryption(
bytes32[] memory ctsHandles,
bytes4 callbackSelector
) internal returns (uint256 requestID) {
DecryptionRequestsStruct storage $ = Impl.getDecryptionRequests();
requestID = $.counterRequest;
FHEVMConfigStruct storage $$ = Impl.getFHEVMConfig();
IACL($$.ACLAddress).allowForDecryption(ctsHandles);
IDecryptionOracle($.DecryptionOracleAddress).requestDecryption(requestID, ctsHandles, callbackSelector);
saveRequestedHandles(requestID, ctsHandles);
$.counterRequest++;
}
/**
* @dev MUST be called inside the callback function the dApp contract to verify the signatures,
* @dev otherwise fake decryption results could be submitted.
* @notice Warning: MUST be called directly in the callback function called by the relayer.
*/
function checkSignatures(uint256 requestID, bytes[] memory signatures) internal {
bytes32[] memory handlesList = loadRequestedHandles(requestID);
bool isVerified = verifySignatures(handlesList, signatures);
if (!isVerified) {
revert InvalidKMSSignatures();
}
emit DecryptionFulfilled(requestID);
}
/**
* @dev Private low-level function used to link in storage an array of handles to its associated requestID.
*/
function saveRequestedHandles(uint256 requestID, bytes32[] memory handlesList) private {
DecryptionRequestsStruct storage $ = Impl.getDecryptionRequests();
if ($.requestedHandles[requestID].length != 0) {
revert HandlesAlreadySavedForRequestID();
}
$.requestedHandles[requestID] = handlesList;
}
/**
* @dev Private low-level function used to extract the decryptedResult bytes array and verify the KMS signatures.
* @notice Warning: MUST be called directly in the callback function called by the relayer.
*/
function verifySignatures(bytes32[] memory handlesList, bytes[] memory signatures) private returns (bool) {
uint256 start = 4 + 32; // start position after skipping the selector (4 bytes) and the first argument (index, 32 bytes)
uint256 length = getSignedDataLength(handlesList);
bytes memory decryptedResult = new bytes(length);
assembly {
calldatacopy(add(decryptedResult, 0x20), start, length) // Copy the relevant part of calldata to decryptedResult memory
}
FHEVMConfigStruct storage $ = Impl.getFHEVMConfig();
return
IKMSVerifier($.KMSVerifierAddress).verifyDecryptionEIP712KMSSignatures(
handlesList,
decryptedResult,
signatures
);
}
/**
* @dev Private low-level function used to compute the length of the decryptedResult bytes array.
*/
function getSignedDataLength(bytes32[] memory handlesList) private pure returns (uint256) {
uint256 handlesListlen = handlesList.length;
uint256 signedDataLength;
for (uint256 i = 0; i < handlesListlen; i++) {
FheType typeCt = FheType(uint8(handlesList[i][30]));
if (uint8(typeCt) < 9) {
signedDataLength += 32;
} else if (typeCt == FheType.Uint512) {
//ebytes64
signedDataLength += 128;
} else if (typeCt == FheType.Uint1024) {
//ebytes128
signedDataLength += 192;
} else if (typeCt == FheType.Uint2048) {
//ebytes256
signedDataLength += 320;
} else {
revert UnsupportedHandleType();
}
}
signedDataLength += 32; // add offset of signatures
return signedDataLength;
}
`,
);
fheTypes.forEach((fheType: AdjustedFheType) =>
res.push(`
/**
* @dev Converts handle from its custom type to the underlying bytes32. Used when requesting a decryption.
*/
function toBytes32(e${fheType.type.toLowerCase()} value) internal pure returns (bytes32 ct) {
ct = e${fheType.type.toLowerCase()}.unwrap(value);
}
`),
);
return res.join('');
}
function handleTFHEPadToBytesForEbytes(fheType: AdjustedFheType): string {
if (!fheType.type.startsWith('Bytes')) {
return '';
}
const bytesLength = fheType.bitLength / 8;
return `
/**
* @dev Left-pad a bytes array with zeros such that it becomes of length ${bytesLength}.
*/
function padToBytes${bytesLength}(bytes memory input) internal pure returns (bytes memory) {
uint256 inputLength = input.length;
if (inputLength > ${bytesLength}) {
revert InputLengthAbove${bytesLength}Bytes(inputLength);
}
bytes memory result = new bytes(${bytesLength});
uint256 paddingLength = ${bytesLength} - inputLength;
for (uint256 i = 0; i < paddingLength; i++) {
result[i] = 0;
}
for (uint256 i = 0; i < inputLength; i++) {
result[paddingLength + i] = input[i];
}
return result;
}
`;
}
function generateCustomMethodsForImpl(): string {
return `
/**
* @dev If 'control's value is 'true', the result has the same value as 'ifTrue'.
* If 'control's value is 'false', the result has the same value as 'ifFalse'.
*/
function select(bytes32 control, bytes32 ifTrue, bytes32 ifFalse) internal returns (bytes32 result) {
FHEVMConfigStruct storage $ = getFHEVMConfig();
result = IFHEVMExecutor($.FHEVMExecutorAddress).fheIfThenElse(control, ifTrue, ifFalse);
}
/**
* @notice Verifies the ciphertext (FHEVMExecutor) and allows transient (ACL).
* @param inputHandle Input handle.
* @param inputProof Input proof.
* @param toType Input type.
* @return result Result.
*/
function verify(
bytes32 inputHandle,
bytes memory inputProof,
FheType toType
) internal returns (bytes32 result) {
FHEVMConfigStruct storage $ = getFHEVMConfig();
result = IFHEVMExecutor($.FHEVMExecutorAddress).verifyCiphertext(inputHandle, msg.sender, inputProof, toType);
IACL($.ACLAddress).allowTransient(result, msg.sender);
}
/**
* @notice Performs the casting to a target type.
* @param ciphertext Ciphertext to cast.
* @param toType Target type.
* @return result Result value of the target type.
*/
function cast(
bytes32 ciphertext,
FheType toType
) internal returns (bytes32 result) {
FHEVMConfigStruct storage $ = getFHEVMConfig();
result = IFHEVMExecutor($.FHEVMExecutorAddress).cast(ciphertext, toType);
}
/**
* @notice Does trivial encryption.
* @param value Value to encrypt.
* @param toType Target type.
* @return result Result value of the target type.
*/
function trivialEncrypt(
uint256 value,
FheType toType
) internal returns (bytes32 result) {
FHEVMConfigStruct storage $ = getFHEVMConfig();
result = IFHEVMExecutor($.FHEVMExecutorAddress).trivialEncrypt(value, toType);
}
/**
* @notice Does trivial encryption.
* @param value Value to encrypt.
* @param toType Target type.
* @return result Result value of the target type.
*/
function trivialEncrypt(
bytes memory value,
FheType toType
) internal returns (bytes32 result) {
FHEVMConfigStruct storage $ = getFHEVMConfig();
result = IFHEVMExecutor($.FHEVMExecutorAddress).trivialEncrypt(value, toType);
}
/**
* @notice Computes FHEEq operation.
* @param lhs LHS.
* @param rhs RHS.
* @param scalar Scalar byte.
* @return result Result.
*/
function eq(bytes32 lhs, bytes memory rhs, bool scalar) internal returns (bytes32 result) {
bytes1 scalarByte;
if (scalar) {
scalarByte = 0x01;
} else {
scalarByte = 0x00;
}
FHEVMConfigStruct storage $ = getFHEVMConfig();
result = IFHEVMExecutor($.FHEVMExecutorAddress).fheEq(lhs, rhs, scalarByte);
}
/**
* @notice Computes FHENe operation.
* @param lhs LHS.
* @param rhs RHS.
* @param scalar Scalar byte.
* @return result Result.
*/
function ne(bytes32 lhs, bytes memory rhs, bool scalar) internal returns (bytes32 result) {
bytes1 scalarByte;
if (scalar) {
scalarByte = 0x01;
} else {
scalarByte = 0x00;
}
FHEVMConfigStruct storage $ = getFHEVMConfig();
result = IFHEVMExecutor($.FHEVMExecutorAddress).fheNe(lhs, rhs, scalarByte);
}
function rand(FheType randType) internal returns(bytes32 result) {
FHEVMConfigStruct storage $ = getFHEVMConfig();
result = IFHEVMExecutor($.FHEVMExecutorAddress).fheRand(randType);
}
function randBounded(uint256 upperBound, FheType randType) internal returns(bytes32 result) {
FHEVMConfigStruct storage $ = getFHEVMConfig();
result = IFHEVMExecutor($.FHEVMExecutorAddress).fheRandBounded(upperBound, randType);
}
/**
* @notice Allows the use of handle by address account for this transaction.
* @dev The caller must be allowed to use handle for allowTransient() to succeed.
* If not, allowTransient() reverts.
* The Coprocessor contract can always allowTransient(), contrarily to allow().
* @param handle Handle.
* @param account Address of the account.
*/
function allowTransient(bytes32 handle, address account) internal {
FHEVMConfigStruct storage $ = getFHEVMConfig();
IACL($.ACLAddress).allowTransient(handle, account);
}
/**
* @notice Allows the use of handle for the address account.
* @dev The caller must be allowed to use handle for allow() to succeed. If not, allow() reverts.
* @param handle Handle.
* @param account Address of the account.
*/
function allow(bytes32 handle, address account) internal {
FHEVMConfigStruct storage $ = getFHEVMConfig();
IACL($.ACLAddress).allow(handle, account);
}
/**
* @notice Allows the handle to be publicly decryptable.
* @dev The caller must be allowed to use handle for makePubliclyDecryptable() to succeed.
* If not, makePubliclyDecryptable() reverts.
* @param handle Handle.
*/
function makePubliclyDecryptable(bytes32 handle) internal {
FHEVMConfigStruct storage $ = getFHEVMConfig();
bytes32[] memory handleArray = new bytes32[](1);
handleArray[0] = handle;
IACL($.ACLAddress).allowForDecryption(handleArray);
}
/**
* @dev This function removes the transient allowances in the ACL, which could be useful for integration
* with Account Abstraction when bundling several UserOps calling the FHEVMExecutor Coprocessor.
*/
function cleanTransientStorageACL() internal {
FHEVMConfigStruct storage $ = getFHEVMConfig();
IACL($.ACLAddress).cleanTransientStorage();
}
/**
* @dev This function removes the transient proofs in the InputVerifier, which could be useful for integration
* with Account Abstraction when bundling several UserOps calling the FHEVMExecutor Coprocessor.
*/
function cleanTransientStorageInputVerifier() internal {
FHEVMConfigStruct storage $ = getFHEVMConfig();
IInputVerifier($.InputVerifierAddress).cleanTransientStorage();
}
/**
* @notice Returns whether the account is allowed to use the handle, either due to
* allowTransient() or allow().
* @param handle Handle.
* @param account Address of the account.
* @return isAllowed Whether the account can access the handle.
*/
function isAllowed(bytes32 handle, address account) internal view returns (bool) {
FHEVMConfigStruct storage $ = getFHEVMConfig();
return IACL($.ACLAddress).isAllowed(handle, account);
}
/**
* @notice Returns whether the handle is allowed to be publicly decrypted.
* @param handle Handle.
* @return isAllowed Whether the handle can be publicly decrypted.
*/
function isPubliclyDecryptable(bytes32 handle) internal view returns (bool) {
FHEVMConfigStruct storage $ = getFHEVMConfig();
return IACL($.ACLAddress).isAllowedForDecryption(handle);
}
`;
}
function handleSolidityTFHERand(fheType: AdjustedFheType): string {
let res = '';
if (fheType.supportedOperators.includes('rand')) {
res += `
/**
* @dev Generates a random encrypted value.
*/
function randE${fheType.type.toLowerCase()}() internal returns (e${fheType.type.toLowerCase()}) {
return e${fheType.type.toLowerCase()}.wrap(Impl.rand(FheType.${fheType.isAlias ? fheType.aliasType : fheType.type}));
}
`;
}
if (fheType.supportedOperators.includes('randBounded')) {
res += `
/**
* @dev Generates a random encrypted ${fheType.bitLength}-bit unsigned integer in the [0, upperBound) range.
* The upperBound must be a power of 2.
*/
function randE${fheType.type.toLowerCase()}(uint${fheType.bitLength} upperBound) internal returns (e${fheType.type.toLowerCase()}) {
return e${fheType.type.toLowerCase()}.wrap(Impl.randBounded(upperBound, FheType.${fheType.isAlias ? fheType.aliasType : fheType.type}));
}
`;
}
return res;
}