Kaspalytics Learn is a work in progress.
Updated: Aug 2, 2026

The P2SH Covenant Envelope

How a Kaspa covenant program is committed to, revealed, and executed — the KCC1 script public key, the signature script layout, and the two-pass evaluation that makes covenants possible.

Every KCC1 covenant lives inside an ordinary Kaspa P2SH output. No new output type, no new address format. This page pins down the exact bytes on both sides of that envelope, because the rest of the series depends on knowing precisely where the covenant program R comes from.

Background: P2SH Script Format and Pay-To-Script Hash.

The locking side

A KCC1 covenant instance uses script public key version 0 with this script:

OP_BLAKE2B OP_DATA_32 Blake2b(R) OP_EQUAL

35 bytes, and every one of them is fixed except the hash. Blake2b(x) here means unkeyed BLAKE2b with a 32-byte output, no key and no salt.

scriptPublicKey.script — the counter covenant's lock 35 bytes
OP_BLAKE2B aa @0 · hash the top stack item
OP_DATA_32 20 @1 · push the next 32 bytes
Blake2b(R) e9c4ef0278b3d9f5d6d234ae89e2e38f30462fb9b9eeb32ed7caa76625552706 @2 · the commitment
OP_EQUAL 87 @34 · compare
This is the entire on-chain footprint of a covenant while it is unspent.

The consequence is worth stating plainly, because it shapes everything downstream:

An unspent covenant output exposes only Blake2b(R). Not the program, not its state, not even the fact that it is a covenant.

You cannot read a covenant’s state off the chain until somebody spends it. Indexers therefore recover state from spending transactions, and a covenant that wants to inspect a sibling has to be handed that sibling’s bytes as witness data.

The hash function split

KCC1 uses two different hash functions, and mixing them up is a common early mistake:

WhereFunctionWhy
P2SH redeem-script commitment (this page)BLAKE2bFixed by Kaspa’s existing P2SH consensus rules
Everything KCC1 defines itself — template hashes, dispatch tags, virtual-element commitmentsBLAKE3, 32-byte outputKCC1’s own choice, written Hash(x) in the spec

So Hash(R) and Blake2b(R) are different values over the same bytes, and the spec means different things by them.

The unlocking side

The spending input’s signature_script must be push-only and must have this logical layout:

PushArguments(arguments)
[OP_DATA_4 dispatch_tag]
PushMinimal(R)

Three rules govern it:

  1. The arguments come first, in parameter order, as described in The Value ABI.
  2. The dispatch tag is omitted for a program with exactly one entrypoint, and required for a program with two or more. See Entrypoints & Dispatch.
  3. PushMinimal(R) must be the final push.

Our counter contract has one entrypoint taking no arguments, so its signature script is the minimal possible case — just the program:

signature_script — spending the counter 59 bytes
OP_DATA_58 3a push 58 bytes
R (the covenant program) 08070000000000000002aabb01085279519358cd7e010252797e7eb976c97601ae937cbc7eaa02000001aa7e01207e7c7e01877e00c388757551 state + logic, pushed as data
No arguments and one entrypoint, so the whole signature script is a single push of R.

The critical subtlety: those 58 bytes are pushed as data. They are not executed during signature-script evaluation. The leading 08 is a byte of the counter’s state, not an opcode being run.

Execution: two passes over one stack

The engine runs signature_script and script_public_key back to back on a shared stack, then runs the revealed program.

Executes
signature_script
push-only pass
R (58 bytes of data)
OP_BLAKE2B
pop R, push its hash
blake2b(R)
OP_DATA_32 e9c4ef…2706
push the committed hash
blake2b(R)e9c4ef…2706
OP_EQUAL
must match, or the spend fails
true
R is deserialized and executed
now against the remaining stack — the arguments
…entrypoint arguments…
P2SH evaluation. Only after the hash check does R become executable code.

Two things follow from this shape, and both matter later:

  • After the hash check, the stack holds exactly the arguments (and the dispatch tag, when present). The program was consumed to become the code being run. That is why the argument encoding in The Value ABI is defined in terms of stack elements.
  • The revealed program must leave a truthy value on the stack for the spend to be valid, like any redeem script.

The commitment is total

Blake2b(R) covers every byte of R — logic and state.

Change a single byte of embedded state and Blake2b(R) changes, even though the template is untouched.

This is not a limitation to work around; it is the enforcement mechanism. It is precisely why a covenant can pin down its successor’s address: state and address are rigidly bound, so committing to one commits to the other.

It also means a covenant instance’s address changes on every transition. Address reuse is not a thing for stateful covenants, and any tooling that assumes a stable address will be wrong immediately.

Conformance vector

KCC1 gives a minimal worked example. For the one-byte program R = 51 (OP_1):

Blake2b(R) = ce57216285125006ec18197bd8184221cefa559bb0798410d99a5bba5b07cd1d

scriptPublicKey.version = 0
scriptPublicKey.script  = aa20ce57216285125006ec18197bd8184221cefa559bb0798410d99a5bba5b07cd1d87
signature_script        = 0151

Note the signature script: 0151 is OP_DATA_1 followed by the byte 0x51. It pushes the one-byte redeem script as data — it does not execute OP_1 during signature-script evaluation. The distinction is the same one as above, just small enough to see whole.

Next

You now know where R comes from and what the stack looks like when it starts running. Next: The Value ABI, which defines how the typed values on that stack are encoded.

References

Kaspalytics strives to provide accurate data - our highest level of effort is given to data validation and maintenance. However, we cannot guarantee 100% accuracy. Data is subject to inaccuracies and change.

Contact | © 2026 Kaspalytics