Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Public inputs

What a proof binds to, how the public side is named, and what happens when it drifts from the circuits.

Two roles, one value model

Public inputs (what the prover commits to when building a proof) and public outputs (what the circuit reports when it runs) are both ordered FieldValue bags over the same canonical-hex value model:

RoleTypeDirection
Inputs a request commits toPublicInputBagrequest → witness → circuit
Outputs the circuit reportsOutputBagcircuit → public outputs in the proof

Names never cross the private boundary: a value is either a public Field parameter (visible) or a private witness (never logged), enforced by the witness model (docs/witness-model.md).

The per-operation surface

The exact public surface of each circuit is a shared spec, not a Rust convention: interfaces::circuit::expectations pins, per operation, the ordered public parameter names and the public word count the compiled circuit must report. The real UltraHonk provider verifies the word count against the pinned surface after every proof and refuses to name words when the counts disagree — a circuit source that drifts from the expectations spec fails loudly instead of mislabeling outputs.

The current operation surfaces (see docs/circuit-model.md for full signatures):

  • register — public: account_address; returns nothing.
  • deposit — public: token_address, account_address, old_commitment; returns (new_commitment, nullifier).
  • merge — public: token + owner + two old commitments + root_hi, root_lo; returns (new_commitment, nullifier_a, nullifier_b).
  • transfer — public: token, sender/recipient addresses, old sender commitment, root_hi, root_lo; returns (recipient_commitment, change_commitment, nullifier).
  • withdraw — public: token, account address, commitment, root_hi, root_lo; returns (change_commitment, nullifier).

Addresses are public — that is the Confidential Token privacy shape, where who moves value is visible but how much is not.

State binding

State-bound operations (merge, transfer, withdraw) carry a StateReference whose 256-bit root is committed to as two 128-bit field halves (root_hi, root_lo) — a full root does not fit one BN254 field. The halves are folded into the circuit’s nullifier, so a proof cut against root A cannot be replayed against root B (docs/ultrahonk.md, docs/security.md). The split convention is shared by the fixtures, the witness path, and the verifier’s structural checks (StateReference::root_halves).

Binding and mismatch

A proof’s public outputs are part of its envelope, and verification checks that the submitted context matches the proof (see docs/verification.md). Changing any bound public output after the proof exists fails verification — pinned by tests/tests/invariants/public_inputs_bound.rs and the security suite’s wrong-context tests.