Skip to main content

Node and Consensus

The node knows nothing about notes or private balances. It validates ordinary UTXO rules, transparent signatures, asset rules, scripts and proofs. There is no pool opcode, no pool balance account and no scheduler for pool transactions. The pool is a set of AuthScript leaves running opcodes that POSITRONIC adds for general use.

What the contract leaf checks​

1. Select leafleaf and control blockhash to the poolcommitmentNIP-044 tree2. Check shapeinput and output counts,state and reserveoutpoints, no refsOP_TXHASH, counts3. Read stateS_old from input 0,S_new from output 0,same UNIQUE and Casset fields4. Bind dataPoseidon of thepublication; anchorfrom TXHASH 0x011fOP_POSEIDON5. Verify proofpinned vkHash,Groth16 over theordered public inputsOP_ZKVERIFYevery check must pass or the spend fails · the contract has no signing-key branch
FacilityRole in the contract
Script tree verification (NIP-044)Proves that the revealed leaf belongs to the pool commitment
OP_TXFIELD, OP_INPUTFIELDInspect the current or an indexed spent input, including its script and commitment
OP_INPUTCOUNT, OP_OUTPUTCOUNTEnforce the exact transaction shape
OP_INPUTVALUE, OP_OUTPUTVALUEBind XNA amounts and fee conservation
OP_INPUTASSETFIELD, OP_OUTPUTASSETFIELDRead the UNIQUE identity, its quantity and the state digest in the asset message
OP_OUTPUTSCRIPT, OP_OUTPUTAUTHCOMMITMENTKeep exact destinations and recreate the state under the same commitment
OP_TXHASH (NIP-042)Authenticate the outpoints, forbid reference inputs and compute the anchor
OP_CAT, OP_SPLIT, size and equalityBuild and validate canonical byte layouts
OP_POSEIDONReproduce the publication hash and turn the transaction hash into a field element
OP_ZKVERIFYVerify the proof against the pinned verification key and the ordered public inputs

The reserve guard is a separate script. It requires the transaction to spend the state under the pool commitment, keep the UNIQUE identity in output 0 and recreate the state under the same commitment. The state leaf in turn authenticates the exact reserve outpoint, script and value. Neither one is an independent permission to move funds.

Each leaf stays within 10,000 bytes, the script tree limit (NIP-044), and 512 counted opcodes, the AuthScript execution budget (NIP-046).

OP_ZKVERIFY​

OP_ZKVERIFY (0xc3) verifies a Groth16 proof over the BN254 curve. It runs only inside AuthScript, including script tree leaves.

proof vk input_1 … input_k k profile OP_ZKVERIFY → true / false
ItemRule
ProfileOne byte. 01 is Groth16. 02 is Groth16 with a public tree transition
Public inputs1 to 16, each a canonical 32-byte big-endian BN254 scalar. k is a minimal Script number
Proof128 bytes: compressed points A (32), B (64) and C (32)
Verification keyCompressed, 232 + 32·(k+1) bytes. Invalid points, infinity and points outside the G2 subgroup are rejected
ResultAn empty proof gives false. A non-empty invalid proof fails the script
Cost280 sigops per opcode in a revealed leaf. At most four OP_ZKVERIFY per standard transaction

The script must authenticate the verification key and bind the public inputs to its statement. The pool leaves do both: each leaf embeds the SHA-256 of its form's key and computes the public inputs from the transaction itself. The node caches positive results and prepared keys.

Profile 2: public tree transitions​

Profile 1 · C4Groth16 onlyproof vk in_1 … in_k k 01 OP_ZKVERIFYGroth16 over BN254, 1 to 16 public inputstree insertions are proven inside the circuit24 proving files, about 691 MiBProfile 2 · C5Groth16 plus a public tree transitionchunk0 … chunk4 form proof vk in… k 02 OP_ZKVERIFYsame Groth16 check, and the node replaysthe public tree insertions with Poseidon24 proving files, about 167 MiBThe custody leaf pins the profile, the form and the verification key hash.A C5 proof on its own also verifies under profile 1. The immutable leaf, not the key handed to a wallet, is what enforces profile 2.

With profile 2, used by C5, the node also checks the public part of the state transition. The stack adds five transcript chunks of up to 3072 bytes each and the form number (0 to 7 for D0 to W_full):

chunk0 chunk1 chunk2 chunk3 chunk4 form proof vk public_inputs… k 02 OP_ZKVERIFY

The transcript holds the old and new 109-byte state openings, the nullifier, the new commitments in order and their public insertion paths. The checker binds the openings to S_old and S_new, binds the nullifier and the commitments to the public inputs, and verifies every tree insertion with Poseidon. For deposits it also recomputes the dep input from the amount and the commitment. The ownership and membership of the spent note stay in the Groth16 proof: their paths are not in the transcript.

FormPoseidon permutations
D0, D1203
T1336
T2534
T3732
T4930
W_partial, W_full138

Shape checks come before hashing, and a well-formed transcript reserves all its work before any hash runs, even if verification fails later. Profile 2 adds a per-script allowance of 1024 permutations for this work, which also counts against the shared transaction and block Poseidon budgets. Policy accepts 20,000 public-work units per transaction, and a block accepts 200,000.

Activation​

Everything the pool needs is active on the reset testnet from block 10 and on regtest from block 0. Nothing is scheduled on mainnet.

RuleTestnet heightRegtest override
OP_TXHASH format (NIP-042)10-txhashheight
Asset message field, OP_INPUTFIELD, Poseidon Merkle paths (NIP-043)10-assetmessageheight, -inputfieldheight, -merkleposeidonheight
AuthScript script trees (NIP-044)10-authscripttreeheight
AuthScript execution budgets (NIP-046)10-authscriptbudgetheight
OP_ZKVERIFY and Poseidon work accounting10-zkverifyheight, -poseidonworkheight
Public tree transitions (profile 2)10-zkpublictreeheight

Every public validator and miner must run a node with profile 2 before C5 spends are broadcast, because older nodes reject them. C4 pools keep profile 1 and are not affected.

getblockchaininfo reports the state of profile 2:

"zk_public_tree": {
"activation_height": 10,
"active_at_tip": true,
"active_for_next_block": true
}

The booleans evaluate the effective flags, including the features that profile 2 depends on. C5 wallets refuse to create a private identity, fund or publish unless active_for_next_block is true. The answer comes from the RPC node and does not prove that the node validates consensus.

Running a node for pool wallets​

Wallets rebuild the pool state from a fully validating node. They follow the state output from the birth transaction through the spent index.

txindex=1
spentindex=1
Who callsMethods
Scanner, read-onlygetblockhash, getbestblockhash, getblockcount, getblock, getrawtransaction, gettxout, getspentinfo
Walletvalidateaddress, decoderawtransaction, testmempoolaccept, sendrawtransaction, plus the methods the wallet already uses for its transparent coins

A node without -spentindex can still be scanned block by block from the pool birth, with one getblock per block. Never expose an unlocked node wallet or key export to a browser.

Test coverage​

  • The node's unit tests include eight public C5 fixtures with real Groth16 proofs. They cover omitting or mutating either predicate, the public fields, foreign keys, chunk encoding, activation gates and work reservation.
  • Isolated regtest batteries confirmed 1,512 pool operations across XNA and ordinary assets with 0, 2 and 8 decimals, covering all 27 Legacy, PQ and ECDSA combinations, and rejected every tested malformed transaction and block.
  • Fresh peers validated 925-block chains with 378 pool operations, independent wallets competed for the state and rebuilt after a reorganization, and scanners recovered their notes despite unreadable publications.
  • Browser and Android wallets completed deposits, assignments and withdrawals on isolated regtest with local proving and signing.

These are finite test samples, not an audit. See Security and privacy.