Skip to content

fix(FHE): guard getDecryptResult and getDecryptResultSafe against uninitialized (zero) handles#66

Open
amathxbt wants to merge 1 commit into
FhenixProtocol:masterfrom
amathxbt:fix/decrypt-result-uninitialized-handle-guard
Open

fix(FHE): guard getDecryptResult and getDecryptResultSafe against uninitialized (zero) handles#66
amathxbt wants to merge 1 commit into
FhenixProtocol:masterfrom
amathxbt:fix/decrypt-result-uninitialized-handle-guard

Conversation

@amathxbt
Copy link
Copy Markdown

@amathxbt amathxbt commented May 3, 2026

Summary

Impl.getDecryptResult and Impl.getDecryptResultSafe in contracts/FHE.sol accept any bytes32 handle without checking whether it is initialized (non-zero). Passing a zero handle forwards uint256(0) to the task manager, which may coincidentally match a real pending or completed decrypt task at slot 0, returning a wrong plaintext the caller treats as valid.

Bug

// Before
function getDecryptResult(bytes32 input) internal view returns (uint256) {
    return ITaskManager(TASK_MANAGER_ADDRESS).getDecryptResult(uint256(input));
    // ↑ input == 0 → queries task slot 0 in the task manager
    //   If slot 0 holds a real completed task, caller receives its result silently.
}

function getDecryptResultSafe(bytes32 input) internal view returns (uint256 result, bool decrypted) {
    return ITaskManager(TASK_MANAGER_ADDRESS).getDecryptResultSafe(uint256(input));
    // ↑ same issue — may return (value, true) for slot 0
}

Impact: Contracts that accidentally call these functions with an uninitialized euintX field get a non-zero, apparently-valid result instead of an error. This can silently corrupt application state (e.g., a balance read from an uninitialized storage slot returns a non-zero plaintext).

Fix

  • getDecryptResult: revert with a clear message when the handle is zero.
  • getDecryptResultSafe: return (0, false) (semantically "not yet decrypted") for zero handles, consistent with the function's safe semantics.
function getDecryptResult(bytes32 input) internal view returns (uint256) {
    require(Common.isInitialized(input), "FHE: getDecryptResult called with uninitialized handle");
    ...
}

function getDecryptResultSafe(bytes32 input) internal view returns (uint256 result, bool decrypted) {
    if (!Common.isInitialized(input)) return (0, false);
    ...
}

…lized handles

Passing bytes32(0) (an uninitialized ciphertext handle) to getDecryptResult
forwards uint256(0) to the task manager, which may accidentally match
an existing pending or completed decrypt task at slot 0, returning a
potentially wrong uint256 result that the caller treats as a valid plaintext.

getDecryptResult now reverts with a clear message on a zero handle.
getDecryptResultSafe returns (0, false) for zero handles, consistent
with the semantic of "not yet decrypted".
@amathxbt amathxbt requested a review from a team as a code owner May 3, 2026 10:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant