ADR-021: EBEMT Contract Split (Hub and Token)
| Status | Accepted |
| Date | 2026-04-27 |
Context
EBEMT.sol bundles three orthogonal concerns into one contract:
- Token mechanics — encrypted balances, transfers, pending balance, total-supply counter.
- Identity — controllers (EPK→address), EPK registration, controller rotation.
- Governance and compliance — auditor and compliance keys, owner, upgrade authority, and (with ADR-018 approaching) mint/burn role sets.
The deployment plans four token instances (zkUSD, zkEUR, zkGBP, zkPLN). Under the monolith every user must register their EPK once per token, every token carries its own compliance keys, and every token will carry its own mint/burn role sets. Concerns (2) and (3) are deploy-once with no per-token state; concern (1) is per-token.
Proposal
Two contracts:
| Contract | Cardinality | Holds |
|---|---|---|
EBHub | One per deployment | Identity (EPK→controller registry); compliance and auditor keys (trcPk, totalSupplyPk); mint/burn entry points and their verifiers; governance roles; bounded EBEMT registry; upgrade authority for itself and every EBEMT |
EBEMT | One per token | ERC-20 + encrypted balance state; pending balance; autoEncrypt; encrypted total-supply counter; onlyHub executors that mutate state on Hub-driven mint/burn and counter rotation |
Both are UUPS proxies. Hub's owner authorizes Hub upgrades; Hub authorizes EBEMT upgrades. EBEMT does not inherit OwnableUpgradeable — its only privileged operations are onlyHub, reached through Hub's typed forwarders. The Hub address is bound on each EBEMT proxy at initialization (set once and never rewritten); the EBEMT implementation bytecode is independent of any specific Hub and is reusable across staging / prod / future Hubs.
Identity and governance live on the same contract because they share the same lifecycle, upgrade authority, and blast radius. Splitting them into separate Registry and Admin contracts would still require Admin to authorize Registry upgrades — a cosmetic split that buys nothing.
Identity in Hub
EPK registration and controller rotation move from EBEMT to Hub. The registerEpk proof's aux-commitment rebinds from the EBEMT address to the Hub address — domain separation is preserved (cross-chain replay still blocked by block.chainid; cross-deployment squatting still blocked by Hub's stable proxy address).
Controller-rotation signatures use Hub's EIP-712 domain; transfer-domain controller signatures (encrypted transfer, encrypted-to-public, pending activation) keep the token's domain — the operation lives there, the signature lives there.
Transfer-domain EPK nonces stay per-token (a user transferring on zkUSD does not consume nonce space on zkEUR). Hub-domain EPK nonces (for controller rotation) live on Hub.
Governance, compliance, and issuance in Hub
trcPk, totalSupplyPk, the role sets, and the mint/burn entry points all live on Hub. The auditor key, its proof verifiers, and the role checks that gate them are issuer-facing infrastructure that's deployment-wide, so co-locating them removes per-token rotation churn and surfaces "who can issue on this deployment" in one place.
The mint/burn entry points take the target EBEMT as the first argument, run authorization and proof verification Hub-side, then dispatch to the corresponding onlyHub executor on the EBEMT. Token-side primitives mutate balance + counter state and emit per-token events; per-token event filtering is preserved for off-chain consumers.
EBEMT consults Hub on every controller-authenticated operation (controller lookup) and on every encrypted transfer (trcPk for proof public inputs). The extra warm cross-contract reads and the cross-contract dispatch on mint/burn are negligible against ZK verification.
Role-set scope: deployment-wide. A single role enrollment applies across every token. This matches the operational reality of an issuer running a token family, but a compromised minter inflates supply on every token simultaneously. The compensating controls from ADR-018 (multisig custody, separation from owner key, auditor reconciliation against totalSupplySk) apply unchanged. The same scoping applies to burn authorizations: a (burner, EPK) enrollment authorizes burns of that EPK's balance on every EBEMT — consistent with the EPK identity itself being deployment-wide. If per-token segmentation becomes necessary later, the role mappings can be re-keyed without touching EBEMT (Hub is upgradeable).
Atomic deployment-wide auditor-key rotation. Hub maintains a registry of EBEMTs and exposes a single owner-driven entry point that rotates totalSupplyPk and replaces every registered EBEMT's encrypted-total-supply counter under the new key — all in one transaction. The owner provides one new ciphertext per EBEMT; pre-rotation pins on each counter defeat front-running.
Hub exposes typed forwarders for owner-driven onlyHub operations (EBEMT upgrades, key rotation). We deliberately do not add a generic "execute as owner" escape hatch — every EBEMT-targeted owner operation gets a typed forwarder so the Hub ABI stays narrow and auditable.
Per-token state stays per-token
The encrypted total-supply counter remains per-token: each token's issued supply is a separate quantity that the auditor reconciles against its own authorized-mint ledger. AutoEncrypt and pending-balance state are per-EPK-per-token by definition. The totalSupplyPk itself is deployment-wide on Hub — every EBEMT's counter is encrypted under the same auditor key, rotated atomically.
Consequences
Easier:
- One EPK registration grants the user access to every token.
- Setting compliance keys, role rotations, etc. happen once and apply everywhere.
- One Hub call rotates the auditor key and atomically swaps every EBEMT's counter.
- One contract (Hub) governs the entire upgrade graph and holds all issuer-facing surface; EBEMT shrinks to "the encrypted ERC-20" with
onlyHubexecutors for state mutations.
Harder:
- Cross-contract reads on the encrypted-transfer hot path. Gas cost is negligible, but Hub misconfiguration at deploy time is a new bricking mode.
- The SDK now has two EIP-712 domains to track: controller rotation against Hub, everything else against the token.
- A compromised Hub owner, a compromised minter, or a
trcPkrotation touches all tokens at once. Acceptable in exchange for centralized governance, but worth documenting. - Hub holds permanent state (the EPK registry); a Hub redeploy strands user registrations. Treat it as frozen infrastructure — upgrade in place via UUPS only.
- Hub mixes identity and governance state in one ABI. If a future change wants distinct upgrade authorities for the two surfaces, that requires extracting one half into a separate contract.
No-ops:
- ZK circuits unchanged (the aux-commitment is opaque to Noir).
- All EIP-712 type hashes unchanged (only the domain separator shifts for controller rotation).
- No data migration: this is a pre-deployment redesign.
This ADR amends ADR-018 by relocating the minter and burner role sets from EBEMT to Hub. The role semantics and security model from ADR-018 are unchanged; the entry points move to Hub and take the target EBEMT as their first argument.