Smart Contracts
The POSITRONIC upgrade turns Neurai from a transaction-and-asset chain into a programmable covenant environment. It does this by adding a large set of new script opcodes, a transaction-introspection layer, reference inputs (transaction v3), and AuthScript — a new execution model that decouples authentication (who may spend) from authorization (under what conditions), and that natively supports post-quantum ML-DSA-44 signatures.
This page is the technical reference for the scripting layer. For the upgrade overview (blocks, DePIN messaging, MCP, wallets), see the POSITRONIC upgrade page.
Everything documented here as Active is implemented and gated behind consensus verification flags on testnet / regtest in the DePIN-Test branch, pending audit and a coordinated mainnet activation. Items marked Proposed are specified in NIPs but not yet present in the node code. The line is drawn explicitly in each section.
How It Fits Together
POSITRONIC scripting rests on four pillars:
- Introspection — scripts can read fields of the spending transaction (its outputs, inputs, locktime, values, asset payloads) and of the chain itself (height, median-time-past, chain id). Before POSITRONIC, a script could only validate the data handed to it; now it can constrain the transaction that spends it.
- Covenants —
OP_CHECKTEMPLATEVERIFYandOP_CHECKSIGFROMSTACK, combined withOP_CAT/OP_SPLITand 64-bit arithmetic, let an output dictate the shape of the transaction that spends it. This is the basis for vaults, congestion control, DEX sell-orders and atomic swaps. - Reference inputs (tx v3) — a new
vrefinsection lets a script read an existing UTXO without spending it, providing oracles and external state to covenants. - AuthScript & Post-Quantum — a witness-v1 execution model whose spending condition is a 32-byte commitment, with pluggable authentication: none, post-quantum (ML-DSA-44), or legacy secp256k1.
Each new opcode is gated by its own SCRIPT_VERIFY_* flag. A flag that occupies a previously unassigned opcode slot fails closed (BAD_OPCODE) on old nodes; a flag that re-purposes a NOP/reserved slot degrades to a no-op unless DISCOURAGE_UPGRADABLE_NOPS is set. This is what makes the rollout a clean soft-fork-style activation.
Opcode Catalogue
All opcodes below are Active (implemented with consensus handlers) unless noted. Stack effect notation is (before -- after), top of stack on the right.
Transaction Introspection
| Opcode | Hex | Stack effect | Description |
|---|---|---|---|
OP_TXHASH | 0xb5 | (selector -- hash) | Double-SHA256 over a bitmask-selected set of transaction fields (version, locktime, prevouts, sequences, outputs, current input). |
OP_TXFIELD | 0xb6 | (selector -- bytes) | Raw bytes of a single field of the UTXO being spent (value, AuthScript commitment, scriptPubKey). |
OP_TXLOCKTIME | 0xc5 | (-- nLockTime) | The transaction nLockTime as 4 raw bytes. |
OP_OUTPUTVALUE | 0xcc | (index -- value) | XNA value of output index. |
OP_OUTPUTSCRIPT | 0xcd | (index -- scriptPubKey) | Full scriptPubKey of output index (including any asset payload). |
OP_INPUTVALUE | 0xd6 | (index -- value) | Value of the prevout consumed by input index (NIP-024). |
OP_INPUTCOUNT | 0xd0 | (-- n) | Number of inputs (NIP-014). |
OP_OUTPUTCOUNT | 0xd1 | (-- n) | Number of outputs (NIP-014). |
OP_CHAINCONTEXT | 0xd7 | (selector -- value) | Chain-level context: height, median-time-past, or chain id (NIP-026). |
Covenants & Signatures
| Opcode | Hex | Stack effect | Description |
|---|---|---|---|
OP_CHECKTEMPLATEVERIFY | 0xb3 | (hash -- hash) | Verifies the spending transaction matches a committed template (BIP-119 style). Leaves the hash on the stack. |
OP_CHECKSIGFROMSTACK | 0xb4 | (sig msg pubkey -- bool) | Verifies a signature over an arbitrary message popped from the stack (oracles, delegation). secp256k1 or ML-DSA-44. |
OP_CHECKSIGADD | 0xde | (sig n pubkey -- n') | Accumulating multi-signature primitive; n'=n+1 on a valid signature. Accepts legacy and PQ keys (NIP-039). |
OP_CHECKSIG_ED25519 | 0xdd | (sig msg pubkey -- bool) | Strict RFC-8032 PureEd25519 verifier (NIP-035). |
Byte Manipulation & Arithmetic
| Opcode | Hex | Stack effect | Description |
|---|---|---|---|
OP_CAT | 0x7e | (a b -- a‖b) | Concatenate two byte strings (BIP-347). |
OP_SPLIT | 0xb7 | (data pos -- left right) | Split a byte string at pos. |
OP_REVERSEBYTES | 0xbc | (data -- reversed) | Reverse byte order (endianness conversion). |
OP_MUL / OP_DIV / OP_MOD | 0x95 / 0x96 / 0x97 | (a b -- result) | Re-enabled under 64-bit signed integers. |
Asset Introspection
| Opcode | Hex | Stack effect | Description |
|---|---|---|---|
OP_OUTPUTASSETFIELD | 0xce | (index selector -- field) | A field of the asset payload attached to output index. |
OP_INPUTASSETFIELD | 0xcf | (index selector -- field) | A field of the asset payload of input index's prevout. |
Reference Inputs (tx v3)
| Opcode | Hex | Stack effect | Description |
|---|---|---|---|
OP_REFINPUTCOUNT | 0xd4 | (-- n) | Number of reference inputs in vrefin. |
OP_REFINPUTFIELD | 0xd2 | (index selector -- field) | A base field of a reference input (read-only, not spent). |
OP_REFINPUTASSETFIELD | 0xd3 | (index selector -- field) | An asset field of a reference input. |
Output Authentication
| Opcode | Hex | Stack effect | Description |
|---|---|---|---|
OP_OUTPUTAUTHCOMMITMENT | 0xd5 | (index -- commitment) | The 32-byte AuthScript commitment of output index (NIP-023). |
Modern & SNARK-Friendly Hashes
| Opcode | Hex | Output | NIP |
|---|---|---|---|
OP_KECCAK256 | 0xba | 32 bytes | NIP-030 |
OP_BLAKE2B | 0xbb | 32 bytes | NIP-030 |
OP_BLAKE3 | 0xc8 | 32 bytes | NIP-034a |
OP_SHA3_256 | 0xca | 32 bytes | NIP-034a |
OP_SHA512 | 0xcb | 64 bytes | NIP-034a |
OP_POSEIDON | 0xc9 | 32 bytes | NIP-036 |
OP_CHECKMERKLEINCLUSION | 0xc1 | bool | NIP-031 |
Covenants
A covenant is a spending condition that constrains the transaction allowed to spend an output. POSITRONIC offers two complementary covenant primitives.
OP_CHECKTEMPLATEVERIFY (CTV)
What it does. OP_CHECKTEMPLATEVERIFY (0xb3) commits an output to an exact template of the transaction that may spend it: its version, locktime, input count, sequences, output set, and the index of the spending input. If the spending transaction does not hash to the committed 32-byte template, the script fails.
Characteristics.
- No signature is needed — CTV is self-verifying by consensus rule.
- The hash is not consumed on success (NOP-upgrade semantics), so scripts usually follow it with
OP_DROP. - A non-32-byte argument behaves as a NOP unless
DISCOURAGE_UPGRADABLE_NOPSrejects it as non-standard. - Maximally rigid: it fixes every output down to the satoshi. This is its strength (anti-double-spend, deterministic outcome) and its limit (no dynamic amounts on its own).
Template fields committed by the default CTV hash:
SHA256(
nVersion (4 bytes LE)
nLockTime (4 bytes LE)
inputCount (4 bytes LE)
SHA256(all sequences)
outputCount (4 bytes LE)
SHA256(all serialized outputs)
inputIndex (4 bytes LE)
)
Example — fixed three-way distribution. An output that can only ever be spent into a transaction paying 30 / 50 / 20 XNA to three predetermined recipients:
witnessScript:
<32-byte-CTV-hash>
OP_CHECKTEMPLATEVERIFY
OP_DROP
OP_TRUE
Spending it requires no key; only a transaction whose outputs hash to the committed template is valid.
OP_CHECKSIGFROMSTACK (CSFS)
What it does. OP_CHECKSIGFROMSTACK (0xb4) verifies a signature over a message that is popped from the stack rather than computed from the transaction. This breaks the assumption that a signature must commit to a specific transaction, enabling oracles and delegation.
Characteristics.
- The message can be any bytes: a price, a contract term, a commitment.
- Accepts both secp256k1 (33-byte pubkey) and ML-DSA-44 (
0x05-prefixed, 1313-byte pubkey). - The per-element size cap widens from 520 to 3072 bytes whenever CSFS is enabled, so it can carry a 2421-byte PQ signature and a 1313-byte PQ public key.
Example — oracle-gated spend. A script that only proceeds if a trusted oracle signed a particular price:
witness: [ price_bytes, oracle_sig, witnessScript ]
witnessScript:
<oracle_pubkey>
OP_CHECKSIGFROMSTACK # verifies oracle_sig over price_bytes
OP_VERIFY
... # downstream logic can now trust price_bytes
Building Templates Dynamically — CAT, SPLIT, 64-bit Math
CTV alone commits to a static template. To express partially fixed transactions (the seller's payout is fixed, the buyer's change is free), a script assembles the template at validation time:
OP_CAT(0x7e) concatenates fixed prefixes with witness-supplied output bytes.OP_SPLIT(0xb7) andOP_REVERSEBYTES(0xbc) parse and re-order serialized fields.OP_MUL/OP_DIV/OP_MODoperate on 64-bit signed integers (the old limit was 4 bytes / 32-bit), which is what makesprice × quantitychecks possible on full XNA amounts.
When SCRIPT_VERIFY_64BIT_INTEGERS is active, numeric operands widen from 4 to 8 bytes and OP_MUL/DIV/MOD are re-enabled. Introspection opcodes that return an 8-byte value (OP_OUTPUTVALUE, OP_INPUTVALUE, asset amount fields) are converted to a CScriptNum so they can feed directly into arithmetic. OP_CHAINCONTEXT requires this flag to be co-active.
Transaction v3 and Reference Inputs
POSITRONIC introduces transaction version 3 with a new vrefin section: a vector of outpoints the transaction references but does not spend.
tx v3 {
vin [ ... ] // spent inputs (consume the UTXO, require auth)
vrefin [ ... ] // reference inputs (read-only, NEW)
vout [ ... ] // outputs
}
Serialization & consensus. vrefin is serialized between vout and nLockTime and only when nVersion == 3. It is committed in the txid/wtxid and the signature hash, so tampering with a reference invalidates signatures and CTV hashes. Each referenced outpoint must exist and be unspent in the UTXO set, must not also appear in vin, and remains in the UTXO set after the transaction confirms. Multiple transactions may reference the same UTXO without conflict.
Why it matters. Reference inputs turn any existing UTXO into a portable, read-only source of state. A covenant can read an oracle's published UTXO, a price tag, or a configuration object without that UTXO being consumed — the foundation for oracles and richer DePIN state machines.
The reference-input opcode family. Three opcodes read vrefin without spending it:
OP_REFINPUTCOUNT(0xd4) —(-- n).OP_REFINPUTFIELD(0xd2) —(index selector -- field)with selectors:0x01value (8-byte LE /CScriptNumunder 64-bit)0x02AuthScript commitment (32 bytes)0x03full scriptPubKey (size-capped)
OP_REFINPUTASSETFIELD(0xd3) —(index selector -- field), with selectors0x01–0x07(name, amount, units, reissuable, has-IPFS, IPFS hash, asset type).
All three are gated by SCRIPT_VERIFY_REFINPUTS and degrade to NOPs on nodes that do not recognize the flag.
Asset & Chain Introspection
Asset fields. OP_OUTPUTASSETFIELD (0xce) and OP_INPUTASSETFIELD (0xcf) parse the Neurai asset payload embedded in a scriptPubKey and push a selected field, so a covenant does not have to hand-parse the serialized asset blob:
| Selector | Field |
|---|---|
0x01 | Asset name |
0x02 | Amount (8-byte LE / CScriptNum) |
0x03 | Units (decimals) |
0x04 | Reissuable flag |
0x05 | Has-IPFS flag |
0x06 | IPFS hash |
0x07 | Asset type |
Chain context. OP_CHAINCONTEXT (0xd7, NIP-026) exposes blockchain position to a script via a 1-byte selector: 0x01 height, 0x02 median-time-past, 0x03 chain id (0 mainnet / 1 testnet / 2 regtest). Because height and MTP change with the chain tip, the mempool re-validates any transaction whose script observed chain context on each new block.
AuthScript & Post-Quantum
AuthScript is the headline new script type. It is a witness version 1 program whose witness program is a 32-byte commitment (TX_WITNESS_V1_AUTHSCRIPT), encoded as a Bech32m address. Its purpose is to separate two planes that classic Script conflates:
- Authentication plane — who may spend, verified before the script runs, with the signature and public key kept out of the script stack.
- Script (authorization) plane — under what conditions, evaluated with only the functional arguments on the stack.
This separation is what lets enormous post-quantum keys and signatures coexist with covenant logic: a 2420-byte ML-DSA-44 signature and a 1312-byte public key would blow past the 80-byte standard stack-item limit if they were ordinary stack pushes, so AuthScript validates them outside the script entirely.
Witness Layout
witness.stack = [
auth_type, // 1 byte: 0x00 NoAuth | 0x01 PQ | 0x02 Legacy
<auth payload>, // signature + pubkey (absent when auth_type = 0x00)
arg1 ... argN, // functional arguments for the script
witnessScript // the script to execute (always last)
]
Auth Types
| Type | Byte | Authentication | Signature | Public key |
|---|---|---|---|---|
| NoAuth | 0x00 | none — the script must be self-restricting | — | — |
| Post-Quantum | 0x01 | ML-DSA-44 (FIPS 204) | 2420 + 1 hashtype = 2421 B | 0x05-prefix + 1312 = 1313 B |
| Legacy | 0x02 | secp256k1 ECDSA | ~71–73 B | compressed 33 B |
The verifier enforces type consistency: a 0x01 payload must carry a PQ key and a 0x02 payload must carry a non-PQ key. Under SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, legacy keys must be compressed.
The Commitment
The 32-byte program in the scriptPubKey (OP_1 0x20 <32 bytes>) is a tagged hash binding the version, the authentication descriptor, and the script:
commitment = TaggedHash("NeuraiAuthScript",
version_byte ‖ auth_descriptor ‖ SHA256(witnessScript))
where auth_descriptor is 0x00 for NoAuth, or auth_type ‖ Hash160(pubkey) otherwise. The verifier recomputes this from the revealed witnessScript and auth payload and checks it against the committed program before running anything. OP_OUTPUTAUTHCOMMITMENT (0xd5) lets a covenant read this commitment from an output, so one AuthScript can require its spends to flow into specific other AuthScripts.
Signature Hash
AuthScript introduces a third signature version, SIGVERSION_AUTHSCRIPT (= 2), alongside base and witness-v0. Its sighash preimage uses the literal witnessScript as the scriptCode and additionally commits the auth_type byte, so a signature made under one authentication method cannot be replayed under another.
Post-Quantum Addresses & Derivation
- Algorithm: ML-DSA-44 (FIPS 204) — private key 2560 B, public key 1312 B, signature 2420 B.
- Address format: Bech32m, witness v1, 32-byte program. HRPs are
nq(mainnet),tnq(testnet),rnq(regtest) — e.g.tnq1.... - HD derivation: PQ keys derive under purpose
100', account pathm/100'/<coin>'/0', with receiving keys atm/100'/<coin>'/0'/0/iand change at.../1/i. Coin type is1900'on mainnet and1'on testnet/regtest. The 32-byte private bytes of each derived child seed the ML-DSA-44 keygen. - Not WIF-compatible: a 2560-byte ML-DSA-44 secret has no WIF representation and no pubkey-recovery, so PQ keys cannot be swept via WIF import.
Policy Notes
The auth payload (PQ signature + key) is exempt from the 80-byte standard stack-item limit because it never enters the script stack; functional arguments remain subject to the normal limits. Mempool relay, witness relay and RBF rules were widened to accommodate the larger PQ script elements, and the wallet persists a commitment → spend-data map (auth type, witnessScript, key reference) since the 32-byte commitment alone is not enough to reconstruct how to spend an AuthScript output.
Covenant Patterns
These are reference patterns built from the primitives above. They are illustrative compositions, not canonical scripts.
Partial-Fill Sell Order (DEX)
A stateful UTXO that lets buyers purchase fractions of an asset listing without the seller re-signing each fill. The covenant enforces correct payment, correct asset delivery, and that the remainder re-locks into the same covenant.
- Opcodes:
OP_OUTPUTVALUE,OP_OUTPUTSCRIPT,OP_OUTPUTASSETFIELD,OP_MUL, plusOP_CHECKSIGon the seller-cancellation path. - Mechanism: read output 0's value and recipient (payment to seller), read output 1's asset name/amount (delivery to buyer), read output 2's script and remaining amount (the re-locked remainder), and require
payment ≥ unit_price × quantity. - Characteristics: self-enforcing (no seller interaction per fill); first-confirmed transaction wins on contention; seller keeps a privileged cancellation branch.
OP_IF
# --- public partial-fill branch ---
0 OP_OUTPUTVALUE <unit_price> <qty> OP_MUL OP_GREATERTHANOREQUAL OP_VERIFY
0 OP_OUTPUTSCRIPT <seller_spk> OP_EQUALVERIFY
1 0x01 OP_OUTPUTASSETFIELD <asset_name> OP_EQUALVERIFY
1 0x02 OP_OUTPUTASSETFIELD <qty> OP_EQUALVERIFY
2 OP_OUTPUTSCRIPT <same_covenant_spk> OP_EQUALVERIFY
2 0x02 OP_OUTPUTASSETFIELD <total> <qty> OP_SUB OP_EQUALVERIFY
OP_TRUE
OP_ELSE
# --- seller cancellation branch ---
<seller_pubkey> OP_CHECKSIG
OP_ENDIF
Atomic Swap with CTV + CAT
The seller fixes the output that pays them; the buyer supplies the output that delivers the asset to their chosen address. OP_CAT assembles the full output set at validation time, OP_SHA256 hashes it, and OP_CHECKTEMPLATEVERIFY confirms the assembled template equals the actual transaction. Net effect: a swap where the buyer cannot redirect the seller's payment, the seller cannot redirect the buyer's asset, and the whole thing is atomic.
Mixed Legacy / Post-Quantum Spends
Because authentication is per-input, a single transaction can mix a legacy ECDSA input and an AuthScript PQ input. Each input verifies under its own auth type; the transaction is atomic. This is the migration path: holders can move value from classical to post-quantum protection one input at a time.
Script Limits & Verification Flags
| Limit | Value |
|---|---|
| Default max stack element | 520 bytes |
| Widened element cap (when CSFS / Merkle-inclusion / CHECKSIGADD enabled) | 3072 bytes |
Numeric operand width (under SCRIPT_VERIFY_64BIT_INTEGERS) | 8 bytes (int64) |
| ML-DSA-44 signature / public key | 2420 B / 1312 B (+1 each serialized) |
Each feature is gated by a dedicated flag (bit position in the script verify bitfield):
| Flag | Bit | Enables |
|---|---|---|
SCRIPT_VERIFY_AUTHSCRIPT | 16 | AuthScript witness-v1 execution |
SCRIPT_VERIFY_CAT | 17 | OP_CAT |
SCRIPT_VERIFY_CHECKTEMPLATEVERIFY | 18 | OP_CHECKTEMPLATEVERIFY |
SCRIPT_VERIFY_CHECKSIGFROMSTACK | 19 | OP_CHECKSIGFROMSTACK (widens element cap) |
SCRIPT_VERIFY_TXHASH | 20 | OP_TXHASH |
SCRIPT_VERIFY_64BIT_INTEGERS | 28 | 64-bit arithmetic; OP_MUL/DIV/MOD |
SCRIPT_VERIFY_REFINPUTS | 31 | OP_REFINPUT* family |
SCRIPT_VERIFY_OUTPUTAUTHCOMMITMENT | 32 | OP_OUTPUTAUTHCOMMITMENT |
SCRIPT_VERIFY_CHAINCONTEXT | 34 | OP_CHAINCONTEXT (co-requires bit 28) |
SCRIPT_VERIFY_MERKLE_INCLUSION | 36 | OP_CHECKMERKLEINCLUSION (widens element cap) |
SCRIPT_VERIFY_POSEIDON | 38 | OP_POSEIDON |
SCRIPT_VERIFY_ED25519 | 39 | OP_CHECKSIG_ED25519 |
SCRIPT_VERIFY_CHECKSIGADD | 40 | OP_CHECKSIGADD (widens element cap) |
(Introspection, byte-op and asset-field opcodes occupy the intervening bits 21–30.)
Native Merkle Inclusion
OP_CHECKMERKLEINCLUSION (0xc1, NIP-031) verifies that a leaf belongs to a Merkle tree at a given root, with the stack effect (leaf scheme_id proof root -- bool). It supports four tree schemes:
| Scheme | Id | Node hash |
|---|---|---|
| Bitcoin/Neurai | 0x01 | double SHA-256 |
| SHA-256 plain | 0x02 | single SHA-256 |
| Keccak-256 plain | 0x03 | Keccak-256 (requires NIP-030) |
| BLAKE2b plain | 0x04 | BLAKE2b-256 (requires NIP-030) |
The proof encodes a 1-byte depth (max 32), the sibling hashes, and a direction bitmap. This gives covenants an efficient, native way to validate membership (allow-lists, airdrops, state commitments) without re-implementing the hashing in script.
Proposed (Not Yet Implemented)
The following are specified in NIPs but are not present in the node code and are documented here for forward visibility:
- Reference Scripts (NIP-015) — publish a witnessScript once in a carrier UTXO and reuse it across transactions via
vrefinand a newAUTHSCRIPT_REFwitness mode, instead of inlining the script in every spend. Depends on reference inputs (already active). - Zero-knowledge opcode family (NIP-016 / NIP-018) —
OP_ZKVERIFY,OP_ZKVERIFYREFandOP_ZKTXBINDto verify Groth16/BN254 proofs on-chain and bind them to transaction context. No verifier backend or opcode handlers exist yet.
See Also
- POSITRONIC upgrade overview
- Assets — the asset types these opcodes introspect
- Developer Hub — SDKs and libraries, including
@neuraiproject/neurai-scripts(aScriptBuilderwith these opcode constants) and@neuraiproject/neurai-sign-transaction(NoAuth / PQ / Legacy signing)