Authenticated Events
Authenticated events provide a cryptographically verifiable stream of Move events from Sui smart contracts. Unlike regular events, authenticated events can be verified by a light client without trusting any intermediary, making them suitable for applications that require trustless event consumption.
Why authenticated events?
Regular Sui events are indexed and queryable, but verifying them requires trusting the RPC provider. Authenticated events solve this by:
- Cryptographic commitments: Events are committed into a Merkle Mountain Range (MMR) structure stored onchain (see the MMR section below).
- Checkpoint verification: The commitment is tied to Sui checkpoints, which are signed by the validator committee.
- Light client verification: Clients verify the chain from the genesis committee, to checkpoint signatures, to event inclusion proofs.
This enables cryptographic verification of event completeness and correctness, with no full node required, and is lightweight enough to run in a browser or on mobile.
Quick start
Use regular events for indexing, telemetry, and dashboards where trusting an RPC provider or indexer is acceptable. Use authenticated events when a consumer needs cryptographic proof of event completeness and correctness rather than trusting an intermediary, such as a light client, bridge, or cross-chain workflow. See Security Best Practices for guidance on choosing trust boundaries.
Use the following examples to emit authenticated events from a Move contract and consume them from a client.
Emitting authenticated events (Move)
The following Move module emits an authenticated event:
module my_package::my_module;
use sui::event;
public struct MyEvent has copy, drop {
value: u64,
data: vector<u8>,
}
public entry fun do_something(value: u64) {
// Emit an authenticated event
event::emit_authenticated(MyEvent {
value,
data: vector::empty(),
});
}
The event is automatically associated with the package that defines the event type. Only that package can emit events to its stream.
Backward compatibility with events
Authenticated events are fully backward-compatible with regular Sui events. An authenticated event is a regular event with additional metadata for verification. Existing event consumers continue to work unchanged.
To upgrade:
- Contract change: Replace
event::emit(...)calls withevent::emit_authenticated(...)in your Move code. - Package upgrade: Deploy the updated package (requires a contract upgrade).
- Optional: Update consumers to use the authenticated events client for cryptographic verification.
Existing indexers, explorers, and tools that consume regular events continue to see authenticated events without modification.
Consuming events (Rust reference client)
The reference Rust client verifies and streams events from a package:
use sui_light_client::authenticated_events::AuthenticatedEventsClient;
use sui_types::base_types::SuiAddress;
use futures::StreamExt;
use std::sync::Arc;
// Initialize with genesis committee (establishes trust root)
let client = Arc::new(
AuthenticatedEventsClient::new(rpc_url, genesis_committee)
.await?
);
// Stream events from your package
let stream_id = SuiAddress::from(package_id);
let mut stream = client.clone().stream_events(stream_id).await?;
while let Some(result) = stream.next().await {
match result {
Ok(event) => {
// event.event contains the verified Move event data
// event.checkpoint indicates when it was committed
println!("Verified event at checkpoint {}", event.checkpoint);
}
Err(e) => {
// Transient errors (TransportError, RpcError) are retried automatically.
// Terminal errors require action:
// - VerificationError: Data integrity issue, try a different RPC endpoint
// - InternalError: Invalid state, investigate the cause
// See "Error Handling" section for details.
eprintln!("Terminal error: {:?}", e);
break;
}
}
}
Resuming from a checkpoint
To resume a stream, the client needs a verified starting state. To guarantee completeness, this requires an Object Checkpoint State (OCS) inclusion proof showing the EventStreamHead at that checkpoint, which only exists if authenticated events were emitted for the stream at that checkpoint. In practice, this is the checkpoint sequence of the last event the client received before a disconnection.
If the specified checkpoint does not have events for that stream_id, the client fails to initialize.
let last_checkpoint = 12345;
let mut stream = client.clone()
.stream_events_from_checkpoint(stream_id, last_checkpoint)
.await?;
Architecture
The following diagram shows the flow from event emission to light client verification:
┌─────────────────────────────────────────────────────────────────┐
│ Move Contract │
│ event::emit_authenticated(MyEvent { ... }) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ EventStreamHead (Onchain) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ mmr: vector<u256> // Merkle Mountain Range root │ │
│ │ checkpoint_seq: u64 // Last update checkpoint │ │
│ │ num_events: u64 // Total events in stream │ │
│ └─────────────────────────────────────────── ──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Light Client Verification │
│ 1. Fetch EventStreamHead with OCS inclusion proof │
│ 2. Verify proof against checkpoint (signed by committee) │
│ 3. Fetch events from ListAuthenticatedEvents RPC │
│ 4. Recompute MMR from events, compare to EventStreamHead │
└─────────────────────────────────────────────────────────────────┘
Key components
| Component | Description |
|---|---|
emit_authenticated<T>() | Move function to emit verified events |
EventStreamHead | Onchain commitment structure per package |
ListAuthenticatedEvents | gRPC API to fetch events with pagination |
AuthenticatedEventsClient | Reference Rust client for verification |
| MMR (Merkle Mountain Range) | Append-only commitment structure |
emit_authenticated and accumulator settlement
Users call emit_authenticated() to emit authenticated events. This writes a regular event alongside metadata including the stream_id to transaction effects.
Events are batched and settled at checkpoint boundaries through the accumulator settlement process:
- Event emission: During transaction execution, authenticated events are emitted.
- Consensus commit batching: Authenticated events are grouped by consensus commit, and a merkle tree is computed over events for each
stream_id. - Checkpoint settlement: At each checkpoint, validators execute a settlement transaction that:
- Appends each consensus commit's merkle root to the stream's MMR
- Updates the
EventStreamHeadobject for thatstream_idwith the new MMR state and event count
API reference
The authenticated events API provides services to fetch events and to verify object inclusion.
EventService
The event service lists authenticated events for a stream:
service EventService {
rpc ListAuthenticatedEvents(ListAuthenticatedEventsRequest)
returns (ListAuthenticatedEventsResponse);
}
Request parameters:
stream_id(required): Package address that emitted the eventsstart_checkpoint: Checkpoint to start from (default: 0)page_size: Events per page (default: 1000, maximum: 1000)page_token: Pagination token from the previous response
Response:
events: List ofAuthenticatedEventwith checkpoint, transaction index, event index, and payloadhighest_indexed_checkpoint: Latest indexed checkpointnext_page_token: Token for the next page (empty if no more events)
ProofService
The proof service verifies that an object exists at a specific checkpoint:
service ProofService {
rpc GetObjectInclusionProof(GetObjectInclusionProofRequest)
returns (GetObjectInclusionProofResponse);
}
Used to verify that the EventStreamHead object exists at a specific checkpoint.
Request parameters:
object_id(required): TheEventStreamHeadobject ID (derived from the package address)checkpoint(required): Checkpoint sequence number to prove inclusion at
Response:
object_ref: Object reference (object_id, version, digest)inclusion_proof: OCS merkle proof containing:merkle_proof: BCS-encoded proof nodesleaf_index: Position in the merkle treetree_root: Root digest (32 bytes)
object_data: BCS-encodedEventStreamHeadobjectcheckpoint_summary: BCS-encoded checkpoint summary for verification
The inclusion proof verifies that the EventStreamHead was written at the specified checkpoint. This is essential for resuming streams: the checkpoint must be one where events were actually emitted.
Client configuration
Configure the client with custom pagination, polling, and timeout settings:
let config = ClientConfig::new(
page_size, // Events per RPC call (max 1000)
poll_interval, // How often to poll for new events
max_pagination_iterations, // Max pages before forcing checkpoint boundary
rpc_timeout, // RPC call timeout
)?;
let client = AuthenticatedEventsClient::new_with_config(
rpc_url,
genesis_committee,
config
).await?;
Merkle Mountain Range (MMR)
An MMR is a forest of perfect binary trees (mountains). After n leaves are appended, mountain sizes correspond to the set bits in the binary representation of n. For example, when n = 13 (1101 in binary with bit 3, 2, and 0 set), the mountains have sizes 2^3 = 8, 2^2 = 4, and 2^0 = 1. The diagram below shows an expanded MMR (internal nodes and leaves).
The MMR state in EventStreamHead is stored as a compact vector of digests indexed by tree height (not the full expanded tree). This keeps onchain state O(log n) in the number of appended leaves.
Key properties of an MMR:
- Compact onchain accumulator state:
O(log n)digests. - Append-only updates: new leaves can be incorporated using only the current accumulator state (without the expanded MMR).
- Inclusion proofs are
O(log n)in size. Event-level MMR inclusion proofs are not yet exposed by current RPC services (this is separate from OCS inclusion proofs exposed byProofService). These inclusion proofs can lead to many interesting applications, such as moving data storage offchain, efficient cross-chain reads, and proving inclusion in zero-knowledge (ZK) proofs.
Expanded MMR with 13 leaves
Tree 0 (size 8)
H0
┌────────┴────────┐
H1 H2
┌─────┴─────┐ ┌─────┴─────┐
H3 H4 H5 H6
┌──┴──┐ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐
L0 L1 L2 L3 L4 L5 L6 L7
Tree 1 (size 4)
H7
┌──── ─┴─────┐
H8 H9
┌──┴──┐ ┌──┴──┐
L8 L9 L10 L11
Tree 2 (size 1)
L12
Leaves (Lx) correspond to the per-consensus-commit merkle tree digests, which in turn contain AuthenticatedEvent as their leaves.
Trust model
The client establishes trust through the following chain:
- Genesis committee: The client is initialized with the genesis validator committee public keys.
- Trust ratcheting: When epochs change, the client verifies the new committee was signed by the previous one.
- Checkpoint verification: Each checkpoint summary is verified against the committee's aggregate signature.
- Object inclusion proof: The
EventStreamHeadobject is proven to be modified at a specific checkpoint. - MMR verification: Events are verified against the
EventStreamHead's MMR commitment.
The client verifies a checkpoint range by:
- Starting with a verified
EventStreamHeadfrom the preceding checkpoint (or empty state for a new stream) - Fetching events in the range and appending them to the MMR
- Fetching the
EventStreamHeadat the final checkpoint and proving its inclusion through an OCS proof - Comparing the locally computed MMR against the onchain
EventStreamHead
If any event is missing, modified, or out of order, the computed MMR does not match the onchain state.
The client automatically handles epoch transitions and committee changes.
Security guarantees
Completeness: When you receive event N from a stream, you are guaranteed to have received all events 0..N-1 in the correct order. The MMR structure ensures that any missing or reordered events would cause verification to fail.
What completeness does not guarantee: An intermediary (for example, an RPC provider) is not obligated to return all events up to a requested checkpoint height. To detect withholding, clients must verify against the onchain EventStreamHead, which contains the authoritative event count. The reference client does this automatically. If the events received do not match the onchain commitment, verification fails.
Correctness: Each event's content is committed to the MMR. Any modification to event data would cause the computed MMR to diverge from the onchain state.
Limitations
- Events are retrievable only if they are indexed on the full node and have not been pruned.
- Events must be consumed in checkpoint order.
- The
EventStreamHeadmust be updated at the resume checkpoint (authenticated events must have been emitted). - One stream per package (
stream_id= package address). - Event-level MMR inclusion proofs are not supported.
Error handling
The following table summarizes the error types and how the client handles them:
| Error type | Recoverable | Action |
|---|---|---|
TransportError | Yes | Automatic retry |
RpcError (Unavailable, DeadlineExceeded) | Yes | Automatic retry |
VerificationError | No | Stop. Data integrity issue |
InternalError | No | Stop. Invalid state |
The stream automatically retries transient errors. Terminal errors indicate verification failures or invalid state and require investigation.