# Inputs, Advice and the Journal

> A guest has no I/O system calls. Its public input, the prover's advice and its journal are three regions of memory. What each holds, what the proof binds, and the pattern every guest that takes data follows.

An Apogee guest has no file descriptors, no streams and no I/O syscall. Its inputs and outputs are three regions of memory, read and written with ordinary loads and stores, and the proof binds two of them.

## The three regions

| Region | SDK | Holds | Size | Bound by the proof |
| --- | --- | --- | --- | --- |
| Public input | `public_input()`, `read_input(buf)` | the statement's bytes, chosen by whoever asks for the proof | at most 16,380 bytes | yes, its initial contents |
| Advice | `advice()` | bytes the prover chooses | up to 2 GiB | **no** |
| Journal | `commit(bytes)`, `journal()` | what the guest appended | at most 16,380 bytes | yes, its final contents |

```rust
let input: &[u8] = guest_sdk::public_input(); // a slice over the input window, no copy
let data: &[u8] = guest_sdk::advice();        // a slice over the advice region
guest_sdk::commit(b"result");                  // appends to the journal
```

- `public_input()` and `advice()` return slices over memory; nothing is copied. `read_input(buf)` copies `min(buf.len(), input.len())` bytes and returns the count, so it may return short.
- `commit` appends and keeps a length word, which is what makes the proof bind an exact byte string rather than a zero-padded window. It exits with status **70** rather than overflow the window.
- `advice()` on a run given no advice is a fatal `OutOfBounds`, not an empty slice: a run with no advice has no advice region at all, and pays nothing for one.

## What "bound" means

The statement a proof establishes carries the public input bytes, the journal bytes and the exit status. The proof shows that the input window held exactly the statement's input before the guest's first access, and that the journal window held exactly the statement's output when the guest exited. That rests on the memory argument, not on anything the guest does: there is no hash the guest must compute and no convention it must follow.

Advice is different. The advice region's initial contents are whatever the prover wrote there, and nothing ties them to the program identity, the statement or any gate. A proof says that *some* advice exists under which the program, given this input, published this journal. That is exactly as strong as the guest's own checks on the advice.

## The pattern: commit, supply, check

A guest with a large input takes the bulk as advice, which the public input commits to, and checks one against the other before anything derived from the advice reaches the journal:

```rust title="Check the advice before trusting it"
fn main() {
    let want = guest_sdk::public_input(); // 32 bytes: keccak256 of the advice
    let data = guest_sdk::advice();       // the prover's bytes, bound by nothing
    if guest_sdk::keccak256(data).as_slice() != want {
        guest_sdk::exit(1); // refused before anything derived from it is committed
    }
    let sum = data.iter().fold(0u32, |s, b| s.wrapping_add(u32::from(*b)));
    guest_sdk::commit(&sum.to_le_bytes()); // the journal: what the proof publishes
}
```

The check need not be a hash of the whole advice. It can be a Merkle path checked against a root the input carries, as in the [ledger example](https://apogee.gweb3networks.com/docs/blockchain-native#ledger-native), or a signature over the data, or a constraint the result itself satisfies, such as a claimed sorted order that the guest verifies in one pass instead of sorting. What matters is that the thing it is checked against is bound.

> [!CAUTION]
> Committing any function of unchecked advice publishes a value the prover chose. This is the most common way to write a guest whose proof means nothing.

## Structured data

Encode structured inputs with a `no_std` serializer such as `postcard` over `serde` with the `alloc` feature, which is what the repository's own Ethereum guest uses for its block witness. Two habits keep a format honest:

- **Use fixed-width integers.** `u32` and `u64`, never `usize`, whose width differs between your host and the guest.
- **Insist on one encoding per value** when it matters. A deserializer that accepts trailing bytes or non-minimal varints admits two byte strings for one value. Where uniqueness matters, decode, re-encode and compare, as the Ethereum guest's `BlockWitness::decode` does.

## Outputs that grow

The journal holds 16,380 bytes. An output that grows with the work, such as one record per transaction, has no fixed bound and will eventually exit 70. Publish a digest instead: hash the records as you produce them and commit the 32-byte result, then let whoever needs the records recompute them natively and compare. The repository's stateless Ethereum validator publishes a 43-byte journal for a whole block this way.

For a proof checked on Ethereum, keep both public values a **fixed length**. The deployed verifier contract is built for one input length and one output length, and refuses anything else ([Settle on-chain](https://apogee.gweb3networks.com/docs/launch/on-chain#shape)).

## The exit status

Nothing is published at exit beyond what was committed, and a run that panics or exits nonzero has a valid proof of what it did. So a verifier reads the exit status before it reads the journal. On-chain, the verifier contract takes the expected status as an argument, and an application passes `0`. The SDK's own statuses:

| Status | Meaning |
| --- | --- |
| 0 | `main` returned, or `exit(0)` |
| 70 | `commit` would have passed 16,380 bytes |
| 71 | an allocation would have reached the stack: see [the heap](https://apogee.gweb3networks.com/docs/launch/write#heap) |
| 72 | a delegation answered something its shim refuses |
| 101 | a panic, which prints nothing |

Choose your own failure codes outside these, as the ledger example does with 1, 2 and 3.

## What the proof does not say

- **Nothing orders the journal's writes**, and nothing forces a guest to read its input. The proof binds the windows' contents, not the accesses that produced them.
- **Advice is writable.** A store into the advice region is an ordinary store. It is still unbound either way.
- **The journal is the window's whole final contents.** `commit` maintains that form. A guest that writes the window directly must keep it: a length of at most 16,380, that many bytes, then zeros.

The specification states all of this precisely: [Public values and advice](https://apogee.gweb3networks.com/docs/auditors/spec/public-values).
