tower-broadcastEvents

The Limitation

Shielded types cannot be emitted directly in events. The following will not compile:

event ConfidentialEvent(suint256 confidentialData); // Compilation error

This restriction exists because events are stored in transaction logs, which are publicly accessible on-chain. Emitting a shielded value in an event would defeat the purpose of shielding it in the first place -- the value would be visible to anyone inspecting the logs.

This applies to all shielded types: suint, sint, sbool, saddress, and sbytes.

The Workaround: Encrypted Events via Precompiles

Although native encrypted events are not yet supported, you can achieve private event data today using the AES-GCM and ECDH precompiles. The approach is to encrypt the sensitive data before emitting it in a regular (unshielded) event, so that only the intended recipient can decrypt it.

Here is the general flow:

  1. Generate a shared secret between the sender and the intended recipient using the ECDH precompile at address 0x65.

  2. Derive an encryption key from the shared secret using the HKDF precompile at address 0x68.

  3. Encrypt the event data using the AES-GCM Encrypt precompile at address 0x66.

  4. Emit a regular event containing the encrypted bytes. Since the event parameter is bytes (not a shielded type), this compiles and works normally.

  5. Recipient decrypts the data using the AES-GCM Decrypt precompile at address 0x67, either off-chain or on-chain if needed.

Precompile Reference

Precompile
Address
Purpose

ECDH

Shared secret generation

AES-GCM Encrypt

Encrypt data with AES-GCM

AES-GCM Decrypt

Decrypt data with AES-GCM

HKDF

Key derivation from shared secret

Code Example

Below is a minimal private token showing how to emit encrypted transfer events. The contract holds a keypair; users register their public keys so the contract can encrypt event data that only the recipient can read.

For a full implementation, see the SRC20: Private Token tutorial.

The built-in helpers ecdh(), hkdf(), and aes_gcm_encrypt() are compiler-provided globals — no imports needed. See the Precompiles reference for details on each.

Decryption (Off-Chain)

The recipient reconstructs the shared secret off-chain using their own private key and the contract's public key (ECDH is symmetric). They then derive the same encryption key via HKDF and decrypt the event data using AES-GCM Decrypt.

The client libraries provide built-in helpers for this:

What Not to Do

Do not attempt to work around the restriction by casting a shielded value to its unshielded counterpart and then emitting it:

Casting from a shielded type to an unshielded type makes the value visible in the execution trace. Emitting it in an event then permanently records it in publicly accessible logs.

Future Improvements

Native encrypted events are planned for a future version of Seismic. This will allow shielded types to be emitted directly in events without requiring manual encryption via precompiles. The compiler and runtime will handle encryption transparently, making private events as simple to use as regular events.

Key Takeaway

Privacy in events is achievable today -- it just requires explicit encryption via the aforementioned precompiles. Encrypt sensitive data before emitting it, and ensure only the intended recipient has the keys to decrypt. When native encrypted events ship, migrating will be straightforward: replace the manual encryption logic with direct shielded type emission.

Last updated