From 5811ed27442c54801967a87cf8a4a746dddb278b Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Tue, 24 Feb 2026 22:46:20 +0000 Subject: [PATCH 1/5] kip 21 initial commit --- kip-0021.md | 667 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 667 insertions(+) create mode 100644 kip-0021.md diff --git a/kip-0021.md b/kip-0021.md new file mode 100644 index 0000000..435b77b --- /dev/null +++ b/kip-0021.md @@ -0,0 +1,667 @@ +``` +KIP: 21 +Layer: Consensus (hard fork), Chain-Block UTXO Validation +Title: Partitioned Sequencing Commitment with O(activity) Proving +Authors: Michael Sutton +Status: Draft +Created: 2026-02-17 +``` + +## Abstract + +This KIP replaces the current per-chain-block linear sequencing commitment recurrence with a partitioned sequencing commitment scheme. + +Instead of committing to all accepted transactions in every chain block as one global append-only stream, consensus maintains a commitment over the tips of active application lanes. Each active lane has its own recursive tip hash and last-touch blue score. Global order remains reconstructible via `merge_idx` (Section 10.1), but is no longer directly committed as one monolithic per-block list. The block header continues to expose a single `accepted_id_merkle_root` value (consumed by `OpChainblockSeqCommit`), but post-activation it commits to (i) the active-lanes sparse Merkle tree (SMT) root and (ii) per-block context and mergeset miner payloads, chained through selected-parent recursion. + +The primary goal is prover cost proportional to relevant lane activity (`O(activity)`) for zk proving systems that consume chain-block sequencing commitments as anchors, while preserving global anchoring and enabling bounded-memory eviction of inactive lanes. In addition, `SeqStateRoot(B)` commits block-level context needed by based zk systems (timestamp, DAA score, and blue score) and mergeset miner payloads, under the same chained commitment, and touched lane-tip updates include the same context hash. + +## Motivation + +ZK provers for application-level state machines, based computation lanes, and other “app-local” systems often need to prove statements that are naturally scoped to a single application lane. + +Under the current linear per-block recurrence, proving correctness for lane-specific execution can require processing or committing to global activity in every block, even if a prover only cares about one lane. This creates prover cost proportional to global throughput. + +By committing to lane tips (and lane-local block activity digests) rather than to the entire global stream, this KIP makes lane proofs proportional to lane activity, enabling `O(activity)`-style proving. + +This scheme does not assume a fixed bound on the number of active lanes. In the worst case, each transaction may belong to a distinct lane. However, the committed active set is bounded by the number of lanes touched within the activity window `F`, and `F` is strictly less than pruning depth. Therefore, node storage is not increased asymptotically: the “hot” commitment set scales with recent activity, not with historical diversity of lane IDs. + +## Goals + +1. Keep a single 32-byte header commitment consumable by `OpChainblockSeqCommit`. +2. Make per-lane proving scale with that lane’s own activity. +3. Enable bounded-memory behavior via inactivity purge keyed by blue score. +4. Define an incremental consensus state that supports reorg rollbacks with diff application/reversal. +5. Commit additional block context and mergeset miner payloads needed by based zk systems under the same chained commitment. +6. Keep pre-activation behavior unchanged. + +Requirements: + +* Remain forward-compatible with a future account-level extraction granularity without replacing the core recurrence. + +## How to Read This KIP + +This document mixes consensus rules and proving/operations guidance. Use this reading path: + +* Consensus implementers: read *Terminology* through *Script Semantics*, then *Reorg-Safe Incremental Maintenance* and *Purge Index*, then *Relationship to Covenants++ Workstream* and *Backward Compatibility*. +* Proving/witness implementers: read *Per-Lane Block Activity Digest (§4.2)*, *Update or Initialize Lane Tips (§5.2)*, and *Commitment Structure (§6)* (especially §6.3-§6.7), then *Complexity (§10)* and *Optional Persistent Witness Store (§11)*. +* Quick orientation: read Abstract, Motivation, and *Mental Model (Reader Map)* first. + +Where explicitly marked `(Normative)`, sections are intended to be consensus-critical. Sections marked `(Informative)` explain proving models and operational patterns that do not by themselves change consensus rules. + +## Mental Model (Reader Map) + +The shortest way to think about the construction is: + +1. start from the accepted transaction sequence `AcceptedTxList(B)` (selected-parent coinbase first); +2. extract lane IDs (`lane_id_bytes`) from transactions; +3. group transactions by lane; +4. compute one `activity_digest_lane(B)` per touched lane, with `merge_idx` committed per tx; +5. update each touched lane tip recursively (existing lane uses previous lane tip; re-appearing lane after purge anchors to `SeqCommit(parent(B))`); +6. store updated lane-tip records in the active-lanes SMT to get `ActiveLanesRoot(B)`; +7. compute block context hash (`timestamp`, `daa_score`, `blue_score`) and miner-payload root; +8. combine these into `SeqStateRoot(B)`; +9. chain through selected parent to get `SeqCommit(B)`; +10. publish `SeqCommit(B)` in `accepted_id_merkle_root`. + +This yields one header commitment for consensus, while preserving lane-local witnesses for `O(activity)` proving between anchors. + +## Specification + +This specification is written in two passes so readers can first follow the state transition mechanics and then see exactly how that state is committed in the header: + +1. **Active lane management:** how lane tips are updated and purged as a function of accepted transactions. +2. **What the header commits to:** how we commit those tips (and additional context) into `SeqCommit(B)`. + +### 1. Terminology + +* Lane (`lane`): the logical application lane tracked by this commitment scheme. (In this KIP, lanes are extracted from `tx.subnetwork_id`; future KIPs may define finer-grained lane extraction such as account-level lanes.) +* Lane Tip: the current recursive hash tip for one lane. +* Active Lane: a lane whose lane tip is currently present in the commitment set. +* Last-Touch Blue Score: blue score of the chain block that last updated the lane tip. +* Active Lanes Root (`ActiveLanesRoot(B)`): the sparse-Merkle root committing to the active lane tips at chain block `B` (Section 6.3). +* Sequencing State Root (`SeqStateRoot(B)`): commits to the active-lanes root and additional context/miner-payload data at chain block `B` (Section 6.6). This is distinct from the UTXO commitment (`utxo_commitment`). +* Sequencing Commitment (`SeqCommit(B)`): the value stored in `B.header.accepted_id_merkle_root` post-activation (Section 6.7). +* Mergeset Index (`merge_idx`): the 0-based index of a transaction within `AcceptedTxList(B)` (Section 4.2). +* Accepting Block (`B`): the chain block whose `SeqCommit(B)` is being computed. +* Mergeset Miner Payload: raw coinbase payload bytes from mergeset blocks, committed by this KIP (Section 6.5.2). + +### 2. Lane Extraction + +For this KIP, lanes are extracted from accepted transactions as follows: + +* For each accepted transaction `tx`, the lane selector is `tx.subnetwork_id`. +* This includes the selected-parent coinbase transaction, which uses the dedicated coinbase subnetwork ID. +* This KIP does not merge built-in/native IDs into a single SYS lane; native, coinbase, and registry subnet IDs are mapped as distinct lanes. + +This keeps the mechanism consensus-native and deterministic with current transaction structure. Future KIPs may define additional lane extraction modes (e.g., account-level lanes), while reusing the same commitment machinery. + +#### 2.1 Canonical Lane ID Encoding (Normative) + +This KIP defines a canonical lane identifier byte string `lane_id_bytes`. + +Current encoding (subnetwork lanes): + +* Let `subnetwork_id` be the canonical 20-byte `tx.subnetwork_id`. +* Define `lane_id_bytes = subnetwork_id` (20 bytes, no padding). + +System-reserved subnetwork IDs: + +* IDs of the form `x || 0x00..0x00` (one first byte `x`, followed by 19 zeros) are reserved for system usage by consensus. +* This reserved set includes the currently hardcoded subnet IDs. + +Consensus nodes treat `lane_id_bytes` as opaque identifiers at this layer. + +Rationale for keying: + +* The committed SMT key is `lane_key(lane) = H_lane_key(lane_id_bytes(lane))` (Section 6.1). +* Because key width is normalized by `H_lane_key`, zero-padding lane IDs is unnecessary. +* This also keeps lane-ID encoding decoupled from SMT keyspace layout and leaves room for future composite lane IDs. + +### 3. Data Model + +Consensus maintains a per-block state `S(B)` derived from `S(parent(B))`: + +``` +S(B) = { + ActiveLanes: map lane -> (lane_tip_hash, last_touch_blue_score) +} +``` + +Only lanes that are currently “active” (not purged) appear in `ActiveLanes`. + +### 4. Hashing and Encodings + +This section defines byte-level hashing and encoding rules used by per-lane activity and tip updates. + +#### 4.0 Encoding and Hash Domain Definitions + +All hash functions in this KIP are BLAKE3 with explicit domain separation tags. + +Keyed-hash notation: + +``` +H_tag(m) = BLAKE3(key = tag, input = m) +``` + +Domain tags: + +| Symbol | Domain tag | +| --- | --- | +| `H_lane_key` | `"SeqCommitLaneKey"` | +| `H_lane_tip` | `"SeqCommitLaneTip"` | +| `H_activity_leaf` | `"SeqCommitActivityLeaf"` | +| `H_mergeset_context` | `"SeqCommitMergesetContext"` | +| `H_miner_payload` | `"SeqCommitMinerPayload"` | +| `H_miner_payload_leaf` | `"SeqCommitMinerPayloadLeaf"` | +| `H_leaf` | `"SeqCommitActiveLeaf"` | +| `H_node` | `"SeqCommitActiveNode"` | +| `H_seq` | `"SeqCommitmentMerkleBranchHash"` | + +`H_seq` is the same branch hasher domain separator used by the currently deployed post-covenants sequencing commitment path (`SeqCommitmentMerkleBranchHash`). This KIP formalizes that behavior and extends it with lane/state commitments. + +For convenience, `H_seq(x, y)` means `H_seq(x || y)` where `x` and `y` are 32-byte values. + +#### 4.1 Primitive Encodings and Transaction Digest + +Primitive encodings: + +* `le_u{32,64}(x)`: fixed-width little-endian encoding. +* `var_bytes(b)`: `le_u64(len(b)) || b`. +* `blue_work_bytes(work)`: big-endian bytes of `work` with leading zero bytes removed (empty byte-string for `work = 0`). +* `blue_work_encoding(work)`: `var_bytes(blue_work_bytes(work))`. + +`blue_work_encoding` matches the consensus hashing semantics used by `rusty-kaspa` header hashing (`write_blue_work`). + +Transaction digest: + +``` +tx_digest(tx) = seq_commit_tx_digest(tx.id, tx.version) +``` + +#### 4.2 Per-Lane Block Activity Digest + +Intuition: This section defines what “happened for lane `lane` in block `B`” as a single hash. We do this by +committing to (i) the transaction digest and (ii) a block-local index (`merge_idx`) which lets upper layers +reconstruct global order when needed. + +Let `AcceptedTxList(B)` be the ordered list of all accepted transactions in chain block `B`, in canonical acceptance order. The sequence starts with the selected-parent coinbase transaction, followed by accepted non-coinbase transactions in consensus order. + +For each `tx` in `AcceptedTxList(B)`, define its mergeset index: + +Definition: + +``` +merge_idx(tx) = position of tx in AcceptedTxList(B) // 0..len-1 +``` + +For a lane `lane`, define `LaneTxList(B, lane)` to be the list of all accepted transactions in `B` whose lane extraction yields `lane`, in the same relative order they appear in `AcceptedTxList(B)`. + +Define the per-lane activity leaf for each transaction: + +``` +activity_leaf(tx) = H_activity_leaf(tx_digest(tx) || le_u32(merge_idx(tx))) +``` + +Define the per-lane activity digest as a Merkle root over the leaf list: + +``` +activity_digest_lane(B) = MerkleRoot([activity_leaf(tx) for tx in LaneTxList(B, lane)]) +``` + +Merkle-root semantics match the sequencing commitment Merkle-root behavior specified by this KIP: + +* Empty list root is `ZERO_HASH` (32 zero bytes). +* Single entry root is `H_seq(entry, ZERO_HASH)` (not the entry itself). +* Internal nodes are `H_seq(left, right)`. + +#### 4.3 Tip Update Hash + +For lane `lane` in accepting block `B`, define a recursive update step: + +``` +TipUpdateHash(parent_ref, lane_id_bytes(lane), activity_digest, MergesetContextHash(B)) = + H_lane_tip(parent_ref || lane_id_bytes(lane) || activity_digest || MergesetContextHash(B)) +``` + +where: + +* `parent_ref` is either the previous lane tip hash (if lane is already active) or a global anchor (Section 5.2). +* `lane_id_bytes(lane)` is the canonical lane ID encoding for lane `lane` (Section 2.1). +* `activity_digest` is `activity_digest_lane(B)`. +* `MergesetContextHash(B)` is defined in Section 6.5.1. + +### 5. Per-Block State Transition + +This section defines the deterministic state transition from `S(parent(B))` to `S(B)`. Conceptually, only two things can change at block `B`: touched lanes are updated, and stale lanes are purged. + +Let `S_prev = S(parent(B))`. + +#### 5.1 Group Activity by Lane + +Define the set of touched lanes in block `B`: + +``` +Touched(B) = { lane | exists tx in AcceptedTxList(B) with lane(tx) = lane } +``` + +For each `lane in Touched(B)`, compute `activity_digest_lane(B)` as in Section 4.2. + +#### 5.2 Update or Initialize Lane Tips + +Before iterating touched lanes, compute: + +``` +ctx_hash_B = MergesetContextHash(B) +``` + +For each `lane in Touched(B)`: + +1. Compute `activity_digest_lane(B)`. +2. If `lane` exists in `S_prev.ActiveLanes`: + + * `parent_ref = S_prev.ActiveLanes[lane].lane_tip_hash` +3. Else: + + * `parent_ref = SeqCommit(parent(B))` +4. Compute `tip_next = TipUpdateHash(parent_ref, lane_id_bytes(lane), activity_digest_lane(B), ctx_hash_B)`. +5. Set: + + * `ActiveLanes[lane].lane_tip_hash = tip_next` + * `ActiveLanes[lane].last_touch_blue_score = B.blue_score` + +This gives: + +* continuity for already-active lanes, +* and global anchoring for lanes re-appearing after inactivity purge. + +Rationale: a lane that has been purged has no committed tip in the active-set root. Anchoring a re-appearing lane to `SeqCommit(parent(B))` re-attaches it to a globally committed point without requiring retention of inactive lane tips. Reactivation proving with non-inclusion checkpoints is described in Section 10.3. + +#### 5.3 Inactivity Purge + +To allow bounded memory, lanes are purged after a fixed inactivity threshold. + +Let `F` be a protocol parameter measured in blue score units. + +A lane `lane` is purged at block `B` if: + +``` +ActiveLanes[lane].last_touch_blue_score + F <= B.blue_score +``` + +Purged lanes are removed from `ActiveLanes`. + +### 6. Commitment Structure + +This section defines how state is projected into the single header commitment. The structure is intentionally layered: active-lanes state first, then per-block context/miner payloads, then selected-parent chaining. + +At a high level: + +1. Commit the active lanes map into `ActiveLanesRoot(B)` via an SMT. +2. Commit additional per-block context and miner payload data. +3. Hash these together into `SeqStateRoot(B)`. +4. Chain the state root through selected-parent ancestry into `SeqCommit(B)`. + +#### 6.1 Keying + +Define the sparse-Merkle key for lane `lane`: + +``` +lane_key(lane) = H_lane_key(lane_id_bytes(lane)) // 32-byte key +``` + +where `lane_id_bytes(lane)` is the canonical lane ID encoding for lane `lane` (Section 2.1). + +#### 6.2 Leaf Value + +Leaf payload for lane `lane`: + +``` +leaf_payload_lane = + lane_id_bytes(lane) + || lane_tip_hash + || le_u64(last_touch_blue_score) +``` + +where: + +* `lane_id_bytes(lane)` is the canonical lane ID encoding for lane `lane` (Section 2.1). +* `lane_tip_hash` and `last_touch_blue_score` are taken from `ActiveLanes[lane]`. + +Leaf hash: + +``` +leaf_hash_lane = H_leaf(leaf_payload_lane) +``` + +#### 6.3 Root + +The sparse Merkle root over all active leaves is the active lanes root: + +``` +ActiveLanesRoot(B) = SMT_Root({ lane_key(lane) -> leaf_hash_lane | lane in ActiveLanes }) +``` + +Consensus sets: + +``` +B.header.accepted_id_merkle_root = SeqCommit(B) +``` + +#### 6.4 Sparse Merkle Tree Definition (Normative) + +This KIP defines `SMT_Root` as a fixed-depth (256) sparse Merkle tree: + +1. Keys are 256-bit strings (`lane_key`), interpreted MSB-first as a path. +2. Leaves exist at depth 256 and hold `leaf_hash_lane` (32 bytes) for active lanes; all other leaves are empty. +3. Empty leaf hash is `EMPTY_0 = ZERO_HASH`. +4. Internal empty node hash at height `i+1` is: + +``` +EMPTY_{i+1} = H_node(EMPTY_i || EMPTY_i) +``` + +5. A non-empty internal node hash is: + +``` +node_hash = H_node(left_child_hash || right_child_hash) +``` + +6. The empty-tree root is `EMPTY_256`. + +The root is the hash of the top node after setting all active leaves and filling all other subtrees with their corresponding `EMPTY_i` values. + +#### 6.5 Additional Committed Data (Normative) + +This KIP additionally commits (i) accepting-block header context and (ii) miner payloads from all mergeset blocks. + +##### 6.5.1 Accepting Block Context Commitment + +Upper layers often need a verifiable "clock" and height-like indices relative to `SeqCommit(B)`. + +Define: + +``` +MergesetContextHash(B) = + H_mergeset_context( + le_u64(B.header.timestamp) + || le_u64(B.header.daa_score) + || le_u64(B.header.blue_score) + ) +``` + +`MergesetContextHash(B)` is committed both as part of `SeqStateRoot(B)` and inside each touched lane-tip update in `B`. + +##### 6.5.2 Miner Payload Commitment (Mergeset Blocks) + +To support based zk systems, this KIP commits raw coinbase payload bytes from all mergeset blocks of the accepting block `B`, including blocks whose coinbase transactions are not accepted. + +Rationale: + +* Non-selected-parent coinbase transactions are not accepted into `AcceptedTxList(B)`, but their coinbase payloads still carry consensus-relevant data: the accepting block's reward construction uses mergeset coinbase payload fields (e.g., payout destination data). +* In practice, coinbase payloads are also the conventional place where miners signal extra metadata (e.g., software/pool identity), so these payload bytes can be relevant for L2 applications. +* Therefore, this KIP commits these payload bytes via a dedicated block-level path, rather than through accepted-transaction sequencing. +* Each committed payload is bound to block identity (`X.hash`) and `blue_work` to provide secure topological metadata. + +For each mergeset block `X`: + +* Let `payload_bytes(X)` be the raw payload bytes of the coinbase transaction of `X`. +* Consensus rule update: for coinbase payload bytes, the legacy hard size limit is relaxed for miner-payload use, and the payload segment above that legacy limit is charged via transient mass. + +For each mergeset block `X`, define: + +``` +miner_payload_hash(X) = H_miner_payload(payload_bytes(X)) + +miner_payload_leaf(X) = H_miner_payload_leaf( + X.hash + || blue_work_encoding(X.header.blue_work) + || miner_payload_hash(X) +) +``` + +Let `MinerPayloadLeaves(B)` be the list of `miner_payload_leaf(X)` over all included mergeset blocks `X`, ordered by the deterministic mergeset traversal order used by consensus (selected parent first, then the remaining mergeset blocks in consensus topological order). + +Define: + +``` +MinerPayloadRoot(B) = MerkleRoot(MinerPayloadLeaves(B)) +``` + +Merkle-root branching uses `H_seq` semantics (Section 4.2). + +#### 6.6 Sequencing State Root (Normative) + +Define: + +$$ +\mathrm{SeqStateRoot}(B) = +H_{\mathrm{seq}}\Big( +\mathrm{ActiveLanesRoot}(B), +H_{\mathrm{seq}}(\mathrm{MergesetContextHash}(B), \mathrm{MinerPayloadRoot}(B)) +\Big). +$$ + +Notes: + +* `ActiveLanesRoot(B)` is a direct child of `SeqStateRoot(B)`. +* `MergesetContextHash(B)` and `MinerPayloadRoot(B)` are committed as the right child hash. + +#### 6.7 Sequencing Commitment Recurrence (Normative) + +Define: + +$$ +\mathrm{SeqCommit}(B) = +H_{\mathrm{seq}}\big(\mathrm{SeqCommit}(\mathrm{parent}(B)), \mathrm{SeqStateRoot}(B)\big). +$$ + +where `parent(B)` is `B`'s selected parent. + +Notes: + +* `SeqCommit(B)` is chained through selected-parent ancestry (KIP-15 style recurrence). +* `SeqStateRoot(B)` is not directly exposed in the header; it is committed by `SeqCommit(B)`. +* `ActiveLanesRoot(B)` is not directly exposed in the header; it is committed by `SeqStateRoot(B)` and therefore by `SeqCommit(B)`. + +### 7. Script Semantics + +`OpChainblockSeqCommit` (aka `OpChainBlockHistoryRoot` in [4, 5]) returns the 32-byte commitment value stored in the header field `accepted_id_merkle_root` of a chain block, subject to existing chain-ancestor and depth checks. + +This KIP defines post-activation `accepted_id_merkle_root` semantics for `OpChainblockSeqCommit`. + +### 8. Reorg-Safe Incremental Maintenance + +Implementations maintain sequencing commitment state incrementally via reversible per-block diffs. + +#### 8.1 Diff Content + +For each chain block `B`, define `SeqCommitDiff(B)` containing: + +1. lane mutations: + + * `(lane, old_record_opt, new_record_opt)` for every inserted, updated, or removed lane. +2. purge-index mutations: + + * insert, update, and delete mutations in the score index for all touched/purged lanes. +3. optional root transition cache: + + * `old_root`, `new_root` for fast rollback/apply paths. + +#### 8.2 Reorg Operations + +Given a selected-parent reorg: + +1. walk down the old chain path and reverse `SeqCommitDiff`s in reverse order. +2. walk up the new chain path and apply `SeqCommitDiff`s in forward order. + +This mirrors the existing UTXO-diff strategy and avoids full-state recomputation. + +### 9. Purge Index (Oldest-First / Heap-Oriented) + +Implementations provide oldest-score-first access for purge processing, equivalent to: + +1. `peek_oldest_score()` +2. `pop_oldest_while(score <= purge_cutoff)` + +#### 9.1 On-Disk Ordered Index (RocksDB-Friendly) + +The canonical index is persisted in score order, enabling iterator-based popping without full scans. + +Recommended key layout: + +``` +LastTouchByScoreKey = + PREFIX_LAST_TOUCH_BY_SCORE + || be_u64(last_touch_blue_score) + || lane_key +``` + +Where: + +* `be_u64` is big-endian encoding. +* `lane_key` disambiguates collisions for equal scores. + +Rationale: + +* RocksDB keys are lexicographically ordered. +* With `be_u64(score)`, forward iteration yields oldest scores first. + +Equivalent encodings are allowed if they preserve deterministic oldest-first extraction semantics. + +#### 9.2 In-Memory Cache + +An implementation may maintain an in-memory min-heap over `(last_touch_blue_score, lane_key)` for hot-path purge checks. + +The heap is a cache/hint layer only; canonical correctness is defined by persisted state. Heap entries can become stale under updates/reorgs, so candidates popped from heap are validated against current persisted lane records before deletion. + +#### 9.3 Reorg and Diff Requirements + +All index mutations (insert/update/delete in `LastTouchByScore`) are part of `SeqCommitDiff` and are reversible under reorg. + +#### 9.4 IBD Bootstrap at Pruning Point (Normative Guidance) + +IBD from the pruning point (PP) must provide enough sequencing-commitment state for a new node to continue normal chain-block processing from PP onward. + +At PP, an implementation must have (or reconstruct) the following minimum data: + +* Full active-lane leaf data at PP (`lane_id_bytes`, `lane_tip_hash`, `last_touch_blue_score` for every active lane), sufficient to reconstruct `ActiveLanesRoot(PP)` in full. +* `SeqCommit(parent(PP))` (equivalently, `PP` selected-parent `accepted_id_merkle_root`). +* `MergesetContextHash(PP)` and `MinerPayloadRoot(PP)` (or equivalent raw data to deterministically recompute them). + +These are sufficient to verify `SeqStateRoot(PP)` and then verify `PP.accepted_id_merkle_root = SeqCommit(PP)` by the recurrence in Section 6.7. + +From that verified PP state, a node processes subsequent chain blocks by the same incremental rules in Sections 5-9 (touch/update/purge + diff maintenance). + +Operational note: + +* Implementations should retain the PP active set analogously to PP UTXO-set retention, and when PP advances, update it by advancing over sequencing-commitment diffs from the old PP to the new PP. +* Alternatively, an implementation may reconstruct the PP active set in linear time using the optional persistent witness-store structure (Section 11). + +## Informative: Proving and Witness Operations + +Sections 10 and 11 describe proving-oriented usage patterns and optional witness-serving infrastructure. They explain how to consume the committed structure, but do not add consensus rules beyond Sections 1-9. + +### 10. Complexity + +Let: + +* $n = |AcceptedTxList(B)|$ +* $t = |Touched(B)|$ +* $p =$ number of purged lanes at $B$ + +Then: + +* Grouping activity by lane is $O(n)$. +* Updating tips is $O(t)$. +* Purge checks with the ordered index are $O(p \log |ActiveLanes|)$. +* SMT update is $O((t+p)\log(2^{256})) = O((t+p)\cdot 256) = O(t+p)$ with a large constant factor (256 levels). + +#### 10.1 Global Order Reconstruction + +If an upper layer needs global order reconstruction, it can reconstruct it by collecting the per-lane transaction sets and sorting by $`\mathrm{merge\_idx}`$ within the accepting block $B$. Verifying this reconstruction requires lane witnesses under the committed $SeqCommit(B)$ anchor and proving the relevant active-lanes SMT diff (or an equivalent witness relation) between anchors. This can be done by tracking (or recomputing) active-lanes SMT updates, or via the optional persistent witness-store approach in Section 11. + +#### 10.2 Two-Anchor Lane Proof Model + +For lane-local proving, the intended model is: + +1. provide two global anchors: $SeqCommit(B_{start})$ and $SeqCommit(B_{end})$; +2. provide lane-inclusion witnesses for lane $\ell$ under both anchors (via $ActiveLanesRoot$ / $SeqStateRoot$); +3. provide a compressed lane-diff witness from $`\mathrm{lane\_tip}(\ell, B_{\text{start}})`$ to $`\mathrm{lane\_tip}(\ell, B_{\text{end}})`$. + +The lane-diff witness size is proportional to lane activity between anchors, so this path is $O(a)$ rather than $O(\Delta B)$, where $a$ is app activity and $\Delta B$ is the number of chain blocks between anchors. + +Including $MergesetContextHash(B)$ in touched lane-tip updates is what makes accepting-block clock/context available on the lane-local path without forcing per-block global sequencing commitment processing. + +Miner payloads remain a block-level commitment ($MinerPayloadRoot(B)$). Apps that rely on this data still track block-level witnesses across intermediate chain blocks. + +#### 10.3 Reactivated Lane Proof Pattern + +For a lane that was purged and later re-appears, proving continuity typically needs both lane inclusion and lane non-inclusion witnesses: + +1. inclusion at the old anchor for the last pre-purge lane tip; +2. non-inclusion checkpoints for $`\mathrm{lane\_key}(\ell)`$ under $ActiveLanesRoot$ while iterating the selected-parent $SeqCommit$ chain, with at least one checkpoint per $F$ blue-score window; +3. inclusion at/after reactivation for the new lane tip anchored by $SeqCommit(parent(B_{reactivate}))$. + +Each non-inclusion checkpoint at blue score $s$ certifies inactivity for the preceding $F$-window ($[s-F, s]$) by the purge rule, so exclusion proofs do not need to be repeated inside that covered segment. + +This checkpoint pattern shows that no committed intermediate lane tip for $\ell$ exists between the old tip and the reactivation event. + +### 11. Optional Persistent Witness Store + +Clients (e.g., zk provers) may need inclusion and non-inclusion witnesses for a lane under older anchors (e.g., “5 hours ago”). Requiring each client to track the SMT live is heavy, and requiring nodes to rewind or replay diffs per witness request is also heavy. An optional auxiliary indexing layer can reconstruct witnesses directly from stored commitment structure. + +Implementation hints: + +* Maintain a unified, content-addressed commitment-node store: + `node_hash -> (left_child_hash, right_child_hash)` + covering both `H_seq` nodes (sequencing commitment and state roots) and SMT internal nodes (`H_node` under `ActiveLanesRoot`). + +* Witnesses can then be reconstructed directly from a header anchor `accepted_id_merkle_root = SeqCommit(B)` by descending the stored commitment DAG to the relevant subtree root (e.g., `ActiveLanesRoot(B)`; the descent from `SeqCommit(B)` to `ActiveLanesRoot(B)` follows a fixed path: right child to `SeqStateRoot(B)`, then left child to `ActiveLanesRoot(B)`, before the key-driven SMT path), and then following the SMT path for `lane_key(lane)` while collecting siblings. + +* Empty subtree hashes (`EMPTY_i`) are computable from the spec; the store only needs to retain non-empty nodes. + +Pruning and cleanup: + +* GC-like reference counting: maintain `refcount[node_hash]` alongside `node_hash -> (left,right)`. + +* Cleanup rule: on first insertion of `node_hash -> (left,right)` (i.e., if `node_hash` is not already present), increment `refcount[left]` and `refcount[right]`. Separately, treat every retained header anchor (`accepted_id_merkle_root`) as one additional reference to its root (increment on retention, decrement on prune), even if the root node already exists. When a header is pruned, decrement the root’s refcount; whenever a refcount hits zero, delete that node and recursively decrement its children. This layer is optional and does not replace reorg diffs for hot-path consensus maintenance. + +## Future Compatibility with vProgs CD + +This KIP is designed to remain compatible with a future full vProgs CD specification. + +Expected direction: + +* Account-level lanes may use a richer diff-style update rule (CD-like, with transaction vertices and account vertices). +* Subnet-based lanes defined by this KIP remain unchanged, so zk provers built over the current subnet-lane logic can continue to operate as-is after a CD upgrade. +* A full CD model may allow re-anchoring to a previously proven lane/account state, instead of always falling back to `SeqCommit(parent(B))` when state is absent from the active set. + +This section is informative only; it does not change the normative subnet-lane rules specified above. + +## Relationship to Covenants++ Workstream + +This KIP is a standalone consensus specification for partitioned sequencing commitments. + +Within the Covenants++ workstream, it complements KIP-16, KIP-17, and KIP-20. TN12 (the experimental testnet for Covenants++) already includes those KIPs and `OpChainblockSeqCommit` (earlier named `OpChainBlockHistoryRoot`, see [4, 5]). This KIP specifies the commitment semantics consumed by that opcode. + +## Backward Compatibility + +* This KIP is a consensus-changing hard fork: post-activation blocks are not consensus-compatible with nodes that do not implement these rules. +* Post-activation, `accepted_id_merkle_root` follows `SeqCommit(B)` as defined here, coinbase payload bytes above the legacy hard size limit are admitted and charged via transient mass, and `OpChainblockSeqCommit` consumes this commitment path. + +## Why SMT + +Sparse authenticated map vs dense tree: this commitment tracks a sparse key-value map over lane IDs, with updates driven by touched/purged lanes. Dense tree representations are not practical here because diff/update work scales with global key-space structure rather than with per-block mutations. + +SMT vs Patricia: both SMT and Patricia-style sparse tries are viable and have similar asymptotic update properties (`O(mutations)` per block). This KIP chooses SMT for operational simplicity and proof regularity: implementation is simpler, and fixed-shape proofs are more uniform for verifier behavior and zk-circuit design. + +Implementation note: storage/IO overhead can be reduced with known sparse-tree optimization techniques (see [6]). + +## References + +[1] KIP-15: Canonical Transaction Ordering and Sequencing Commitments. +[2] Subnets sequencing commitments (original seed to this design): [https://research.kas.pa/t/subnets-sequencing-commitments/274](https://research.kas.pa/t/subnets-sequencing-commitments/274) +[3] vProgs yellow paper: [https://github.com/kaspanet/research/blob/main/vProgs/vProgs_yellow_paper.pdf](https://github.com/kaspanet/research/blob/main/vProgs/vProgs_yellow_paper.pdf) +[4] On the design of based zk-rollups over Kaspa's UTXO-based DAG consensus: [https://research.kas.pa/t/on-the-design-of-based-zk-rollups-over-kaspas-utxo-based-dag-consensus/208](https://research.kas.pa/t/on-the-design-of-based-zk-rollups-over-kaspas-utxo-based-dag-consensus/208) +[5] L1/L2 canonical bridge entry/exit mechanism: [https://research.kas.pa/t/l1-l2-canonical-bridge-entry-exit-mechanism/258](https://research.kas.pa/t/l1-l2-canonical-bridge-entry-exit-mechanism/258) +[6] Optimizing Sparse Merkle Trees: [https://ethresear.ch/t/optimizing-sparse-merkle-trees/3751](https://ethresear.ch/t/optimizing-sparse-merkle-trees/3751) From e7dc5d4a01d1a3dd5cc34c9b389b0414602b809a Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Tue, 24 Feb 2026 23:02:52 +0000 Subject: [PATCH 2/5] diagram --- kip-0021.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/kip-0021.md b/kip-0021.md index 435b77b..abc23c7 100644 --- a/kip-0021.md +++ b/kip-0021.md @@ -65,6 +65,35 @@ The shortest way to think about the construction is: This yields one header commitment for consensus, while preserving lane-local witnesses for `O(activity)` proving between anchors. +The following diagram illustrates the underlying proving scheme: + +``` +Selected-parent SeqCommit chain (global, deep): + + SeqCommit(B0) -> SeqCommit(B1) -> SeqCommit(B2) -> SeqCommit(B3) -> ... -> SeqCommit(BN) + | | | | | + v v v v v + ActiveLanes0 ActiveLanes1 ActiveLanes2 ActiveLanes3 ActiveLanesN + +For one target lane (lane = L), proofs only touch the two anchors and lane-local transition: + + anchor start anchor end + | | + v v + include lane_key(L) under ActiveLanes0 include lane_key(L) under ActiveLanesN + | ^ + | | + +------------ compressed lane-local tip transition ---------------+ + lane_tip(L, B0) => ... lane activity ... => lane_tip(L, BN) +``` + +Interpretation: + +- The global chain remains linear and deep. +- Lane proving uses two global anchors plus a lane-local compressed transition. +- Proof size tracks lane activity between anchors, rather than all intermediate chain blocks. + + ## Specification This specification is written in two passes so readers can first follow the state transition mechanics and then see exactly how that state is committed in the header: From 70fa11e7d1bdc563555f73cf2e415979902f6262 Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Tue, 24 Feb 2026 23:14:24 +0000 Subject: [PATCH 3/5] refs alignment --- kip-0021.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kip-0021.md b/kip-0021.md index abc23c7..fb265b3 100644 --- a/kip-0021.md +++ b/kip-0021.md @@ -688,9 +688,9 @@ Implementation note: storage/IO overhead can be reduced with known sparse-tree o ## References -[1] KIP-15: Canonical Transaction Ordering and Sequencing Commitments. -[2] Subnets sequencing commitments (original seed to this design): [https://research.kas.pa/t/subnets-sequencing-commitments/274](https://research.kas.pa/t/subnets-sequencing-commitments/274) -[3] vProgs yellow paper: [https://github.com/kaspanet/research/blob/main/vProgs/vProgs_yellow_paper.pdf](https://github.com/kaspanet/research/blob/main/vProgs/vProgs_yellow_paper.pdf) -[4] On the design of based zk-rollups over Kaspa's UTXO-based DAG consensus: [https://research.kas.pa/t/on-the-design-of-based-zk-rollups-over-kaspas-utxo-based-dag-consensus/208](https://research.kas.pa/t/on-the-design-of-based-zk-rollups-over-kaspas-utxo-based-dag-consensus/208) -[5] L1/L2 canonical bridge entry/exit mechanism: [https://research.kas.pa/t/l1-l2-canonical-bridge-entry-exit-mechanism/258](https://research.kas.pa/t/l1-l2-canonical-bridge-entry-exit-mechanism/258) -[6] Optimizing Sparse Merkle Trees: [https://ethresear.ch/t/optimizing-sparse-merkle-trees/3751](https://ethresear.ch/t/optimizing-sparse-merkle-trees/3751) +[1] KIP-15: Canonical Transaction Ordering and Sequencing Commitments. +[2] Subnets sequencing commitments (original seed to this design): [https://research.kas.pa/t/subnets-sequencing-commitments/274](https://research.kas.pa/t/subnets-sequencing-commitments/274) +[3] vProgs yellow paper: [https://github.com/kaspanet/research/blob/main/vProgs/vProgs_yellow_paper.pdf](https://github.com/kaspanet/research/blob/main/vProgs/vProgs_yellow_paper.pdf) +[4] On the design of based zk-rollups over Kaspa's UTXO-based DAG consensus: [https://research.kas.pa/t/on-the-design-of-based-zk-rollups-over-kaspas-utxo-based-dag-consensus/208](https://research.kas.pa/t/on-the-design-of-based-zk-rollups-over-kaspas-utxo-based-dag-consensus/208) +[5] L1/L2 canonical bridge entry/exit mechanism: [https://research.kas.pa/t/l1-l2-canonical-bridge-entry-exit-mechanism/258](https://research.kas.pa/t/l1-l2-canonical-bridge-entry-exit-mechanism/258) +[6] Optimizing Sparse Merkle Trees: [https://ethresear.ch/t/optimizing-sparse-merkle-trees/3751](https://ethresear.ch/t/optimizing-sparse-merkle-trees/3751) From 6740f9287098a0d43d9c1aacc01d8250b442b5a3 Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Wed, 25 Feb 2026 10:09:25 +0000 Subject: [PATCH 4/5] ibd clarification --- kip-0021.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kip-0021.md b/kip-0021.md index fb265b3..4806028 100644 --- a/kip-0021.md +++ b/kip-0021.md @@ -567,7 +567,7 @@ The heap is a cache/hint layer only; canonical correctness is defined by persist All index mutations (insert/update/delete in `LastTouchByScore`) are part of `SeqCommitDiff` and are reversible under reorg. -#### 9.4 IBD Bootstrap at Pruning Point (Normative Guidance) +#### 9.4 IBD Bootstrap at Pruning Point (Normative) IBD from the pruning point (PP) must provide enough sequencing-commitment state for a new node to continue normal chain-block processing from PP onward. @@ -583,7 +583,7 @@ From that verified PP state, a node processes subsequent chain blocks by the sam Operational note: -* Implementations should retain the PP active set analogously to PP UTXO-set retention, and when PP advances, update it by advancing over sequencing-commitment diffs from the old PP to the new PP. +* Implementations should retain the PP active set analogously to PP UTXO-set retention, and when PP advances, update it by advancing over sequencing-commitment diffs from the old PP to the new PP. This is required so IBD syncers can provide the minimum PP data listed above to syncing nodes. * Alternatively, an implementation may reconstruct the PP active set in linear time using the optional persistent witness-store structure (Section 11). ## Informative: Proving and Witness Operations @@ -621,7 +621,7 @@ The lane-diff witness size is proportional to lane activity between anchors, so Including $MergesetContextHash(B)$ in touched lane-tip updates is what makes accepting-block clock/context available on the lane-local path without forcing per-block global sequencing commitment processing. -Miner payloads remain a block-level commitment ($MinerPayloadRoot(B)$). Apps that rely on this data still track block-level witnesses across intermediate chain blocks. +Miner payloads remain a block-level commitment ( $MinerPayloadRoot(B)$ ). Apps that rely on this data still track block-level witnesses across intermediate chain blocks. #### 10.3 Reactivated Lane Proof Pattern From bd4cfe43a6035ef1c3335ff16ea1f0bd11f98cf7 Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Thu, 12 Mar 2026 20:56:13 +0000 Subject: [PATCH 5/5] refine vProg compatibility and commitment framing --- kip-0021.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/kip-0021.md b/kip-0021.md index 4806028..a245d89 100644 --- a/kip-0021.md +++ b/kip-0021.md @@ -36,7 +36,7 @@ This scheme does not assume a fixed bound on the number of active lanes. In the Requirements: -* Remain forward-compatible with a future account-level extraction granularity without replacing the core recurrence. +* Remain forward-compatible with future vProg-based lanes and richer lane-local update rules without replacing the core recurrence. ## How to Read This KIP @@ -103,7 +103,7 @@ This specification is written in two passes so readers can first follow the stat ### 1. Terminology -* Lane (`lane`): the logical application lane tracked by this commitment scheme. (In this KIP, lanes are extracted from `tx.subnetwork_id`; future KIPs may define finer-grained lane extraction such as account-level lanes.) +* Lane (`lane`): the logical application lane tracked by this commitment scheme. (In this KIP, lanes are extracted from `tx.subnetwork_id`; future KIPs may define additional lane families such as vProg-based lanes.) * Lane Tip: the current recursive hash tip for one lane. * Active Lane: a lane whose lane tip is currently present in the commitment set. * Last-Touch Blue Score: blue score of the chain block that last updated the lane tip. @@ -122,7 +122,7 @@ For this KIP, lanes are extracted from accepted transactions as follows: * This includes the selected-parent coinbase transaction, which uses the dedicated coinbase subnetwork ID. * This KIP does not merge built-in/native IDs into a single SYS lane; native, coinbase, and registry subnet IDs are mapped as distinct lanes. -This keeps the mechanism consensus-native and deterministic with current transaction structure. Future KIPs may define additional lane extraction modes (e.g., account-level lanes), while reusing the same commitment machinery. +This keeps the mechanism consensus-native and deterministic with current transaction structure. Future KIPs may define additional lane families and lane-local update rules (e.g., vProg-based CD commitments), while reusing the same outer commitment machinery. #### 2.1 Canonical Lane ID Encoding (Normative) @@ -144,7 +144,7 @@ Rationale for keying: * The committed SMT key is `lane_key(lane) = H_lane_key(lane_id_bytes(lane))` (Section 6.1). * Because key width is normalized by `H_lane_key`, zero-padding lane IDs is unnecessary. -* This also keeps lane-ID encoding decoupled from SMT keyspace layout and leaves room for future composite lane IDs. +* This also keeps lane-ID encoding decoupled from SMT keyspace layout and leaves room for future composite lane IDs, including vProg-derived lane identifiers. ### 3. Data Model @@ -661,9 +661,12 @@ This KIP is designed to remain compatible with a future full vProgs CD specifica Expected direction: -* Account-level lanes may use a richer diff-style update rule (CD-like, with transaction vertices and account vertices). +* A vProg can be modeled as a lane under this KIP, just as subnets are modeled as lanes today. +* In that setting, the outer commitment layer defined here (`ActiveLanesRoot(B)` inside `SeqCommit(B)`) already serves as the global live-vProg tree described in the vProgs DAG-root model (see [3]). +* A vProg lane tip would recurse from its previous anchor together with the newly introduced CD tips belonging to that vProg in the accepting block: concretely, the latest unproven account-state vertices written to by that vProg's transactions in the current mergeset. +* Thus this KIP already prepares a substantial subset of the vProgs commitment design: the global outer partitioning and anchoring machinery can remain the same, while the lane-local update rule becomes CD-specific. * Subnet-based lanes defined by this KIP remain unchanged, so zk provers built over the current subnet-lane logic can continue to operate as-is after a CD upgrade. -* A full CD model may allow re-anchoring to a previously proven lane/account state, instead of always falling back to `SeqCommit(parent(B))` when state is absent from the active set. +* A full CD model may also allow re-anchoring to a previously proven vProg/account commitment, instead of always falling back to `SeqCommit(parent(B))` when state is absent from the active set. This section is informative only; it does not change the normative subnet-lane rules specified above.