Understand/Protocol

Protocol

Why the chain is built this way. Several obvious-looking simplifications here are load-bearing, the failure modes show what happened when they were not respected.

Block rate1 / sGHOSTDAG BlockDAG
Anchor maturity~10 min600 × BPS
Anchor max age~7.5 hpruning_depth / 4
Hz / Hk~14%measured, of Kaspa
Left the pool0turnstileOut, ever

Shielded state transition

Orchard's note-commitment tree is append-only and position-dependent, so on a BlockDAG the append sequence must be a pure function of consensus order:

coinbase mint first, then accepted actions in consensus applied order
block N−1 block N block N+1 applies coinbase mint, then accepted actions coinbase commits state_root(N−1) coinbase commits state_root(N) state_root = tree anchor + nullifier accumulator + turnstile totals + burn root A block commits its parent, never itself, so there is no circularity, and the commitment carries the block's full proof-of-work.
Append order is a pure function of consensus order: coinbase mint first, then accepted actions as applied. Any other order yields a different root from identical data, which is why the tree is position-dependent and clients must never reorder blocks.

Any other order produces a different root from identical data. This is the whole argument, ordering is not an implementation detail here, it is the data.

Each block's coinbase commits the parent's shielded state root:

state_root = tree anchor  +  nullifier accumulator  +  turnstile totals  +  burn root

Because that commitment is inside the coinbase, it is PoW-anchored: a state divergence surfaces as a block rejection rather than silent drift. There is no circularity, a block commits its parent, never itself.

Supply integrity

Per bundle (cryptographic)
the binding signature verifies under bvk = Σ cv_net − commit(value_balance, 0), which is valid only if the bundle's true net value equals its declared public value_balance. Only the coinbase may mint
Per block (public integers)
pool = cumulative_coinbase − cumulative_fees − burns, which may never go negative (PoolUnderflow)

Both are auditable from a plain node: GetShieldedSupply, or /info/shielded over the REST API. Today turnstileOut is zero, nothing has ever left the pool.

What this does not do. The turnstile bounds what can leave; it never measures what is inside. See known weaknesses for why that matters.

Anchors and liveness

A spend proves a Merkle path to a past root. It is accepted only if that root is known, its producing block is a selected-chain ancestor, and it sits inside the maturity window:

Minimum maturity~10 min 600 × BPS
Maximum age~7.5 h pruning_depth / 4
  • Multi-producer anchors. Several blocks can produce the same root, so all producers are retained and any matured canonical one suffices. Activated at DAA 757,000.
  • Drop, do not disqualify. A transaction whose anchor is stale or whose nullifier is already claimed is dropped from the state transition, not fatal to the block. Liveness survives a builder that mines a stale transaction.
  • Applied fees only. The coinbase must be built from fees that were actually applied, otherwise a dropped spend's fee creates unbacked supply. This was a real bug; see failure modes.

Consequence for wallets: a note is not spendable the moment it arrives, and the mempool guard is relay policy, best-effort and one-directional. A builder can still mine a transaction that consensus then drops, harmless, funds intact, nullifiers not recorded, fee not collected.

Trusting peer history

Backfill is cryptographic, not reputational. The frontier is a pure function of the leaf sequence, so replaying every cmx from genesis must reproduce exactly the frontier a node already holds, a value it never learned from the serving peer. A peer that fabricates, omits, reorders or truncates cannot match it.

This is why a fresh node can take shielded history from any peer without trusting it, and why a pruned node can serve complete wallet balances from its first sync onward. The verify pass sets the history_complete flag; an offline import does not set it.

Merged-mining economics

Both chains use identical PoW, so hashrate = 2 × difficulty × BPS. Kaspa mints 10 blocks/s to ZKas's 1, which produces one governing relationship:

ZKas per-block work > Kaspa per-block work   ⟺   Hz / Hk > 10%

Measured: ZKas ≈ 50 PH/s at ~1.0 blocks/s; Kaspa ≈ 357 PH/s ⇒ ratio ≈ 14%, so a ZKas block currently costs ~1.4× a Kaspa block. That is not an anomaly, it is what a 10:1 block-rate ratio does at a 14% hashrate share.

Kaspa-visibility of ZKas blocks

coverage = min(1, 10 × Hz / Hk)

A ZKas block lands in Kaspa's DAG carrying its tag only if it also cleared Kaspa's target. At 14% coverage is 100%; if Kaspa doubles while ZKas stays flat it falls to ~70%. This is a data-availability property for anything that anchors to Kaspa, and it degrades silently.

What adding hashrate actually does

ScenarioEffect on KAS incomeEffect on ZKAS income
An existing Kaspa pool enables merge-mining0.0%, no hashrate joined Kaspaevery existing ZKas miner is diluted by the new share
New hashrate joins both chainsdiluted in proportiondiluted in proportion
A large merge-miner leavesunchangedremaining miners gain, after a ~44 min retarget lag
  • Total ZKAS/day never changes. Emission is fixed per block and the DAA restores 1 BPS, so new hashrate dilutes, it never inflates.
  • There is no shutdown price. Marginal cost of the ZKas leg is zero, so difficulty can rise arbitrarily without making any merge-miner unprofitable. The only exit trigger is ZKAS/day falling below a fixed ops cost.
  • Exits hurt more than entries. Difficulty retargets over ~44 minutes, so an abrupt exit strands the chain at too-high difficulty and stretches block times until it corrects.
Security, stated honestly. 51% of ZKas ≈ 25 PH/s ≈ 7% of Kaspa's hashrate. "Inherits Kaspa's security" is only true in proportion to our share, an attacker never needs to beat Kaspa, only to redirect a slice of it, and that slice shrinks as Kaspa grows.

Kaspa interface (Toccata)

Relevant if you want Kaspa-side logic to verify ZKas state. Live on Kaspa mainnet since DAA 474,165,565.

OpZkPrecompile 0xa6
Groth16/BN254 at 140,000 grams, or risc0 succinct STARK at 250,000. Block compute limit is 500,000 ⇒ ~3 Groth16 verifies per Kaspa block, so proofs must batch. This ceiling does not bind the merged-mining tag reader — see below
OpChainblockSeqCommit 0xd4
returns a chain block's sequencing commitment; selected chain only, within ~12 hours
KIP-20 / KIP-17
covenant lineage ids; transaction introspection
KIP-21
commits every mergeset block's raw coinbase payload plus hash and blue_work under SeqCommit(B), this is how a covenant reaches a merged-mining tag. Leaf = H(block_hash ‖ blue_work ‖ H(coinbase_payload))
OpBlake3WithKey 0xda
keyed hashing
Not available
no opcode reads a coinbase directly; no Halo2 verification, so Orchard proofs must be wrapped into Groth16 or risc0; risc0 pinned to 3.0.4
The link is one-directional. A Kaspa covenant can verify ZKas state but can never gate a ZKas note, notes have no script and their only spend authority is one key. Only the Kaspa-side leg can be made conditional; a conditional ZKas-side release needs ZKas-side crypto or a bonded operator. The tag also proves publication with PoW weight behind it, not that the referenced block is valid or canonical.
Reading the tag is free of that ceiling. Verifying "Kaspa block B′ with blue_work W had coinbase payload P, P tags ZKas block H, and P pays miner M" is a KIP-21 leaf, a Merkle path, OpChainblockSeqCommit, then byte-offset parsing — pure hashing, no OpZkPrecompile. Only statements about ZKas internal state spend the budget. Two facts sit in those same bytes: the miner’s payout script is at a fixed offset immediately before the tag, so every tag is attributable and blue_work-weighted; and the magic must appear exactly once, so the Kaspa→ZKas map is 1:1 and non-repudiable. The tag still proves publication, never validity.

This asymmetry is why rich shared-state DeFi has to live beside the chain rather than on it, see what is not possible.

Settlement on Kaspa

Rich shared-state applications cannot live on ZKas, no VM, one asset. They can live beside it, on a Kaspa-side rollup with a wrapped representation, where privacy applies at entry and exit rather than continuously. That path is research-stage but it is not hypothetical:

Settlement proofs
End-to-end suite passes 4/4 against real Groth16, no Docker, no hosted prover. Proving takes 24.8 s for a 222,668-byte seal at roughly 26.2M cycles; the whole suite runs in 256.94 s.
What Kaspa can check
OpZkPrecompile verifies Groth16/BN254 at 140,000 grams against a 500,000 block compute limit, about three verifies per Kaspa block, so proofs must batch. There is no Halo2 verification, so Orchard proofs have to be wrapped into Groth16 or risc0.
What it cannot do
Gate a ZKas note. The link is one-directional: a covenant verifies ZKas state, it never authorizes a ZKas spend. Conditional release on the ZKas side needs a single-bundle construction or a bonded operator.
Peg status
The KAS↔ZKAS peg is built and gated off. Kaspa cannot mint or track a pegged token, which is the external blocker, not our code.

Privacy model

HiddenNot hidden
Amounts, addresses, memo contentsThat a transaction occurred, when, and its action count
Sender identity, no "from" field existsCoinbase note recipients and values, which are publicly recomputable
Wallet balances and relationshipsWhich miner produced each block, via the merged-mining tag
Which note a spend consumed, full-tree membershipNetwork-level metadata, unless you use Tor

Anonymity is statistical, not per-transaction. Membership is maximal, a spend proves inclusion in the entire tree, and privacy is mandatory, so nobody is flagged for using it. But the cryptography hides contents, not patterns: with low concurrent volume, timing correlation can still link activity, and coinbase leaves add tree size without adding confusion because anyone can recompute them. Quantified on live mainnet: 6,184,135 notes, ~91% coinbase mints, only ~5.5% ever spent — so a non-mint spend hides in roughly 547,000 user notes, an order of magnitude below the headline figure, and the gap widens every block because the set grows from mining rather than usage. Quote the real number, not the tree size.

Not post-quantum. Orchard/Halo2 is not PQ, and encrypted notes are subject to harvest-now-decrypt-later.

Disclosure is neither scoped nor revocable. With no ZIP-32, one seed means one viewing key for everything it ever received. Any disclosure, proof of reserves, an auditor, a registry address, must use a dedicated seed.
Verified against zkas-rusty at zkas-v1.0.9. If this page contradicts the code, the code is right. Report an error.