On-Chain Components
Contracts and circuits that produce encrypted compliance payloads for threshold compliance.
Overview
The threshold compliance system is built around a simple idea: every on-chain operation that hides a piece of information must also attach that information encrypted under a compliance key, so that it can be decrypted later if required by law enforcement.
The holder of the compliance key - the threshold compliance authority - is treated as a black box in this section. In practice, it is not a single entity but a Guardian Committee that holds a threshold-shared decryption key and follows a structured revoking process to handle decryption requests. From the perspective of contracts and circuits, all that matters is that a public key TRC_PK exists on-chain and users encrypt against it.
For the Encrypted Balances system, the modification is straightforward: each operation that hides an amount must include an additional ElGamal ciphertext encrypting that amount under TRC_PK, and the circuit must prove this ciphertext is correctly formed. The Shielded Layer will require a more involved approach - the compliance payload is larger (it must include enough information to link transactions forward and backward for follow-up requests), but this is out of scope for the current document.
Threshold Compliance Key
The deployment-wide threshold compliance public key (TRC_PK in the Decryption Protocol) lives on the Hub:
// EBHub
Point public trcPk;
This is a Grumpkin point. It is set during Hub initialization and corresponds to the key held by the Guardian Committee. The owner-only Hub.setTrcPk function rotates the key post-deploy; the rotation applies to every EBEMT in the deployment in a single call. Rotation semantics are described in Decryption Protocol: Key Rotation.
The "Recursive" in TRC_PK is forward-looking: the system at this stage is plain threshold compliance, and the recursive aspect arrives in a later iteration.
Compliance Ciphertext
The compliance ciphertext for a given operation is an ElGamal encryption of the hidden amount under trcPk:
c_trc = Enc(amount, trcPk, r_trc) = (r_trc * G, amount * G + r_trc * trcPk)
Where r_trc is a fresh random scalar chosen by the user. This ciphertext is stored on-chain alongside the transaction data, so that the threshold compliance authority can later retrieve and decrypt it.
The compliance ciphertext uses the same exponential ElGamal scheme as user balances (it could use an arbitrary serialization scheme instead though because homomorphic addition is not required). Decryption yields amount * G, from which the plaintext amount is recovered via the discrete-log solver. Since compliance amounts are bounded to u64, this is always feasible.
Modified Operations
Three operations in the Encrypted Balances system hide their amount and therefore attach a compliance ciphertext: encryptedTransfer, encryptedMint, and encryptedBurn. The corresponding circuits each gain trc_pk and trc_ciphertext as public inputs and trc_randomness as a private input, with the same encryption-correctness constraint binding the compliance ciphertext to the same hidden amount the rest of the proof talks about.
The publicToEncryptedTransfer and encryptedToPublicTransfer operations both have the amount visible on-chain (as a public ERC-20 burn and mint respectively), so no compliance ciphertext is needed for either; publicMint and publicBurn are likewise in the clear.
Transfer
In the base system, the transfer circuit proves correct re-encryption of the sender's balance and encryption of the transfer amount under the recipient's key. With threshold compliance, the circuit gains:
New public inputs:
trcPk- the threshold compliance public key (read from contract storage)c_trc- the compliance ciphertext(R_trc, C_trc), an encryption ofamountundertrcPk
New private inputs (witness):
r_trc- the randomness used in the compliance ciphertext
New circuit constraints:
R_trc == r_trc * GC_trc == amount * G + r_trc * trcPk
These constraints prove that c_trc is a correctly formed encryption of the same amount that appears in the rest of the transfer statement. The contract stores c_trc in the EncryptedTransfer event.
Encrypted mint
encryptedMint(epk, mintCt, tsCt, trcCt, proof) already proves the same hidden amount is encrypted under both the recipient's EPK (mintCt) and the auditor's totalSupplyPk (tsCt). Threshold compliance adds the same constraint a third time, against trcPk:
Additions to the eb-mint circuit:
- Public inputs:
trc_pk,trc_ciphertext - Private input:
trc_randomness - Constraint:
trc_ciphertext == Enc(amount, trc_pk, trc_randomness)
The contract reads trcPk from storage, passes it (and trcCt) as public inputs, and emits trcCt in the EncryptedMint event alongside tsCt.
Encrypted burn
Symmetric with encryptedMint: encryptedBurn(epk, newBalance, tsCt, trcCt, …, proof) proves tsCt and trcCt both encrypt the same hidden burn_amount removed from the EPK's balance.
Additions to the eb-burn circuit: identical to eb-mint (trc_pk, trc_ciphertext, trc_randomness, encryption-correctness constraint), bound to burn_amount instead of amount.
The contract emits trcCt in the EncryptedBurn event.
Note on r_trc
The circuit does not constrain how r_trc is chosen - the user (user for transfers, minter for mints, burner for burns) is free to pick any scalar. Choosing r_trc with full entropy is in the user's own interest: a predictable or low-entropy r_trc could allow an adversary to decrypt the compliance ciphertext independently. What the circuit does guarantee is that the user knows r_trc and has used it to form the ciphertext honestly, which prevents malleability attacks where a ciphertext encrypting a different value is substituted.
Contract Changes
For each of the three hidden-amount operations, the EBEMT contract:
- Reads
trcPkfrom the Hub via STATICCALL (Hub.getTrcPk()). - Passes
trcPkand the user-suppliedc_trcas public inputs to the proof verifier. - Verifies the proof (which now includes the compliance encryption constraints).
- Emits
c_trcin the operation's event (EncryptedTransfer,EncryptedMint, orEncryptedBurn) so it can be indexed and retrieved by the threshold compliance authority.
The on-chain gas cost increase per operation is modest: two additional Grumpkin points (4 field elements) per emitted event, and the verifier checks a slightly larger proof.
Future: Shielded Layer
In the Shielded Layer, the transaction graph is hidden - both sender and recipient are anonymous. The compliance payload must therefore include more than just the amount: it must contain enough information to allow the threshold compliance authority to link transactions forward and backward through the UTXO pool when processing follow-up requests.
In the Encrypted Balances system, the hidden payload is short - a single u64 amount - and fits naturally in one ElGamal ciphertext. In the Shielded Layer, the payload is much larger. Rather than encrypting everything with asymmetric encryption (ElGamal in this case), we use the standard approach of generating an ephemeral shared secret via Diffie-Hellman and using it as a key for SNARK-friendly symmetric encryption. For decryption, it is then sufficient to reveal the ephemeral key (see Decryption Protocol). The exact structure of the shielded compliance payload and its circuit constraints will be specified when the Shielded Layer functionality is added.