Understand/Reference

Reference

Exact values, byte layouts and endpoint shapes. Descriptive only — read from source at zkas-v1.0.9, with live figures marked.

Transaction version2shielded
Per action3,156 B884 wire + 2,272 proof
Bundle budget124,744 B38 actions
Decimals810⁸ sompi = 1 ZKAS
Address payload43 Bversion 9, bech32

Addresses, keys and units

Address encoding
bech32; HRP zkas: mainnet · zkastest: testnet · zkasdev: devnet · zkassim: simnet
Version byte
Version::ShieldedOrchard = 9
Raw payload
43 bytes = diversifier(11) ‖ pk_d(32)
Script class
ShieldedOrchard => ScriptClass::NonStandard — a 43-byte script is a raw Orchard recipient
Key tree
32-byte seed → spending key → FVK → IVK, external scope only
Diversified addresses
supported by Orchard, but no shipped component derives index > 0. One wallet, one address
Mnemonics
none. No BIP-39 anywhere. Key material is a raw 32-byte seed, hex-encoded
Units
SOMPI_PER_ZKAS = 100_000_000 — 8 decimals

What each key reveals. The IVK decrypts every incoming note of that seed, irrevocably once shared. The OVK, if attached at send, makes outgoing details recoverable by you. Message signing proves address control without spending but discloses the FVK — use a dedicated seed for it.

Emission and supply

Initial subsidy60 ZKAS / block at 1 BPS
Miner receives57 ZKAS after the 5% dev fee
Halvingevery 3 months
Tail6 → 0.6 ZKAS/s, step at month 24
Max supplynone perpetual tail
Curve total~703M ZKAS

The dev fee is skimmed from the subsidy, never from transaction fees — value is conserved, and it is appended as one extra coinbase output, paid every 1000 blocks as a shielded note.

AgeCumulative supplyAgeCumulative supply
3 months~351M3 years~873M
6 months~527M5 years~911M
1 year~665M10 years~1.005B
2 years~854M20 years~1.195B

Long-run inflation is ~2.2% at tail onset (~year 2), decaying toward ~1% and below over decades. Emission is heavily front-loaded: roughly half of all curve issuance happens in the first quarter. The tail is perpetual by design — it funds miner security forever rather than relying on fee revenue.

Consensus parameters

ParameterValue
target_time_per_block1000 ms (1 BPS)
Coinbase maturity100 blocks (COINBASE_MATURITY_SECONDS = 100)
shielded_anchor_depth600 × BPS — ~10 min; minimum anchor maturity before a note is spendable
max_shielded_anchor_agepruning_depth / 4 — ~7.5 h; older anchors are dropped
Standard transaction mass cap500,000 transient mass at 4 per byte ⇒ bundle ≤ 124,744 bytes
Storage masszero for shielded transactions
Subnetwork lanes50 per block, gas 1e9 per lane; selection freezes once full
Difficulty window2641 s ÷ 4 = 661 samples (min 150) ⇒ retarget over ~44 min
Dev feeZKAS_DEV_FEE_PERMILLE = 50 (5%), consensus-enforced, payout interval 1000 blocks
Activationsmerged mining + Toccata from genesis; multi-producer anchors and dev-fee accrual at DAA 757,000
Wire identitygenesis hash is the network_domain in every sighash — a signature from another chain can never replay here

Transaction format and sighash

A shielded transaction is version 2, non-coinbase, with no transparent inputs or outputs. The Orchard bundle travels in the transaction payload.

QuantityValue
Max actions per bundle512 (MAX_ACTIONS_PER_BUNDLE); 38 in a standard transaction
Per-action wire size884 B — nullifier 32, rk 32, cmx 32, cv_net 32, epk 32, enc_ciphertext 580, out_ciphertext 80, spend_auth_sig 64
Bundle header117 B
Proof size2720 + 2272·n
Total bundlewire_len(n) = 2837 + 3156·n
enc_ciphertext (580 B)version, diversifier 11, value 8, rseed 32, memo 512, tag 16 — read with the IVK
out_ciphertext (80 B)recoverable only if an OVK was attached at send
Burn flagBUNDLE_FLAG_BURN = 0b100 gates a trailing value(8) ‖ kaspa_recipient(32)

What the signatures commit to

sighash = BLAKE2b(
    network_domain (genesis hash, 32B)    # binds to this chain
  ‖ flags ‖ value_balance ‖ anchor ‖ action_count
  ‖ for each action:
       nullifier ‖ rk ‖ cmx ‖ cv_net ‖ epk
     ‖ enc_ciphertext ‖ out_ciphertext    # <- the memo IS committed
       # spend_auth_sig excluded: it signs this digest
  ‖ len(tx_context) ‖ tx_context )

tx_context = version ‖ subnetwork_id ‖ lock_time ‖ gas    # 38 bytes
Signed — inside the sighash network_domain anchor value_balance flags per action nullifier rk cmx cv_net epk enc_ciphertext the 512-byte memo lives here out_ciphertext tx_context 38 bytes Excluded, by design spend_auth_sig it signs this digest the Halo 2 proof bound via the action fields tx.payload contains the bundle — circular
Because enc_ciphertext is inside the digest, memo bytes are already authenticated by both the spend-auth and binding signatures — which is what makes publicly indexable app data possible with no consensus change.
The payload is deliberately outside the signature — it contains the bundle, so hashing it would be circular. The consequence is the useful one: the memo and both ciphertexts are authenticated, while arbitrary payload bytes would not be. Decoding also rejects trailing bytes, so the payload must be exactly the canonical bundle. This is what makes authenticated public app data possible with no fork.

Timelocks. The transaction-level absolute lock_time is real and bound into the sighash, so coarse timeouts work. A per-note relative timelock does not exist and is not a small change — it needs a new note field, a changed note commitment, a new circuit public input and regenerated keys.

Fees are byte-proportional, therefore per-action: 1–2 actions = 0.0186 ZKAS, 38 actions = 0.2458 ZKAS. A flat fee below the node minimum is rejected by relay; the wallet raises it automatically.

Node RPC (shielded)

gRPC. Four shielded methods on top of the inherited Kaspa surface — together the whole trustless client interface: mirror the tree, place a birthday, audit supply, read mining income.

GetShieldedBlocks
RequeststartHash (exclusive chain-block cursor), limit, metadataOnlyResponseblocks[] — per chain block: hash, blueScore, daaScore, timestamp, coinbaseTxid, coinbaseOutputs[] (scriptPublicKey, value, commitment), acceptedActions[] with parallel acceptedTxids[] in applied order; plus reorged (cursor orphaned → rescan) and sinkBlueScore
GetShieldedTreeState
Request—ResponseblockHash, daaScore, size, leaf, ommers[], historyFromDaaScore, historyComplete
GetShieldedSupply
Request—ResponsecumulativeCoinbase, cumulativeFees, cumulativeBurns, poolValue, noteCount, as of a named chain block
GetShieldedCoinbaseRewards
Requestrecipients[] (43-byte hex), startHash, limit = blocks scannedResponserewards[] keyed by (coinbaseTxid, outputIndex), plus scannedBlocks, nextCursor
Never order blocks client-side. Ingesting in DAG or arrival order instead of consensus order corrupts the wallet's note-commitment tree — the tree is position-dependent, so order is the data. Consume the canonical stream and honour reorged.
Two fields decide whether a balance is trustworthy. historyFromDaaScore is the oldest height this node can serve; a wallet whose birthday is below it cannot be fully scanned here and any balance it reports is a lower bound. historyComplete says the node can enumerate to genesis. Check both before showing a number to a user.

Coinbase mints are public: recipient and value are visible with no viewing key. A block's coinbase pays its mergeset, not its finder.

Public REST API

Unauthenticated, read-only, over the live mainnet node. Base https://explorer.zkas.info/api. Useful for dashboards, indexers and monitoring without running anything.

/info/blockdag
blockCount, headerCount, difficulty, networkName, tipHashes, pruningPointHash, pastMedianTime
/info/pulse
measured block rate: bps15m, averageBlockTime15m, blocks15m, 15-second blockBins and difficultyBins
/info/shielded
turnstile and tree: anchor, noteCount, nullifierCount, turnstileIn, turnstileOut, emissionPerBlock
/info/coinsupply
circulatingSupply in sompi, emissionModel, maxSupply (null)
/info/blockreward · /info/halving
current subsidy; next reduction amount, DAA score, ETA, percent
/info/miners
per-pool attribution over the last hour: blocks, share, derived hashrate, source, distinctAddresses
/info/merged-mining
peers observed merge-mining, with the Kaspa node each is paired to
/info/network · /info/nodes · /info/relay
peer counts, masked peer subnets, relay credit
/info/work-history · /info/reorgs
difficulty over time; observed reorg depths
/info/fee-estimate
priorityBucket / normalBuckets / lowBuckets with feerate and ETA
/blocks/recent · /blocks/:id
recent blocks with coinbase outputs; one block by hash
/transactions/:id · /transactions/search · /transactions/count
transaction lookup
/health
liveness
B=https://explorer.zkas.info/api

# tip, difficulty, pruning point
curl -s $B/info/blockdag

# network hashrate in PH/s, from MEASURED bps
D=$(curl -s $B/info/blockdag | jq .difficulty)
P=$(curl -s $B/info/pulse    | jq .bps15m)
echo "$D $P" | awk '{printf "%.1f PH/s\n", 2*$1*$2/1e15}'

# shielded pool, tree size, current anchor
curl -s $B/info/shielded

# who mined the last hour, with hashrate share
curl -s $B/info/miners | jq '.miners[] | {label:.key, blocks, share}'
Do not derive hashrate from the nominal 1 BPS. Use difficulty with the measured bps15m: hashrate = 2 × difficulty × BPS. The chain does not run at exactly 1.000 blocks/second.
GET /info/shielded
{ "anchor": "3d8ee39caf1cd16dd8dd…88bd6fc9",
  "blueScore": "5512774", "emissionPerBlock": 37.79763149,
  "noteCount": 5752657, "nullifierCount": 139889,
  "turnstileIn": "27029204918145767", "turnstileOut": "0" }

GET /info/pulse
{ "bps15m": 0.9777777777777777, "blocks15m": 880,
  "averageBlockTime15m": 1.0227272727272727, "binSeconds": 15 }

GET /info/halving
{ "currentAmount": 37.79763149, "nextHalvingAmount": 35.67621345,
  "reductionPercent": 5.61, "blocksRemaining": 387168,
  "nextHalvingDate": "in ~4 days", "atTailFloor": false }

Live, September 2026. turnstileOut is 0 and maxSupply is null: nothing has ever left the pool, and emission has a perpetual tail rather than a cap.

Address routes (/addresses/:address/…) are inherited from the Kaspa explorer API and are not meaningful on mainnet — there are no transparent addresses to query.

Wallet API

The full zkas-walletd HTTP surface, its flags, the /api/status field semantics and the payout sizing model live on their own page: Wallets & walletd → endpoints.

Versions and compatibility

ComponentCurrentNotes
Node + walletdzkas-v1.0.9one release carries linux-amd64, osx-arm64, osx-x86_64 and win64 builds
Transaction version2 shielded1 is the Toccata baseline; the shielded bundle only exists on version 2
P2P default (≤ v1.0.7)16111 listencollided with Kaspa; current releases listen on 16811
Forward-compatibility rule for clients. Several /api/status fields — spend_ready, blocks_behind, warming, loading, missing_history, the pending_* pair — are serde(default), so an older daemon simply omits them. Treat absent as the safe value and never hard-fail on an unknown field.

Glossary

note
a shielded output holding a value and an owner; the unit of value, and the unit that costs proving time
cmx
note commitment — the tree leaf; hides the note's contents
nullifier
published when a note is spent; prevents double-spending without revealing which note
anchor
a past commitment-tree root a spend proves membership against
frontier
the ~32-node summary of the tree a node keeps instead of the whole tree
witness
the Merkle path from a note to an anchor. Wallets hold their own
FVK / IVK / OVK
full / incoming / outgoing viewing key
turnstile
the public integer ledger bounding value entering and leaving the pool
mergeset
the parallel DAG blocks a chain block merges — and what its coinbase pays
aux-PoW
merged mining: proving work for this chain via a parent chain's block header
sompi
the base unit; 108 sompi = 1 ZKAS
transient mass
Kaspa's size-based transaction cost, 4 per byte, capped at 500,000 for a standard transaction
Toccata
the Kaspa upgrade that shipped covenants and zk verification opcodes — see the Kaspa interface
Verified against zkas-rusty at zkas-v1.0.9. Byte layouts, endpoint shapes and parameters are read from source; figures marked measured come from the live mainnet node. If this page contradicts the code, the code is right — report it.