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
| Facility | Role in the contract |
|---|---|
| Script tree verification (NIP-044) | Proves that the revealed leaf belongs to the pool commitment |
OP_TXFIELD, OP_INPUTFIELD | Inspect the current or an indexed spent input, including its script and commitment |
OP_INPUTCOUNT, OP_OUTPUTCOUNT | Enforce the exact transaction shape |
OP_INPUTVALUE, OP_OUTPUTVALUE | Bind XNA amounts and fee conservation |
OP_INPUTASSETFIELD, OP_OUTPUTASSETFIELD | Read the UNIQUE identity, its quantity and the state digest in the asset message |
OP_OUTPUTSCRIPT, OP_OUTPUTAUTHCOMMITMENT | Keep 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 equality | Build and validate canonical byte layouts |
OP_POSEIDON | Reproduce the publication hash and turn the transaction hash into a field element |
OP_ZKVERIFY | Verify 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
| Item | Rule |
|---|---|
| Profile | One byte. 01 is Groth16. 02 is Groth16 with a public tree transition |
| Public inputs | 1 to 16, each a canonical 32-byte big-endian BN254 scalar. k is a minimal Script number |
| Proof | 128 bytes: compressed points A (32), B (64) and C (32) |
| Verification key | Compressed, 232 + 32·(k+1) bytes. Invalid points, infinity and points outside the G2 subgroup are rejected |
| Result | An empty proof gives false. A non-empty invalid proof fails the script |
| Cost | 280 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
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.
| Form | Poseidon permutations |
|---|---|
D0, D1 | 203 |
T1 | 336 |
T2 | 534 |
T3 | 732 |
T4 | 930 |
W_partial, W_full | 138 |
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.
| Rule | Testnet height | Regtest 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 accounting | 10 | -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 calls | Methods |
|---|---|
| Scanner, read-only | getblockhash, getbestblockhash, getblockcount, getblock, getrawtransaction, gettxout, getspentinfo |
| Wallet | validateaddress, 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.