# PCASH Protocol Specification

This specification defines the deterministic PCASH state transition: how a node turns canonical Ethereum history into PCASH blocks. It covers canonical commitments, transaction wire formats, proof relations, the account-rule interface, genesis, fork rules, and, in Appendix A, the criterion for when a later protocol may claim monetary continuity with this chain. Except for Appendix A, a requirement belongs here only when differing implementations could derive different PCASH state, compute different protocol commitments, or disagree on whether a transaction or proof is valid. Appendix A instead defines when a later protocol may claim canonical monetary continuity with this chain; it imposes no V1 state transition.

Node, SDK, wallet, RPC, storage, relay, recovery, and application conventions are outside protocol semantics except where a section says otherwise. The reference implementation documents its choices for each subsystem: [node](reference/node.md), [wallet and SDK](reference/wallet.md), [mempool and relay](reference/mempool.md), [publication catalog](reference/catalog.md), [source verification](reference/source-verification.md), and [account rules](reference/account-rules.md). Design rationale is in the [design notes](design-notes.md).

**How this document is organized.** Section 1 explains every concept the protocol uses and how the pieces fit together; read it first. Sections 2–8 define data: encodings and primitives, configuration, notes, accounts, state, blocks, and proofs. Sections 9–15 define processing: §9 gives the per-block procedure and how a node reads Ethereum; §10–§13 admit and execute transactions of each type and define fees; §14 covers issuance and block settlement; §15 covers system payouts. Sections 16–17 list parameters and conformance vectors.

## Conventions

* **Requirement words.** MUST, MUST NOT, SHOULD, and MAY are requirements on implementations. Formulas, layouts, tables, and check orders are normative. Explanatory prose says why a rule exists or how the pieces fit, and adds no requirement of its own. Sections marked as reference conventions (§15.1) are not consensus rules.
* **Byte strings.** `‖` is byte concatenation. In a wire layout, `name (n)` is a field of `n` bytes, `u8`, `u16`, `u32`, and `u64` are unsigned integers of that many bits, and every multi-byte integer is big-endian (`u16be` and `uint64be` say so explicitly). `count × [ ... ]` repeats a group `count` times.
* **Field elements and widths.** Circuit values are elements of the BN254 scalar field (§2.2). Inside a hash input, `uintN(x)` is the integer `x`, which MUST fit in `N` bits, used as a field element. The one exception is the contract address, where `uint160(...)` explicitly takes the low 160 bits (§12.4). `xHigh128` and `xLow128` (also written `x_hi128`, `x_lo128`) are the high and low 128 bits of a 256-bit value `x` split big-endian; the split is lossless and is never a reduction modulo the field.
* **Hashes.** `poseidon(...)` is the length-tagged Poseidon2 sponge of §2.3. `keccak256` is Ethereum's original Keccak-256, not NIST SHA3-256. `H(...)` in §15.1 also means `poseidon(...)`.
* **Formulas.** Code blocks give exact formulas and layouts. Operand order in every hash is normative.
* **L1** means Ethereum, the chain PCASH derives from.
* **Canonical** has three senses, distinguished by context. A block, transaction, or hash is canonical when it is part of the current canonical Ethereum chain or the PCASH chain derived from it. A field value is canonical when it is below `p` (§2.2). An encoding is canonical when it is the unique valid representation of its value.

## 1. Overview

This section explains what a PCASH node computes and introduces every concept the rest of the specification uses. It is a guide, not the rule text: where it states a rule, the section it names is normative, and that section governs if the two ever differ.

### 1.1 What a node computes

PCASH is a private-value network built as a sovereign rollup on Ethereum. Ethereum supplies ordering and data availability: PCASH transactions are posted in ordinary Ethereum transactions, as calldata or blobs. Ethereum does not execute or validate them. There is no PCASH smart contract on Ethereum and no Ethereum-enforced validity. Instead, every PCASH node reads canonical Ethereum history and applies the rules in this document to it. Because the rules are deterministic, every correct node derives the same PCASH chain.

The chain starts at a configured Ethereum block, the **anchor**. Every canonical Ethereum block from the anchor onward produces exactly one PCASH block: Ethereum block `anchor + N` produces PCASH block `N`. No one proposes or signs PCASH blocks. A PCASH block is the result of executing, in Ethereum order, the PCASH transactions that the Ethereum block carries, then settling fees and issuance for the block, then committing the resulting state and history. When Ethereum reorganizes, the PCASH blocks derived from abandoned Ethereum blocks are discarded and the replacement blocks are derived from the common parent state (§9.5).

Native PCASH is the network's own money and its fee currency. It starts at zero: genesis has no notes, no accounts, and no spent nullifiers. New native PCASH is created only by issuance to successful qualifying transactions (§14), up to a gross total of 1,000,000,000 PCASH, with no calendar deadline. Fees move existing PCASH and never create supply. There is no public spendable balance and no public form of PCASH.

### 1.2 Concepts

**Notes.** Value exists only as notes. A note has an owner, an asset ID, and an amount, plus a secret known to whoever holds the note. Asset ID `0` is native PCASH. A nonzero asset ID names one instance of the standard fixed-supply fungible asset (§11.6), which anyone can create. The ledger never stores a note's contents. It stores a commitment built in three layers (§4.3): an **owner commitment** hides the owner behind the note secret; a **note-body commitment** adds the asset ID and amount; and an **output commitment** adds the note's position in the output tree. The data needed to recompute a commitment is the note's **opening**. Receiving a note requires no registration and no consent from the recipient.

**The output tree.** Every note ever created is appended, as its output commitment, to one append-only Merkle tree keyed by a sequential `outputIndex` (§6.2). Spent notes are never removed from it.

**Nullifiers.** Spending a note publishes its **nullifier**, a value derived from the note's output commitment, its owner's note key, and its secret (§4.3). Every valid spend of the same note produces the same nullifier, and nothing public links the nullifier to the note. Nodes keep the set of all nullifiers ever published (§6.4); a transaction that presents one already in the set fails. The same set also holds three other kinds of one-time markers: dummy nullifiers for padding slots, application markers that account rules use for replay protection, and asset-issuance markers that make each asset's issuance happen once (§11.3).

**Accounts.** An account is the control point for spending. It is identified by a 160-bit address and stored as one `AccountState` row in the account tree (§5, §6.3). The row commits the account's address, its **nullifier-key commitment** (a commitment to the account's note key root, `nkRoot`, normally secret and fixed forever at creation), its **rule set**, its kind, a revision counter, and an optional **receive key** that senders can use to encrypt note openings to it. There are two kinds:

* A **user account** (`USER`, kind `0`) belongs to an Ethereum address. The ECDSA key for that address authorizes its creation (§12.2), because no rules exist before it. After creation, only the account's installed rules can authorize its spending and its updates (§12.3). There is one user account per address.
* A **contract account** (`CONTRACT`, kind `1`) has no key. Its address is derived from its contents, its rules are fixed at creation, and it can never be updated (§12.4). A user account sponsors its creation. Afterwards a contract can hold native and asset notes, pay native fees, and pay out directly, without any user account initiating its actions.

A note's owner is an account address. Spending existing value requires authorization from the owning account's installed rules, so it requires a registered account. One case needs no registered account: a transaction whose inputs are all padding spends nothing and exercises no account's authority (§11.4).

**Actions.** An **action** is the complete content that one transaction asks account rules to authorize: for type `0x00`, its inputs, outputs, fee, issuance destination, and time bounds; for an account update or a contract creation, the account change together with its fee. Every admitted transaction except a user-account creation carries exactly one action, so this document counts qualifying actions and qualifying transactions interchangeably.

**Account rules and applications.** An account's rule set is a sparse Merkle tree of up to 256 **entries** (§5.2). An entry commits the hash of one circuit's verification key, called an **application**, plus a configuration commitment that the application interprets. A user account's entries are called **policies**; a contract account's entries are called **functions**. An application decides whether to authorize an action. It returns five values to the protocol, the permanent account-rule statement (§8.2): its configuration commitment, a commitment to the exact action it approves, the recent block it read context from, the account it acts for, and the chain ID. PCASH attaches no meaning to an entry beyond that statement: there are no capability flags. Anyone can write an application, and a weak one endangers only the accounts that install it.

**Proofs.** Every transaction type except user-account creation carries one CHONK zero-knowledge proof (§8.1). The proof folds a stack of circuits: one or more applications, then a **protocol kernel** that enforces the ledger rules (note membership and ownership, nullifier derivation, output construction, value conservation, asset rules) and checks that the applications approved the exact action, then a **finalizer** that accepts only protocol kernels, then a **wire circuit** that exposes the transaction's public inputs. Nodes verify only the final proof, against a wire verification key pinned for the transaction type (and, for type `0x00`, for its profile). A type `0x00` proof uses one of three private kernels: the **direct** kernel, where one account owns every real input; the **native fee-payer** kernel, where a user account pays the native fee of a contract account's asset action; and the **issuance** kernel, which creates a new asset (§11.5). Types `0x02` and `0x03` each have their own kernel. CHONK is the client-side proof-folding scheme of the Barretenberg proving system, and Mega and MegaZK are its circuit and verification-key formats. A node verifies proofs by calling the pinned Barretenberg release (§8.1, §16); it does not implement the scheme itself. Which account acted, which rule approved, which notes were spent, and what amounts moved stay private.

**Transactions.** A transaction is one byte string, `txBytes`, with a common envelope naming its type and its PCASH chain ID (§10.1). There are four types:

* `0x00`, a **private-value transaction** (§11): spends and creates notes of native PCASH, of one private asset, or both. It uses one of two fixed **profiles**: S, with 4 input slots and 5 output slots, or L, with 32 of each.
* `0x01`, **user-account creation** (§12.2): registers a user account. It carries no proof and pays no PCASH fee.
* `0x02`, **contract-account creation** (§12.4): publishes a contract account, sponsored and paid for by a user account.
* `0x03`, **user-account update** (§12.3): replaces a user account's rule set or receive key, paid for from the account's own notes.

Types `0x00`, `0x02`, and `0x03` all contain the same **private-value section** (§10.3): the proof, its public inputs, and one fixed-size data item per output. Public inputs include the nullifier of every input slot and the note-body commitment of every output slot, the recent block the proof used, the open fee, the issuance destination, and the time window. Every slot is always filled: unused slots hold **padding** (class `DUMMY`), which is indistinguishable from real slots from outside the proof, so the public shape reveals only the profile.

**Recent block.** Every proof names one `recentPcashBlockHash`. A node accepts the transaction only while that block is canonical and at most `RECENT_PCASH_BLOCK_WINDOW_SECONDS` (one hour) older than the block being derived (§10.6). Inside the proof, the circuits open whatever state they need beneath that single hash: the output tree, the account tree, Ethereum history, or earlier PCASH blocks (§8.4). Nothing about which parts were opened is public.

**Batches, posters, and relaying.** An Ethereum transaction sent to the configured `INBOX_ADDRESS` is an **Inbox transaction**. Its **carrier**, either its calldata or the payload decoded from its blobs, holds one **batch**: a short header followed by a list of items, each item a candidate `txBytes` (§9). Anyone may post a batch; there is no sequencer. The batch header names the poster's **poster owner commitment**, the recipient of open fees and open-route issuance, which is not the Ethereum address that sent the carrier.

**Admission, outcomes, and fatal errors.** An item is **admitted** as a transaction only if its bytes decode completely as a transaction type active at the Ethereum block's timestamp and its envelope names this chain (§10.2). Non-admitted items are skipped and produce no result. Every admitted transaction executes in order against the state left by every earlier successful transaction, and receives exactly one **outcome** (§10.5): `Succeeded`, or the first failed check in a fixed order. A failed transaction changes no state, but its exact bytes and outcome are part of the block (§7.3, §7.8). When a node cannot evaluate something at all, such as a missing blob or a verifier that cannot run, that is a **fatal derivation error**: derivation halts at that Ethereum block and retries, and never turns the problem into an outcome.

**Fees and issuance destinations.** Every private-value section declares an `openFeeAmount`, existing native PCASH paid publicly to the poster of the batch that successfully includes it, and an `issuanceOwnerCommitment`, where any issuance goes (§13). A nonzero issuance commitment names a one-time destination fixed by the proof; zero sends issuance to the successful poster. A positive open fee and a nonzero issuance commitment are mutually exclusive. A wallet can also pay a chosen relayer privately, as an ordinary encrypted output.

**Issuance and settlement.** After all transactions in a block have executed, the node counts the successful qualifying transactions (every successful type `0x00` transaction), computes one issuance amount `rewardEach` that each of them earns (§14.2), and appends **system payout notes**: first designated issuance in transaction order, then one payout per poster combining that poster's open fees and open-route issuance. System payouts are ordinary native notes with public amounts and hidden owners (§15). Because settlement appends these notes after the block's transactions, each transaction reserves room in the output tree for the payout notes it could cause, and fails with `OutputCapacity` if that room is not available (§14.3).

**State, blocks, and history.** After each block, the state is summarized by a **StateRoot** (§6.5) committing the output tree, the account tree, the nullifier set, gross issuance, and the issuance controller's lag. The **block header** (§7.5) commits the parent block, number, timestamp, the block's transaction results, the StateRoot, a tree of every Ethereum block since the anchor with its event logs (§7.2), and a tree of every earlier PCASH block (§7.6). The block hash is the root of the header tree.

### 1.3 The derivation loop

The normative block procedure is at the start of §9. This outline shows the whole of derivation at a glance, with the section that defines each step.

```text
parent = empty genesis state and no parent block                      (§7.7)
for N = 0, 1, 2, ...:
    B     = canonical Ethereum block GENESIS_L1_BLOCK_NUMBER + N
    rules = fork rules active at B.timestamp                            (§3.2)
    monetary inputs from the parent state and B.timestamp               (§14.1)
    for each top-level Ethereum transaction in B, in index order:
        if it is not sent to INBOX_ADDRESS: continue                    (§9.1)
        candidate batch = its calldata, or its decoded blobs            (§9.1–9.2)
            (unavailable blobs are a fatal error; a malformed carrier is ignored)
        frame the batch into items, or ignore it whole                  (§9.3)
        for each item, in order:
            if the item is not admitted: record why; continue           (§10.2)
            outcome = first failing check, else Succeeded               (§10.5, §11–§12)
            if Succeeded: apply its effects to the working state
            record TransactionResult and keep the exact bytes           (§7.3, §7.8)
    settle the block: rewardEach, system payouts, issuance, lag         (§14.2–14.3)
    append Ethereum block leaf N; build StateRoot and header            (§6.5, §7.2, §7.5)
    commit the block atomically, then append history leaf N             (§14.4, §7.6)
```

### 1.4 Where terms are defined

| Term | Defined in |
|---|---|
| anchor, `GENESIS_L1_BLOCK_NUMBER`, `CHAIN_ID`, `INBOX_ADDRESS` | §3.1 |
| fork, activation timestamp | §3.2 |
| `ownerId`, `nkRoot`, note key `noteNk` | §4.1–4.2 |
| owner, note-body, and output commitments; nullifier | §4.3 |
| `AccountState`, account kinds, revision, receive key | §5.1 |
| account-rule entry, rule set, policy, function | §5.2 |
| `authorizationAccountId` | §5.3 |
| output tree, account tree, nullifier set, StateRoot | §6 |
| Ethereum history tree, normalized logs | §7.2 |
| `TransactionResult`, `contentHash`, `transactionId` | §7.3–7.4 |
| block header, block hash, history tree, genesis | §7.5–7.7 |
| CHONK proof, application, kernel, finalizer, wire circuit | §8.1 |
| account-rule statement, action commitment | §8.2–8.3 |
| Inbox transaction, carrier, batch, poster owner commitment | §9 |
| envelope, admission, private-value section, outcome | §10 |
| profile, slot class, transaction commitment | §11.1–11.3 |
| asset ID, standard fungible asset | §11.6 |
| open fee, issuance destination, private relay payment | §13 |
| baseline, offer, allowance, lag, `rewardEach` | §14 |
| system payout note | §15 |

## 2. Encodings and Primitives

### 2.1 Integer encodings

Multi-byte integers in PCASH wire formats are unsigned and big-endian, except for the signed monetary lag of §14 and §6.5. Field elements are encoded as 32-byte big-endian integers. Ethereum and PCASH account addresses are 20 bytes, widened to `uint160` inside field arithmetic.

Value ranges:

* `CHAIN_ID` is a PCASH network identifier `< 2^32`. `L1_CHAIN_ID` is the EIP-155 identifier of the Ethereum chain that carries PCASH data.
* A PCASH block number is `< 2^64`.
* Every note amount and every native-PCASH fee MUST be `< 2^128`. Asset input and output sums, burn amounts, and an asset's original issuance MUST be `< 2^131`.
* Gross native issuance, Q128 rates, and the signed Q128 lag use the widths prescribed by §14. Monetary arithmetic never reduces values modulo the proof field.
* Native amounts use 18 decimal places: one PCASH is `10^18` atomic units.

### 2.2 Field

All circuit arithmetic is over the BN254 scalar field:

```text
p = 21888242871839275222246405745257275088548364400416034343698204186575808495617
  = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
```

Every public input interpreted as a field element MUST satisfy `x < p`. A value `< p` is called **canonical**.

### 2.3 Poseidon2 hash

PCASH uses Poseidon2 over the BN254 scalar field for its circuit-friendly commitments. (The nullifier set, the transaction-results root, output-data hashes, and receive-key digests use Keccak-256 instead, as defined where they appear.) Parameters: state width `t = 4`, rate `3`, capacity `1`, S-box `x^5`, `R_F = 8` full rounds, `R_P = 56` partial rounds, and matrices and round constants exactly as in the PCASH V1 source asset `poseidon2_bn254_t4_rf8_rp56.json`. In that asset, `M_E = externalMatrix`, `D_I = internalDiagonal`, and `RC = roundConstants`; every asset value is interpreted as a field element.

For a state `s` of four field elements, define `extern(s) = M_E · s`. Define `intern(s)` by `S = s[0] + s[1] + s[2] + s[3]` and `intern(s)[i] = S + D_I[i] × s[i]`. The permutation is:

1. Set `state = extern(state)`.
2. For full rounds `r = 0..3`, add `RC[4r + i]` to each `state[i]`, apply `x^5` to every element, then apply `extern`.
3. For partial rounds `r = 0..55`, add `RC[16 + r]` to `state[0]`, apply `x^5` only to `state[0]`, then apply `intern`.
4. For full rounds `r = 0..3`, add `RC[72 + 4r + i]` to each `state[i]`, apply `x^5` to every element, then apply `extern`.

The first full-round block therefore consumes `RC[0..15]`, the partial rounds consume `RC[16..71]`, and the second full-round block consumes `RC[72..87]`.

Every `poseidon(...)` in this specification is the following length-tagged sponge:

```text
poseidon(x_1, ..., x_N) = Poseidon2_sponge(x_1, ..., x_N)
```

`Poseidon2_sponge`: let `N` be the number of inputs. Initialize the 4-element state to `[0, 0, 0, N << 64]`. If `N = 0`, apply one permutation and return state element `0`. Otherwise, process the inputs in chunks of 3, zero-padding the final chunk: add each chunk element into `state[0..2]` mod `p`, then apply one permutation. Return state element `0`. There is no additional final permutation after the last absorbed chunk. Implementations MUST use this length-tagged sponge; the bare permutation with capacity `0` produces different values. Known-answer values and worked multi-chunk cases are in `vectors/poseidon2_vectors.json`.

### 2.4 Merkle trees

Most PCASH commitments are binary Merkle trees whose internal nodes are `poseidon(left, right)`. Empty leaves are `0`; `EMPTY[h + 1] = poseidon(EMPTY[h], EMPTY[h])`; the empty root of a depth-`D` tree is `EMPTY[D]`.

A membership proof for a depth-`D` tree is `D` sibling nodes, listed from the leaf level upward. At height `h`, bit `h` of the tree key (least significant bit first) selects the side: bit `0` means the current node is the left child.

```text
cur = leaf
idx = key
for h in 0..D-1:
    if (idx & 1) == 0: cur = poseidon(cur, siblings[h])
    else:              cur = poseidon(siblings[h], cur)
    idx = idx >> 1
root = cur
```

The circuit-facing trees are listed below; each is defined in the section cited. The state and header trees absorb a domain tag at every node instead of using this plain node function. The nullifier set (§6.4) and the transaction-results tree (§7.3) are Keccak trees and are defined separately.

* **Output-commitment tree** (§6.2): depth 40, append-only, keyed by `outputIndex` (`uint64 < 2^40`, sequential from `0`). It contains indexed commitments to ordinary note bodies, including system payouts and padding bodies.
* **Address Account State Tree** (§6.3): depth 40, mutable, keyed by `accountIndex` (`uint64 < 2^40`, assigned sequentially from `0` when an address's account is first created).
* **State commitment tree** (§6.5): depth `STATE_TREE_DEPTH = 4`, keyed by protocol-assigned integers `0–15`; every internal node absorbs `STATE_COMMITMENT_DOMAIN`.
* **Block-header tree** (§7.5): depth `BLOCK_HEADER_TREE_DEPTH = 4`, keyed by protocol-assigned integers `0–15`; every internal node absorbs `BLOCK_HEADER_DOMAIN`.
* **Ethereum blockhash tree** (§7.2): depth `ETHEREUM_BLOCK_TREE_DEPTH = 40`, append-only, keyed by `blockNumber − anchorNumber`.
* **PCASH history tree** (§7.6): depth `HISTORY_TREE_DEPTH = 40`, append-only, keyed by PCASH block number, committing only completed prior blocks.
* **Account-rule-set tree** (§5.2), a user account's policy set or a contract account's function set: depth `ACCOUNT_RULE_SET_DEPTH = 8`, sparse. Slot assignment is not protocol-constrained. Empty root `EMPTY[8]`.

### 2.5 Domain tags

Most Poseidon commitments take a domain tag as their first operand, so that a value computed for one purpose cannot be reinterpreted as another. Each tag is derived from a context string:

```text
DOMAIN = uint256(keccak256("pcash.v1." || context_string)) mod p
```

The prefix is the literal nine-byte ASCII string `pcash.v1.`, including its trailing dot. Interpret the digest as a big-endian unsigned integer before reducing it modulo `p`. `assets/gen_domain_tags.mjs` is the deterministic generator, and `assets/domain_tags.json` is the generated source asset consumed across implementations.

| Constant | Context string |
|---|---|
| `NULLIFIER_KEY_COMMITMENT_DOMAIN` | `nullifier_key_commitment` |
| `NOTE_NULLIFIER_KEY_DOMAIN` | `note_nullifier_key` |
| `ADDRESS_OWNER_DOMAIN` | `address_owner` |
| `DUMMY_OWNER_DOMAIN` | `dummy_owner` |
| `PAYOUT_SEED_DOMAIN` | `payout_seed` |
| `PAYOUT_TX_CONTEXT_DOMAIN` | `payout_tx_context` |
| `PAYOUT_NOTE_SECRET_DOMAIN` | `payout_note_secret` |
| `PAYOUT_BATCH_CONTEXT_DOMAIN` | `payout_batch_context` |
| `OWNER_COMMITMENT_DOMAIN` | `owner_commitment` |
| `NOTE_BODY_COMMITMENT_DOMAIN` | `note_body_commitment` |
| `OUTPUT_COMMITMENT_DOMAIN` | `output_commitment` |
| `NULLIFIER_DOMAIN` | `nullifier` |
| `DUMMY_NULLIFIER_DOMAIN` | `dummy_nullifier` |
| `APPLICATION_NULLIFIER_DOMAIN` | `application_nullifier` |
| `IMPLICIT_NOTE_SECRET_SEED_DOMAIN` | `implicit_note_secret_seed` |
| `TRANSACT_NOTE_SECRET_DOMAIN` | `transact_note_secret` |
| `TX_COMMITMENT_GLOBAL_DOMAIN` | `tx_commitment_global` |
| `TX_COMMITMENT_INPUT_DOMAIN` | `tx_commitment_input` |
| `TX_COMMITMENT_OUTPUT_DOMAIN` | `tx_commitment_output` |
| `TX_COMMITMENT_DOMAIN` | `tx_commitment` |
| `ASSET_ID_DOMAIN` | `asset_id` |
| `STANDARD_FUNGIBLE_LAW_ID` | `standard_fungible_law` |
| `STANDARD_FUNGIBLE_CONFIGURATION_DOMAIN` | `standard_fungible_configuration` |
| `FIXED_ISSUANCE_SECRET_COMMITMENT_DOMAIN` | `fixed_issuance_secret_commitment` |
| `ASSET_ISSUANCE_REPLAY_DOMAIN` | `asset_issuance_replay` |
| `ASSET_ISSUANCE_NULLIFIER_DOMAIN` | `asset_issuance_nullifier` |
| `PRIMARY_FEE_PAYER_ACTION_DOMAIN` | `primary_fee_payer_action` |
| `NATIVE_FEE_ACTION_DOMAIN` | `native_fee_action` |
| `CONTRACT_FEE_SPONSORSHIP_ACTION_DOMAIN` | `contract_fee_sponsorship_action` |
| `STATE_COMMITMENT_DOMAIN` | `state_commitment` |
| `BLOCK_HEADER_DOMAIN` | `block_header` |
| `GENESIS_EXTRA_DATA_DOMAIN` | `genesis_extra_data` |
| `ETHEREUM_BLOCK_LOGS_DOMAIN` | `ethereum_block_logs` |
| `ETHEREUM_LOG_DOMAIN` | `ethereum_log` |
| `ETHEREUM_LOG_TOPICS_DOMAIN` | `ethereum_log_topics` |
| `ETHEREUM_LOG_DATA_DOMAIN` | `ethereum_log_data` |
| `HISTORY_LEAF_DOMAIN` | `history_leaf` |
| `AUTHORIZATION_ACCOUNT_DOMAIN` | `authorization_account` |
| `ACCOUNT_UPDATE_AUTHORIZATION_ACTION_DOMAIN` | `account_update_authorization_action` |
| `ACCOUNT_UPDATE_FUNDED_ACTION_DOMAIN` | `account_update_funded_action` |
| `ACCOUNT_STATE_COMMITMENT_DOMAIN` | `account_state_commitment` |
| `ACCOUNT_RULE_ENTRY_DOMAIN` | `account_rule_entry` |
| `CONTRACT_ADDRESS_DOMAIN` | `contract_address` |

Keccak-based commitments (the nullifier set, the transaction-results root, and transaction IDs) use their own UTF-8 labels, given where they are defined.

## 3. Network Configuration and Forks

### 3.1 Configuration and chain identity

A PCASH network is fixed by its derivation configuration:

* `CHAIN_ID`: the PCASH network identifier. Different chain instances and genesis anchors MUST use distinct nonzero `CHAIN_ID` values below `2^32`.
* `L1_CHAIN_ID`: the EIP-155 chain ID of the Ethereum chain that carries the data.
* `INBOX_ADDRESS`: the Ethereum address whose incoming transactions carry PCASH batches (§9.1).
* `GENESIS_L1_BLOCK_NUMBER` and `GENESIS_L1_BLOCK_HASH`: the **anchor**, the Ethereum block that produces PCASH block `0`.

The anchor is elapsed issuance time zero and produces block `0`; there is no separate, delayed monetary activation. The production launch identity is fixed before genesis; local and test identities are assigned separately. The public chain identity is the pair `(chainId, genesisHash)`, where `genesisHash` is the ordinary block hash of block `0` (§7.7).

### 3.2 Forks

A **fork** is a human-readable name and an Ethereum header activation timestamp compiled into a software release. Aster is the launch rule set and is active from genesis. The compiled fork schedule is empty: no later fork is scheduled. A future release may add one. Fork names and timestamps are not hashed into `genesisHash`, `StateRoot`, or a separate upgrade identity.

For an Ethereum block with header timestamp `t`, derivation applies the rules of the latest compiled fork whose activation timestamp is `<= t`. Activation is by timestamp rather than height so that the scheduled wall-clock boundary is predictable. Historical replay uses each block's actual timestamp, so a reorganization reselects fork rules from the same deterministic input used during initial derivation.

At each activation timestamp, the fork definition fixes:

* the complete accepted batch-version set, as ascending unique bytes (§9.3);
* the transaction-type and verification-key mapping (§10.1);
* whether the fork declares a monetary trust reset (Appendix A).

Each assigned transaction type permanently fixes its wire payload shape, verifier, and type-specific interpretation, and historical entries always decode under that meaning. A fork may activate or retire a type for new blocks. Each assigned batch-version byte likewise permanently fixes the framing parser and structural bounds used to recover an ordered list of typed transactions; a different framing rule requires a different batch version. Historical derivation selects the exact rules in force at each Ethereum block's timestamp.

## 4. Notes and Nullifiers

A note is a unit of private value. The ledger stores only commitments to notes; whoever holds a note's opening can prove things about it without revealing it. This section defines how notes name their owners, how they are committed, and how spending them produces nullifiers.

### 4.1 Owners

A note's owner is a 160-bit PCASH account address. Every note binds an `ownerId` derived from that address:

```text
ownerId = poseidon(ADDRESS_OWNER_DOMAIN, uint160(address))
```

For a user account, `address` is the Ethereum-style secp256k1 address whose key authorizes the account's creation (§12.2); after that, the account's installed applications authorize its private-value transactions and updates (§8.2). For a contract account, `address` is derived from the contract's immutable contents (§12.4) and has no corresponding key. Both kinds map to the same kind of `ownerId`.

Padding output slots use a fixed placeholder owner that no account controls:

```text
DUMMY_OWNER_ID = poseidon(DUMMY_OWNER_DOMAIN, 0xdead)
```

### 4.2 Nullifier keys

Each account binds one nonzero BN254 scalar, its **nullifier-key root** `nkRoot`, through a public commitment stored in its account state (§5.1). `nkRoot` is secret unless a contract chooses to publish it. The note key used in note nullifiers is derived from it:

```text
nullifierKeyCommitment = poseidon(NULLIFIER_KEY_COMMITMENT_DOMAIN, nkRoot)      // published in every account
noteNk                 = poseidon(NOTE_NULLIFIER_KEY_DOMAIN, nkRoot)            // note nullifiers
```

Whoever spends the account's notes knows `nkRoot`. A contract account may also publish its `nkRoot` when its immutable `publishesNkRoot` flag is set (§12.4). `nullifierKeyCommitment` is immutable once written: account creation supplies it and no update can change it (§12.3).

Disclosing `nkRoot` or `noteNk` reveals nullifier material but does not by itself authorize a spend. A real note's nullifier also requires the note's secret, and every transfer still requires a valid authorization proof.

### 4.3 Note commitments and nullifiers

A note's opening is its owner (`ownerId`), a nonzero secret `noteSecret`, its `assetId`, and its `amount`. Its commitment is built in three layers. The owner commitment hides the owner behind the secret. The body commitment adds what the note holds. The output commitment adds where the note sits in the output tree, which the node assigns when the transaction succeeds; separating the body from its position lets a wallet prove a transaction before knowing where its outputs will land.

```text
ownerCommitment      = poseidon(OWNER_COMMITMENT_DOMAIN, ownerId, noteSecret)
noteBodyCommitment   = poseidon(NOTE_BODY_COMMITMENT_DOMAIN, ownerCommitment, assetId, amount)
outputCommitment     = poseidon(OUTPUT_COMMITMENT_DOMAIN, noteBodyCommitment, outputIndex)
nullifier            = poseidon(NULLIFIER_DOMAIN, outputCommitment, noteNk, noteSecret)
dummyNullifier       = poseidon(DUMMY_NULLIFIER_DOMAIN, dummyInputSeed, inputIndex)
applicationNullifier = poseidon(
    APPLICATION_NULLIFIER_DOMAIN,
    authorizationAccountId, selectedEntryCommitment, replayId)
```

The operands are: `outputIndex`, the note's position in the output tree; `inputIndex`, the zero-based input slot index; `dummyInputSeed`, a private nonzero seed chosen once per action; `selectedEntryCommitment`, the `accountRuleEntryCommitment` (§5.2) of the rule entry selected to authorize the action; and `replayId`, a value defined by that rule (§11.3).

`nullifier` is the spend marker of a real note: it binds the exact output (and so its position), the owner's note key, and the note secret, so it is the same for every valid spend of that note and different for every other note. `dummyNullifier` and `applicationNullifier` are the markers of padding input slots and of account-rule replay protection; §11.3 defines when each is used. All three, together with asset-issuance markers (§11.6), share one spent set (§6.4).

`assetId = 0` identifies native PCASH. A nonzero `assetId` identifies one instance of the standard fungible asset (§11.6).

Receiving native or asset notes requires neither registration nor the recipient's consent: a note's owner commitment needs only the owner's address, and nothing checks that the address has an account.

### 4.4 Real notes and padding

* Every real input's `noteSecret` is nonzero.
* A padding (dummy) input slot has no input commitment and no note secret. The protocol kernel derives its nonzero indexed nullifier from one private nonzero `dummyInputSeed` per action. Because the transaction commitment (§11.3) includes every input slot's class, commitment, and nullifier, changing `dummyInputSeed` produces a different authorized transaction.
* The note body has exactly the three semantic operands shown: owner commitment, asset ID, and amount. There is no note-state operand. Applications may commit opaque private material inside `noteSecret`, but consensus exposes no standardized, independently openable state field and no state-transition interface for it.
* For every real output, the protocol kernel receives an opaque nonzero `ownerCommitment` and constructs the body under the selected asset and amount. Consensus does not require the transaction's prover to know the opening of a real output's owner commitment. Recipient acceptance, delivery, and derivation of output secrets are application and wallet policies.
* A padding output has a private nonzero `dummySecret` and the canonical owner commitment `ownerCommitment = poseidon(OWNER_COMMITMENT_DOMAIN, DUMMY_OWNER_ID, dummySecret)`.
* A native-only action has one native input owner. An asset-bearing action has one input owner across every real native and asset input, except on the native fee-payer path (§11.7).

## 5. Accounts

An account is the durable control point for spending. Notes name their owner by address; the account row stored under that address says which rules may authorize spending and what the account's permanent key commitment is. Accounts are created by transaction types `0x01` and `0x02` and updated by type `0x03` (§12).

### 5.1 Account state

Each account is one `AccountState` row, committed as one leaf of the account tree (§6.3):

```text
accountStateCommitment = poseidon(ACCOUNT_STATE_COMMITMENT_DOMAIN,
    uint160(address),
    nullifierKeyCommitment,
    accountRuleSetCommitment,
    accountKind,
    accountRevision,
    receiveKeyPresent,
    receiveKeyHashHigh128,
    receiveKeyHashLow128)
```

This is the only account state commitment. It commits the account's immutable identity, its spending rules, its kind, its revision, and its receive-key state. The complete row and its leaf always change together as one transition. The fields are:

* `address`: the account's 160-bit address.
* `nullifierKeyCommitment`: the commitment to the account's `nkRoot` (§4.2). It commits the account's key hierarchy and is unrelated to the root of the global spent-nullifier set (state keys `6` and `7`, §6.5). **Immutable**: creation supplies it, and every update derives the existing value from live state.
* `accountRuleSetCommitment`: the root of the account's rule set (§5.2), or `EMPTY[8]`. A user account supplies it as its mutable `policySetCommitment`; a contract account supplies it as its immutable `functionSetCommitment`.
* `accountKind`: `0` for a user account, `1` for a contract account. Every reconstruction of an account state commitment enforces this Boolean range, and each relation separately restricts which kind it accepts.
* `accountRevision`: a nonzero `u32`. Consensus sets it to `1` at creation and increments it exactly once on each accepted user-account update. It is never serialized by creation.
* `receiveKeyPresent` (also written `present`): Boolean.
* `receiveKeyHashHigh128`, `receiveKeyHashLow128`: the exact big-endian high and low 128-bit halves of `receiveKeyHash`, never a field reduction.

A **receive key** is an ML-KEM-768 public key of exactly `ML_KEM768_PUBLIC_KEY_LEN = 1,184` bytes that senders may use to encrypt note openings to the account. State stores only its digest:

```text
receiveKeyHash = keccak256(mlKem768PublicKey)
```

If `present = 0`, both digest limbs are zero and the account has no receive key. If `present = 1`, the digest is the Keccak hash of the exact key bytes introduced by creation or by the latest update `REPLACE` operation, and a later update may retain it with `KEEP` (§12.3); either limb may numerically be zero. The raw key is not part of intrinsic state. Raw keys remain recoverable from their creation or latest `REPLACE` body in Ethereum data; a node may store them beside intrinsic state.

A **user account** (`accountKind == 0`) is created by type `0x01` and updated by type `0x03`; there is at most one per Ethereum address. Its creation is authorized by that address's ECDSA key; afterwards it is governed only by its installed rule set. A **contract account** (`accountKind == 1`) is created by type `0x02`, is keyless, derives its address from its contents, and is never updated. A contract's immutability rests on its leaf commitment together with the write rules, not on the collision resistance of the address derivation.

### 5.2 Account-rule entries and rule sets

An account's rule set is a sparse depth-8 Poseidon tree (§2.4), so it holds at most 256 **entries**. Each entry names one authorization circuit, called an **application**, by the hash of its verification key, together with configuration that the application interprets:

```text
accountRuleEntryCommitment = poseidon(ACCOUNT_RULE_ENTRY_DOMAIN, implementationVkHash, configurationCommitment, commitmentBlinder)
```

* `implementationVkHash` is the canonical Poseidon2 hash of the application's 163-field Mega verification key (§8.1). It may commit to any Mega circuit that returns the permanent five-field account-rule statement (§8.2). The verification key itself is a private proving witness and is never published.
* `configurationCommitment` is opaque to the protocol. The selected application interprets it: it might commit to a signer, a threshold, a passkey, a recovery condition, or anything else.
* `commitmentBlinder` separates otherwise identical entries in a user account, whose entries are never published. A wallet SHOULD draw it unpredictably, so that an installed policy cannot be recognized from a known template. A contract function uses the canonical blinder `0` (§12.4), because a contract's entries are published in full in Ethereum data and a published blinder hides nothing.

The entry contains no capability declaration: nothing in it says what kinds of action the application approves. The protocol kernel authenticates the account and the exact action commitment; the application decides which action shapes and conditions it accepts. A narrow application MUST reconstruct enough of the domain-separated action to fail closed for every shape it does not support. A deliberately universal application may treat the action commitment as opaque.

The two account kinds expose the same entries under different names:

| Account kind | An account rule is exposed as | Selected by |
|---|---|---|
| User account (§12.2) | a **policy** | its slot → the entry index |
| Contract account (§12.4) | a **function** | its selector → the entry index |

Duplicate entries add no power. To revoke a user policy, the account must commit a new rule set that excludes every index containing it.

### 5.3 Stable account identity

A proof ties the notes it spends to the account that authorizes them through one derived identity. For a selected account:

```text
ownerId                = poseidon(ADDRESS_OWNER_DOMAIN, uint160(address))
nullifierKeyCommitment = poseidon(NULLIFIER_KEY_COMMITMENT_DOMAIN, nkRoot)
authorizationAccountId = poseidon(AUTHORIZATION_ACCOUNT_DOMAIN, ownerId, nullifierKeyCommitment)
```

`authorizationAccountId` uses the account's public `nullifierKeyCommitment`. It does not change when the account's rule set, receive key, or revision changes. It is distinct from any signing key inside an application, so an account can delegate authority or rotate signers without redirecting its change outputs.

## 6. State

### 6.1 Components

PCASH state is what transactions and block settlement change. The intrinsic V1 transition state consists of:

* the append-only output-commitment tree and its count (§6.2);
* gross native issuance and the signed post-settlement Q128 issuance lag (§14);
* the mutable Address Account State Tree and its count, with each account address assigned one sequential index and one complete current `AccountState` row (§6.3);
* the spent-nullifier sparse set (§6.4).

Canonical PCASH headers, exact `L1Origin` records (§7.1), the Ethereum blockhash tree, and the PCASH history tree are derived chain data, not intrinsic `StateRoot` components (§7). Raw transaction bodies and output data are likewise not `StateRoot` components.

### 6.2 Output tree

The output tree (depth 40, §2.4) holds the output commitment of every note ever created, at sequential `outputIndex` values starting from `0`. Outputs are appended as follows:

* Every successful private-value section appends its selected profile's `O` ordinary output commitments, one per output slot, including padding.
* After the block's ordinary outputs, positive system payouts append additional ordinary native notes: designated issuance in canonical transaction order, then poster payouts in first-positive-contribution order (§14.3).
* User-account creation appends no output. Contract creation and user-account update each carry a five-output fee section and may also produce an open-fee payout note.

A transaction fails with `OutputCapacity` if its outputs, the payout notes it could cause, and the payout notes reserved by earlier successful transactions in the block could together exceed `2^40` outputs (§14.3).

### 6.3 Account tree

The Address Account State Tree (depth 40, §2.4) holds one `accountStateCommitment` (§5.1) per account at its `accountIndex`. The first account created receives index `0`, and each later creation receives the next index. Creation (types `0x01` and `0x02`) fails with `AccountCapacity` unless `accountCount + pendingNewAccounts + 1 <= 2^40`, where `accountCount` is the count at the start of the block and `pendingNewAccounts` is the number of accounts created earlier in the block. Nodes also keep an address-to-index mapping and each account's complete current row; the row and its leaf are always written together.

### 6.4 Nullifier set

The spent-nullifier set is a depth-256 binary Keccak sparse tree keyed directly by the exact nonzero canonical 32-byte BN254 encoding `n` of each spent nullifier. The key is never reduced or rehashed into a namespace:

```text
EMPTY[0]     = keccak256(utf8("pcash.nullifier.empty.v1"))
EMPTY[h + 1] = keccak256(utf8("pcash.nullifier.node.v1") || EMPTY[h] || EMPTY[h])
spentLeaf(n) = keccak256(utf8("pcash.nullifier.leaf.v1") || n)
node(l, r)   = keccak256(utf8("pcash.nullifier.node.v1") || l || r)
```

Sibling arrays run from the leaf upward. Level `h` uses bit `h` of `n`, interpreted as a big-endian unsigned integer, so leaf-up traversal consumes the least-significant bits first. As in §2.4, bit `0` means the current node is the left child: `node(current, sibling)`; bit `1` gives `node(sibling, current)`. The empty root is `EMPTY[256]`. Note, dummy, asset-issuance, and application nullifiers all share this set. The set has no sequential append position, so there is no nullifier count.

### 6.5 StateRoot

After every completed transition, the state is summarized by a fixed depth-4 Poseidon tree whose leaves are the state components:

| Key | Value |
|---:|---|
| 0 | `outputRoot` |
| 1 | `outputCount` |
| 2 | `issuedSoFar` |
| 3 | `lagHigh128` |
| 4 | `accountStateRoot` |
| 5 | `accountCount` |
| 6 | `nullifierRootHigh128` |
| 7 | `nullifierRootLow128` |
| 8 | `lagLow128` |
| 9–15 | `0`, unassigned |

* Output and account counts are in `[0, 2^40]`; occupied indices are below the count and fit `uint40`. Counts are committed because a Merkle root authenticates contents but not the next free append position.
* The lag limbs are the big-endian high and low halves of the canonical signed 256-bit two's-complement encoding of `lagQ` (§14.1).
* The nullifier root is split losslessly into big-endian 128-bit limbs.

```text
stateNode(left, right) = poseidon(STATE_COMMITMENT_DOMAIN, left, right)
StateRoot = root(stateLeaves, depth = 4, node = stateNode)
```

`StateRoot` deliberately excludes Ethereum provenance, PCASH history, source configuration, the header timestamp, raw material, and lookup indexes. Keys 9–15 are literal zero leaves when constructing this root. They preserve the typed opening formula used by immutable circuits; no current circuit may treat an unassigned leaf's zero value as a protocol fact.

## 7. Blocks and History

A PCASH block records what one Ethereum block did to PCASH: the outcome of every admitted transaction, the resulting state, and authenticated history. Its hash is the root of a small typed header tree, so a circuit can open any one header fact without the others.

### 7.1 One PCASH block per Ethereum block

Derivation produces one canonical PCASH block for every canonical Ethereum block from the anchor onward. The anchor produces PCASH block `0`, and each later block has number `parent.number + 1`. PCASH block `N` therefore corresponds to Ethereum block `GENESIS_L1_BLOCK_NUMBER + N`. No earlier Ethereum block produces a PCASH block. A PCASH block's timestamp is its Ethereum block's header timestamp.

Each block has an associated `L1Origin = (blockNumber, blockHash, blockTimestamp, ethereumLogsRoot, ethereumLogCount)`, the exact preimage of its Ethereum history leaf (§7.2). `L1Origin` records which Ethereum input produced the block as derivation provenance. It is not a header field and does not enter the block hash. Explanatory failure operands and diagnostic exclusion strings are not protocol inputs either.

### 7.2 Ethereum history tree and normalized logs

Every PCASH block commits a record of every canonical Ethereum block since the anchor, including each block's event logs, so that account rules can prove Ethereum facts (§8.4).

Derivation maintains a depth-`ETHEREUM_BLOCK_TREE_DEPTH = 40` append-only Poseidon Merkle tree over canonical Ethereum blocks. Leaf `i` commits Ethereum block `anchorNumber + i`, where the configured anchor is leaf `0`:

```text
ethereumBlockLeaf_i = poseidon(
    ETHEREUM_BLOCK_LOGS_DOMAIN,
    uint64(blockNumber),
    blockHashHigh128,
    blockHashLow128,
    uint64(blockTimestamp),
    logsRoot,
    uint64(logCount))
```

PCASH block `N` appends Ethereum leaf `N` before its header is constructed, so `ethereumBlockhashRoot_N` contains indices `0..N`.

**Normalized logs.** For each canonical Ethereum block and its ordered execution receipts, derivation orders every receipt log by transaction index and then by receipt-local log index, and commits the logs in a depth-64 Poseidon tree. How an implementation acquires canonical Ethereum blocks and execution results is outside the protocol. Each log is normalized as follows:

* Its topics are padded to four 32-byte values and each is split losslessly into big-endian 128-bit high and low limbs, eight limbs in total.
* Its data is split into consecutive 16-byte big-endian chunks. The final short chunk is interpreted as its big-endian integer; the separately committed byte length distinguishes encodings that differ only in leading zeros.

```text
topicsHash = poseidon(ETHEREUM_LOG_TOPICS_DOMAIN, uint3(topicCount), topic0High128, topic0Low128, ..., topic3High128, topic3Low128)
dataHash_0 = poseidon(ETHEREUM_LOG_DATA_DOMAIN, uint64(dataByteLength))
dataHash_(j+1) = poseidon(dataHash_j, dataChunk_j)
logLeaf = poseidon(ETHEREUM_LOG_DOMAIN, uint64(transactionIndex), uint64(receiptLocalLogIndex), uint160(emitter), topicsHash, uint64(dataByteLength), dataHash_dataChunkCount)
logsRoot = merkleRoot({ logLeaf_j at index j in canonical log order }, depth = 64)
```

`EMPTY_LOGS_ROOT = EMPTY[64]` under the ordinary `poseidon(left, right)` node function.

Each Ethereum leaf binds the exact Ethereum block identity, timestamp, normalized-log root, and log count.

*Note, how applications use the tree.* An application that authenticates an Ethereum event opens key `6` of a recent PCASH header, then the exact Ethereum leaf beneath that root, then the exact log beneath `logsRoot`, and then interprets the emitter, topics, and data according to its own semantics. Unrelated logs change only the block's log root and the selected path; they do not enlarge the application circuit. A proof-serving index need retain only log coordinates, leaf indices and hashes, and shared tree nodes; the caller supplies the public raw log and recomputes its leaf against the returned opening. From the authenticated Ethereum block hash, a generic application may instead use raw header, receipt, state, storage, transaction, timestamp, or PREVRANDAO proofs.

The commitment is derived state and requires no PCASH-owned Ethereum contract. Canonical Ethereum history and the derivation rules determine the tree; circuits need no separate source or anchor opening. PCASH does not reproduce Ethereum's receipts root in its header; the proof-friendly normalized-log root inside each Ethereum leaf takes its place.

### 7.3 Transaction results

Every admitted transaction (§10.2) contributes one result to its block:

```text
TransactionResult = (l1BlockNumber, l1BlockHash, l1TransactionIndex, batchItemIndex, contentHash, outcomeCode)
```

where `contentHash = keccak256(txBytes)` (§7.4) and `outcomeCode` is the transaction's outcome (§10.5). Results are ordered by Ethereum transaction index and then by batch item index. A result's dense zero-based position among the block's results is its `blockTransactionIndex`. By contrast, `batchItemIndex` is the item's original zero-based position in its carrier and may have gaps where items were not admitted.

`Succeeded` means that the transaction's state transition applied. Any deterministic execution failure records the first outcome selected by the precedence rules of §10.5 and leaves state unchanged for that transaction. Two nodes that assign different outcomes disagree on the canonical block even if they compute the same `stateRoot`.

The results are committed by `transactionResultsRoot`, a Keccak construction. `W64` and `W16` encode the stated unsigned integer in big-endian order and left-pad it to 32 bytes, rejecting values outside the stated width.

```text
TRANSACTION_RESULTS_ROOT_DOMAIN = keccak256(utf8("pcash.block.transactionresults.v1"))
TRANSACTION_RESULT_LEAF_DOMAIN  = keccak256(utf8("pcash.block.transactionresult.v1"))
TRANSACTION_RESULT_NODE_DOMAIN  = keccak256(utf8("pcash.block.transactionresultnode.v1"))
transactionResultLeaf_i = keccak256(
    bytes32(TRANSACTION_RESULT_LEAF_DOMAIN) || W64(i) ||
    W64(l1BlockNumber_i) || bytes32(l1BlockHash_i) ||
    W64(l1TransactionIndex_i) || W64(batchItemIndex_i) ||
    bytes32(contentHash_i) || W16(outcomeCode_i))
transactionResultNode(left, right) = keccak256(bytes32(TRANSACTION_RESULT_NODE_DOMAIN) || bytes32(left) || bytes32(right))
merkleRoot([]) = bytes32(0)
transactionResultsRoot = keccak256(bytes32(TRANSACTION_RESULTS_ROOT_DOMAIN) || W64(count) || bytes32(merkleRoot))
```

`i` is `blockTransactionIndex`. The binary tree pairs adjacent nodes left to right, and an unpaired final node is promoted unchanged. The indexed leaves and the outer count bind order and shape.

### 7.4 Transaction identity

A transaction has two identifiers, for two different questions.

`contentHash = keccak256(txBytes)` names one exact serialization; before inclusion it is the only identifier a transaction has. It does not name a logical payment or intent: many valid proof byte strings can encode one statement, and re-proving the same action produces different bytes. Conflicts between transactions are determined by the nullifiers they consume, not by content hashes.

`transactionId` names one exact canonical inclusion:

```text
TRANSACTION_ID_DOMAIN = keccak256(utf8("pcash.transaction.id.v1"))
transactionId = keccak256(
    bytes32(TRANSACTION_ID_DOMAIN) ||
    bytes32(l1BlockHash) ||
    uint64be(l1TransactionIndex) ||
    uint64be(batchItemIndex) ||
    bytes32(contentHash))
```

The integer widths, big-endian encoding, concatenation order, UTF-8 label bytes, and Keccak-256 are normative. `transactionId` is derived from committed fields and is not committed again. Identical bytes included at different Ethereum coordinates have distinct transaction IDs, and an Ethereum reorganization that changes the block hash invalidates the old ID. Do not add `CHAIN_ID`, `genesisHash`, or the PCASH block hash separately: the chain ID is already inside `contentHash`, because it is serialized in the transaction envelope (§10.1).

### 7.5 Block header and block hash

The header is a fixed depth-4 Poseidon tree with empty leaf `0`:

| Key | Value |
|---:|---|
| 0 | `parentHash` |
| 1 | `number` |
| 2 | `timestamp` |
| 3 | `transactionResultsRootHigh128` |
| 4 | `transactionResultsRootLow128` |
| 5 | `stateRoot` |
| 6 | `ethereumBlockhashRoot` |
| 7 | `historyRoot` |
| 8 | `extraData` |
| 9–15 | `0`, unassigned |

`parentHash`, `stateRoot` (§6.5), `ethereumBlockhashRoot` (§7.2), `historyRoot` (§7.6), and `extraData` (§7.7) are canonical BN254 field elements. `number` and `timestamp` fit `uint64`. The transaction-results-root limbs are a lossless big-endian split of `transactionResultsRoot` (§7.3). Keys 9–15 are literal zero leaves when constructing this root. They preserve the typed opening formula used by immutable circuits; no current circuit may treat an unassigned leaf's zero value as a protocol fact.

```text
headerNode(left, right) = poseidon(BLOCK_HEADER_DOMAIN, left, right)
blockHash = root(headerLeaves, depth = 4, node = headerNode)
```

Raw `L1Origin`, fork name, software version, and batch version are not header fields.

### 7.6 PCASH history tree

For block `N`, `historyRoot_N` commits exactly the completed PCASH blocks `0..N-1`:

```text
historyLeaf_i = poseidon(HISTORY_LEAF_DOMAIN, uint40(i), blockHash_i)
historyRoot_N = merkleRoot({ historyLeaf_i at index i | 0 <= i < N }, depth = 40)
```

Block `0` therefore commits `EMPTY[40]`. After block `N`, `historyLeaf_N` is appended to the accumulator used by block `N + 1`. A rule may use authenticated historical state as facts or as authorization; history authentication itself imposes no freshness or revocation semantics. The history root authenticates old headers but does not imply the availability of every mutable historical leaf witness. Witness availability is an implementation and service property; see the [node reference](reference/node.md).

### 7.7 Genesis

Initial state has empty output, account, and nullifier trees, zero gross issuance, and the initialized signed issuance lag (§14). The anchor Ethereum block has zero elapsed issuance time and produces ordinary PCASH block `0`: its admitted transactions execute, its Ethereum history leaf is appended, and its completed state is committed.

Block `0` commits the network configuration (§3.1) in its `extraData`:

```text
extraData_0 = poseidon(
    GENESIS_EXTRA_DATA_DOMAIN,
    uint32(CHAIN_ID), uint64(L1_CHAIN_ID), uint160(INBOX_ADDRESS),
    uint64(GENESIS_L1_BLOCK_NUMBER),
    GENESIS_L1_BLOCK_HASH_hi128, GENESIS_L1_BLOCK_HASH_lo128)
extraData_N = 0 for N > 0
```

Block `0` has `parentHash = 0` and `extraData = extraData_0`, and `genesisHash = blockHash_0`. Later blocks have `extraData = 0`; the parent chain carries the genesis identity.

### 7.8 Block body and delivery manifests

**Body.** The canonical block body retains the exact `txBytes` of every admitted transaction, successful or failed, aligned one-to-one with the block's `TransactionResult` values by `blockTransactionIndex`. A full block response supplies those bytes; a compact response may omit them. The body is authenticated by requiring `keccak256(txBytes_i) = contentHash_i` for every result. Transaction bodies are canonical PCASH history, not StateRoot leaves. A node can rebuild admitted-transaction projections by scanning PCASH blocks and bodies, without reconstructing an Ethereum batch carrier.

**Delivery manifests** (node requirement, not consensus: manifests are not committed by any root, and a different manifest layout does not fork the chain). Every canonical block retains a compact Ethereum delivery manifest for each Inbox posting processed in its Ethereum block. A manifest contains:

* the Ethereum block and transaction coordinates, transaction hash, and sender;
* the carrier type, carrier hash and length, and ordered blob versioned hashes;
* the batch-level exclusion reason, if any;
* each established item boundary, with its content hash, optional inclusion transaction ID, and optional exclusion reason;
* for every validly framed batch, additionally the poster owner commitment, the explicit poster context (§9.3), and `itemsHash`: the Keccak-256 of the batch bytes after the 65-byte version, poster commitment, and poster context, that is `count (u16)` followed by each item's `len (u32) ‖ txBytes` in order, exactly as framed, including non-admitted items.

The ordered manifests cover every Inbox transaction in the Ethereum block exactly once. Admitted manifest items are in bijection with the block's `TransactionResult` values: every result has exactly one matching admitted item, no admitted item lacks a result, and no excluded item occupies a result coordinate. A manifest need not retain raw non-admitted bytes once this recovery context is stored. Admitted items refer to the canonical PCASH transaction bodies; original rejected or unparseable bytes remain retrievable only from the identified Ethereum calldata or beacon blob sidecars.

Explanatory exclusion reasons are not committed in the PCASH block header. The manifest is nevertheless mandatory core node history: it is stored and rewound with its canonical block and MUST NOT be reconstructed from a synthetic carrier.

## 8. Proofs and Account Rules

Every transaction type except user-account creation carries one zero-knowledge proof. This section defines the proof system, the interface between an account's authorization application and the protocol, and what a proof may read.

### 8.1 CHONK proofs and verification keys

All client proofs are zero-knowledge CHONK proofs over BN254, produced and verified by the V1-pinned Barretenberg build. One proof folds an ordered stack of Mega circuits:

1. one or more private **applications**, each an account's selected authorization rule (or, for asset issuance, the standard issuance application, §11.6);
2. one **protocol kernel**, which enforces the transaction's ledger rules and links the applications;
3. one **family finalizer**, which accepts only release-pinned kernel verification keys;
4. one **wire circuit**, which alone exposes the transaction type's ordered public inputs. Under the pinned CHONK implementation, the wire circuit uses MegaZK.

The expected external BN254 G2 and Grumpkin G1 CRS bytes are part of the compiled V1 profile and are checked wherever those runtime files are consumed. There are no per-circuit setup ceremonies; soundness depends on the authenticated universal CRS and the pinned proof-system implementation.

**Verification keys.** Every application, kernel, and finalizer uses the canonical 163-field Mega verification key (VK). Its persistent implementation identity is Barretenberg's canonical Mega VK hash, `poseidon(vk_0, ..., vk_162)`. An account-rule entry commits that exact hash (§5.2). The protocol kernel receives the selected application's VK privately, authenticates its hash through the installed entry, and links the preceding application through CHONK's databus. Protocol finalizers accept only release-pinned kernel VK hashes. The final 115-field MegaZK wire VK is fixed by the active transaction-type rule and is the only VK used in consensus verification.

**Verification.** The compressed CHONK proof is carried in `txBytes`. A proof is valid exactly when the pinned verifier accepts it:

1. Reconstruct the transaction's ordered public inputs (§11.2, §12.3, §12.4), each as a 32-byte big-endian word. A word `>= p` has already failed as `PublicInputNotInField`.
2. Decompress the proof with the pinned Barretenberg release (`ChonkDecompressProof`).
3. Require the first `k` elements of the decompressed proof's `hiding_oink_proof` field, where `k` is the number of public inputs, to equal the reconstructed words byte for byte.
4. Verify the decompressed proof against the type's wire VK (`ChonkVerify`).

A failed decompression, a public-input mismatch, or a verifier rejection is `ProofRejected` (§10.5). Every valid proof of a type has that type's pinned length (§16), so a proof of any other length is never valid; no separate length check is needed. The wire VKs are the release circuit bundle's `vks/<name>.chonk.vk` files (§16). The kernel and finalizer VK hashes that the circuits accept are compiled into the circuits themselves (`circuits/libraries/protocol/src/generated/protocol_stack_hashes.nr`); a node never checks them directly, because the wire VK fixes the finalizer and the finalizer fixes the kernels.

### 8.2 The account-rule statement

Every protocol kernel that authorizes value or account changes (the direct, fee-payer, authorized-issuance, user-account-update, and contract-creation kernels) links private account-rule applications. An application defines the condition under which its entry authorizes the exact action and account contribution that the kernel supplies.

**Permanent account-rule statement.** Every account-rule application receives and returns exactly these five values, as private CHONK databus values:

```text
[configurationCommitment, actionCommitment, recentPcashBlockHash, authorizationAccountId, pcashChainId]
```

* `configurationCommitment` is an opaque, application-owned value committed by the selected entry (§5.2). PCASH defines neither its derivation nor its meaning.
* `actionCommitment` binds the complete action being authorized. §8.3 lists the action commitment each kernel supplies.
* `recentPcashBlockHash` provides authenticated application context (§8.4).
* `authorizationAccountId` binds the stable identity derived from the supplied account fields (§5.3). The direct, fee-payer, issuance, and contract-creation kernels prove that the account exists under the recent block's account-state root, except on the direct path when every input is `DUMMY` (§11.4). The user-account-update kernel instead binds the account's current state commitment into the public action commitment, which the node recomputes from the live row (§12.3).
* `pcashChainId` is authenticated ambient context, analogous to the EVM's `block.chainid`.

The application defines its complete authorization relation and must constrain whatever action fields, network, signer, threshold, or external facts its policy intends. A narrow application MUST privately open and recompute the action formulas it accepts. An application that approves any nonzero action commitment for its authenticated account is universal by construction. A rule may sign the raw action and account identity or a semantic projection of them, but its circuit must bind that authorization to the authenticated account and the exact action commitment it returns.

All five values stay private. Consensus learns that the folded proof is valid, not which policy ran, which configuration it opened, or what secret satisfied it.

### 8.3 Action commitments

The `actionCommitment` a kernel supplies depends on what is being authorized. Distinct action domains keep an approval for one role from becoming authority for another.

| Action | `actionCommitment` | Defined in |
|---|---|---|
| Direct private-value transaction | the complete `transactionCommitment`, including private note inputs and `assetBurnAmount` | §11.3–11.4 |
| Asset creation by a registered account | the same `transactionCommitment` | §11.6 |
| Primary account in a fee-payer action | `primaryFeePayerActionCommitment` | §11.7 |
| Fee payer sponsoring a contract's action, or sponsoring a contract's creation | `contractFeeSponsorshipActionCommitment`, which additionally binds the contract address | §11.7, §12.4 |
| User-account update and its fee | `accountUpdateFundedActionCommitment` | §12.3 |

A rule written for one of the role-separated or update actions is not authority over another: the separation stops a sponsorship from being replayed on a direct path or against another contract.

### 8.4 What a proof can open

Every proof-bearing transaction names one `recentPcashBlockHash`. Its execution succeeds only while that hash identifies a canonical block in the recent-block window (§10.6); block `N` first becomes usable in block `N + 1`. The hash authenticates the complete depth-4 header (§7.5), and a circuit opens only the header and state facts it consumes. A rule that does not consume a fact does not pay for its path.

* Current account-rule selection always proves `recentPcashBlockHash -> stateRoot -> accountStateRoot`, then opens the selected account and rule entry.
* A private-value transaction proves its required output and account memberships against that one completed state snapshot. There are no independently selected output, account, or state-root windows. Direct actions whose inputs are all padding retain the block and state-root bindings but do not require account membership (§11.4).
* The header's `number` and `timestamp` open directly beneath the header. Intrinsic roots open through `stateRoot`. Ethereum facts open through `ethereumBlockhashRoot` (§7.2), and earlier PCASH blocks through `historyRoot` (§7.6).

The permanent account-rule statement carries `recentPcashBlockHash` and `pcashChainId` directly (§8.2), so applications read context through the same anchor. A rule may prove older PCASH or Ethereum facts through the recent header's append-only roots; authenticating them does not make them current.

### 8.5 Policies and functions

Every authorized action uses the same entry mechanism: the protocol kernel proves the entry's membership under the exact committed Mega VK hash and links the preceding application through CHONK, all privately. User accounts expose entries as policies whose slot maps to an entry index; contract accounts expose entries as callable functions whose selector maps to an entry index (§5.2).

Rules are permissionless. Anyone may write and install any application. A weak or trivial application cannot bypass the protocol kernel, which enforces the ledger, asset, or account-update invariants outside the application. Account membership authenticates installed authority except in the direct all-padding case (§11.4), which spends no existing value. Permissionless rules may intentionally use broader reusable approvals. Installed applications may be arbitrarily inexpensive; the protocol does not impose the reference wallet's ECDSA cost on every action. All-padding actions still execute the ordinary recursive application and direct stack, whether or not the supplied account exists.

PCASH imposes no configuration schema, salt requirement, signing convention, or replay formula on an account rule. The reference implementation's optional examples and rendering profile are described in the [account rules reference](reference/account-rules.md).

## 9. Deriving Blocks from Ethereum

This section gives the procedure that turns each Ethereum block into a PCASH block, then defines how a node finds PCASH data in an Ethereum block and splits it into candidate transactions. PCASH consumes only the few Ethereum facts listed in §9.6 and never executes the EVM.

**Block procedure.** For each canonical Ethereum block `B` from the anchor onward, a node derives PCASH block `N = B.number - GENESIS_L1_BLOCK_NUMBER` as follows:

1. Let `t = B.timestamp`, which becomes the PCASH block's timestamp (§7.1). For `N > 0`, require `t >= parentTimestamp`; a regression halts derivation.
2. Select the fork rules active at `t` (§3.2).
3. Compute the block quote from the parent state and elapsed time (§14.1). Initialize the capacity reservation state (§14.3) and the count of accounts created in this block (§6.3).
4. For each top-level transaction of `B`, in index order, that is an Inbox transaction (§9.1), obtain its candidate batch (§9.1–9.2) and frame it (§9.3). Then, for each framed item in order:
   1. Decide admission (§10.2). A non-admitted item is recorded in the delivery manifest (§7.8) and skipped.
   2. Run the admitted transaction's checks in its type's order (§10.5, §11–§12), against the state left by every earlier successful transaction.
   3. If every check passes, apply its effects. In every case, record its `TransactionResult` and exact bytes (§7.3, §7.8).
5. Settle the block: compute `rewardEach`, append the system payouts, and set the new gross issuance and lag (§14.2–14.3).
6. Append Ethereum history leaf `N` (§7.2), compute `StateRoot` (§6.5), and build the header and block hash (§7.5), whose `historyRoot` covers blocks `0..N-1` (§7.6).
7. Commit the block atomically (§14.4). Block `N`'s hash then becomes selectable as a recent block (§10.6), and history leaf `N` is appended for block `N + 1`.

If required input or evaluation machinery is unavailable at any step, derivation halts at `B` and retries (§9.2, §10.5); it never records a guess. Reorganizations re-run this procedure from the common ancestor (§9.5).

### 9.1 Inbox and carriers

`INBOX_ADDRESS` is a routing tag, not a smart contract. A top-level Ethereum transaction with `to == INBOX_ADDRESS` is an **Inbox transaction**. It selects exactly one batch **carrier**, its calldata or its blobs, and the carrier yields one candidate batch:

* If its `blobVersionedHashes` list is empty, its complete calldata is the candidate batch bytes.
* If `blobVersionedHashes` is nonempty, its calldata MUST be empty, the hash count MUST be at most `MAX_BATCH_BLOBS`, every hash MUST use KZG version `0x01`, and the ordered blobs MUST be available and match those hashes. Their canonical decoding (§9.2) is the candidate batch bytes.
* A transaction with both nonempty calldata and blob hashes, an unsupported blob-hash version, or more than `MAX_BATCH_BLOBS` blob hashes is ignored in full. No blob retrieval is attempted for such a transaction.

Internal calls made by Ethereum smart contracts are not recognized: only top-level transactions are Inbox transactions. An Inbox transaction's Ethereum value, receipt status, and any code at `INBOX_ADDRESS` are ignored.

`MAX_BATCH_BLOBS = 6`, matching Ethereum's Fusaka maximum for one blob transaction. `MAX_BATCH_BYTES = (MAX_BATCH_BLOBS × 4,096 − 1) × 31 = 761,825`: one field element is reserved for the length header and every other field element carries 31 bytes. The candidate batch MUST contain at most `MAX_BATCH_BYTES`, whether it arrived as calldata or as blobs.

### 9.2 Blob encoding

A blob is 4,096 consecutive 32-byte field elements. A blob carrier encodes the candidate batch bytes as follows:

* The first byte of every field element is zero.
* The first field element stores the candidate-batch byte length as an eight-byte big-endian integer in bytes 1 through 8; the rest of that field element is zero.
* Starting at the next field element, each field element stores up to 31 consecutive candidate-batch bytes in bytes 1 through 31.
* The final partial field element and all remaining field elements are zero.
* The transaction MUST carry the minimum number of blobs needed for the declared nonzero length, the declared length MUST be at most `MAX_BATCH_BYTES`, and exactly one value is encoded.

Any other representation is noncanonical, and the transaction is ignored in full.

**Availability versus validity.** An implementation verifies untrusted blob bytes by recomputing each EIP-4844 KZG commitment and its versioned hash and matching the transaction's ordered `blobVersionedHashes`. Failure to obtain every blob with matching content is an availability failure: derivation halts at that Ethereum block and retries, rather than treating the transaction as invalid. Once all hashes have been verified, a noncanonical blob encoding or a batch-level parse failure is deterministic malformed input, and the transaction is ignored in full.

### 9.3 Batch framing

The candidate batch bytes parse as:

```text
batchVersion (1, = 0x01) ‖ posterOwnerCommitment (32) ‖ posterContext (32) ‖ count (u16) ‖ count × [len (u32) ‖ txBytes]
```

* `batchVersion` selects only the encoding that recovers the ordered `txBytes` items. It never selects the Ethereum carrier, transaction parsing, verification keys, or execution semantics. Version `0x01` is the only defined encoding.
* The 67-byte header carries a canonical, nonzero field element `posterOwnerCommitment` and a canonical field element `posterContext`, even when no item pays an open fee or earns issuance. The poster commitment names the recipient of every open fee and any open-route issuance in the batch (§13); it is not the Ethereum sender address. The context is chosen before posting and imposes no inclusion-height or freshness constraint. The reference payout-recovery convention uses it (§15.1).
* `count` items follow, each a `u32` length and then that many bytes.

**Ignoring a whole batch.** The batch is ignored in full, producing no transactions and no results, when any of the following holds, because its claimed framing cannot be trusted:

* the candidate batch is longer than `MAX_BATCH_BYTES`;
* `batchVersion` is not a batch version accepted at the block's timestamp (§3.2);
* `posterOwnerCommitment` is zero or not canonical, or `posterContext` is not canonical;
* `count` exceeds `MAX_BATCH_ENTRIES`;
* the bytes end before the header, `count`, or any item's `u32` length is complete, or a declared item length, at any position, runs past the end of the bytes;
* bytes remain after the last declared item.

Conditions that ignore an Inbox transaction before any batch exists are in §9.1–9.2.

**Oversized items.** If a declared item length is fully present but exceeds `MAX_TX_BYTES`, framing consumes exactly those bytes without allocating that declared size, the item is not admitted (`EntryTooLarge`), and parsing continues at the next item.

`MAX_TX_BYTES = 131,072` is a fixed parsing bound. Exact per-type structural decoding (§10.2) enforces every transaction's wire shape; an item between its real maximum size and this bound fails structural decoding and is not admitted.

There is no batch atomicity: each item is admitted and executed on its own.

### 9.4 Ordering

Transactions are ordered by Ethereum block number, then Ethereum transaction index, then batch item index. `batchItemIndex` is the item's original zero-based position in its carrier and may have gaps among admitted transactions. `blockTransactionIndex` is a separate dense zero-based ordinal over the admitted transactions of one PCASH block (§7.3).

Each transaction executes at its position against the state produced by all prior successful transactions. When transactions conflict, the first one to succeed changes state, and each later conflicting transaction fails with its first applicable outcome. An earlier transaction that fails its own checks consumes nothing, so it cannot block a later one.

### 9.5 Reorganizations and finality

Derivation is a pure function of canonical Ethereum history. When Ethereum reorganizes, the node removes the orphaned PCASH suffix and derives the replacement suffix from the common parent state, with the ordinary derivation rules. The required result is exactly the state a fresh node would derive from genesis over the new canonical history. Ethereum finality does not change transaction or block validity.

A reorganization also removes abandoned PCASH block hashes from the recent-block window immediately (§10.6).

### 9.6 Unsupported Ethereum data

PCASH consumes only: each block's number, hash, and timestamp; each top-level transaction's index, recipient, sender, calldata, and blob versioned hashes; resolved blob bytes; and each block's receipt logs with their transaction and receipt-local indices, for the Ethereum history tree (§7.2). If an implementation cannot decode a block, transaction, or receipt into those required values, or cannot resolve a structurally valid blob carrier, derivation halts rather than skipping data and risking a different result. Unrecognized blob-hash versions are deterministically invalid carriers as defined in §9.1; they do not acquire meaning retroactively. V1 does not attempt to predict or encode future Ethereum fork activations.

V1 has no per-block proof-verification budget.

## 10. Transactions

This section defines what every transaction has in common: its envelope, when an item counts as a transaction at all, the shared private-value section, and how execution selects an outcome. Sections 11 and 12 define each type's specific rules.

### 10.1 Envelope and types

Every transaction has one common envelope:

```text
txBytes = txType (u8) ‖ pcashChainId (u32be) ‖ txPayload
```

An assigned `txType` permanently fixes its payload grammar and type-specific interpretation; private-value profiles select exact capacities and verifier keys within that grammar. An assigned type MUST NOT be reinterpreted or reused. A fork MAY activate or retire a type for new blocks without changing how historical bytes decode (§3.2).

`pcashChainId` occurs exactly once in `txBytes`, at the fixed envelope offset. Proof and signature statements consume that envelope value rather than an independently configured one: position `0` of the private-value and account-update public inputs, and the numeric `pcashChainId` of the account-creation EIP-712 message (§12.1), both use it. Proof and signature statements use this conversion, whose result is below `p` by construction:

```text
chainIdWord = bytes28(0) || uint32be(pcashChainId)
```

The four assigned types are:

| `txType` | Transaction | Carries a proof | Defined in |
|---|---|---|---|
| `0x00` | private-value transaction | yes | §11 |
| `0x01` | user-account creation | no | §12.2 |
| `0x02` | contract-account creation | yes | §12.4 |
| `0x03` | user-account update | yes | §12.3 |

### 10.2 Admission and structural decoding

An item is **admitted** as a transaction if and only if its complete bytes structurally decode under a `txType` active at the executing Ethereum block's timestamp, and its envelope `pcashChainId == CHAIN_ID`. Admission happens before signature recovery, proof verification, or any state access, so every node decides it identically.

An item that is not admitted is one of the following **non-admission classes**. They are not outcome codes: a non-admitted item produces no transaction, no transaction result, and no transaction ID, only a gap in `batchItemIndex` and a manifest entry (§7.8).

| Class | Meaning |
|---|---|
| `EntryTooLarge` | the declared item length exceeds `MAX_TX_BYTES` (§9.3) |
| `Empty` | the item has no bytes |
| `UnknownKind` | the type byte is unassigned or not active at the block's timestamp |
| `UnknownPrivateValueShape` | a type `0x00` profile selector other than `0` or `1` (§10.4) |
| `UnknownAuthorizationMode` | a user-account creation authorization mode other than `0` or `1` (§12.2) |
| `Truncated` | the bytes end before the structure does, including a declared length beyond the remaining bytes |
| `TrailingBytes` | bytes remain after the structure ends |
| `Overflow` | a declared length overflows the decoder's offset arithmetic |
| `WrongChainId` | the envelope chain ID is not `CHAIN_ID` |

A correct envelope chain ID paired with a signature or proof produced for another domain is admitted and then fails execution. A wrong envelope chain ID is non-admitted without invoking recovery or verification. Carriers and batches rejected before trustworthy item boundaries are framed likewise produce no transaction results.

**Structural decoding** determines exact field boundaries and complete byte consumption without applying value semantics. Only three discriminants determine structure: `txType`, the type `0x00` profile selector `shapeId`, and the user-account-creation `authorizationMode`. No other semantic discriminant may determine the boundary of later fields. Parsers therefore preserve every other raw discriminant (receive-key modes, `publishesNkRoot`, `yParity`), declared lengths, and 32-byte words until ordered execution validation. They MUST NOT require an invalid raw Boolean or enum value to fit a semantic type merely to finish decoding; such values are execution failures of the admitted transaction.

**Receive-key fields.** All three receive-key sites (user creation, user update, contract creation) encode `receiveKeyMode (u8) ‖ receiveKeyLength (u16be) ‖ receiveKeyBytes[receiveKeyLength]`. Structural decoding always consumes the declared bytes. User-account and contract-account creation define `ABSENT = 0` with length `0` and `PRESENT = 1` with length `ML_KEM768_PUBLIC_KEY_LEN`. User-account update defines `KEEP = 0` and `CLEAR = 1`, each with length `0`, and `REPLACE = 2` with length `ML_KEM768_PUBLIC_KEY_LEN`. Any other mode and length combination is an admitted execution failure. A contract's `publishesNkRoot` flag does not select structure, because the nullifier-key material that follows it is always 32 bytes.

### 10.3 The private-value section

One structure, the **private-value section**, carries every relation that spends notes or pays a fee: the payload of a type `0x00` transaction after its profile byte, and the fee payment appended to types `0x02` and `0x03`. With `I` input slots and `O` output slots:

```text
privateValueSection = (I + O + 5) × verifierInput (32) ‖ proofLen (u32) ‖ proof ‖ O × { outputDataLen_i (u16) ‖ outputData_i }
```

* The serialized words are the private-value verifier inputs (§11.2) other than those derivation supplies itself: position `0` (the chain ID, taken from the envelope) and the `O` output-data hashes. Account-action sections always use the S profile, without a profile byte.
* Derivation computes `outputDataHash_i = uint256(keccak256(outputData_i)) mod p` from the exact posted bytes.
* Length prefixes only delimit structure. A declared length beyond the remaining bytes is structural `Truncated`.
* Every private-value section carries exactly `O` output-data items. After structural decoding, execution checks them in two passes:
  1. For each `i` in index order, if `outputDataLen_i ≠ 1,750`, the outcome is `InvalidOutputDataSize`.
  2. For each `i` in index order, if `outputData_i[0] ≠ 0x01`, the outcome is `InvalidOutputDataVersion`.

  Nothing else about the content is checked. Genuine encrypted envelopes and compliant randomized filler are equally valid; zero-byte counts, entropy, ciphertext validity, and carrier cost do not affect acceptance. (Note: the reference envelope is `1 + 1088 + 12 + 633 + 16` bytes.)
* A valid proof always has the carrying type's pinned length (§16), so a proof of any other length fails verification (`ProofRejected`, §8.1).

### 10.4 Type payloads

* `txType 0x00`, **private-value transaction**: `shapeId (u8) ‖ privateValueSection`. The structural selector is `0` for profile S or `1` for profile L (§11.1); any other selector is non-admitted as `UnknownPrivateValueShape`. No other top-level payment type is assigned.
* `txType 0x01`, **user-account creation**: the authorization mode, the user-account creation payload, a recent block hash, a deadline, and an ECDSA signature only for signed authorization (§12.2). It has no proof and no fee. It is authorized by the authenticated top-level Ethereum sender or by node-side `ecrecover`, followed by shared creation-only state checks. Consensus derives `accountRevision = 1`.
* `txType 0x02`, **contract-account creation**: the unsigned contract body (§12.4), then a private-value section paid by a sponsoring user account. The proof has twenty-one public inputs: the section's twenty, then the contract address consensus derives from the body. `openFeeAmount = 0` with no private relay payment is valid and may spend nothing: every input and output slot is `DUMMY`, so the section authorizes the publication alone.
* `txType 0x03`, **user-account update**: the proposed policy commitment and receive-key operation, the asserted update action commitment, and a private-value section that pays the fee from the updated account's own native notes (§12.3). The section's `recentPcashBlockHash` and `validUntilTimestamp` are the update's recent block and deadline. Consensus derives the immutable `nullifierKeyCommitment`, the next revision, and the resulting account state row from live state. The proof has twenty-one public inputs: the section's twenty, then the update action commitment.

### 10.5 Execution and outcomes

Once admitted, a transaction executes its type's checks in a fixed order. The first failing check determines the `outcomeCode`; if every check passes, the outcome is `Succeeded`. An implementation may parallelize internal computation only if it always selects the same first failure. These orders are consensus.

Private value (§11):

```text
InvalidOutputDataSize
InvalidOutputDataVersion
PublicInputNotInField
RecentPcashBlockNotInWindow
Expired
NotYetValid
ZeroNullifier
DuplicateInputNullifier
NullifierAlreadySpent
OutputCapacity
ZeroOutputCommitment
ProofRejected
```

User-account creation (§12.2):

```text
InvalidReceiveKeyPresence
InvalidAccountSender (direct mode)
InvalidYParity (signed mode)
InvalidAccountSignature (signed mode)
NonCanonicalAccountField
ZeroAccountStateCommitment
AccountAlreadyExists
RecentPcashBlockNotInWindow
Expired
AccountCreationDeadlineOutsideWindow
AccountCapacity
```

Contract-account creation (§12.4):

```text
InvalidOutputDataSize
InvalidOutputDataVersion
PublicInputNotInField
InvalidPublishesNkRootFlag
InvalidReceiveKeyPresence
NonCanonicalContractField
InvalidContractNkRoot
EmptyContractFunctionSet
SelectorsNotStrictlyIncreasing
ZeroContractFunctionCommitment
ZeroContractAccountCommitment
ContractAddressExists
AccountCapacity
RecentPcashBlockNotInWindow
Expired
NotYetValid
ZeroNullifier
DuplicateInputNullifier
NullifierAlreadySpent
OutputCapacity
ZeroOutputCommitment
ProofRejected
```

User-account update (§12.3):

```text
InvalidOutputDataSize
InvalidOutputDataVersion
PublicInputNotInField
InvalidReceiveKeyOperation
NonCanonicalAccountField
RecentPcashBlockNotInWindow
Expired
NotYetValid
AccountNotFound
WrongAccountKind
AccountRevisionExhausted
ClearWithoutReceiveKey
ReplaceWithIdenticalReceiveKey
ZeroAccountStateCommitment
ActionCommitmentMismatch
ZeroNullifier
DuplicateInputNullifier
NullifierAlreadySpent
OutputCapacity
ZeroOutputCommitment
ProofRejected
```

Rules that apply across these lists:

* **Update fields.** `PublicInputNotInField` on an update covers the section's words and the appended action commitment. `NonCanonicalAccountField` covers only the proposed policy set commitment.
* **Fee-bearing account actions.** The section checks of an account action are the private-value checks of §11.2 applied to the same words. A fee-bearing account action records its fee, marks its four nullifiers, and appends its five output commitments exactly as a private-value transaction does.
* **Nullifier checks.** Nullifier value errors precede the state-dependent spent check, with `ZeroNullifier` before `DuplicateInputNullifier`. When several operands fail one category, report the lowest wire slot.
* **Contract validation order.** Contract function-set validation precedes the derived account-commitment, address-collision, and capacity checks.

**Failures versus fatal errors.** A clean verifier rejection, including malformed proof encoding, a wrong proof length, or a public-input mismatch, is `ProofRejected`. A verifier crash, timeout, missing verification key, invalid verifier response, or any other inability to obtain the pinned verifier's verdict is a **fatal derivation error**. A false state predicate is an included failed transaction; a state read, I/O, corruption, or other infrastructure error is fatal. Fatal derivation errors halt derivation at the Ethereum block and never become transaction outcomes.

**Atomicity.** All checks and state changes for one transaction are atomic. Only `Succeeded` marks nullifiers, records fees, updates accounts, or appends outputs.

**Outcome codes.** The stable `uint16 outcomeCode` values are:

| Code | Outcome |
|---:|---|
| 0 | `Succeeded` |
| 1 | `InvalidOutputDataSize` |
| 2 | `InvalidReceiveKeyPresence` |
| 3 | `InvalidReceiveKeyOperation` |
| 4 | `InvalidYParity` |
| 5 | `InvalidPublishesNkRootFlag` |
| 6 | `PublicInputNotInField` |
| 7 | `ZeroOutputCommitment` |
| 8 | `RecentPcashBlockNotInWindow` |
| 9 | `Expired` |
| 10 | `NotYetValid` |
| 11 | `ZeroNullifier` |
| 12 | `DuplicateInputNullifier` |
| 13 | `NullifierAlreadySpent` |
| 14 | `ProofRejected` |
| 15 | `OutputCapacity` |
| 16 | `AccountCapacity` |
| 17 | `InvalidAccountSignature` |
| 18 | `AccountNotFound` |
| 19 | `AccountAlreadyExists` |
| 20 | `AccountRevisionExhausted` |
| 21 | `WrongAccountKind` |
| 22 | `ClearWithoutReceiveKey` |
| 23 | `ReplaceWithIdenticalReceiveKey` |
| 24 | `ActionCommitmentMismatch` |
| 25 | `NonCanonicalAccountField` |
| 26 | `ZeroAccountStateCommitment` |
| 27 | `AccountCreationDeadlineOutsideWindow` |
| 28 | `ContractAddressExists` |
| 29 | `NonCanonicalContractField` |
| 30 | `ZeroContractAccountCommitment` |
| 31 | `InvalidContractNkRoot` |
| 32 | `EmptyContractFunctionSet` |
| 33 | `SelectorsNotStrictlyIncreasing` |
| 34 | `ZeroContractFunctionCommitment` |
| 35 | `InvalidAccountSender` |
| 36 | `InvalidOutputDataVersion` |

The integers are explicit protocol values, not positions inferred from a language-level array. Unknown numeric values are unsupported and MUST NOT be shifted, aliased, or reinterpreted.

### 10.6 Recent block window and time bounds

**The recent block.** Every proof-selected protocol-state snapshot is identified by one public `recentPcashBlockHash`. Let `currentTimestamp` be the timestamp of the block being derived. The hash passes the window check if and only if it is nonzero and is the hash of an already committed block `B` of the current canonical PCASH chain with

```text
0 <= currentTimestamp - B.timestamp <= RECENT_PCASH_BLOCK_WINDOW_SECONDS
```

The block being derived is not yet committed, so block `N` first becomes usable in block `N + 1`, and values produced by earlier transactions in the same block cannot be selected. The proof privately opens the state it consumes beneath this block (§8.4).

A block hash passes only while it belongs to the current parent-linked header chain and remains within the window. An orphaned block hash fails immediately rather than remaining usable until its former age limit: after a reorganization removes a block, an admitted transaction naming it fails with `RecentPcashBlockNotInWindow` and must be rebuilt to succeed.

**Time bounds.** `notBeforeTimestamp` and `validUntilTimestamp` bound when the authorized action may execute. `validUntilTimestamp` is a pure deadline and does not extend the lifetime of the referenced block.

**Authorization lifetime versus proof lifetime.** Every proof is separately bound to its exact `recentPcashBlockHash` and can succeed only while that snapshot passes the window check. If the selected application's authorization condition does not itself commit to the snapshot, the same unchanged action may be re-proved against a newer qualifying snapshot without being reauthorized. Changing any action field produces a different action and requires authorization for that action.

## 11. Private-Value Transactions (`txType 0x00`)

A private-value transaction spends notes and creates notes. It moves native PCASH, one standard non-native asset, or both, in one shared relation. Every private-value transaction has the same public shape for its profile: a fixed number of input slots, each publishing one nullifier, and a fixed number of output slots, each publishing one note-body commitment and one data item. The proof shows privately that the real slots obey the ledger rules and that the owning account's rule approved the exact transaction.

This section first defines what nodes see and check (§11.1–11.2), then the relation the proof establishes (§11.3–11.8).

### 11.1 Profiles

Exactly two public profiles select compiled instances of one shared relation:

| Profile | shapeId | Inputs I | Outputs O | Public fields | Wire words | Compressed proof bytes |
|---|---:|---:|---:|---:|---:|---:|
| S | 0 | 4 | 5 | 20 | 14 | 23,840 |
| L | 1 | 32 | 32 | 102 | 69 | 26,464 |

* Dimensions are compiled from `assets/private_value_profiles.json`, never inferred from vector lengths or configured by a client.
* Each profile selects its own exact wire VK.
* Real occupancy is private: which slots carry value is not public, within the limits each kernel sets (§11.4–11.7), and minimally occupied L actions are valid.
* S and L share notes, the output tree, and the spent set. A note keeps its creation secret when it is spent through the other profile.

With exact output-data sizes, `txBytes = 170 + 32*I + 1784*O + proofBytes`. An S transaction occupies 33,058 bytes and an L transaction 84,746 bytes. One L transaction plus the 67-byte batch header and 4-byte item framing occupies 84,817 bytes, within the 126,945-byte capacity of one blob. Both carriers admit both profiles; blob-first publication and native-only L proving are product support policies, not validity rules.

### 11.2 Public inputs and validation

**Public inputs.** For the selected profile, the proof's public inputs are, in this fixed order:

```text
0                chainId
1                recentPcashBlockHash
2 .. 1+I         nullifiers
2+I .. 1+I+O     outputBodyCommitments
2+I+O .. 1+I+2O  outputDataHashes
2+I+2O           openFeeAmount
3+I+2O           issuanceOwnerCommitment  (0 = open route)
4+I+2O           notBeforeTimestamp       (< 2^64; 0 = unconstrained)
5+I+2O           validUntilTimestamp      (< 2^64, nonzero)
```

There are `I + 2*O + 6` public inputs. The wire serializes the `I + O + 5` non-derived fields in that order, omitting the chain ID and every output-data hash (§10.3). Derivation supplies the envelope chain ID and hashes all `O` exact output-data items, including late L slots.

The fields mean:

* `nullifiers`: one per input slot. A real input publishes its note's nullifier; a padding input or marker slot publishes its marker (§11.3).
* `outputBodyCommitments`: one note-body commitment per output slot, including padding. Every body appends one ordinary output commitment.
* `outputDataHashes`: the hash of each output's exact data item.
* `openFeeAmount` and `issuanceOwnerCommitment`: the public fee and the issuance destination (§13).
* `notBeforeTimestamp` and `validUntilTimestamp`: the time window (§10.6).

`assetId`, slot classes, the selected kernel path, application VKs, owner accounts, funding composition, and amounts are not public inputs.

**Rules the proof enforces.** The following hold for every valid proof, and nodes do not check them separately: `notBeforeTimestamp < 2^64`; `validUntilTimestamp < 2^64` and nonzero; `openFeeAmount < 2^128`; a nonzero `issuanceOwnerCommitment` requires `openFeeAmount = 0`; and in the fee sections of types `0x02` and `0x03`, `issuanceOwnerCommitment = 0`. A transaction that violates one of them fails with `ProofRejected`, unless an earlier check fails first: for example, a zero `validUntilTimestamp` fails as `Expired`, and an open fee large enough to overflow the payout reservation fails as `OutputCapacity` (§14.3). The selected relation privately opens the state roots it needs through `recentPcashBlockHash -> stateRoot`, and nested rules may open other authenticated facts they consume (§8.4).

**Validation.** Nodes apply the complete private-value precedence of §10.5. In detail:

1. Check output-data sizes, then versions (§10.3).
2. Require every reconstructed public input to be canonical (`PublicInputNotInField`).
3. Require `recentPcashBlockHash` within the recent-block window (§10.6).
4. Require `currentTimestamp <= validUntilTimestamp` (`Expired`).
5. Require `notBeforeTimestamp <= currentTimestamp` (`NotYetValid`). `notBeforeTimestamp == 0` is unconstrained below, and there is no maximum lookahead.
6. Require the nullifiers, in this order: no zero value, pairwise distinct, then globally unseen. Report the lowest failing input slot.
7. Require the capacity reservations of §14.3 (`OutputCapacity`).
8. Compute the `O` final output commitments below and require each to be nonzero, in output-slot order (`ZeroOutputCommitment`).
9. Verify the proof against the selected profile's pinned wire VK (`ProofRejected`).

**Effects.** Only if every check succeeds, atomically:

1. Insert all `I` nullifiers into the spent set (§6.4).
2. Append the `O` output commitments at `outputIndex0 = nextOutputIndex`, for `0 <= i < O`:

   ```text
   outputCommitment_i = poseidon(OUTPUT_COMMITMENT_DOMAIN, outputBodyCommitment_i, outputIndex0 + i)
   ```

3. Update the capacity reservations (§14.3).
4. Record the settlement obligations `(openFeeAmount, issuanceOwnerCommitment, posterOwnerCommitment)` (§13), and count the transaction as a qualifying action (§14).

### 11.3 Slot classes, markers, and the transaction commitment

**Slot classes.** Every slot has a private class. Classes `0..2` are shared by inputs and outputs and name the value the slot holds: `DUMMY = 0` (padding), `NATIVE = 1`, `ASSET = 2`. `ASSET_ISSUANCE = 3` and `APPLICATION = 4` are input-only **marker** classes: they carry no note, and exist only to publish a one-time marker in the spent set. The classes are private transaction-commitment fields.

An input slot that holds no note still publishes a nonzero nullifier. The three kinds of non-note input differ in who defines their nullifier and what it is scoped to:

| Class | Nullifier defined by | Scoped to |
|---|---|---|
| `DUMMY` | the protocol kernel, from one fresh per-action seed | nothing |
| `ASSET_ISSUANCE` | the fixed issuance relation (§11.6) | the asset |
| `APPLICATION` | the selected account rule (§8.2) | (account, entry) |

Slot requirements:

* A dummy, issuance, or application input has zero input commitment. A native or asset input's input commitment is the spent note's nonzero output commitment (§4.3), which includes its output index.
* A native or asset output has a positive 128-bit amount. A dummy output has asset ID zero and amount zero.
* Every real note output carries a nonzero opaque owner commitment, which must differ from the transaction's `issuanceOwnerCommitment`. Every dummy output carries the canonical dummy-owner commitment derived from a private nonzero secret (§4.4).
* Every output body commitment and every input nullifier is nonzero, including padding.

**Transaction commitment.** The transaction commitment binds the complete action: the profile, the fee, issuance destination, time bounds, private burn amount, and every slot's index, class, commitment, and nullifier or data hash. It is what an account rule approves (§8.3).

```text
globalSlot = poseidon(
    TX_COMMITMENT_GLOBAL_DOMAIN,
    openFeeAmount, issuanceOwnerCommitment, notBeforeTimestamp, validUntilTimestamp,
    assetBurnAmount)

inputSlotDigest_i = poseidon(
    TX_COMMITMENT_INPUT_DOMAIN,
    i, inputSlotClass_i, inputCommitment_i, nullifier_i)

outputSlotDigest_i = poseidon(
    TX_COMMITMENT_OUTPUT_DOMAIN,
    i, outputSlotClass_i, outputBodyCommitment_i, outputDataHash_i)

transactionCommitment = poseidon(
    TX_COMMITMENT_DOMAIN,
    shapeId, N_IN, N_OUT,
    globalSlot,
    inputSlotDigest_0, ..., inputSlotDigest_(N_IN-1),
    outputSlotDigest_0, ..., outputSlotDigest_(N_OUT-1))
```

The commitment has no reserved global operand and no public-application operand. It binds the selected profile, the complete ordered action, and the private `assetBurnAmount`, without binding transient tree roots, which is why an unchanged action can be re-proved against a newer recent block (§10.6). Binding each slot's index and class prevents moving an output or reinterpreting a slot without changing the authorized action. Require `0 <= assetBurnAmount < 2^131`.

**Selecting the relation.** The selected protocol kernel derives `hasAsset` in both directions: it is true if and only if any input is `ASSET` or `ASSET_ISSUANCE`, or any output is `ASSET`. An `APPLICATION` marker carries no asset value and does not select the asset path.

* If `hasAsset` is false, `assetId` and `assetBurnAmount` are zero.
* If `hasAsset` is true, `assetId` is nonzero, and:
  * one or more `ASSET` inputs with no `ASSET_ISSUANCE` marker take the direct relation (§11.4) or the two-account fee-payer relation (§11.7);
  * no `ASSET` input, exactly one `ASSET_ISSUANCE` marker, no `APPLICATION` input, and at least one `ASSET` output take an issuance kernel (§11.6) and require `assetBurnAmount = 0`;
  * every other asset shape fails.

The slot grammar determines the relation in every case but one. Slot classes name namespaces, not owners, so they cannot express whether a second account funds the fee. A direct action and a fee-payer action therefore use different private protocol kernels, both accepted by the same private-value finalizer. The fee-payer kernel requires its two authenticated accounts to differ, so a one-account action cannot gratuitously take that path.

**Padding inputs.** The protocol kernel derives every dummy input's nullifier as `poseidon(DUMMY_NULLIFIER_DOMAIN, dummyInputSeed, inputIndex)` from one private nonzero seed. A dummy input is shape noise, not application replay protection: its value is slot-indexed and fresh per action, which is the exact opposite of what a marker requires.

**Application markers.** An account rule that must allow some fact to be used only once, such as a claim against an escrow, consumes an `APPLICATION` slot whose nullifier is:

```text
applicationNullifier = poseidon(
    APPLICATION_NULLIFIER_DOMAIN,
    authorizationAccountId, selectedEntryCommitment, replayId)
```

The authenticated account and the exact selected account-rule entry supply the scope. `replayId` is defined entirely by that rule and is never interpreted by any fixed relation. Scoping to the entry rather than to the account alone means a sibling entry cannot consume another entry's markers. Because markers are scoped to one entry, two installed entries never share replay state. An application whose S and L variants must share replay state MUST therefore be installed as one entry whose VK accepts both profiles; its S opening excludes the canonically zero trailing capacity. Separately installed note-funded entries need no shared replay state, because the real-note nullifiers they consume already prevent double spending.

The operand list is closed and normative. It binds no slot index, no input note commitment, no output, no fee, no timestamp, no recent root, no chain ID, and no proof randomness. If it bound any of them, a party could reshape the transaction (spend different inventory, move the marker to another slot, change the fee) and derive a fresh marker for the same fact. Independence from slot and transaction shape is what makes the value one-shot at all. No PCASH nullifier binds the chain ID. A marker that must be unguessable, or must differ across chains, carries a secret inside `replayId`, as `noteSecret` and the issuance secret do elsewhere.

The direct kernel (§11.4) and the fee-payer kernel (§11.7) enforce markers by authenticating both the account and the selected entry against the same transaction; the fee-payer kernel scopes every marker to its primary account. Each kernel:

* takes a private `replayId` per slot, requires it to be zero in every non-`APPLICATION` slot, and requires each `APPLICATION` slot's nullifier to equal the value above;
* relies on the selected application to derive its own canonical `replayId` independently and assert the same equality against the same nullifier, so the two agree only if the replay identities are equal. The application receives `selectedEntryCommitment` privately and does not re-derive its own entry, which would be a self-reference with no fixed point.

Standard issuance rejects `APPLICATION` inputs. There is no protocol cap on the number of `APPLICATION` slots; an application caps itself.

**Output construction.** The kernel constructs all `O` output bodies from the owner commitments, the slot-derived asset ID, and the amounts. For `ASSET` outputs it uses the action's nonzero asset ID; for `NATIVE` and `DUMMY` outputs it uses zero. It does not open a real output's owner commitment. For each dummy slot `i`, it requires a private `dummySecret_i != 0`, derives `ownerCommitment_i = poseidon(OWNER_COMMITMENT_DOMAIN, DUMMY_OWNER_ID, dummySecret_i)`, and rejects any other owner commitment.

### 11.4 The direct path

The direct path is the ordinary one-account spend: one account owns every real input, and one of its installed rules authorizes the complete transaction. It handles native payments, asset transfers and burns, and actions that spend nothing.

A direct action contains no `ASSET_ISSUANCE` input and either at least one real note input or only `DUMMY` inputs: the kernel requires `realInputCount != 0 OR every input slot is DUMMY`. It MAY contain `NATIVE` inputs, `ASSET` inputs, or both, and it MAY contain `APPLICATION` markers, whose nullifiers the direct protocol kernel enforces (§11.3). A marker is not a dummy, so a marker without a real input fails.

**With real inputs.** All real inputs belong to one authenticated `USER` or `CONTRACT` account. A `NATIVE` input uses `assetId = 0`, and an `ASSET` input uses the action's nonzero asset ID. A native-only action additionally has no `ASSET` output and `assetBurnAmount = 0`. The kernel:

1. proves every real input's note membership and canonical nullifier;
2. constructs native, asset, and dummy output bodies;
3. enforces native conservation including the fee, and asset conservation including the burn (§11.6);
4. authenticates one application entry against the supplied rule-set commitment by its exact Mega VK hash;
5. links that application's private return statement `[configurationCommitment, transactionCommitment, recentPcashBlockHash, authorizationAccountId, pcashChainId]`.

The account ID is derived from the supplied account fields (§5.3); historical account membership authenticates those fields whenever any input is not `DUMMY`.

**With only padding inputs.** An action whose inputs are all `DUMMY` spends nothing. For such an action:

* it requires no registered account, because the kernel does not prove that the supplied account exists;
* the output rules of §11.3 and both conservation equations force `openFeeAmount = 0`, `assetBurnAmount = 0`, `assetId = 0`, and only `DUMMY` outputs;
* it is still a qualifying action if it succeeds (§14), and its issuance uses the ordinary destination rules (§13).

The kernel still opens the supplied account and rule set, binds the exact application VK and transaction statement, and recursively verifies the application. Account and key well-formedness, the account-leaf and Merkle computations, and rule membership all remain mandatory. Only one check is conditional:

```text
allInputsDummy OR reconstructedAccountRoot == historicalAccountRoot
```

`allInputsDummy` is derived in constrained code from every private input class being exactly `DUMMY`; `APPLICATION` and `ASSET_ISSUANCE` never qualify. The supplied account fields and rule set on this path therefore do not represent authenticated installed-account authority.

Dummy nullifiers remain globally single-spend (§10.5), so an accepted proof cannot earn issuance twice. Registration itself earns no issuance. A registration and an all-padding action may be published in one batch with independent outcomes; the action is valid regardless of the registration's position or success, including when no registration is published. Spending a recovered reward later requires normal historical account and note membership.

Both cases use the same direct kernel and VK within their profile, followed by the ordinary finalizer and wire. The fee-payer, issuance, and contract-creation kernels require unconditional account membership; the account-update kernel relies on the node's check of the live row (§12.3). The folded stack is `selected application → direct protocol kernel → private-value finalizer → private-value wire`. A zero-fee action remains valid when ordinary conservation holds.

### 11.5 Protocol kernels

The protocol kernels own ledger structure: exact slot classification, note-body construction, dummy padding, the transaction commitment, timestamp and amount bounds, note membership, namespace isolation, conservation, burn, and fees. The standard issuance application owns asset identity and one-shot supply (§11.6). An asset creator supplies no circuit or executable policy.

A type `0x00` proof privately selects one of three kernels, each compiled for S and L:

* the **direct** kernel (§11.4): one account owns every real input;
* the **native fee-payer** kernel (§11.7): a contract account's asset action with a distinct user account paying the native fee;
* the **authorized issuance** kernel with optional native funding (§11.6): creates a new asset's supply.

The private-value finalizer accepts only these three kernel VKs, and all three converge on the same public wire. Nothing public distinguishes which kernel ran.

Every kernel that spends notes authenticates the relevant owner account, derives its `authorizationAccountId`, authenticates each selected installed entry, and links each application. For every real input slot it proves exact note membership, owner binding, amount range, asset namespace, and canonical nullifier. Application identity, account identity, note secrets, nullifier-key material, account openings, and amounts remain private inside the folded proof.

Real-input witnesses cover every `ASSET` and `NATIVE` slot exactly once; the protocol kernel separately owns marker and dummy slots. One transaction may contain only one nonzero asset ID. All positive fees are funded by native notes. Contract-owned native inputs remain on the direct path; the fee-payer kernel does not reinterpret them as a user contribution. Zero-fee native and asset actions remain consensus-valid.

`assetBurnAmount` is a private transaction field: it changes the transaction commitment and is therefore available to any installed application that opens the transaction, but it is not a public proof input.

### 11.6 Standard fungible assets

Every nonzero asset is an instance of one semantic protocol law, the standard fungible asset: a fixed supply issued once, then only transferred, split, merged, or burned. An asset's identity is a commitment to its immutable configuration:

```text
issuanceSecretCommitment = poseidon(
    FIXED_ISSUANCE_SECRET_COMMITMENT_DOMAIN, issuanceSecret)

standardFungibleConfigurationCommitment = poseidon(
    STANDARD_FUNGIBLE_CONFIGURATION_DOMAIN,
    instanceSalt, metadataCommitment, initialSupply,
    issuanceSecretCommitment)

assetId = poseidon(
    ASSET_ID_DOMAIN,
    STANDARD_FUNGIBLE_LAW_ID,
    standardFungibleConfigurationCommitment)
```

* `instanceSalt`, `metadataCommitment`, `issuanceSecret`, `issuanceSecretCommitment`, `standardFungibleConfigurationCommitment`, and `assetId` MUST be nonzero.
* `0 < initialSupply <= 5 × (2^128 - 1)`, and the selected issuance shape is valid only when its asset output slots can represent that supply.
* `STANDARD_FUNGIBLE_LAW_ID` identifies the transfer, burn, and fixed one-shot issuance semantics of this section; it is not a compiled VK hash. The network separately pins the implementing VKs.
* There is no asset registry, transition VK, executable asset configuration, mutable supply object, public supply counter, or public asset-ID field.

**Asset definition.** The canonical definition opening of one standard asset is `standardFungibleAssetDefinition = (instanceSalt, metadataCommitment, initialSupply, issuanceSecretCommitment)`. Together with `STANDARD_FUNGIBLE_LAW_ID` it recomputes the configuration commitment and `assetId`. It contains the issuance-secret commitment rather than the secret, and grants no authority to issue or spend. Wallets and applications MAY retain it privately to authenticate metadata, prove the immutable original supply, or establish lineage to a successor asset under Appendix A. Consensus never requires it to be public, and a successor application MAY identify a source asset by `assetId` alone.

**Transfer and burn.** A nonzero asset is ordinary notes under its own namespace with a second conservation equation, so transfer and burn take the direct path (§11.4) alongside native PCASH: the direct protocol kernel authenticates the account, opens every real input in either namespace, links the selected application, and enforces both conservation equations itself. There is no separate transfer relation, and no configuration opening is needed on transfer. The direct relation requires one to `I` asset inputs when the action carries an asset, no `ASSET_ISSUANCE` marker, and zero to `O` asset outputs. It range-checks each note amount to 128 bits and enforces:

```text
sum(asset inputs)  = sum(asset outputs)  + transaction.assetBurnAmount
sum(native inputs) = sum(native outputs) + openFeeAmount
```

Native conservation intermediates are bounded by the compiled slot capacities: for L, each sum of notes is below `2^133` and an output sum plus the fee is below `2^134`. Asset sums and burns keep the independent `< 2^131` semantic bound. All these quantities are strictly below the field modulus, so both equalities have their integer meanings. `assetBurnAmount` is not a separate relation witness: the kernel opens the value already bound by `transactionCommitment`.

The direct relation proves that every real asset and native input belongs to its one authenticated account. An uncovered real input, or a note witness attached to a slot that holds no note, fails. Inputs owned by different accounts fail on the direct path and are admitted only by §11.7, which authenticates a second account for the single purpose of funding the fee. One account owns every real input to an asset-bearing action on the direct path, and one selected installed rule authorizes its complete contribution against the exact transaction commitment. The rule may enforce commercial terms such as price, required PCASH recipients, royalties, redemption conditions, schedules, reserve proofs, or successor custody.

**Issuance.** Fixed issuance is a distinct application and kernel, because it derives the asset identity from a committed configuration, fixes the initial supply, and consumes a one-shot marker, none of which an ordinary spend does:

```text
issuance with optional native funding:
    standard issuance application → installed account authorization
    → issuance kernel → private-value finalizer → private-value wire
```

The standard issuance application VK is protocol-pinned. The application returns five private fields, with field 3 reserved as canonical zero:

```text
standard issuance:
    [assetId, transactionCommitment,
     recentPcashBlockHash, 0, pcashChainId]
```

The standard issuance application requires no asset input, no `APPLICATION` marker, exactly one `ASSET_ISSUANCE` marker, one to `O` asset outputs, and `assetBurnAmount = 0`. It:

1. opens the complete standard configuration and proves `issuanceSecretCommitment`;
2. recomputes `assetId`;
3. derives its replay identity `replayId = poseidon(ASSET_ISSUANCE_REPLAY_DOMAIN, issuanceSecret)` and publishes `poseidon(ASSET_ISSUANCE_NULLIFIER_DOMAIN, assetId, replayId)` in the marker slot;
4. requires the sum of asset outputs to equal `initialSupply` exactly.

The marker has the same construction as an application marker (scope operands, then a replay identity), except that its scope is the asset, because the asset's replay scope must be independent of the authorizing account and installed entry. The secret MUST be a nonzero high-entropy field element. It both authorizes the initial allocation and blinds the public marker. Every reshaped issuance of the same asset derives the same final nullifier, so the global spent set accepts at most one. Ordinary transfer, split, merge, and burn use real-note nullifiers and no issuance marker. No later mint path exists.

Every issuance additionally folds an installed authorization application from an existing `USER` or `CONTRACT` account, whose account and exact rule VK are authenticated against the selected recent state. That application authorizes the asset's creation and its issuance route, not merely a fee. The one issuance kernel accepts optional native note funding owned by this same account and enforces `sum(native inputs) = sum(native outputs) + openFeeAmount`. With no native inputs, positive native outputs and positive fees are impossible.

### 11.7 Asset transfer with a native fee payer

On the direct path every real input belongs to the single authorizing account, so an action spending a contract's asset inventory must also be funded by the contract, not by the party who initiated it. This relation admits a second account in exactly one role: a user account supplies native PCASH for the open fee, or for one exact private relay output, of a named contract account's asset action, and receives its own native change.

The relation:

* authenticates two accounts and their selected installed rules, and requires the primary account's kind to be `CONTRACT`, the fee payer's kind to be `USER`, and their `authorizationAccountId`s to differ;
* partitions inputs by slot class: `ASSET` inputs and every `APPLICATION` marker belong to the primary account, and `NATIVE` inputs belong to the fee payer. At least one `NATIVE` input is required;
* enforces `sum(asset inputs) = sum(asset outputs) + assetBurnAmount` and `sum(native inputs) = openFeeAmount + privateRelayAmount + nativeChangeAmount`;
* permits at most one ordinary private native relay output and one native change output. Change must open to `ownerCommitment(feePayerOwnerId, changeSecret)` for a private nonzero secret. The installed sponsorship rule signs the exact relay amount and owner commitment;
* admits no other native payment, no asset contribution from the fee payer, and no native input owned by the primary account. Payouts of the asset to third parties remain governed by the primary contract account's rule. An `ASSET_ISSUANCE` marker is rejected.

The fee payer contributes no asset, owns no marker, and can lose at most `openFeeAmount + privateRelayAmount`. Exchange and every other multi-party settlement use application contracts and sequential actions rather than multi-owner type `0x00` proofs, because those require parties to contribute value to each other rather than only to the open fee and selected relay payment.

The two installed applications return their role-separated five-field statements privately. The fee-payer kernel authenticates both exact Mega VK hashes, links both applications, enforces the complete type `0x00` relation, and returns the same `I + 2*O + 6`-field statement for the selected profile as the direct and issuance kernels. Nothing about the second account is public: no transaction type, mode bit, public input, or slot distinguishes a fee-paid action from a direct one.

**Role-separated action commitments.** The two rules are verified against domain-separated actions:

```text
primaryFeePayerActionCommitment = poseidon(
    PRIMARY_FEE_PAYER_ACTION_DOMAIN, transactionCommitment)

contractFeeSponsorshipActionCommitment = poseidon(
    CONTRACT_FEE_SPONSORSHIP_ACTION_DOMAIN,
    transactionCommitment,
    primaryContractAddress)
```

The primary application returns `[primaryConfigurationCommitment, primaryFeePayerActionCommitment, recentPcashBlockHash, primaryAuthorizationAccountId, pcashChainId]`. The sponsor returns the corresponding contract-fee action, whose authenticated address operand must equal the contract named in its authorization.

The separation is necessary. A primary rule might omit native constraints because this relation controls all native value. Replayed through the direct path, where the primary account itself owns the native inputs, that rule would permit exactly the redirection this relation forbids. Separate domains make the two authorizations non-interchangeable: a direct authorization cannot become a primary one, a primary authorization cannot spend the sponsor's notes, and sponsorship is never authority over application state or over another contract account.

### 11.8 Nonzero-asset provenance invariant

Every reachable nonzero-asset note descends from exactly one valid standard issuance. The rules above prove this by induction:

* Genesis and every canonical reconstruction begin with no nonzero-asset note.
* Standard issuance is the only relation that may create a nonzero-asset output without consuming a nonzero-asset input, and it authenticates the complete configuration and derived asset ID.
* Every direct or sponsored transfer with a nonzero asset requires at least one real asset input and constructs every asset output using exactly that input's asset ID.
* Account creation, account update, and every other non-issuance relation cannot create nonzero-asset value without consuming it.

Checkpoint restore, restart, reorg replay, and fresh reconstruction MUST reproduce outputs through canonical transaction execution; they MUST NOT admit independent output-tree contents that bypass this invariant. Conformance testing MUST attempt nonzero-asset creation through every non-asset relation and require it to be rejected.

The standard law fixes fungible transfer and burn semantics. The issuer has no later mint, freeze, pause, clawback, blacklist, transfer-tax, receiver-hook, or administrative-spend path. Controlled distribution is ordinary contract custody: initial issuance may create notes owned by a contract, and later movement of those notes must satisfy one installed rule capable of authorizing the contract's complete contribution. Contract conditions apply only while the contract owns the notes.

## 12. Account Transactions

Three transaction types create and change accounts (§5). A user account is created by its Ethereum key (`0x01`) and afterwards changed only by an update its own rules authorize (`0x03`). A contract account is created once (`0x02`), sponsored by a user account, and never changes. Updates and contract creations pay their fees through an ordinary private-value section (§10.3).

### 12.1 Protocol-defined EIP-712 message

Signed user-account creation uses EIP-712 with domain:

```text
EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)
name = "PCASH"   version = "1"   chainId = L1_CHAIN_ID   verifyingContract = INBOX_ADDRESS
```

The exact primary type is `PcashUserAccountCreation(uint256 pcashChainId,address account,uint256 nullifierKeyCommitment,uint256 policySetCommitment,bytes32 receiveKeyHash,bytes32 recentPcashBlockHash,uint256 validUntilTimestamp)`. ECDSA is over secp256k1 with digest `keccak256(0x1901 || domainSeparator || hashStruct(message))`. The signature MUST have `0 < r < n`, `0 < s <= n/2`, and y-parity `0` or `1`. Node-side `ecrecover` MUST return the exact account address.

This is the only message the protocol itself signs over. Private-value and administration ECDSA messages are relations implemented by optional account-rule circuits (§8.2), not ambient protocol authority.

### 12.2 User-account creation (`txType 0x01`)

Creation registers a user account for an Ethereum address. It carries no proof, pays no PCASH fee, and earns no issuance. Because no rule exists before the account does, creation is authorized by the address's Ethereum key, in one of two modes.

**Payload.**

```text
authorizationMode (u8) ‖ address (20) ‖ nullifierKeyCommitment (32) ‖ policySetCommitment (32) ‖
receiveKeyMode (u8) ‖ receiveKeyLength (u16be) ‖ receiveKeyBytes[receiveKeyLength] ‖ recentPcashBlockHash (32) ‖
validUntilTimestamp (u64) ‖ authorizationBytes
```

**Direct mode** (`authorizationMode = 0`) has empty `authorizationBytes`. The serialized account address MUST equal the authenticated sender of the top-level Ethereum transaction carrying this batch item; a mismatch is `InvalidAccountSender`. This applies equally to calldata and blob carriers. No sender claimed inside PCASH bytes or in a submission RPC request supplies this authority. The Ethereum transaction authenticates the complete carrier, including the account fields, PCASH chain ID, recent block, and deadline.

**Signed mode** (`authorizationMode = 1`) requires exactly `r (32) ‖ s (32) ‖ yParity (1)` as `authorizationBytes`. Any Ethereum sender may carry it. The signature is over §12.1's exact domain and primary type, with `pcashChainId` decoded from the common envelope, `nullifierKeyCommitment` from the account body, and `receiveKeyHash = keccak256(receiveKeyBytes)` for `PRESENT`, else `bytes32(0)`.

Other modes are structurally undecodable (`UnknownAuthorizationMode`); missing or extra authorization bytes are `Truncated` or `TrailingBytes`. A direct entry never falls back to signature recovery, and a signed entry never falls back to Ethereum sender authorization.

**Checks,** in the user-account-creation precedence of §10.5:

1. The receive-key encoding is `ABSENT = 0` with length `0` or `PRESENT = 1` with length `ML_KEM768_PUBLIC_KEY_LEN = 1,184`; any other combination is `InvalidReceiveKeyPresence`.
2. Direct mode: the address equals the Ethereum sender (`InvalidAccountSender`). Signed mode: the parity byte is `0` or `1` (`InvalidYParity`), then the signature is valid and recovers the serialized address (`InvalidAccountSignature`, covering invalid scalar ranges, high `s`, failed recovery, and recovery to another address).
3. `nullifierKeyCommitment < p` and nonzero, and `policySetCommitment < p` (`NonCanonicalAccountField`).
4. The resulting user `accountStateCommitment` is nonzero (`ZeroAccountStateCommitment`).
5. No account exists at the address (`AccountAlreadyExists`).
6. The recent block is canonical and in the recent window (`RecentPcashBlockNotInWindow`).
7. The candidate block's timestamp does not exceed `validUntilTimestamp` (`Expired`).
8. `validUntilTimestamp <= selectedBlockTimestamp + RECENT_PCASH_BLOCK_WINDOW_SECONDS` (`AccountCreationDeadlineOutsideWindow`). The selected block is historical, so once `Expired` passes, `selectedBlockTimestamp <= validUntilTimestamp` is implied and is not a separate outcome.
9. Account capacity (`AccountCapacity`).

**Effects.** Consensus derives `accountRevision = 1`; it is neither serialized nor signed. On success, assign the next 40-bit `accountIndex` and atomically write the complete account state row, the account state commitment, and the address-to-index mapping. Raw receive-key publication material is not part of intrinsic state.

Creation cannot update an existing account. Neither authorization mode grants any authority after creation: later actions must satisfy the installed policy set.

### 12.3 User-account update (`txType 0x03`)

An update may replace a user account's policy set, its receive key, or both; an update that changes neither is valid and only increments the revision. It is authorized by one of the account's current policies and pays its own fee from the account's notes, so any relayer can include it. The account's address is public in the payload.

**Payload.** The proposed mutable account fields:

```text
address (20) ‖ newPolicySetCommitment (32) ‖
receiveKeyOperation (u8) ‖ receiveKeyLength (u16be) ‖ receiveKeyBytes[receiveKeyLength]
```

followed by `accountUpdateActionCommitment (32) ‖ privateValueSection`.

**Receive-key operations.** The valid encodings are `KEEP = 0` with length `0`, `CLEAR = 1` with length `0`, and `REPLACE = 2` with length `ML_KEM768_PUBLIC_KEY_LEN = 1,184`. Any other combination is `InvalidReceiveKeyOperation`.

* `KEEP` copies the current presence flag and digest.
* `CLEAR` requires the current receive-key state to be present and produces an absent state with zero digest; otherwise it fails with `ClearWithoutReceiveKey`.
* `REPLACE` derives the exact Keccak-256 digest of its key and requires it to differ from the current digest; otherwise it fails with `ReplaceWithIdenticalReceiveKey`.

Consequently, a transition that leaves the receive-key state unchanged has exactly one canonical encoding: `KEEP`.

**Fee section.** The private-value section is the update's fee payment (§13):

* every input slot is a `NATIVE` note owned by the updated account, or `DUMMY`;
* every output slot is `DUMMY` except an optional ordinary private `NATIVE` relay output and at most one `NATIVE` change note returning to the account's owner identity;
* `assetBurnAmount = 0`, and `sum(native inputs) = openFeeAmount + privateRelayAmount + changeAmount`.

A zero fee with every slot dummy is valid. The proof's ordered public inputs are the twenty private-value inputs of §11.2 (profile S), followed by `accountUpdateActionCommitment` at position `20`. `pcashChainId` is the zero-extended common-envelope value, and the recent block hash and `validUntilTimestamp` are the section's own words. The proof verifies only against the protocol-pinned `user_account_update` VK.

**The action commitment.** At the transaction's exact sequential processing position, consensus reads the immutable `nullifierKeyCommitment` and `currentAccountRevision` from the live row, derives `newAccountRevision = currentAccountRevision + 1` without overflow, and derives the live current and proposed leaves:

```text
currentAccountStateCommitment = accountStateCommitment(address, nullifierKeyCommitment, currentPolicySetCommitment, USER,
                               currentAccountRevision, currentReceiveKeyPresent,
                               currentReceiveKeyHashHigh128, currentReceiveKeyHashLow128)
newAccountStateCommitment     = accountStateCommitment(address, nullifierKeyCommitment, newPolicySetCommitment, USER,
                               newAccountRevision, newReceiveKeyPresent,
                               newReceiveKeyHashHigh128, newReceiveKeyHashLow128)

accountUpdateActionCommitment = poseidon(
    ACCOUNT_UPDATE_AUTHORIZATION_ACTION_DOMAIN,
    uint160(address),
    accountIndex,
    currentAccountStateCommitment,
    newAccountStateCommitment,
    validUntilTimestamp)
```

The installed rule authorizes the transition and its fee together, through the funded action:

```text
accountUpdateFundedActionCommitment = poseidon(
    ACCOUNT_UPDATE_FUNDED_ACTION_DOMAIN,
    accountUpdateActionCommitment,
    transactionCommitment)
```

where `transactionCommitment` is the private-value commitment (§11.3) of the section's transaction. The public action commitment binds the transition alone, so consensus can recompute it from live state. The funded action binds it to the exact fee transaction, so one authorization cannot be reattached to different notes, a different fee, or a different change note.

**The update kernel.** The account-update kernel:

1. takes the account's `nkRoot` and derives `nullifierKeyCommitment` from it;
2. privately reconstructs both account leaves, and requires `newAccountRevision == currentAccountRevision + 1` without overflow and `transaction.validUntilTimestamp` equal to the action's deadline;
3. opens the recent block's state root and output root;
4. proves every `NATIVE` input's membership and canonical nullifier under the account's note key, and enforces the change and conservation rules above;
5. proves a nonzero four-field rule entry against `currentPolicySetCommitment`, and requires the canonical Mega VK hash to equal that entry's `implementationVkHash`;
6. derives `authorizationAccountId` (§5.3), links the preceding application, and requires its private return statement to equal `[configurationCommitment, accountUpdateFundedActionCommitment, recentPcashBlockHash, authorizationAccountId, pcashChainId]`.

The change note's secret is a free kernel witness; the authorizing rule binds whichever opening it approved.

**Execution.** Execution MUST validate the receive-key operation against the live current receive-key state, derive the resulting account state row, and reconstruct the action commitment from the exact live row and the exact proposed policy and receive-key operation, including earlier successful transactions in the same block, then require equality with the serialized proof input. The action commitment binds the complete resulting account transition rather than the operation byte; the `CLEAR` and `REPLACE` preconditions make the operation encoding canonical.

**Checks,** in the account-update precedence of §10.5:

1. Output-data sizes, then versions (`InvalidOutputDataSize`, `InvalidOutputDataVersion`, §10.3).
2. Every section word and the appended `accountUpdateActionCommitment` is canonical (`PublicInputNotInField`).
3. The receive-key operation's mode and length are a valid combination (`InvalidReceiveKeyOperation`).
4. `newPolicySetCommitment < p` (`NonCanonicalAccountField`).
5. The recent block is in the window (`RecentPcashBlockNotInWindow`), then the deadline has not passed (`Expired`), then `notBeforeTimestamp` has been reached (`NotYetValid`) (§10.6).
6. An account exists at the address (`AccountNotFound`), and it is a user account (`WrongAccountKind`).
7. `currentAccountRevision + 1` fits in `u32` (`AccountRevisionExhausted`).
8. The receive-key operation's state precondition holds (`ClearWithoutReceiveKey`, then `ReplaceWithIdenticalReceiveKey`).
9. The new account state commitment is nonzero (`ZeroAccountStateCommitment`).
10. The reconstructed `accountUpdateActionCommitment` equals the serialized one (`ActionCommitmentMismatch`).
11. The section's nullifier, capacity, and output-commitment checks, as in §11.2 steps 6–8, then proof verification against the `user_account_update` VK (`ProofRejected`).

**Effects.** On success, atomically mark the nullifiers, record the fee, append the five output commitments, and replace the canonical row and account state commitment. (The failure-isolated derived index records absence, a new key publication, or a reference to the prior publication.) On failure the transaction has no effect.

Validation MUST NOT reject duplicate `nullifierKeyCommitment` values across addresses.

### 12.4 Contract-account creation (`txType 0x02`)

A contract account is a keyless account whose kind is committed in its account state (`accountKind == 1`), whose address derives from its contents, and which consensus never updates. It exposes an immutable set of functions, each one account-rule entry (§5.2). Calling a function means folding that entry's application into the active protocol kernel, which authenticates the exact action and the account's contribution. There is no node-executed contract code and no contract storage. Notes owned by a contract are spendable only through one of its committed functions.

Contract creation is unsigned, and the contract's full contents are published in Ethereum data. A contract has nothing to spend before it exists, so a user account sponsors its creation through an appended private-value section: the sponsor authorizes the publication and pays its fee from its own native notes, so any relayer can include it. An `openFeeAmount` of zero with no private relay payment spends nothing and leaves inclusion to a self-publisher or a selected publisher willing to carry it.

**Body.**

```text
publishesNkRoot (u8) ‖ nullifierKeyMaterial (32) ‖
receiveKeyMode (u8) ‖ receiveKeyLength (u16be) ‖ receiveKeyBytes[receiveKeyLength] ‖
functionCount (u8) ‖
functionCount × { selector (u8) ‖ implementationVkHash (32) ‖ configurationCommitment (32) }
```

The valid receive-key encodings are `ABSENT = 0` with length `0` and `PRESENT = 1` with length `ML_KEM768_PUBLIC_KEY_LEN = 1,184`; any other combination is `InvalidReceiveKeyPresence`.

**Nullifier-key publication.** The body carries exactly one representation of the account's nullifier-key material, selected by `publishesNkRoot` after structural decoding:

```text
publishesNkRoot = 0:  nullifierKeyMaterial = nullifierKeyCommitment
publishesNkRoot = 1:  nullifierKeyMaterial = nkRoot
                      nullifierKeyCommitment = poseidon(NULLIFIER_KEY_COMMITMENT_DOMAIN, nkRoot)
```

* A **private contract** (`publishesNkRoot = 0`) carries `nullifierKeyCommitment` and does not reveal `nkRoot`.
* A **published-root contract** (`publishesNkRoot = 1`) carries a nonzero `nkRoot`, and the commitment is derived from it. Publishing the root lets any party that knows one of the contract's note openings derive that note's nullifier, so parties other than the creator can spend the contract's notes whenever one of its functions authorizes it.

A contract body never carries both, so it cannot encode an `nkRoot` and a `nullifierKeyCommitment` that disagree. `publishesNkRoot` is an immutable part of the contract's identity because it is encoded in the body and in the contract-address preimage. It is not a separate account-state-commitment operand; the address already binds it.

A function entry is exactly 65 bytes in the order shown. At the maximum `functionCount` of 255, the largest contract payload is 17,796 bytes, and the complete transaction size is bounded by its release-pinned proof and five fixed-size output-data items.

**Function set.** `functionSetCommitment` is derived from the complete canonical function list rather than serialized beside it:

```text
contractFunctionCommitment_i = poseidon(ACCOUNT_RULE_ENTRY_DOMAIN, implementationVkHash_i, configurationCommitment_i, 0)
functionSetCommitment        = depth-8 sparse root over (selector_i -> contractFunctionCommitment_i)
```

A contract function is an ordinary account-rule entry (§5.2) whose `commitmentBlinder` is the canonical `0`: a contract's entries are published in full, so a published per-function blinder would hide nothing, and distinct deployments already differ through `nullifierKeyCommitment`, which the address preimage binds. The selector is the entry's position in the depth-8 rule-set tree, not a hash-derived selector. It remains private when the function is called, because the function opening and application VK are kernel witnesses.

**Address derivation.**

```text
receiveKeyHashField = uint256(keccak256(mlKem768PublicKey)) mod p    // 0 when hasReceiveKey == 0
contractPreimage    = poseidon(CONTRACT_ADDRESS_DOMAIN, nullifierKeyCommitment, functionSetCommitment, receiveKeyHashField, publishesNkRoot)
address             = uint160(contractPreimage)                       // low 160 bits of the canonical integer
```

A contract address therefore binds `nullifierKeyCommitment`, the derived `functionSetCommitment`, the receive-key hash, and `publishesNkRoot`. Two creations with the same complete identity derive the same address, and the second is rejected as an existing account. An application that needs a distinct instance must use a distinct `nkRoot` or a distinct committed function configuration. An ECDSA key that happens to correspond to a contract address authorizes nothing, because every consumed note authenticates an installed account rule (§11.5) and contract accounts reject all writes.

**Sponsorship.** The appended section is the sponsor's fee payment (§13):

* every input slot is a `NATIVE` note owned by the sponsor, or `DUMMY`;
* every output slot is `DUMMY` except an optional ordinary private `NATIVE` relay output and at most one `NATIVE` change note returning to the sponsor's owner identity;
* `assetBurnAmount = 0`, and `sum(native inputs) = openFeeAmount + privateRelayAmount + changeAmount`.

The contract-creation kernel authenticates one sponsor account of kind `USER` under the recent block's account-state root together with its selected installed rule, opens the recent block's output root, proves every native input and the change rule above, links the sponsor's application, and requires its private return statement to equal `[configurationCommitment, poseidon(CONTRACT_FEE_SPONSORSHIP_ACTION_DOMAIN, transactionCommitment, contractAddress), recentPcashBlockHash, sponsorAuthorizationAccountId, pcashChainId]`. This is the same sponsorship action as §11.7, with the contract being created as the named contract, so one installed sponsorship rule serves both. Reusing it is sound: a native-only transaction cannot satisfy the asset fee-payer kernel, which requires an asset input, and the sponsor's nullifiers are single-spend.

`contractAddress` is public input position `20`. Consensus derives it from the body exactly as in the address derivation above, so a proof for one body cannot fund another. The section is not part of the address preimage: two parties may race to publish the same contract identity under different sponsorships, and exactly one succeeds.

**Checks,** in the contract-creation precedence of §10.5 after the section's output-data and field checks:

1. `publishesNkRoot` is `0` or `1` (`InvalidPublishesNkRootFlag`), then the receive-key mode and length are a valid creation combination (`InvalidReceiveKeyPresence`).
2. `nullifierKeyMaterial < p` and nonzero; then, for each function in body order, `implementationVkHash_i < p` and `configurationCommitment_i < p` (`NonCanonicalContractField`). Zero is allowed for a function's VK hash and configuration commitment.
3. Derive `nullifierKeyCommitment` from `nullifierKeyMaterial`: for `publishesNkRoot == 0` the material is the commitment itself; for `publishesNkRoot == 1` it is `nkRoot` and the commitment is derived. The resulting `nullifierKeyCommitment` must be nonzero and not equal to `poseidon(NULLIFIER_KEY_COMMITMENT_DOMAIN, 0)` (`InvalidContractNkRoot`).
4. The function set is nonempty (`EmptyContractFunctionSet`), then selectors are strictly increasing (`SelectorsNotStrictlyIncreasing`), then each `contractFunctionCommitment` is nonzero (`ZeroContractFunctionCommitment`), all in function index order. A zero function commitment would be indistinguishable from an absent sparse-tree leaf and could never be called.
5. Derive `functionSetCommitment` from the complete canonical list; each function commitment is an account-rule entry commitment and its selector is its entry index. The full function opening is consensus-verified and present in Ethereum data.
6. Derive `address` from the canonical operands and compute the account state commitment, and require it to be nonzero (`ZeroContractAccountCommitment`); then require no existing account at that address (`ContractAddressExists`); then require capacity (`AccountCapacity`).
7. Apply the section checks of §10.5 in order, and verify the proof against the protocol-pinned `contract_creation` wire VK with the derived address as its last public input.

**Effects.** On success, atomically write the complete kind-`1`, revision-`1` account state row and its leaf at the new `accountIndex`, together with the section's nullifiers, fee, and output commitments. The raw body and receive key remain in Ethereum data, not in intrinsic state.

## 13. Fees, Relay Payments, and Issuance Destinations

A transaction reaches Ethereum only if someone posts it and pays the Ethereum gas. PCASH gives transactions two ways to pay whoever does that, and, because successful type `0x00` transactions earn issuance, a way to say where that issuance goes.

Every private-value transaction, user-account update, and contract creation declares `(openFeeAmount, issuanceOwnerCommitment)` in its proof-bound private-value section.

**Open fee.** `openFeeAmount` is existing native PCASH offered publicly to the poster of the batch that successfully includes the action, identified by the batch's `posterOwnerCommitment` (§9.3). Native conservation is `sum(native inputs) = sum(ordinary native outputs) + openFeeAmount`. Issuance is created only at settlement, after the transaction, so it can never fund the same action's inputs or outputs.

**Private relay payment.** A wallet may also, or instead, pay a selected relayer with an ordinary encrypted native output, which is included in the ordinary-output sum above. It has no public fee classification. The proof requires every real output's owner commitment, including a relay payment's, to differ from `issuanceOwnerCommitment` (§11.3); by wallet convention the relay note's owner commitment is also fresh and unlinkable to it (§15.1).

**Issuance destination.** For a qualifying type `0x00` action:

* `issuanceOwnerCommitment = 0` sends issuance to the successful poster (the **open route**);
* a nonzero value sends issuance to that exact one-time owner commitment (the **designated route**). The destination is proof-bound, so copying the transaction into another batch cannot redirect it.

A nonzero issuance commitment requires `openFeeAmount = 0`, and a positive open fee requires a zero issuance commitment. `openFeeAmount = 0` with a zero issuance commitment is valid and leaves issuance open to the poster. User-account updates and contract creations earn no issuance and require `issuanceOwnerCommitment = 0`. The proof enforces these rules (§11.2); nodes do not check them separately. User-account creation has neither a mandatory nor an optional PCASH fee.

The open fee and issuance are distinct accounting components even when settlement combines them in one native note (§14.3). A selected relayer is paid from an ordinary output and may separately receive issuance through the designated commitment on an eligible action. Self-publication uses a zero open fee and no relay-payment output, and an eligible action may designate the wallet's own one-time issuance commitment.

**Who pays.** A user-account update spends the updated account's own notes (§12.3). Contract creation uses a registered user-account sponsor (§12.4). Subsequent contract actions may pay from contract-owned native notes without any user initiator. The two-account sponsorship relation (§11.7) bounds the sponsor's contribution to the open fee, one exact ordinary relay output, and the sponsor's change, without granting authority over the primary account's value.

## 14. Native Issuance and Block Settlement

Native PCASH is created only when a block is settled, as the issuance component of system payout notes (§14.3). Issuance follows a declining supply curve. A controller raises or lowers the per-transaction offer according to whether issuance is behind or ahead of that curve, and each block splits its issuance equally among its successful qualifying transactions. The per-block computation has three stages: a **block quote** computed from the parent state before any transaction executes (§14.1), a **capacity reservation** as each transaction executes (§14.3), and **settlement** after the last transaction (§14.2–14.3).

**Constants.** In this section, `S` is the supply cap, `I` is gross issuance, and `p(·)` is reference progress; they are unrelated to profile S, the input count `I`, and the field modulus `p`. Let:

* `S = 1,000,000,000 × 10^18` atomic units, the gross supply cap;
* `T = 365 × 86,400` seconds, the curve's reference duration;
* `tau = 12` seconds, the reference interval between qualifying actions;
* `H = 2 × 86,400` seconds, the controller's response half-life;
* `M = 4`, the maximum internal multiplier;
* `k = 3`, the maximum issuance speed: a block's allowance is `k` times the baseline;
* `Q = 2^128`, the fixed-point scale.

The initial multiplier is provisionally `1/16`, represented by initial lag `-8 days × Q` (the initial state's `lagQ`, §7.7). The maximum remembered positive lag is `LQ = 4 days × Q`. There is no registration grant, issuance balance, entitlement tree, separate native spending representation, calendar deadline, or saved allowance from an empty or underfilled block.

**Qualification.** Only successful type `0x00` actions qualify, once per action, in either profile. Qualification counts actions, not participants: publicly callable contracts may authorize repeated qualifying actions. Registrations, updates, contract creations, folded applications, failed actions, and system notes do not qualify. A qualifying action's issuance goes to its issuance destination (§13), independently of any ordinary private relay output, and the action remains valid when its actual issuance is zero.

### 14.1 The block quote

The block quote is computed once per block, before any of its transactions execute, from the parent state (`I = issuedSoFar` and `lagQ`, §6.5) and the block's elapsed time. For block `0`, elapsed time is zero. For each later block, `elapsed = timestamp - parentTimestamp`; a timestamp regression is invalid and halts derivation.

**Exhausted supply.** If `I = S`, issuance has ended. The quote is all zero: baseline, offer, allowance, and `oneActionMax` are `0`, `rewardEach` will be `0`, and the next state keeps `issuedSoFar = S` and the parent's `lagQ` unchanged. Elapsed time is not added to the lag and no exponent is evaluated. The rest of §14.1 and §14.2 does not apply. Exhaustion is declared only at `I = S`.

**Supply curve.** Otherwise `I < S`. The equivalent reference progress is `p(I) = T × (1 - sqrt(1 - I/S))`, and the corresponding ideal curve is `I(p) = S × (2p/T - (p/T)^2)`. `T` shapes the declining baseline. It is not an expiry, nor a guarantee that issuance completes in one year. Consensus evaluates the curve with exact integer intermediates and Q128 outputs:

```text
q          = isqrt(floor((S - I) × Q² / S))
progressQ  = T × (Q - q)
baselineQ  = floor(2 × S × tau × q / T)
allowanceQ = k × baselineQ
```

At `I = S`, `q = 0` and `progressQ = T × Q`. The square-root floor rounds the baseline down and progress up. The same `progressQ` function is used before and after settlement, so progress differences telescope.

**Lag.** `lagQ` measures, in Q128 seconds, how far issuance is behind (positive) or ahead of (negative) the reference pace. The parent state stores the signed post-settlement `lagQ`. Before computing the offer, set:

```text
offerLagQ = min(LQ, lagQ + elapsed × Q)
```

This clamp discards excess positive delay from authenticated state. There is no negative-lag clamp. A missed Ethereum slot advances lag by its elapsed seconds but does not multiply the block allowance.

**Offer.** If `offerLagQ < -128 × H × Q`, set `multiplierQ = 0`. This cutoff is applied before the exponent is quantized. Otherwise evaluate, in exact integers:

```text
expQ16      = trunc(offerLagQ × 65536 / (H × Q))      # toward zero
shift       = floor(expQ16 / 65536)
frac        = expQ16 - shift × 65536                  # 0 <= frac < 65536
factorQ16   = ((195766423245049 × frac + 971821376 × frac² + 5127 × frac³ + 2^47) >> 48) + 65536
scaled      = shift < 0 ? (Q × factorQ16) >> -shift : (Q × factorQ16) << shift
multiplierQ = min(M × Q, scaled >> 16)
offerQ      = floor(baselineQ × multiplierQ / Q)
```

`multiplierQ` approximates `Q × 2^(offerLagQ / (H × Q))`. The exponent is quantized to 1/65536 of a half-life (2.63671875 seconds) by truncation toward zero, and `factorQ16` approximates `65536 × 2^(frac / 65536)` with the cubic of Bitcoin Cash's `aserti3-2d` difficulty algorithm, whose coefficients, rounding constant, and operation order are unchanged. Whole half-lives are exact: `offerLagQ = j × H × Q` gives `multiplierQ = 2^j × Q` for `-128 ≤ j ≤ 2`, so the initial lag gives exactly `Q / 16`. Against `2^(offerLagQ / (H × Q))` the relative error before the final floor is below 0.013%. The cutoff and the `LQ` clamp keep `-128 ≤ shift ≤ 2`, and the right shifts floor nonnegative values. The polynomial exceeds the signed 64-bit range before its shift; no operation may wrap.

At the underflow boundary, `offerLagQ = -128 × H × Q` evaluates normally to `multiplierQ = 1`. A zero encoded offer is not terminal: elapsed time can raise the lag and restore a positive offer.

**One-action maximum.** The most any single action could earn in this block, if it were the only qualifying action, is:

```text
oneActionMax = min(floor(offerQ / Q), floor(allowanceQ / Q), S - I)
```

Issuance is **active** in the block when `oneActionMax > 0`. This predicate decides capacity reservations (§14.3) and does not depend on how many actions eventually qualify.

### 14.2 Complete-block equal settlement

After all of the block's transactions have executed, let `n` be the count of successful qualifying actions. The count does not change the quote. For `n > 0`, every qualifying action receives the same integer-atomic issuance:

```text
rewardEach = min(floor(offerQ / Q), floor(allowanceQ / (n × Q)), floor((S - I) / n))
nextIssued = I + n × rewardEach
nextLagQ   = offerLagQ - (progressQ(nextIssued) - progressQ(I))
```

For `n = 0`, issuance is zero and `nextLagQ = offerLagQ`. The block's post-settlement state is `issuedSoFar = nextIssued` and `lagQ = nextLagQ`, except in the exhausted case of §14.1.

The following are consequences of these formulas, not additional rules:

* Division remainders stay unissued. There is no transaction-order remainder recipient, final sweep, or allowance carry-over.
* If fewer than `n` atomic units remain, `rewardEach = 0` and the entire block pays zero; a later block with fewer qualifying actions may consume the remainder. Such a block is not exhausted, so its lag still advances by elapsed time.
* With several qualifying actions, the allowance and the remaining supply are divided equally, so actual issuance can be below both the internal offer and `oneActionMax`. Sparse or uneconomic participation can extend distribution indefinitely.
* Fees and burns do not change gross issuance, including fees that settle in the same note as issuance. Spent, unspent, lost, and burned issuance all remain included in it.

### 14.3 Capacity reservation and payout order

Settlement appends payout notes after all of the block's ordinary outputs, and it must never fail for lack of output-tree space. So each transaction with a private-value section reserves, before it may succeed, room for every payout note it could cause.

**Reservation.** Let `MAX_NOTE = 2^128 - 1` and `noteCount(A) = floor(A / MAX_NOTE) + (A mod MAX_NOTE != 0)`, the number of notes a payout of `A` needs. At the start of each block:

```text
nextOutputIndex    = parent outputCount
pendingPayoutSlots = 0
posterTotal        = {}         // accepted open-fee total per posterOwnerCommitment, across all batches
```

For each transaction with a private-value section (types `0x00`, `0x02`, and `0x03`), at the `OutputCapacity` step of its precedence (§10.5), with `O` its output count, `f` its `openFeeAmount`, and `C` the `posterOwnerCommitment` of its batch:

```text
feeSlots = noteCount(posterTotal[C] + f) - noteCount(posterTotal[C])
issSlot  = 1 if the transaction is type 0x00 and issuance is active (§14.1), else 0
require nextOutputIndex + O + pendingPayoutSlots + feeSlots + issSlot <= 2^40
        // all arithmetic is exact; otherwise the outcome is OutputCapacity
```

A zero-fee type `0x00` transaction still reserves `issSlot`; zero-fee types `0x02` and `0x03` reserve nothing beyond their outputs. The reservation state changes only if the transaction succeeds:

```text
pendingPayoutSlots += feeSlots + issSlot
posterTotal[C]     += f
nextOutputIndex    += O          // the transaction's ordinary outputs are appended now
```

A transaction that fails for capacity is not reconsidered, even when settlement ends up using fewer notes than were reserved.

**Settlement.** After the block's transactions have executed, compute `rewardEach` (§14.2) once and append the system payouts, in this order:

```text
// 1. Designated issuance, one payout per action, in transaction order.
for each successful type 0x00 transaction t, in block order:
    if t.issuanceOwnerCommitment != 0:
        appendPayout(t.issuanceOwnerCommitment, openFee = 0, issuance = rewardEach)

// 2. Poster groups: open fees, plus open-route issuance, per poster commitment.
for each successful transaction t with a private-value section, in block order:
    C = posterOwnerCommitment of t's batch
    group[C].openFee  += t.openFeeAmount
    if t is type 0x00 and t.issuanceOwnerCommitment == 0:
        group[C].issuance += rewardEach
    // a group's position is the first t that adds a positive amount to it
for each group (C, openFee, issuance), in position order:
    appendPayout(C, openFee, issuance)

appendPayout(C, openFee, issuance):
    A = openFee + issuance
    if A == 0: append nothing
    append floor(A / MAX_NOTE) notes of amount MAX_NOTE, then one note of A mod MAX_NOTE if nonzero,
        each an ordinary native note owned by C (§15), at nextOutputIndex, with split indices from 0
```

The open fee transfers existing PCASH; issuance creates new PCASH. Matching poster commitments aggregate across every batch in the block; distinct commitments and distinct PCASH blocks never aggregate. Each payout record keeps separate open-fee and issuance components even when one physical note combines them (§15.2). Gross issuance advances by exactly `n × rewardEach`. System notes have no transaction result of their own and earn no issuance.

### 14.4 Atomic commit

Commit output leaves, nullifiers, account changes, gross issuance, post-settlement lag, ordered payouts, and canonical block history atomically. A verifier or infrastructure failure aborts derivation rather than omitting payouts or changing the accepted set. New system notes and newly registered accounts become usable only through a later completed authenticated boundary (§10.6).

## 15. System Payout Notes and Recovery

A system payout note is the note settlement creates for a designated issuance or a poster group (§14.3). For every positive system payout amount `A`, canonical nonzero owner commitment `C`, and assigned output index `j`, construct:

```text
body = poseidon(NOTE_BODY_COMMITMENT_DOMAIN, C, 0, A)
output = poseidon(OUTPUT_COMMITMENT_DOMAIN, body, j)
```

`C` is the action's `issuanceOwnerCommitment` for a designated payout, or the batch's `posterOwnerCommitment` for a poster payout. The note is spent with the ordinary rules: owner opening, account authorization, membership, amount bounds, and note nullifier. There is no system-note flag and no separate spending relation. The amount and system origin are public; the owner is hidden behind its secret-opening commitment. Distinct output indices distinguish otherwise identical payouts. Each designated issuance produces its own payout. Poster payouts aggregate per poster commitment across the complete block (§14.3), and one note may combine an open fee with issuance.

A payout note carries no encrypted output data, so its owner must be able to find it and reconstruct its opening another way. §15.1 gives the reference wallet's conventions for doing that from a seed; §15.2 gives the records a node keeps so that such recovery works.

### 15.1 Recoverable ownership

This subsection defines reference conventions, not consensus rules. Conformance vectors cover them (§17) so that wallets interoperate. `H(...)` is `poseidon(...)`.

**Designated issuance.** The reference wallet derives a designated issuance destination with these domain-separated derivations, where `ownerId` and `nkRoot` belong to the recipient:

```text
payoutSeed = H(PAYOUT_SEED_DOMAIN, noteNk(nkRoot))
txContext = H(PAYOUT_TX_CONTEXT_DOMAIN, chainId, shapeId, N_IN, N_OUT, nullifier_0, ..., nullifier_(N_IN-1))
issuanceSecret = H(PAYOUT_NOTE_SECRET_DOMAIN, payoutSeed, ISSUANCE_ROLE=3, txContext)
issuanceOwnerCommitment = H(OWNER_COMMITMENT_DOMAIN, ownerId, issuanceSecret)
```

The transaction context includes the compiled shape and the complete ordered nullifier list. It excludes proof bytes, the final transaction hash, the amount, outputs, the block, and the output index. Re-proving against another recent state therefore preserves recovery as long as the profile and nullifiers stay fixed. Every successful transaction consumes all its input nullifiers, including dummy nullifiers, so the same nullifiers cannot succeed again. Whether one authorization permits multiple distinct transactions is determined by the installed account rule.

**Output secrets.** Reference implicit output secrets bind the same compiled dimensions and every ordered nullifier:

```text
noteSecretSeed = H(IMPLICIT_NOTE_SECRET_SEED_DOMAIN, noteNk(nkRoot))
noteSecret_i = H(TRANSACT_NOTE_SECRET_DOMAIN, noteSecretSeed, shapeId, N_IN, N_OUT,
                 nullifier_0, ..., nullifier_(N_IN-1), i, outputEntropy_i)
```

A note keeps this creation secret when it is spent through another profile; spending does not rederive it using the consuming transaction's profile.

**Selected relayers.** On a designated route, the receiving wallet derives `issuanceSecret` from its own payout seed and the transaction context. After the nullifiers are fixed, a selected relayer supplies an authenticated quote containing: that one-time issuance opening; a separate authenticated private receive destination for an ordinary native fee note; the exact private fee amount; the chain and transaction context; and an expiry. The ordinary fee note uses an independent fresh owner commitment and must never reuse the public `issuanceOwnerCommitment`. The wallet's semantic approval binds the exact private fee note, issuance commitment, transaction context, expiry, and funding constraints. The relayer decrypts its output envelope and recomputes the proof-bound ordinary note commitment before publishing. A different issuance secret for the same identity is a different commitment and requires new approval; changing the nullifiers requires a new quote. Quote authentication happens in the wallet, without a second in-circuit recipient-signature system.

**Open route.** For the open route, the poster derives:

```text
posterContext = H(PAYOUT_BATCH_CONTEXT_DOMAIN, chainId, selectedHeadNumber)
posterSecret = H(PAYOUT_NOTE_SECRET_DOMAIN, payoutSeed, POSTER_ROLE=2, posterContext)
posterOwnerCommitment = H(OWNER_COMMITMENT_DOMAIN, ownerId, posterSecret)
```

The reference poster selects its known completed PCASH head number (`uint64`) before submission and carries the resulting context explicitly in every batch prepared at that head (§9.3). It may reuse a selected context across submissions. Neither the selected head nor the context has to match the eventual inclusion block; delayed submissions remain valid and recoverable. A shared commitment makes its batches publicly linkable, including across different inclusion blocks. The node cannot infer common ownership of different commitments. The exact `itemsHash` (§7.8) remains delivery provenance, independently of recovery.

Copying a transaction preserves a designated destination, which is proof-bound. An open destination follows the poster of the successful inclusion: republishing an open-route proof in a different batch that succeeds first redirects both its fee and its issuance.

These are application recovery conventions, not a universal secret derivation for contract keys. A contract rule that designates its issuance route supplies an independent secret or delivery convention; published contract `nkRoot` values cannot supply secret recovery material. Multiple authorizers approve one shared route according to their roles, and a distinct fee sponsor need not own the issuance.

### 15.2 Canonical recovery records

This subsection is a node requirement, not consensus: recovery records are not committed by any root. The node retains designated transaction contexts, explicit batch poster contexts, and ordered payout records, produced by the same settlement pass that creates the payouts. They are discovery material, not another mint authority. Each payout record carries:

* its kind, `designatedIssuance` or `posterPayout`;
* its note amount, with separate open-fee and issuance components;
* its owner commitment, output index, and split index within its payout;
* its recovery contexts.

A designated record identifies its source transaction by Ethereum transaction index and batch item index, and retains that transaction's single context. A poster record identifies its contributing Ethereum transaction indices in encounter order and has no source transaction ID; it retains the distinct contexts of all contributing batches in encounter order, including batches whose only contribution is issuance at zero fee. When a payout splits, each note's open-fee component consumes the payout's remaining open fee before any issuance, so the components across its notes sum to the payout's open fee and issuance. Individual successful fee contributions remain available through batch manifests and transaction records. Untrusted posters can copy a commitment with an unrelated context, so recovery tries every retained context rather than trusting the first.

Canonical blocks, checkpoints, export and import, and suffix replacement preserve these records, or sufficient history to rebuild them. A node advertising seed restoration MUST NOT silently prune them; state roots alone do not suffice.

A fresh wallet scans paginated node records, derives candidate commitments from its seed, reconstructs matching ordinary notes, and obtains canonical membership and spentness evidence at a coherent completed boundary. It requires no saved transaction hashes, quote list, plaintext backup, Ethereum connection, or historical blob retrieval. An explicitly lagging index is not an authoritative zero balance. Orphaned discoveries are replaced on reorganization. Recovered notes and notes delivered by encryption share one native inventory. Consolidation uses ordinary private-value transfers of a supported profile when necessary to fit a requested spend, or when explicitly requested by an operator.

## 16. Parameters

Compiled PCASH V1 rules:

| Parameter | Value |
|---|---|
| `RECENT_PCASH_BLOCK_WINDOW_SECONDS` | 3,600 |
| `OUTPUT_TREE_DEPTH` / `ACCOUNT_TREE_DEPTH` | 40 |
| `NULLIFIER_TREE_DEPTH` | 256 (Keccak sparse set) |
| `ETHEREUM_BLOCK_TREE_DEPTH` / `HISTORY_TREE_DEPTH` | 40 |
| `STATE_TREE_DEPTH` / `BLOCK_HEADER_TREE_DEPTH` | 4 |
| `ACCOUNT_RULE_SET_DEPTH` | 8 |
| State keys | 0/1 output root/count, 2 gross issued, 3/8 signed Q128 lag limbs, 4 account state root, 5 account count, 6/7 nullifier-root limbs, 9–15 unassigned zero leaves |
| Block-header keys | 0 parent hash, 1 number, 2 timestamp, 3/4 transaction-results-root limbs, 5 state root, 6 Ethereum blockhash root, 7 PCASH history root, 8 extra data, 9–15 unassigned zero leaves |
| `AMOUNT_BITS` (all notes and native fees) / asset aggregate bits | 128 / 131 |
| Output data size/version | Exactly 1,750 bytes / `0x01` |
| `ML_KEM768_PUBLIC_KEY_LEN` | 1,184 |
| `MAX_TX_BYTES` | 131,072 (fixed parsing bound, §9.3) |
| `MAX_BATCH_ENTRIES` | 1,024 |
| `MAX_BATCH_BLOBS` | 6 (maximum blobs in one Inbox transaction, §9.1) |
| `MAX_BATCH_BYTES` | 761,825 (six-blob canonical payload capacity, §9.1) |
| Batch versions | `0x01` (nonzero poster owner commitment, count, and length-prefixed typed transactions) |
| Transaction types | private-value `0x00`, user-account creation `0x01`, contract-account creation `0x02`, user-account update `0x03` |
| Aster accepted transaction types | `0x00`, `0x01`, `0x02`, `0x03` |
| Private-value S / L / user-account-update / contract-creation public inputs | 20 / 102 / 21 / 21 |
| Serialized verifier-input fields in a private-value section | S: 14; L: 69; the chain ID comes from the common envelope and output-data hashes are derived from output data |
| Private application return fields | 5 |
| Private-value commitment and wire/effect arity | S: 4 input slots, 5 output slots; L: 32 input slots, 32 output slots |
| Account kinds | user 0, contract 1 |
| Protocol-pinned final wire VKs | private_value_wire, private_value_large_wire, user_account_update_wire, contract_creation_wire |
| Wire VK files | `vks/<name>.chonk.vk` in the release circuit bundle; the bundle manifest records each file's SHA-256, and `sdk/src/constants/proverArtifacts.ts` carries the current digests |
| Type-0 protocol kernels | Direct, native fee-payer, and asset issuance, each compiled for S and L |
| Account-action protocol kernels | user_account_update, contract_creation_with_native_fee_payer |
| Compressed proof bytes: private value S / L / account update / contract creation | 23,840 / 26,464 / 23,872 / 23,872 |
| Barretenberg release | `5.2.0` |
| BN254 G2 CRS SHA-256 | `0x01797bfc4de5a96f0e516a9ea4537d18786dc30cb991aca4274c95822b69c32f` |
| Grumpkin G1 CRS SHA-256 | `0x8df01ac0f564db0b52857d37b363b8f8042cd1d886f8d8fd2677375a6290e870` |
| Launch identity | Selected and pinned by launch preparation, including the anchor |

## 17. Conformance Vectors

The V1 protocol conformance-vector package contains these JSON files under `vectors/`:

* `poseidon2_vectors.json`: the field modulus, hash parameters, the empty-input case, and single- and multi-chunk known answers for §2.3.
* `merkle_vectors.json`: the empty ladder and roots, plus worked least-significant-bit-first membership examples for §2.4.
* `monetary.json`: exact curve, lag, and settlement transitions, exponent quantization boundaries, rounding boundaries, saturation, terminal state, and dust (§14).
* `protocol_vectors.json`: chain-neutral note and dummy nullifiers; permanent transaction slot digests and the transaction commitment; standard asset identity, issuance, and transfer; stable authorization account IDs; the account-update action and its complete unified account-state-commitment operands; reference output-note-secret derivation; designated-issuance and poster recovery; account state roots; the nullifier tree; account-rule entries; genesis `extraData`; Ethereum and PCASH history leaves and trees; transaction-result leaves and roots; exact-inclusion transaction IDs; typed StateRoot and header openings; the block hash; and contract-address derivation.

JSON is only a transport and display container; no commitment consumes JSON bytes. `genesisHash` is derived from canonical block `0` (§7.7) and binds its `extraData`. `node assets/gen_domain_tags.mjs --check` and `node scripts/gen_vectors.mjs --check` independently reconstruct the generated constants and protocol vectors. Example-rule and reference-implementation vectors are documented with their respective components and are not protocol conformance vectors.

## Appendix A. Monetary Trust Resets

This appendix is a cross-version continuity criterion, not an Aster state transition. It defines when a later protocol may claim canonical monetary continuity with this chain after it stops trusting a predecessor proof system. V1 nodes evaluate nothing in it.

A fork declares a **monetary trust reset** when it no longer treats predecessor proofs as sufficient evidence that private value was created, conserved, spent, or authorized correctly. A fork without a reset carries value forward under its ordinary rules; a change of format, verifier, implementation, or parameter does not by itself require one. Let `H` be the final block of the distrusted generation. Blocks after `H` are re-derived under successor rules, so orphaned notes, payouts, and account effects are excluded. Account actions replay only when their authorization and recent-state requirements hold under successor rules.

**Establishing predecessor value.** A successor that recognizes predecessor value as native PCASH MUST independently establish, under the source generation's fixed historical interpretation:

* the commitment opening;
* authenticated membership in the applicable historical state;
* the asset identity and amount;
* ownership and a successor-recognized authorization relation;
* the canonical nullifier;
* that the nullifier was consumed neither at the recovery basis nor by an earlier successor claim.

The fork selects one recovery basis `B <= H` for native PCASH. Terminal recovery uses `B = H`. Snapshot recovery uses an earlier `B` at which native state is still trusted, admits only outputs present and unspent at `B`, and excludes every later native output and every descendant of a claimed note. Either way, the eligible set is one set and its claims are mutually exclusive. No predecessor proof is sufficient merely because it verified under its historical verifier. Historical transaction bytes, commitment and nullifier formulas, account states, and asset identifiers keep exactly their original meanings; a successor MUST NOT reinterpret a historical value to make it admissible. Value controlled by an authorization relation the successor cannot establish MAY remain unavailable.

**Native PCASH.** A successor claiming canonical native continuity MUST bound the aggregate predecessor-native value it admits:

```text
nativeIssuanceEnvelope_H = S
legacyNativeDebit       <= nativeIssuanceEnvelope_H
```

The gross cap `S` of §14 is the conservative proof-independent upper bound. The authenticated issued counter records system issuance, but it does not prove that a distrusted private-value circuit never counterfeited notes. A tighter bound on inherited value requires a separate proof-independent justification. Fees do not enlarge the envelope and burns do not reduce it. All recognized predecessor-native notes compete for the same capacity. Capacities from different generations MUST NOT be added: each later reset establishes one fresh envelope through its boundary. Each accepted claim debits at least the net successor-native value it makes spendable, including any successor fee funded by inherited value.

A release MAY temporarily quarantine predecessor-native value, but MUST NOT describe canonical native continuity as completed unless it provides a deterministic, permissionless, trustless predecessor-native claim path. That path MAY reveal the amount charged to the capacity; it MUST NOT require disclosure of the source commitment, source account, claimant, recipient, or unrelated balances. Admitted value is ordinary native PCASH: its later validity MUST NOT depend on which predecessor claim funded it, and it is not subject to a provenance clawback. The envelope provides containment, not restitution. Indistinguishable counterfeit claims may exhaust it before legitimate claims. Incident-specific rules MAY prioritize claims established by independently trustworthy facts, such as membership below a defect boundary, but every admitted amount still counts against the capacity.

**Standard non-native assets.** A nonzero `assetId` is an immutable identity. A successor MUST NOT assign it different semantics, supply, metadata, or ownership history, and MUST NOT recognize predecessor value under an existing nonzero `assetId`; such notes MAY remain quarantined under their original identifier, and migration to a fresh asset is the only continuation path. Any party MAY create a fresh standard asset and issue a bounded inventory of it to an immutable contract account whose installed application releases that inventory against predecessor-note claims. That application defines its own eligibility policy, including a basis earlier than `H` and the exclusion of later outputs and their descendants. Its fixed supply bounds its payouts under any policy, it requires no reset, and it MAY exist within a generation. Each predecessor claim MUST be accepted at most once per such account, and recognizing it grants the predecessor asset no authority over the successor asset. Consensus does not choose which candidate, if any, carries a predecessor asset's identity.

**V1 side.** V1 needs no generation field, inherited-value counter, asset registry, migration transaction, or state leaf to satisfy this criterion. Indexed note commitments and canonical nullifiers (§4.3), typed terminal state (§6.5), exact retained transaction bytes (§7.8), permanent transaction-type meanings (§3.2), the gross issuance cap (§14), and the immutable standard-asset definition (§11.6) supply every predecessor fact above without accepting a predecessor proof.

## References

* [EIP-2](https://eips.ethereum.org/EIPS/eip-2): low-s signature rule.
* [EIP-712](https://eips.ethereum.org/EIPS/eip-712): typed-data signing.
* [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559): base fee and burn semantics.
* [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844): blob transactions and KZG commitments.
* [Ethereum Execution APIs](https://ethereum.github.io/execution-apis/): Ethereum JSON-RPC and block tags.
