> ## Documentation Index
> Fetch the complete documentation index at: https://fhenix-docs-broken-sample-fixes.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# CommitmentRegistry

> Registry-chain contract that records FHE computation commitments. Teecryptor verifies ciphertext integrity against it before decrypting.

| Aspect | Description |
| - | - |
| **Type** | UUPS-upgradeable Solidity contract deployed on a dedicated **registry chain**. |
| **Function** | Records `(version, handle) → commitHash` entries for every ciphertext the coprocessor produces or verifies, computed results and encrypted inputs alike. |
| **Responsibilities** | • Provide an authoritative source of ciphertext integrity that [Teecryptor](/deep-dive/cofhe-components/teecryptor) checks **before** decrypting.<br />• Group commitments by an opaque `version` tag so a future tfhe-rs / FHE-parameter upgrade can roll out without invalidating earlier ciphertexts.<br />• Enforce write-once semantics per `(version, handle)` to prevent commitment replacement.<br />• Expose paginated enumeration so offchain tooling can audit what has been posted. |
| **Deployment** | One deployment per registry chain, behind an ERC-1967 proxy. |

## What a commitment is

A commitment is the `keccak256` hash of a ciphertext's canonical stored bytes. The [compute pipeline](/deep-dive/cofhe-components/compute-pipeline) produces one for every result it computes, and anchors one for every encrypted input the [ZK Verifier](/deep-dive/cofhe-components/zk-verifier) approves. It batches them and writes them to this registry.

## Who writes and who reads

One component writes commitments, and one component reads them. The write happens when a ciphertext is produced; the read happens later, whenever someone asks to decrypt it.

<Frame>
  <img src="https://mintcdn.com/fhenix-docs-broken-sample-fixes/R2TEFe1LBG2Y-SXN/images/commitment-registry.svg?fit=max&auto=format&n=R2TEFe1LBG2Y-SXN&q=85&s=2e7edcd63f0e61695362cc92e17c8779" alt="Sequence diagram of the commitment write and read" width="1440" height="792" data-path="images/commitment-registry.svg" />
</Frame>

<Tip>
  The compute pipeline and Teecryptor run offchain inside CoFHE; the registry itself is onchain. Hover any component or message in the [explorable version](/deep-dive/cofhe-components/commitment-registry-explorable).
</Tip>

## Why commitments?

The commitment is a safety check in the decryption flow. Before decrypting anything, [Teecryptor](/deep-dive/cofhe-components/teecryptor) confirms that the ciphertext bytes it fetched hash to the commitment anchored onchain. It only ever decrypts a ciphertext the coprocessor actually produced, never a tampered or substituted one.

The registry also makes the coprocessor accountable. Every result it has ever produced is committed publicly, permanently, and write-once, so its computation history is tamper-evident and open to independent audit. Commitments for every host chain land on one dedicated **registry chain**, which gives the decryption path a single place to verify against.

## Storage shape

```solidity theme={null}
mapping(bytes32 version => mapping(bytes32 handle => bytes32 commitHash)) commitments;
mapping(bytes32 version => bytes32[])                                     handlesByVersion;
mapping(bytes32 version => VersionStatus)                                 versionStatus;
mapping(address => bool)                                                  posters;
```

`commitments` is the source-of-truth lookup. `handlesByVersion` is an array kept in parallel so paginated enumeration is `O(limit)` instead of `O(total)`. Storage lives at the ERC-7201 slot derived from `cofhe.storage.CommitmentRegistry`, so the contract is upgrade-safe.

## Version lifecycle

`version` is an opaque `bytes32` tag chosen by the coprocessor when FHE parameters change. The current tag is the number 2 encoded as `bytes32`. Every version moves through a small state machine:

<Frame>
  <img src="https://mintcdn.com/fhenix-docs-broken-sample-fixes/R2TEFe1LBG2Y-SXN/images/commitment-version.svg?fit=max&auto=format&n=R2TEFe1LBG2Y-SXN&q=85&s=16bd5b772be7446bc5dc1dc9f03efff1" alt="State machine for a commitment version: Unset to Active to Deprecated, with Revoked as the terminal state" width="1440" height="943" data-path="images/commitment-version.svg" />
</Frame>

<Tip>
  Only an Active version accepts new commitments. Revoked is terminal.
</Tip>

| State | Meaning | Allowed transitions |
| - | - | - |
| `Unset` | Default. No commitments have been posted under this version. | to `Active` |
| `Active` | Posters may write commitments under this version. Teecryptor honors lookups. | to `Deprecated` or `Revoked` |
| `Deprecated` | New commitments rejected. Existing lookups still resolve. Used during a parameter rollover. | to `Revoked` |
| `Revoked` | Hard kill. No further transitions; the version is dead. | none (terminal) |

The admin-only `setVersionStatus(version, newStatus)` enforces these transitions and reverts with `InvalidVersionTransition` otherwise. The transition emits `VersionStatusChanged(version, oldStatus, newStatus)`.

## Write surface

Only accounts holding the **poster** role can write commitments (`postCommitments`, `postCommitmentsSafe`); posts from anyone else revert with `OnlyPosterAllowed(caller)`. Poster management and version transitions are admin-only. In production, the poster role is held by the coprocessor's relayer signer (OpenZeppelin Relayer).

## Writing commitments

```solidity theme={null}
function postCommitments(
    bytes32 version,
    bytes32[] calldata handles,
    bytes32[] calldata commitHashes
) external onlyPoster;

function postCommitmentsSafe(
    bytes32 version,
    bytes32[] calldata handles,
    bytes32[] calldata commitHashes
) external onlyPoster;
```

Both functions batch-write `(version, handle) → commitHash` rows and require:

* `version` is in `Active` state, otherwise the call reverts with `VersionNotActive(version)`.
* `handles.length == commitHashes.length` and `> 0`, otherwise `LengthMismatch` / `EmptyBatch`.
* Each `commitHash != bytes32(0)`, otherwise `ZeroCommitHash(handle)`.

The difference is in **how duplicates are handled**:

| Function | Duplicate handle under same version | Use case |
| - | - | - |
| `postCommitments` | Reverts the whole batch with `CommitmentAlreadyExists(version, handle)`. | Strict integrity: the caller knows it is posting unique data. |
| `postCommitmentsSafe` | Silently skips the handle; emits `CommitmentsPostedSafe(version, newlyPosted, skipped)`. | Idempotent re-flushes (e.g. when the coprocessor's message broker redelivers a commitment batch). |

`postCommitments` emits `CommitmentsPosted(version, batchSize)`. `postCommitmentsSafe` emits `CommitmentsPostedSafe(version, newlyPosted, skipped)` so the offchain caller can tell whether the round did real work.

Both enforce **write-once per (version, handle)**: a commitment can never be overwritten, only superseded by writing the same handle under a new `version`.

## Reading commitments

| Function | Returns | Notes |
| - | - | - |
| `getCommitment(version, handle)` | `bytes32` | `bytes32(0)` means "not posted". |
| `getVersionStatus(version)` | `VersionStatus` | `Unset` if never registered. |
| `getSize(version)` | `uint256` | Number of handles ever committed under `version`. |
| <code style={{ whiteSpace: "nowrap" }}>getHandleByIndex(version, index)</code> | `bytes32` | Direct array lookup. Reverts on out-of-range. |
| <code style={{ whiteSpace: "nowrap" }}>getHandles(version, offset, limit)</code> | `bytes32[]` | Paginated. Returns an empty array if `offset >= total`; clamps `offset + limit` at `total`. |
| `isPoster(address)` | `bool` | Useful for offchain ops dashboards. |

The paginated `getHandles` is the recommended way to enumerate a version: `getSize` first to compute pages, then `getHandles(version, offset, pageSize)` in a loop.

## Events

| Event | Emitted by | Use |
| - | - | - |
| <code style={{ whiteSpace: "nowrap" }}>CommitmentsPosted</code> | `postCommitments` | Confirm a strict batch landed. Carries the version and batch size. |
| <code style={{ whiteSpace: "nowrap" }}>CommitmentsPostedSafe</code> | `postCommitmentsSafe` | Reconcile "how many were new" in an idempotent flow. Carries newlyPosted and skipped counts. |
| <code style={{ whiteSpace: "nowrap" }}>VersionStatusChanged</code> | `setVersionStatus` | Watch for the `Active` to `Deprecated` transition to know when to stop posting under a version. |
| <code style={{ whiteSpace: "nowrap" }}>PosterAdded</code> / <code style={{ whiteSpace: "nowrap" }}>PosterRemoved</code> | `addPoster` / `removePoster` | Audit role changes. |

## Source

* Solidity: [`contracts/internal/registry-chain/contracts/commitment-registry/CommitmentRegistry.sol`](https://github.com/FhenixProtocol/cofhe-contracts/blob/master/contracts/internal/registry-chain/contracts/commitment-registry/CommitmentRegistry.sol).
* Offchain: the compute pipeline's blockchain poster batches the commitments and posts them to this registry.
* The pipeline sets the commitment version, and bumps it when the FHE parameters change.
