# Authenticated Events

*[Documentation index](/llms.txt) · [Full index](/llms-full.txt)*

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:

1. **Cryptographic commitments**: Events are committed into a Merkle Mountain Range (MMR) structure stored onchain (see the [MMR section](#merkle-mountain-range-mmr) below).
2. **Checkpoint verification**: The commitment is tied to Sui checkpoints, which are signed by the validator committee.
3. **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
:::tip When to use authenticated events

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](/develop/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:

```move
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:

1. **Contract change**: Replace `event::emit(...)` calls with `event::emit_authenticated(...)` in your Move code.
2. **Package upgrade**: Deploy the updated package (requires a contract upgrade).
3. **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:

```rust
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.

```rust
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:

1. **Event emission**: During transaction execution, authenticated events are emitted.
2. **Consensus commit batching**: Authenticated events are grouped by consensus commit, and a merkle tree is computed over events for each `stream_id`.
3. **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 `EventStreamHead` object for that `stream_id` with 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:

```protobuf
service EventService {
  rpc ListAuthenticatedEvents(ListAuthenticatedEventsRequest)
      returns (ListAuthenticatedEventsResponse);
}
```

**Request parameters:**

- `stream_id` (required): Package address that emitted the events
- `start_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 of `AuthenticatedEvent` with checkpoint, transaction index, event index, and payload
- `highest_indexed_checkpoint`: Latest indexed checkpoint
- `next_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:

```protobuf
service ProofService {
  rpc GetObjectInclusionProof(GetObjectInclusionProofRequest)
      returns (GetObjectInclusionProofResponse);
}
```

Used to verify that the `EventStreamHead` object exists at a specific checkpoint.

**Request parameters:**

- `object_id` (required): The `EventStreamHead` object 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 nodes
  - `leaf_index`: Position in the merkle tree
  - `tree_root`: Root digest (32 bytes)
- `object_data`: BCS-encoded `EventStreamHead` object
- `checkpoint_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:

```rust
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 by `ProofService`). 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:

1. **Genesis committee**: The client is initialized with the genesis validator committee public keys.
2. **Trust ratcheting**: When epochs change, the client verifies the new committee was signed by the previous one.
3. **Checkpoint verification**: Each checkpoint summary is verified against the committee's aggregate signature.
4. **Object inclusion proof**: The `EventStreamHead` object is proven to be modified at a specific checkpoint.
5. **MMR verification**: Events are verified against the `EventStreamHead`'s MMR commitment.

The client verifies a checkpoint range by:

1. Starting with a verified `EventStreamHead` from the preceding checkpoint (or empty state for a new stream)
2. Fetching events in the range and appending them to the MMR
3. Fetching the `EventStreamHead` at the final checkpoint and proving its inclusion through an OCS proof
4. 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 `EventStreamHead` must 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.
