Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture

The ZK Covenant Rollup is organized into three layers, each running in a different environment. All cryptographic invariants are enforced at layer boundaries.

Three-layer design

flowchart TB
    subgraph Layer1["Layer 1: On-Chain Scripts"]
        direction LR
        REDEEM["State Verification<br/>redeem script"]
        PERM_SCRIPT["Permission<br/>redeem script"]
        DELEGATE["Delegate/Entry<br/>script"]
    end

    subgraph Layer2["Layer 2: ZK Guest (RISC-V)"]
        direction LR
        GUEST["Guest main()"]
        BLOCK["Block processor"]
        STATE["State updater"]
        JOURNAL["Journal writer"]
    end

    subgraph Layer3["Layer 3: Host / Core"]
        direction LR
        CORE["Core library<br/>(no_std)"]
        HOST_BIN["Host binary<br/>(script builders)"]
    end

    Layer3 -->|"witnesses + blocks"| Layer2
    Layer2 -->|"proof + journal"| Layer1
    CORE -.->|"shared types"| Layer2
    CORE -.->|"shared types"| Layer1

Crate map

The project consists of four crates:

CratePathTargetRole
zk-covenant-rollup-corecore/no_std (RISC-V + native)Shared types, hash functions, script construction
zk-covenant-rollup-guestmethods/guest/RISC-V (riscv32im-risc0-zkvm-elf)ZK proof program
zk-covenant-rollup-methodsmethods/nativeBuild harness for guest ELF
zk-covenant-rollup-hosthost/nativeDemo runner, script builders, tests
graph TD
    GUEST["guest<br/><i>methods/guest/</i>"]
    CORE["core<br/><i>core/</i>"]
    METHODS["methods<br/><i>methods/</i>"]
    HOST["host<br/><i>host/</i>"]

    GUEST --> CORE
    GUEST --> RISC0_ZKVM["risc0-zkvm"]
    HOST --> CORE
    HOST --> METHODS
    HOST --> KASPA["kaspa-txscript"]
    METHODS --> GUEST
    CORE --> SHA2["sha2"]
    CORE --> BLAKE3["blake3"]
    CORE --> BLAKE2B["blake2b-simd"]
    CORE --> BYTEMUCK["bytemuck"]

Core (no_std)

The core crate runs in both the RISC-V guest and on native. It is no_std with alloc support. Key responsibilities:

  • Data typesPublicInput, Account, AccountWitness, ActionHeader, action payloads
  • SMT — 8-level Sparse Merkle Tree with SHA-256 domain-separated hashing
  • Sequence commitment — Blake3-based streaming Merkle tree for block chaining
  • Permission tree — SHA-256 Merkle tree of withdrawal claims
  • Permission script — Byte-level redeem script construction (no_std compatible)
  • P2SH / P2PK — Script public key helpers
  • Transaction ID — V0 (blake2b) and V1 (blake3 payload + rest) computation
#![allow(unused)]
fn main() {
#[derive(Clone, Copy, Debug, Eq, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C, align(4))]
pub struct PublicInput {
    pub prev_state_hash: [u32; 8],

    pub prev_seq_commitment: [u32; 8],

    pub covenant_id: [u32; 8],
}
}

Guest (RISC-V)

The guest runs inside the RISC Zero zkVM. It reads PublicInput and witness data from stdin, processes all blocks, and writes a journal that the on-chain script verifies.

pub fn main() {
    let mut stdin = env::stdin();

    // Read and verify public input
    let public_input = input::read_public_input(&mut stdin);
    let mut state_root = public_input.prev_state_hash;

    // Process all blocks
    let chain_len = input::read_u32(&mut stdin);
    let mut seq_commitment = public_input.prev_seq_commitment;
    let mut perm_builder = StreamingPermTreeBuilder::new();

    for _ in 0..chain_len {
        let block_root = block::process_block(&mut stdin, &mut state_root, &public_input.covenant_id, &mut perm_builder);
        seq_commitment = calc_accepted_id_merkle_root(&seq_commitment, &block_root);
    }

    // Build permission output if exits occurred
    let perm_count = perm_builder.leaf_count();
    let permission_spk_hash = if perm_count > 0 {
        // Read expected redeem script length from host (private input)
        let perm_redeem_script_len = input::read_u32(&mut stdin) as i64;

        let depth = required_depth(perm_count as usize);
        let perm_root = pad_to_depth(perm_builder.finalize(), perm_count, depth);

        // Build once with host-provided length, then assert
        let perm_redeem =
            build_permission_redeem_bytes(&perm_root, perm_count as u64, depth, perm_redeem_script_len, MAX_DELEGATE_INPUTS);
        assert_eq!(perm_redeem.len() as i64, perm_redeem_script_len, "permission redeem script length mismatch");

        // blake2b hash → script_hash
        let script_hash = blake2b_script_hash(&perm_redeem);
        Some(bytes_to_words(script_hash))
    } else {
        None
    };

    // Write journal output
    journal::write_output(&public_input, &state_root, &seq_commitment, permission_spk_hash.as_ref());
}

The guest is deterministic: given the same inputs, it always produces the same journal. The host cannot influence the output except by providing different (but valid) witness data.

Host

The host crate builds transactions and runs the demo. It uses kaspa-txscript’s ScriptBuilder for the state verification and permission redeem scripts. The host is not trusted — everything it produces is verified either by the guest (inside the ZK proof) or by the on-chain script.

What runs where

ComponentEnvironmentTrusted?Verified by
Core types & hashesEverywhereN/A (library)
Guest proof programRISC Zero zkVMYes (proven)ZK verifier on-chain
State verification scriptKaspa nodeYes (consensus)All full nodes
Permission scriptKaspa nodeYes (consensus)All full nodes
Delegate scriptKaspa nodeYes (consensus)All full nodes
Host / operatorOff-chainNoGuest + on-chain scripts

The host can:

  • Choose the range of L1 blocks to process (committed to seq commitment, verified against L1)
  • Filter which transactions within those blocks are L2 actions
  • Provide witness data (SMT proofs, prev tx preimages)

Action order is inherited from L1 transaction order — the host cannot reorder or skip actions.

The host cannot:

  • Forge a valid ZK proof for an invalid state transition
  • Steal funds (covenant enforcement)
  • Credit accounts without real deposits (SPK verification)
  • Process withdrawals without proper authorization (prev tx proof)