``` KIP: 17 Layer: Consensus, Script Engine Title: Covenants and Improved Scripting Capabilities Authors: Ori Newman Status: Implemented and activated in TN10 Updated: 2026-05-31 ``` ## Abstract This proposal is a continuation of KIP 10 [1], introducing full covenant support to Kaspa by extending the scripting language with introspection opcodes to all transaction fields, along with byte-string manipulation primitives. These additions enable scripts to inspect and constrain properties of the spending transaction, allowing validation of stateful transitions encoded implicitly or explicitly in UTXOs. ## Specification ### 1. New Opcodes The following new opcodes are introduced to enhance script functionality: > Unless specified otherwise, numeric return values are pushed as script numbers. Byte fields such as transaction IDs, script public keys, subnet IDs, hashes, and sequences are pushed as raw bytes. #### Transaction Level Introspection Opcodes: 1. `OpTxVersion` (0xb2): Returns the version of the transaction. 2. `OpTxLockTime` (0xb5): Returns the locktime field of the transaction. 3. `OpTxSubnetId` (0xb6): Returns the subnetwork ID of the transaction as raw bytes. 4. `OpTxGas` (0xb7): Returns the gas field of the transaction. 5. `OpTxPayloadSubstr(start, end)` (0xb8): Returns the payload substring of the transaction from `start` to `end`. Returns error for invalid ranges, or when `end-start > MAX_SCRIPT_ELEMENT_SIZE`. #### Input/Output Introspection Opcodes: 1. `OpOutpointTxId(idx)` (0xba): Returns the transaction ID of the outpoint of input `idx`. 2. `OpOutpointIndex(idx)` (0xbb): Returns the index of the outpoint of input `idx`. 3. `OpTxInputScriptSigSubstr(idx, start, end)` (0xbc): Returns the script signature substring of input `idx` from `start` to `end`. Returns error for invalid ranges, or when `end-start > MAX_SCRIPT_ELEMENT_SIZE`. 4. `OpTxInputSeq(idx)` (0xbd): Returns the sequence field of input `idx` as 8 little-endian bytes. 5. `OpTxInputDaaScore(idx)` (0xc0): Returns the DAA score of the block that created the UTXO spent by input `idx`. 6. `OpTxInputIsCoinbase(idx)` (0xc1): Returns whether the UTXO spent by input `idx` is a coinbase. 7. `OpTxPayloadLen` (0xc4): Returns the payload length of the transaction. 8. `OpTxInputSpkLen(idx)` (0xc5): Returns the script public key length of the UTXO spent by input `idx`. 9. `OpTxInputSpkSubstr(idx, start, end)` (0xc6): Returns the script public key substring of the UTXO spent by input `idx` from `start` to `end`. Returns error for invalid ranges, or when `end-start > MAX_SCRIPT_ELEMENT_SIZE`. 10. `OpTxOutputSpkLen(idx)` (0xc7): Returns the script public key length of output `idx`. 11. `OpTxOutputSpkSubstr(idx, start, end)` (0xc8): Returns the script public key substring of output `idx` from `start` to `end`. Returns error for invalid ranges, or when `end-start > MAX_SCRIPT_ELEMENT_SIZE`. 12. `OpTxInputScriptSigLen(idx)` (0xc9): Returns the script signature length of input `idx`. #### Existing Introspection Opcodes: The following KIP 10 opcodes were already active before Toccata and are listed here to show the full introspection surface: 1. `OpTxInputCount` (0xb3): Returns the number of inputs in the transaction. 2. `OpTxOutputCount` (0xb4): Returns the number of outputs in the transaction. 3. `OpTxInputIndex` (0xb9): Returns the index of the current input being evaluated. 4. `OpTxInputAmount(idx)` (0xbe): Returns the amount (value) of the UTXO spent by input `idx`. 5. `OpTxInputSpk(idx)` (0xbf): Returns the serialized script public key of the UTXO spent by input `idx`. 6. `OpTxOutputAmount(idx)` (0xc2): Returns the amount (value) of output `idx`. 7. `OpTxOutputSpk(idx)` (0xc3): Returns the serialized script public key of output `idx`. ##### Note on Arguments and Substring Opcodes Index and range arguments (`idx`, `start`, and `end`) are provided as script numbers and MUST be non-negative. On all substring related opcodes, byte positions are zero-based, `start` is inclusive, and `end` is exclusive. Substring opcodes fail if `start > end` or if the requested byte range is out of bounds. ##### MAX_SCRIPT_ELEMENT_SIZE Post-Toccata activation, `MAX_SCRIPT_ELEMENT_SIZE = 1,000,000`. Pre-Toccata, `MAX_SCRIPT_ELEMENT_SIZE = 520`. Substring and concatenation opcodes are bounded by this value. #### Signature Verification Opcodes: 1. `OpCheckSigFromStack(signature, msg_hash, pubkey)` (0xd7): Verifies a Schnorr signature against a 32-byte `msg_hash` and pushes whether it is valid. 2. `OpCheckSigFromStackECDSA(signature, msg_hash, pubkey)` (0xd8): Verifies an ECDSA signature against a 32-byte `msg_hash` and pushes whether it is valid. #### Hash Opcodes: 1. `OpBlake2bWithKey(data, key)` (0xa7): Returns the BLAKE2b-256 hash of `data` keyed with `key`. The key length MUST be at most 64 bytes. 2. `OpBlake3(data)` (0xd9): Returns the BLAKE3 hash of `data`. 3. `OpBlake3WithKey(data, key)` (0xda): Returns the BLAKE3 keyed hash of `data` using `key`. The key length MUST be exactly 32 bytes. #### Other Opcodes: 1. `OpCat(str1, str2)` (0x7e): Concatenates two byte strings. Returns error if the resulting string exceeds `MAX_SCRIPT_ELEMENT_SIZE`. 2. `OpSubstr(str, start, end)` (0x7f): Returns the substring of a byte string (from start (inclusive) until end (exclusive)). Returns error for invalid ranges, or when `end-start > MAX_SCRIPT_ELEMENT_SIZE`. 3. `OpInvert(str)` (0x83): Returns the bitwise NOT of a byte string. 4. `OpAnd(str1, str2)` (0x84): Returns the bitwise AND of two byte strings. Returns error if the two strings are of different lengths. 5. `OpOr(str1, str2)` (0x85): Returns the bitwise OR of two byte strings. Returns error if the two strings are of different lengths. 6. `OpXor(str1, str2)` (0x86): Returns the bitwise XOR of two byte strings. Returns error if the two strings are of different lengths. 7. `OpMul` (0x95): Returns the product of two numbers. Returns error on overflow. 8. `OpDiv` (0x96): Returns the quotient of two numbers. Returns error on division by zero, or for signed overflow (`i64::MIN / -1`). 9. `OpMod` (0x97): Returns the remainder of two numbers. Returns error on modulo by zero, or for signed overflow (`i64::MIN mod -1`). 10. `OpZkPrecompile` (0xa6): Verifies a zero-knowledge proof (KIP-16). #### Numeric Conversion Opcodes: 1. `OpNum2Bin(num, size)` (0xcd): Encodes `num` as a signed script number occupying exactly `size` bytes. The target size MUST be at most 8 bytes and large enough to represent `num`. 2. `OpBin2Num(bytes)` (0xce): Interprets `bytes` as a signed script number and pushes its minimal numeric encoding. The input MUST fit within the 8-byte script number range. Disabled opcode slots: - `OpLeft` (0x80), `OpRight` (0x81), `OpLShift` (0x98), and `OpRShift` (0x99) remain disabled and are not activated by this KIP. ### 2. Activation The features introduced in this KIP are activated as part of the **Toccata hard fork**. Activation is controlled by the `toccata_activation` DAA score parameter: 1. **Prior to activation:** - New opcodes are treated as invalid and will cause script execution to fail - `MAX_SCRIPT_ELEMENT_SIZE = 520` - `MAX_SCRIPTS_SIZE = 10,000` - `MAX_OPS_PER_SCRIPT = 201` 2. **After activation:** - All new opcodes become available - `MAX_SCRIPT_ELEMENT_SIZE = 1,000,000` - `MAX_SCRIPTS_SIZE = 1,000,000` - `MAX_OPS_PER_SCRIPT = 1,000,000` Note: the post-activation `MAX_SCRIPTS_SIZE` and `MAX_OPS_PER_SCRIPT` values are script-engine ceilings; transient mass bounds transactions before these ceilings are reached in practice. ## Motivation The combination of introspection opcodes and OP_CAT/OP_SUBSTR can be used to implicitly attach state to UTXOs and enforce correct state transitions. For example, we can implement a simple covenant that requires us to increase a counter stored in the transaction payload every time the UTXO is spent, and forces us to send the new transaction to the same scriptPubKey: Given signature script of the form: ` `, the scriptPubKey will be: ``` OpDup OpRot OpRot OpCat "TransactionID" OpBlake2bWithKey OpTxInputIndex OpOutpointTxId OpEqualVerify Op1Add 0 OpTxPayloadLen OpTxPayloadSubstr OpEqualVerify OpTxInputIndex OpTxInputSpk 0 OpTxOutputSpk OpEqualVerify OpTxOutputCount 1 OpEqual ``` This script can be written in pseudocode as: ``` validate(prev_tx, tx){ return hash(prev_tx) == tx.inputs[curr_idx].prev_tx_id AND tx.outputs[0].script_pub_key == prev_tx.inputs[curr_idx].script_pub_key AND len(tx.outputs) == 1 AND tx.payload == prev_tx.payload + 1; } ``` In general, once we can encode some state transition function δ in script, we can validate that `δ(prev_payload, tx) = new_payload`, where δ can also introduce some constraints on `tx` using introspection opcodes (checking its signature, enforcing a locktime, etc). ### Note on Transaction Encoding Many covenant constructions require the spender to provide a full copy of the previous transaction as witness data in order to validate that `tx.inputs[idx].prev_tx_id = hash(prev_tx)`. As a result, the transaction ID hashing algorithm implicitly defines a canonical transaction encoding that scripts can rely on. Covenant scripts may reconstruct or inspect this encoding to verify that a provided previous transaction matches the referenced outpoint. A reference implementation of the transaction ID hashing and encoding logic is provided in [3]. ## Use Cases Covenants enable a variety of use cases, including but not limited to: 1. **Fungible and non-fungible tokens** [4]: Enforce token rules through covenant-constrained UTXOs. 2. **Smart Vaults for enhanced security** [5]: Implement timelocks, multisig, and other security constraints. 3. **Congestion control mechanisms** [6]: Enforce rate limiting and priority rules. 4. **L1-L2 trustless bridges** [8]: Combine KIP-16's ZK proof verification with covenant scripts to enable trustless cross-chain asset movement. 5. **Stateful applications**: Encode application state in UTXOs and enforce transitions through covenant scripts. See [7] for more use cases. ## Backwards Compatibility This proposal requires a hard fork, as it introduces new opcodes to the scripting language. Older software will require an update to support these new features. Existing scripts and addresses remain valid, but cannot use the new functionality without being updated. ## Reference Implementation The implementation is available in `rusty-kaspa` on the toccata and master branches. ## References 1. KIP 10: [kip-0010.md](kip-0010.md) 2. `OP_CAT` BIP: https://github.com/bip420/bip420 3. Reference implementation of transaction ID hashing: https://github.com/kaspanet/rusty-kaspa/blob/2ccc9e4ca9aa184c62c4fb14dbb463834264e01d/consensus/core/src/hashing/tx.rs#L40 4. Cat Protocol for covenants based tokens: https://catprotocol.org/ 5. Vaults: https://utxos.org/uses/vaults/ 6. Congestion Control: https://utxos.org/uses/scaling/ 7. More covenants use cases: https://utxos.org/uses/, https://github.com/sCrypt-Inc/awesome-op-cat#op_cat-use-cases, https://covenants.info/use-cases/ 8. KIP 16: [kip-0016.md](./kip-0016.md)