Documentation Index
Fetch the complete documentation index at: https://cofhe-docs.fhenix.zone/llms.txt
Use this file to discover all available pages before exploring further.
| Aspect | Description |
|---|
| Type | UUPS-upgradeable Solidity contract deployed on a registry chain (Arbitrum One in production). Distinct from the per-host-chain CTRegistry. |
| Function | Records (version, handle) → commitHash entries for every FHE operation result that the coprocessor produces. |
| Responsibilities | • Provide an authoritative source of ciphertext integrity that the Threshold Network checks before issuing a decryption. • Group commitments by an opaque version tag so a future tfhe-rs / FHE-parameter upgrade can roll out without invalidating earlier ciphertexts. • Enforce write-once semantics per (version, handle) to prevent commitment replacement. • Expose paginated enumeration so off-chain tooling can audit what has been posted. |
| Deployment | One deployment per registry chain, behind an ERC-1967 proxy. Initialized with (initialOwner, initialPoster). Owner is Ownable2Step — transfers require explicit accept. |
Why a separate registry chain?
The Threshold Network needs to confirm that the ciphertext it’s about to decrypt is exactly the one the FHE Engine produced (not a tampered or stale handle). The natural place to anchor that proof is on-chain, but doing it on every host chain would force the network to maintain N RPC paths and pay gas on N chains for every FHE operation. Instead, the coprocessor posts commitments to a single registry chain (currently Arbitrum One), and the Threshold Network only watches that one.
This is why the host-chain CTRegistry (which maps temporary → final ciphertext hashes inside one chain’s lifecycle) and CommitmentRegistry (which records the canonical commitment for every produced ciphertext, cross-chain) are deliberately distinct components.
Storage shape
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 (see the FHE Engine COMMITMENT_VERSION notes). Every version moves through a small state machine:
Unset ─┐
▼
Active ─┬──────► Deprecated ──► Revoked
└─────────────────────► Revoked
| State | Meaning | Allowed transitions |
|---|
Unset | Default. No commitments have been posted under this version. | → Active |
Active | Posters may write commitments under this version. The Threshold Network honors lookups. | → Deprecated, → Revoked |
Deprecated | New commitments rejected. Existing lookups still resolve. Used during a parameter rollover. | → Revoked |
Revoked | Hard kill. No further transitions; the version is dead. | — (terminal) |
Owner-only setVersionStatus(version, newStatus) enforces these transitions and reverts with InvalidVersionTransition otherwise. The transition emits VersionStatusChanged(version, oldStatus, newStatus).
Roles and write surface
| Role | How it’s set | What it can do |
|---|
| Owner | initialize(initialOwner, …), then Ownable2Step transfer. | addPoster, removePoster, setVersionStatus, _authorizeUpgrade. |
| Poster | Owner-only addPoster(address). Initial poster supplied to initialize. | postCommitments, postCommitmentsSafe. |
Non-poster posts revert with OnlyPosterAllowed(caller). In production, the blockchain-poster service holds the only poster role and signs through OpenZeppelin Relayer.
Writing commitments
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 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 — caller knows it’s 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 off-chain 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. |
getHandleByIndex(version, index) | bytes32 | Direct array lookup. Reverts on out-of-range. |
getHandles(version, offset, limit) | bytes32[] | Paginated. Returns an empty array if offset >= total; clamps offset + limit at total. |
isPoster(address) | bool | Useful for off-chain 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 |
|---|
CommitmentsPosted(bytes32 indexed version, uint256 batchSize) | postCommitments | Confirm a strict batch landed. |
CommitmentsPostedSafe(bytes32 indexed version, uint256 newlyPosted, uint256 skipped) | postCommitmentsSafe | Reconcile “how many were new” in an idempotent flow. |
VersionStatusChanged(bytes32 indexed version, VersionStatus oldStatus, VersionStatus newStatus) | setVersionStatus | Watch for Active → Deprecated to know when to stop posting under a version. |
PosterAdded(address indexed poster) / PosterRemoved(address indexed poster) | addPoster / removePoster | Audit role changes. |
Upgrades
The contract is UUPSUpgradeable. _authorizeUpgrade is gated by onlyOwner. The constructor calls _disableInitializers() so the implementation contract itself can never be initialized — initialization happens through the proxy via initialize(initialOwner, initialPoster).
Source