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) andCommitmentRegistry (which records the canonical commitment for every produced ciphertext, cross-chain) are deliberately distinct components.
Storage shape
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:
Owner-only
setVersionStatus(version, newStatus) enforces these transitions and reverts with InvalidVersionTransition otherwise. The transition emits VersionStatusChanged(version, oldStatus, newStatus).
Roles and write surface
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
(version, handle) → commitHash rows and require:
versionis inActivestate — otherwise reverts withVersionNotActive(version).handles.length == commitHashes.lengthand> 0— otherwiseLengthMismatch/EmptyBatch.- Each
commitHash != bytes32(0)— otherwiseZeroCommitHash(handle).
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
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
Upgrades
The contract isUUPSUpgradeable. _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
- Solidity:
contracts/internal/registry-chain/contracts/commitment-registry/CommitmentRegistry.sol. - Off-chain poster service:
src/services/blockchain-poster/— introduced in cofhe0.6.0. - FHE Engine commitment-version bumping:
fhe-engine/src/rabbitmq/handlers.rs.