Skip to content

GOV1 Witness

GOV1 is the governance witness payload that binds a registry update transaction to a specific proposal, vote digest, BLKL state transition, and on-chain proposal cell. Version 0x04 splits the transaction evidence across:

  • WitnessArgs.input_type: the GOV1 v4 binding
  • WitnessArgs.lock: validator yes-vote entries with signatures and Merkle proofs
  • A live PBLK proposal input cell: the on-chain anchor whose relative since enforces the review delay
import {
buildGov1WitnessV4,
buildValidatorVoteWitness,
buildWitnessArgs,
encodeRelativeTimestampSince,
} from "../lib/witness.js";
const gov1 = buildGov1WitnessV4({
proposalIdHash, // Uint8Array(32)
voteDigestHash, // Uint8Array(32)
oldRoot, // Uint8Array(32), blake2b(input registry data)
newRoot, // Uint8Array(32), blake2b(output registry data)
proposalDataHash, // Uint8Array(32), blake2b(PBLK proposal cell data)
reviewDelayMs, // bigint, default 72h in milliseconds
});
const voteWitness = buildValidatorVoteWitness([
{
pubkey,
vote: "yes",
timestamp,
signature,
merkleLeafIndex,
merkleProof,
},
]);
const witness = buildWitnessArgs({ lock: voteWitness, inputType: gov1 });
// Set this on the PBLK proposal input, not the registry input.
const since = encodeRelativeTimestampSince(reviewDelayMs);

The registry type script reads WitnessArgs.input_type. This field is exactly 173 bytes:

FieldOffsetSizeValue
magic04 bytesASCII GOV1 (0x47 0x4f 0x56 0x31)
version41 byte0x04
proposal_id_hash532 bytesblake2b hash of the canonical proposal fields
vote_digest_hash3732 bytesblake2b hash of the sorted signed vote records
old_root6932 bytesblake2b of the input registry cell data
new_root10132 bytesblake2b of the output registry cell data
proposal_data_hash13332 bytesblake2b of the PBLK proposal cell data
review_delay_ms1658 bytesLE u64 review delay in milliseconds

Total: 173 bytes (raw payload; molecule BytesOpt adds a 4-byte length prefix on the wire).

Neither proposal_id_hash nor vote_digest_hash may be the zero hash ([0u8; 32]).

Every registry update must include a live input cell whose data hashes to proposal_data_hash. The blacklist-registry type script parses that cell data and requires the registry transition to exactly match the proposal.

PBLK v1 cell data layout for blacklist entry updates:

FieldOffsetSizeValue
magic04 bytesASCII PBLK (0x50 0x42 0x4c 0x4b)
version41 byte0x01
registry_type_id_value532 bytesRegistry Type ID value this proposal targets
action371 byte0x01 add, 0x02 remove
identifier_len381 bytelength of the lock-args identifier
identifier39variablelock args to add or remove
expires_atafter identifier8 bytesLE u64 Unix timestamp in seconds, or 0 for permanent
evidence_hashfinal 32 bytes32 bytesblake2b hash of the evidence text

PBLK v2 cell data layout for registry treasury metadata:

FieldOffsetSizeValue
magic04 bytesASCII PBLK (0x50 0x42 0x4c 0x4b)
version41 byte0x02
registry_type_id_value532 bytesRegistry Type ID value this proposal targets
action371 byte0x03 set treasury
treasury_lock_hash3832 bytesblake2b hash of the treasury lock script
evidence_hash7032 bytesblake2b hash of the evidence text

The registry script rejects the transaction if:

  • The proposal cell is missing
  • More than one input cell has the same proposal_data_hash
  • The proposal targets a different registry Type ID
  • The BLKL output contains any change beyond the proposed add/remove transition, or for set-treasury, changes blacklist entries or sets a treasury lock hash that does not match the proposal

The governance-lock script finds the proposal input by proposal_data_hash and checks that input’s since field. The value must encode a relative median-time-past delay greater than or equal to review_delay_ms:

since = 0x8000_0000_0000_0000 | 0x4000_0000_0000_0000 | review_delay_ms
  • Bit 63 = 1: relative lock
  • Bit 62 = 1: timestamp metric (median block time)
  • Bits 55-0: delay in milliseconds

CKB consensus enforces that the proposal input is old enough before the transaction can be included in a block. The lock script rejects absolute timestamps, block-number locks, epoch locks, and delay values shorter than review_delay_ms.

The governance-lock script reads WitnessArgs.lock:

FieldSizeValue
vote_count1 bytenumber of yes-vote entries submitted for execution
vote_entriesvariablerepeated validator vote entries

Each validator vote entry is:

FieldSizeValue
pubkey33 bytescompressed secp256k1 validator public key
vote1 bytemust be 0x01 (yes) for execution
timestamp_len2 bytes u16byte length of the timestamp string
timestampvariableUTF-8 timestamp included in the signed vote payload
signature65 bytes`r(32)
merkle_leaf_index4 bytes u32validator leaf index in the BLKL validator tree
proof_count1 bytenumber of sibling hashes in the Merkle proof
proofproof_count * 32 bytessibling hashes from leaf to root

The governance-lock recomputes the canonical JSON message each validator yes-vote must have signed:

{
"domain": "ckb-firewall:vote",
"proposalIdHash": "0x...",
"vote": "yes",
"timestamp": "2026-06-01T00:00:00.000Z",
"pubkey": "0x..."
}

This JSON string is hashed with CKB blake2b before secp256k1 recovery. The recovered compressed public key must match the vote entry’s pubkey, and that pubkey must prove membership in the BLKL validator Merkle root.

vote_digest_hash commits to the complete set of validator votes:

vote_digest_hash = blake2b(JSON.stringify(votes.sort_by(pubkey).map({ pubkey, vote, timestamp, signature })))

Each vote carries:

  • pubkey: 33-byte compressed secp256k1 public key
  • vote: "yes", "no", or "abstain"
  • timestamp: ISO 8601 string
  • signature: 65-byte secp256k1 signature over the vote payload
  • merkleLeafIndex and merkleProof: membership proof against the on-chain validator Merkle root

The execute command recomputes vote_digest_hash from stored votes and aborts if it does not match the proposal file.

CheckPerformed by
GOV1 magic, version, and lengthRegistry type script and governance-lock script
old_root / new_root match actual registry cell dataRegistry type script
PBLK proposal input exists and is uniqueRegistry type script and governance-lock script
Proposed add/remove is the only BLKL transitionRegistry type script
Proposal input since is relative MTP delay >= review_delay_msGovernance-lock script
Each yes-vote signature valid for the stated validator pubkeyGovernance-lock script
Each validator pubkey proves membership in the BLKL validator Merkle rootGovernance-lock script
Threshold metGovernance-lock script
Vote digest integrityCLI execute, before submission
  • Witness builder: sdk/cli/src/lib/witness.ts - buildGov1WitnessV4, buildValidatorVoteWitness, buildWitnessArgs, encodeRelativeTimestampSince
  • Proposal-cell helpers: sdk/cli/src/lib/governance-v4.ts - encodeProposalCellData, proposalCellDataHash
  • Proposal types: sdk/cli/src/lib/proposals.ts - ProposalVote, computeVoteDigestHash, voteSigningMessage
  • On-chain GOV1 parser: contracts/blacklist-registry/src/main.rs - GovernanceWitness::parse
  • On-chain validator vote verifier and relative-since check: contracts/governance-lock/src/main.rs - program_entry, verify_relative_since_timestamp