> For the complete documentation index, see [llms.txt](https://hinkal-team.gitbook.io/hinkal/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hinkal-team.gitbook.io/hinkal/technical-description/solana-program/proof-pipeline.md).

# Proof Pipeline

Every spending instruction (`transact2`, `transact6`, `transfer2`, `transfer6`, `swap2`, `swap6`) routes through `proof_processing::process_proof_and_state` in `proof_processing.rs`. The pipeline does four things: prove, validate, mutate state, emit events.

#### Inputs

* `MergedProofData` — assembled by `merge_proof_data` from the two `ProofAccount*` PDAs and the `EncryptedOutputsAccount`. Contains `proof_a/b/c`, the full `public_signals: Vec<Bytes32>`, and the encrypted UTXO blobs.
* `Dimensions` — `(token_number, nullifier_amount, output_amount)`. Each instruction pins this to a specific tuple:
  * `transact2 = 1×2×1`
  * `transact6 = 1×6×1`
  * `transfer2 = 1×2×2`
  * `transfer6 = 1×6×2`
  * `swap2 = 2×2×1`
  * `swap6 = 2×6×1`
* `nullifier_accounts` — slice of `Option<Box<Account<NullifierAccount>>>` indexed `[token_index * nullifier_amount + nullifier_index]`. Length must equal `token_number * nullifier_amount`.
* `nullifiers_to_validate` — the raw nullifier bytes the caller is committing to spend.
* `on_chain_creation` — booleans (one per token leg) indicating whether a leg's output commitment will be created on-chain (e.g. swap output) instead of from the proof's encrypted outputs.

#### Step-by-step

```
1. verify_proof(...)                            # Groth16 BN254
2. ParsedPublicSignals::parse(...)
3. expected = get_hashed_calldata(...)          # Poseidon over recipient,
                                                # signer, encrypted outputs,
                                                # fees, instructions, accounts
   require expected == parsed.calldata_hash()
4. require merkle.root_hash_exists(parsed.root_hash_hinkal())
5. process_amount_changes(...)                  # bounds check on each leg
   process_on_chain_creation(...)               # length match
6. process_nullifiers(...)                      # uniqueness + insertion
7. process_commitments(...)                     # Merkle insert + emit_insert_commitments
8. require signer ∈ whitelisted_relayers
        OR all amount_changes > 0
        OR (relayer_fee == 0 && variable_rate == 0)
9. require parsed.stealth_address_structure().extra_randomization != 0
10. for each (i, mint) in mint_accounts:
       require mint.key() == parsed.get_mint_address(i)
```

#### `verify_proof`

`verify_proof.rs` wraps `groth16-solana::Groth16Verifier`. It deserializes `proof_a` from BN254 G1 affine, negates it (Groth16 verifier expects `-A`), and dispatches to a `verify_with_size::<N>` for the matching public-signal count:

| Dimensions | Public-signal count | Verifying key                            |
| ---------- | ------------------- | ---------------------------------------- |
| `1×2×1`    | 16                  | `basic_solana_1x2x1_verification_key.rs` |
| `1×6×1`    | 20                  | `basic_solana_1x6x1_verification_key.rs` |
| `1×2×2`    | 17                  | `basic_solana_1x2x2_verification_key.rs` |
| `1×6×2`    | 21                  | `basic_solana_1x6x2_verification_key.rs` |
| `2×2×1`    | 22                  | `basic_solana_2x2x1_verification_key.rs` |
| `2×6×1`    | 30                  | `basic_solana_2x6x1_verification_key.rs` |

A mismatched signal count raises `InvalidPublicSignalsSize`; an unknown dimensions tuple raises `UnsupportedDimensions`.

#### `get_hashed_calldata`

The proof publicly commits to a Poseidon hash over: recipient, signer, encrypted outputs, `relayer_fee`, `variable_rate`, the staged `SolanaInstructionData` list, and the `remaining_accounts` keys passed to the instruction. The handler recomputes this and rejects with `CalldataHashMismatch` on any divergence — so a relayer cannot, for instance, swap the recipient or alter the CPI batch after the user signed the proof.

#### Nullifier processing

`process_tokens::process_nullifiers`:

* Length check: `nullifier_accounts.len() == token_number * nullifier_amount`, even.
* Version check on every supplied nullifier PDA.
* `validate_nullifiers` — the bytes the caller committed to spend must equal what the proof's public signals claim.
* For each non-zero nullifier (zero nullifiers act as padding for unused legs), the matching PDA must exist, must currently have `is_used == false`, and is flipped to `true` with a `Nullified` event.

`validate_nullifier_pdas` (called from `swap*` before the pipeline) additionally re-derives each PDA from `(seed, deployer, nullifier_bytes, program_id)` to confirm the supplied account is the canonical one.

#### Commitment insertion

`process_tokens::process_commitments` walks `(token_index, output_index)` over `token_number × output_amount`, skipping legs marked `on_chain_creation`. For each non-zero `out_commitment`:

1. `merkle.insert(out_commitment)` — Poseidon hash up the tree, with the new root written into the 100-root circular buffer.
2. `emit_insert_commitments` — emits `NewCommitment { commitment, index, encrypted_output, on_chain_data }`. `on_chain_data` is zero for proof-derived commitments.

For on-chain-created commitments (proofless deposit, swap output) `process_onchain_commitments*` is called separately and `on_chain_data` carries the full UTXO (`amount`, `mint_address_part1/2`, the stealth-address structure, `timestamp`).

Swap instructions emit via `emit_cpi!` instead of `emit!` because they declare `#[event_cpi]` accounts.

#### Relayer / withdrawal gate

After commitments are inserted, the pipeline checks:

```rust
require!(
    is_signer_relayer
    || token_results.iter().all(|x| *x > BigInt::ZERO)
    || (relayer_fee == 0 && variable_rate == 0),
    HinkalError::RelayerNotWhitelisted
);
```

Any flow that pays a fee must be initiated by a whitelisted relayer; pure deposits (positive amount changes for every leg) are exempt.

#### Stealth address & mint binding

* `extra_randomization` from the public signals must be non-zero (`ZeroRandomizationForbidden`) — this is what makes commitments unlinkable across deposits.
* For every supplied `Mint` account, its key must equal the mint address encoded in the proof's public signals (split into 128-bit parts because BN254 `F_r` is shorter than a 32-byte SPL mint).

#### Output

`process_proof_and_state` returns `ProcessedProof`:

```rust
struct ProcessedProof<'a> {
    parsed_public_signals: ParsedPublicSignals<'a>,
    token_results: Vec<BigInt>,           // amount_change per leg
    stealth_address_structure: StealthAddressStructure,
}
```

The caller (transact / transfer / swap common) then performs the actual SOL/SPL movement and asserts the closing balance equation through `validate_balance_diff`.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://hinkal-team.gitbook.io/hinkal/technical-description/solana-program/proof-pipeline.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
