diff --git a/CHANGELOG.md b/CHANGELOG.md index bd19fc1..9eb0054 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## [Unreleased] ### Added +- `isPubliclyAllowed(uint256 ctHash)` view function on `TaskManager` to query whether a ciphertext handle has been publicly allowed (via `allowGlobal` / `allowPublic`). Delegates to `acl.globalAllowed()`. +- `FHE.isPubliclyAllowed()` typed overloads for all encrypted types (`ebool`, `euint8`, ..., `eaddress`) so contracts can query public-allow status directly via the FHE library. - `publishDecryptResult()` and `publishDecryptResultBatch()` on TaskManager for publishing signed decrypt results on-chain - `verifyDecryptResult()` (reverts on invalid) and `verifyDecryptResultSafe()` (returns false) for signature verification without publishing - `decryptResultSigner` state variable and `setDecryptResultSigner()` admin function diff --git a/contracts/FHE.sol b/contracts/FHE.sol index 52e29ba..15571aa 100644 --- a/contracts/FHE.sol +++ b/contracts/FHE.sol @@ -3047,6 +3047,55 @@ library FHE { ITaskManager(TASK_MANAGER_ADDRESS).allowGlobal(uint256(eaddress.unwrap(ctHash))); } + /// @notice Grants public permission to operate on the encrypted boolean value + /// @dev Allows all accounts to access the ciphertext + /// @param ctHash The encrypted boolean value to grant public access to + function allowPublic(ebool ctHash) internal { + ITaskManager(TASK_MANAGER_ADDRESS).allowGlobal(ebool.unwrap(ctHash)); + } + + /// @notice Grants public permission to operate on the encrypted 8-bit unsigned integer + /// @dev Allows all accounts to access the ciphertext + /// @param ctHash The encrypted uint8 value to grant public access to + function allowPublic(euint8 ctHash) internal { + ITaskManager(TASK_MANAGER_ADDRESS).allowGlobal(euint8.unwrap(ctHash)); + } + + /// @notice Grants public permission to operate on the encrypted 16-bit unsigned integer + /// @dev Allows all accounts to access the ciphertext + /// @param ctHash The encrypted uint16 value to grant public access to + function allowPublic(euint16 ctHash) internal { + ITaskManager(TASK_MANAGER_ADDRESS).allowGlobal(euint16.unwrap(ctHash)); + } + + /// @notice Grants public permission to operate on the encrypted 32-bit unsigned integer + /// @dev Allows all accounts to access the ciphertext + /// @param ctHash The encrypted uint32 value to grant public access to + function allowPublic(euint32 ctHash) internal { + ITaskManager(TASK_MANAGER_ADDRESS).allowGlobal(euint32.unwrap(ctHash)); + } + + /// @notice Grants public permission to operate on the encrypted 64-bit unsigned integer + /// @dev Allows all accounts to access the ciphertext + /// @param ctHash The encrypted uint64 value to grant public access to + function allowPublic(euint64 ctHash) internal { + ITaskManager(TASK_MANAGER_ADDRESS).allowGlobal(euint64.unwrap(ctHash)); + } + + /// @notice Grants public permission to operate on the encrypted 128-bit unsigned integer + /// @dev Allows all accounts to access the ciphertext + /// @param ctHash The encrypted uint128 value to grant public access to + function allowPublic(euint128 ctHash) internal { + ITaskManager(TASK_MANAGER_ADDRESS).allowGlobal(euint128.unwrap(ctHash)); + } + + /// @notice Grants public permission to operate on the encrypted address + /// @dev Allows all accounts to access the ciphertext + /// @param ctHash The encrypted address value to grant public access to + function allowPublic(eaddress ctHash) internal { + ITaskManager(TASK_MANAGER_ADDRESS).allowGlobal(eaddress.unwrap(ctHash)); + } + /// @notice Checks if an account has permission to operate on the encrypted boolean value /// @dev Returns whether the specified account can access the ciphertext /// @param ctHash The encrypted boolean value to check access for @@ -3111,6 +3160,55 @@ library FHE { return ITaskManager(TASK_MANAGER_ADDRESS).isAllowed(uint256(eaddress.unwrap(ctHash)), account); } + /// @notice Checks if an encrypted boolean value is publicly (globally) allowed + /// @param ctHash The encrypted boolean value to check + /// @return True if the ciphertext is publicly allowed, false otherwise + function isPubliclyAllowed(ebool ctHash) internal view returns (bool) { + return ITaskManager(TASK_MANAGER_ADDRESS).isPubliclyAllowed(uint256(ebool.unwrap(ctHash))); + } + + /// @notice Checks if an encrypted 8-bit unsigned integer is publicly (globally) allowed + /// @param ctHash The encrypted uint8 value to check + /// @return True if the ciphertext is publicly allowed, false otherwise + function isPubliclyAllowed(euint8 ctHash) internal view returns (bool) { + return ITaskManager(TASK_MANAGER_ADDRESS).isPubliclyAllowed(uint256(euint8.unwrap(ctHash))); + } + + /// @notice Checks if an encrypted 16-bit unsigned integer is publicly (globally) allowed + /// @param ctHash The encrypted uint16 value to check + /// @return True if the ciphertext is publicly allowed, false otherwise + function isPubliclyAllowed(euint16 ctHash) internal view returns (bool) { + return ITaskManager(TASK_MANAGER_ADDRESS).isPubliclyAllowed(uint256(euint16.unwrap(ctHash))); + } + + /// @notice Checks if an encrypted 32-bit unsigned integer is publicly (globally) allowed + /// @param ctHash The encrypted uint32 value to check + /// @return True if the ciphertext is publicly allowed, false otherwise + function isPubliclyAllowed(euint32 ctHash) internal view returns (bool) { + return ITaskManager(TASK_MANAGER_ADDRESS).isPubliclyAllowed(uint256(euint32.unwrap(ctHash))); + } + + /// @notice Checks if an encrypted 64-bit unsigned integer is publicly (globally) allowed + /// @param ctHash The encrypted uint64 value to check + /// @return True if the ciphertext is publicly allowed, false otherwise + function isPubliclyAllowed(euint64 ctHash) internal view returns (bool) { + return ITaskManager(TASK_MANAGER_ADDRESS).isPubliclyAllowed(uint256(euint64.unwrap(ctHash))); + } + + /// @notice Checks if an encrypted 128-bit unsigned integer is publicly (globally) allowed + /// @param ctHash The encrypted uint128 value to check + /// @return True if the ciphertext is publicly allowed, false otherwise + function isPubliclyAllowed(euint128 ctHash) internal view returns (bool) { + return ITaskManager(TASK_MANAGER_ADDRESS).isPubliclyAllowed(uint256(euint128.unwrap(ctHash))); + } + + /// @notice Checks if an encrypted address is publicly (globally) allowed + /// @param ctHash The encrypted address value to check + /// @return True if the ciphertext is publicly allowed, false otherwise + function isPubliclyAllowed(eaddress ctHash) internal view returns (bool) { + return ITaskManager(TASK_MANAGER_ADDRESS).isPubliclyAllowed(uint256(eaddress.unwrap(ctHash))); + } + /// @notice Grants permission to the current contract to operate on the encrypted boolean value /// @dev Allows this contract to access the ciphertext /// @param ctHash The encrypted boolean value to grant access to @@ -3495,12 +3593,18 @@ library BindingsEbool { function isAllowed(ebool ctHash, address account) internal returns (bool) { return FHE.isAllowed(ctHash, account); } + function isPubliclyAllowed(ebool ctHash) internal view returns (bool) { + return FHE.isPubliclyAllowed(ctHash); + } function allowThis(ebool ctHash) internal { FHE.allowThis(ctHash); } function allowGlobal(ebool ctHash) internal { FHE.allowGlobal(ctHash); } + function allowPublic(ebool ctHash) internal { + FHE.allowPublic(ctHash); + } function allowSender(ebool ctHash) internal { FHE.allowSender(ctHash); } @@ -3731,12 +3835,18 @@ library BindingsEuint8 { function isAllowed(euint8 ctHash, address account) internal returns (bool) { return FHE.isAllowed(ctHash, account); } + function isPubliclyAllowed(euint8 ctHash) internal view returns (bool) { + return FHE.isPubliclyAllowed(ctHash); + } function allowThis(euint8 ctHash) internal { FHE.allowThis(ctHash); } function allowGlobal(euint8 ctHash) internal { FHE.allowGlobal(ctHash); } + function allowPublic(euint8 ctHash) internal { + FHE.allowPublic(ctHash); + } function allowSender(euint8 ctHash) internal { FHE.allowSender(ctHash); } @@ -3967,12 +4077,18 @@ library BindingsEuint16 { function isAllowed(euint16 ctHash, address account) internal returns (bool) { return FHE.isAllowed(ctHash, account); } + function isPubliclyAllowed(euint16 ctHash) internal view returns (bool) { + return FHE.isPubliclyAllowed(ctHash); + } function allowThis(euint16 ctHash) internal { FHE.allowThis(ctHash); } function allowGlobal(euint16 ctHash) internal { FHE.allowGlobal(ctHash); } + function allowPublic(euint16 ctHash) internal { + FHE.allowPublic(ctHash); + } function allowSender(euint16 ctHash) internal { FHE.allowSender(ctHash); } @@ -4203,12 +4319,18 @@ library BindingsEuint32 { function isAllowed(euint32 ctHash, address account) internal returns (bool) { return FHE.isAllowed(ctHash, account); } + function isPubliclyAllowed(euint32 ctHash) internal view returns (bool) { + return FHE.isPubliclyAllowed(ctHash); + } function allowThis(euint32 ctHash) internal { FHE.allowThis(ctHash); } function allowGlobal(euint32 ctHash) internal { FHE.allowGlobal(ctHash); } + function allowPublic(euint32 ctHash) internal { + FHE.allowPublic(ctHash); + } function allowSender(euint32 ctHash) internal { FHE.allowSender(ctHash); } @@ -4421,12 +4543,18 @@ library BindingsEuint64 { function isAllowed(euint64 ctHash, address account) internal returns (bool) { return FHE.isAllowed(ctHash, account); } + function isPubliclyAllowed(euint64 ctHash) internal view returns (bool) { + return FHE.isPubliclyAllowed(ctHash); + } function allowThis(euint64 ctHash) internal { FHE.allowThis(ctHash); } function allowGlobal(euint64 ctHash) internal { FHE.allowGlobal(ctHash); } + function allowPublic(euint64 ctHash) internal { + FHE.allowPublic(ctHash); + } function allowSender(euint64 ctHash) internal { FHE.allowSender(ctHash); } @@ -4622,12 +4750,18 @@ library BindingsEuint128 { function isAllowed(euint128 ctHash, address account) internal returns (bool) { return FHE.isAllowed(ctHash, account); } + function isPubliclyAllowed(euint128 ctHash) internal view returns (bool) { + return FHE.isPubliclyAllowed(ctHash); + } function allowThis(euint128 ctHash) internal { FHE.allowThis(ctHash); } function allowGlobal(euint128 ctHash) internal { FHE.allowGlobal(ctHash); } + function allowPublic(euint128 ctHash) internal { + FHE.allowPublic(ctHash); + } function allowSender(euint128 ctHash) internal { FHE.allowSender(ctHash); } @@ -4683,12 +4817,18 @@ library BindingsEaddress { function isAllowed(eaddress ctHash, address account) internal returns (bool) { return FHE.isAllowed(ctHash, account); } + function isPubliclyAllowed(eaddress ctHash) internal view returns (bool) { + return FHE.isPubliclyAllowed(ctHash); + } function allowThis(eaddress ctHash) internal { FHE.allowThis(ctHash); } function allowGlobal(eaddress ctHash) internal { FHE.allowGlobal(ctHash); } + function allowPublic(eaddress ctHash) internal { + FHE.allowPublic(ctHash); + } function allowSender(eaddress ctHash) internal { FHE.allowSender(ctHash); } diff --git a/contracts/ICofhe.sol b/contracts/ICofhe.sol index a7a8906..67518ae 100644 --- a/contracts/ICofhe.sol +++ b/contracts/ICofhe.sol @@ -103,6 +103,7 @@ interface ITaskManager { function allow(uint256 ctHash, address account) external; function isAllowed(uint256 ctHash, address account) external returns (bool); + function isPubliclyAllowed(uint256 ctHash) external view returns (bool); function allowGlobal(uint256 ctHash) external; function allowTransient(uint256 ctHash, address account) external; function getDecryptResultSafe(uint256 ctHash) external view returns (uint256, bool); diff --git a/contracts/internal/host-chain/contracts/TaskManager.sol b/contracts/internal/host-chain/contracts/TaskManager.sol index 1848a67..42aa242 100644 --- a/contracts/internal/host-chain/contracts/TaskManager.sol +++ b/contracts/internal/host-chain/contracts/TaskManager.sol @@ -737,6 +737,10 @@ contract TaskManager is ITaskManager, Initializable, UUPSUpgradeable, Ownable2St return acl.isAllowed(ctHash, account); } + function isPubliclyAllowed(uint256 ctHash) external view returns (bool) { + return acl.globalAllowed(ctHash); + } + function extractSigner(EncryptedInput memory input, address sender) private view returns (address) { bytes memory combined = abi.encodePacked( input.ctHash, diff --git a/contracts/internal/host-chain/contracts/tests/PubliclyAllowedTest.sol b/contracts/internal/host-chain/contracts/tests/PubliclyAllowedTest.sol new file mode 100644 index 0000000..95d854b --- /dev/null +++ b/contracts/internal/host-chain/contracts/tests/PubliclyAllowedTest.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.13 <0.9.0; + +import {FHE, euint8} from "@fhenixprotocol/cofhe-contracts/FHE.sol"; + +contract PubliclyAllowedTest { + euint8 public lastHandle; + + function createAndAllowGlobal(uint8 value) public returns (euint8) { + euint8 encrypted = FHE.asEuint8(value); + FHE.allowGlobal(encrypted); + lastHandle = encrypted; + return encrypted; + } + + function createWithoutGlobal(uint8 value) public returns (euint8) { + euint8 encrypted = FHE.asEuint8(value); + lastHandle = encrypted; + return encrypted; + } +} diff --git a/contracts/internal/host-chain/test/publiclyAllowed/PubliclyAllowed.ts b/contracts/internal/host-chain/test/publiclyAllowed/PubliclyAllowed.ts new file mode 100644 index 0000000..a21fb2f --- /dev/null +++ b/contracts/internal/host-chain/test/publiclyAllowed/PubliclyAllowed.ts @@ -0,0 +1,108 @@ +import hre from "hardhat"; +import { expect } from "chai"; + +const { ethers } = hre; + +const TASK_MANAGER_ADDRESS = "0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9"; + +async function deployProxyAtAddress( + targetAddress: string, + implementationAddress: string, + initData: string +): Promise { + const ERC1967Proxy = await ethers.getContractFactory("ERC1967Proxy"); + const tempProxy = await ERC1967Proxy.deploy(implementationAddress, initData); + await tempProxy.waitForDeployment(); + + const proxyBytecode = await ethers.provider.getCode(await tempProxy.getAddress()); + await ethers.provider.send("hardhat_setCode", [targetAddress, proxyBytecode]); + + const storageSlots = [ + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00", + "0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300", + "0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199301", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x0000000000000000000000000000000000000000000000000000000000000005", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000000000000000000000000000a", + ]; + + const tempAddress = await tempProxy.getAddress(); + for (const slot of storageSlots) { + const value = await ethers.provider.getStorage(tempAddress, slot); + if (value !== "0x0000000000000000000000000000000000000000000000000000000000000000") { + await ethers.provider.send("hardhat_setStorageAt", [targetAddress, slot, value]); + } + } +} + +describe("PubliclyAllowed Tests", function () { + let taskManager: any; + let testContract: any; + + before(async function () { + const [owner] = await ethers.getSigners(); + + const TaskManager = await ethers.getContractFactory("TaskManager"); + const taskManagerImpl = await TaskManager.deploy(); + await taskManagerImpl.waitForDeployment(); + + const initData = TaskManager.interface.encodeFunctionData("initialize", [owner.address]); + await deployProxyAtAddress(TASK_MANAGER_ADDRESS, await taskManagerImpl.getAddress(), initData); + taskManager = TaskManager.attach(TASK_MANAGER_ADDRESS); + + const ACL = await ethers.getContractFactory("ACL"); + const aclImpl = await ACL.deploy(); + await aclImpl.waitForDeployment(); + + const ERC1967Proxy = await ethers.getContractFactory("ERC1967Proxy"); + const aclInitData = ACL.interface.encodeFunctionData("initialize", [owner.address]); + const aclProxy = await ERC1967Proxy.deploy(await aclImpl.getAddress(), aclInitData); + await aclProxy.waitForDeployment(); + + const PlaintextsStorage = await ethers.getContractFactory("PlaintextsStorage"); + const psImpl = await PlaintextsStorage.deploy(); + await psImpl.waitForDeployment(); + const psInitData = PlaintextsStorage.interface.encodeFunctionData("initialize", [owner.address]); + const psProxy = await ERC1967Proxy.deploy(await psImpl.getAddress(), psInitData); + await psProxy.waitForDeployment(); + + await taskManager.setACLContract(await aclProxy.getAddress()); + await taskManager.setPlaintextsStorage(await psProxy.getAddress()); + await taskManager.setSecurityZones(-128, 127); + + const PubliclyAllowedTest = await ethers.getContractFactory("PubliclyAllowedTest"); + testContract = await PubliclyAllowedTest.connect(owner).deploy(); + await testContract.waitForDeployment(); + }); + + describe("isPubliclyAllowed", function () { + it("should return false for a handle that is not globally allowed", async function () { + const tx = await testContract.createWithoutGlobal(42); + await tx.wait(); + const handle = await testContract.lastHandle(); + expect(await taskManager.isPubliclyAllowed(handle)).to.equal(false); + }); + + it("should return true after allowGlobal is called", async function () { + const tx = await testContract.createAndAllowGlobal(99); + await tx.wait(); + const handle = await testContract.lastHandle(); + expect(await taskManager.isPubliclyAllowed(handle)).to.equal(true); + }); + + it("should return false for a non-existent handle", async function () { + const fakeHandle = 12345; + expect(await taskManager.isPubliclyAllowed(fakeHandle)).to.equal(false); + }); + }); + +});