// SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; import "./libraries/FheTypesBitSizes.sol"; import "./libraries/HandleOps.sol"; contract DecryptionOracle is UUPSUpgradeable, Ownable2StepUpgradeable { /** * @notice Error indicating that the total bit size of the decryption request exceeds * the maximum allowed. * @param maxBitSize The maximum allowed bit size. * @param totalBitSize The total bit size of the decryption request. */ error MaxDecryptionRequestBitSizeExceeded(uint256 maxBitSize, uint256 totalBitSize); /// @notice Name of the contract string private constant CONTRACT_NAME = "DecryptionOracle"; /// @notice Major version of the contract. uint256 private constant MAJOR_VERSION = 0; /// @notice Minor version of the contract. uint256 private constant MINOR_VERSION = 1; /// @notice Patch version of the contract. uint256 private constant PATCH_VERSION = 0; /// @notice Max number of decryption bits (users or public). uint256 internal constant MAX_DECRYPTION_REQUEST_BITS = 2048; /** * @dev Event emitted during each decryption request. The off-chain gateway service is listening to it. */ event DecryptionRequest( uint256 indexed counter, uint256 requestID, bytes32[] cts, address contractCaller, bytes4 callbackSelector ); /** * @dev Should revert when msg.sender is not authorized to upgrade the contract. */ function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner {} /// @custom:storage-location erc7201:fhevm.storage.DecryptionOracle struct DecryptionOracleStorage { uint256 counter; // tracks the number of decryption requests, and used to compute the requestID by hashing it with the dApp address } function getCounter() external virtual returns (uint256) { DecryptionOracleStorage storage $ = _getDecryptionOracleStorage(); return $.counter; } // keccak256(abi.encode(uint256(keccak256("fhevm.storage.DecryptionOracle")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant DecryptionOracleStorageLocation = 0xd86fa2a52e99634194c279afa011b5f5166614c3198dd09bbd002d5fb5c0bc00; /** * @dev Returns the DecryptionOracle storage location. */ function _getDecryptionOracleStorage() internal pure returns (DecryptionOracleStorage storage $) { assembly { $.slot := DecryptionOracleStorageLocation } } /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } /** * @notice Re-initializes the contract. */ /// @custom:oz-upgrades-validate-as-initializer function reinitialize() public virtual reinitializer(2) { __Ownable_init(owner()); } /** @notice Requests the decryption of n ciphertexts `ctsHandles` with the result returned in a callback. * @notice During callback, msg.sender is called with [callbackSelector,requestID,decrypt(ctsHandles[0]),decrypt(ctsHandles[1]),...,decrypt(ctsHandles[n-1]),signatures] * @param requestID is the request index generated by the dApp requesting the decryption. * @param ctsHandles is an array of uint256s handles. * @param callbackSelector the callback selector to be called on msg.sender later during fulfilment */ function requestDecryption( uint256 requestID, bytes32[] calldata ctsHandles, bytes4 callbackSelector ) external virtual { _checkCtHandlesConformancePublic(ctsHandles); DecryptionOracleStorage storage $ = _getDecryptionOracleStorage(); emit DecryptionRequest($.counter, requestID, ctsHandles, msg.sender, callbackSelector); $.counter++; } /// @notice Check the handles' conformance for public decryption requests. /// @dev Checks include: /// @dev - Total bit size for each handle /// @dev - FHE type validity for each handle /// @dev - Handles are allowed for public decryption /// @param ctHandles The list of ciphertext handles function _checkCtHandlesConformancePublic(bytes32[] memory ctHandles) internal view virtual { uint256 totalBitSize = 0; for (uint256 i = 0; i < ctHandles.length; i++) { bytes32 ctHandle = ctHandles[i]; /// @dev Extract the FHE type from the ciphertext handle FheType fheType = HandleOps.extractFheType(ctHandle); /// @dev Add the bit size of the FHE type to the total bit size /// @dev This reverts if the FHE type is invalid or not supported. totalBitSize += FHETypeBitSizes.getBitSize(fheType); } /// @dev Revert if the total bit size exceeds the maximum allowed. if (totalBitSize > MAX_DECRYPTION_REQUEST_BITS) { revert MaxDecryptionRequestBitSizeExceeded(MAX_DECRYPTION_REQUEST_BITS, totalBitSize); } } /** * @notice Getter for the name and version of the contract. * @return string Name and the version of the contract. */ function getVersion() external pure virtual returns (string memory) { return string( abi.encodePacked( CONTRACT_NAME, " v", Strings.toString(MAJOR_VERSION), ".", Strings.toString(MINOR_VERSION), ".", Strings.toString(PATCH_VERSION) ) ); } }