KiiWORKS / Documentation
Kaspa BlockDAG Integration
SOURCE · docs/kaspa-integration.md · synced from the engineering repo
Kaspa BlockDAG Integration
KiiWORKS uses the Kaspa BlockDAG as its trust anchor — a high-throughput, proof-of-work blockchain with 1 block per second and GHOSTDAG consensus. This document covers how KiiWORKS interacts with Kaspa.
Why Kaspa?
| Property | Value |
|---|---|
| Block time | 1 second |
| Consensus | GHOSTDAG (parallel blocks, no orphans) |
| Throughput | ~500 TPS (post-hardfork) |
| Finality | ~10 seconds (10 confirmations) |
| Smart contracts | KIP-17 Covenant introspection (Toccata hardfork, ~30 June 2026) |
| Signature scheme | Schnorr over secp256k1 |
| Mining | kHeavyHash (optical mining compatible) |
Kaspa’s speed and low fees make it practical to anchor every DPP state change individually, rather than batching into daily or weekly anchors.
Architecture
┌─────────────────────┐
│ KiiWORKS Platform │
│ │
│ kiiworks-anvil │ ← HD keys, signing, UTXO selection
│ kiiworks-foundry │ ← RPC client, TX builder
│ kiiworks-forge │ ← DID:Kaspa, Verifiable Credentials
│ kiiworks-stamp │ ← NFT/KRC-20 via anchoring
│ kiiworks-covenant │ ← KIP-10 covenant scripts
│ kiiworks-vault │ ← Key storage
└─────────┬───────────┘
│ wRPC (Borsh)
│ ws://127.0.0.1:17210
┌─────────┴───────────┐
│ kaspad (Rust node) │
│ Testnet-12 / Main │
└─────────────────────┘
Crate Reference
kiiworks-foundry — RPC Client & Transaction Builder
The foundry crate provides the low-level Kaspa interface:
KaspaRpc: wRPC client using Borsh serialisationconnect(url)→ establish persistent connectionget_utxos_by_addresses(addresses)→ query unspent outputssubmit_transaction(tx)→ broadcast signed transactionget_block_dag_info()→ network status
TransactionBuilder: Fluent TX construction.add_input(outpoint, utxo_entry).add_output(address, amount).with_change_address(address).with_fee(amount).build()→ unsignedTransaction
NetworkId:Mainnet,Testnet10,Testnet11,Testnet12,Custom(String)
kiiworks-anvil — Keys, Signing, UTXO Selection
HdKeyDerivation: BIP-44 pathm/44'/111111'/0'/0/{index}- From mnemonic → master key → derived keypair
SchnorrSigner: Transaction signingsign_transaction(tx, private_keys, utxo_entries)→ signed TX- BLAKE2b-256 sighash with key
b"TransactionSigningHash"
UtxoSelector: Coin selection algorithms- Largest-first selection
- Minimum change output (≥ 1 KAS to avoid KIP-9 mass penalty)
BurnAnchor: P2PK anchoringbuild_anchor_script(anchor_type, hash)→ script with hash embeddedextract_anchor_hash(script)→ recover hash from script
kiiworks-forge — DID & Verifiable Credentials
DidKaspaMethod: DID operationscreate(public_key, network)→did:kaspa:{network}:{hex_pubkey}resolve(did)→DidDocumentwith verification methods- DID documents follow W3C DID Core 1.0
VcIssuer: Verifiable Credential issuanceissue(claims, issuer_did, private_key)→ signed VC (JSON-LD)verify(vc)→ resolve issuer DID, verify Schnorr signature
TrustTag: Blockchain-anchored trust markers- Hash of VC → anchor on Kaspa → tag ID = TX ID
kiiworks-stamp — NFT & Token Operations
- NFT minting: Create non-fungible tokens via on-chain anchoring
- KRC-20: Kaspa token standard operations (mint, transfer, burn)
- All operations use the P2PK burn strategy since Kaspa has no OP_RETURN
kiiworks-covenant-sdk — KIP-10 Covenants
Available after the Toccata hardfork (~30 June 2026, KIP-17 covenant introspection).
CovenantScript: Script compilation- Lock conditions: timelock, hash preimage, multisig, data check
- Unlock proofs: satisfy one or more conditions
EscrowCovenant: Pre-built escrow pattern- Payer deposits → conditions met → payee withdraws
- Automatic refund if conditions not met by expiry
On-Chain Anchoring
P2PK Burn Strategy
Kaspa does not support OP_RETURN (rejected as NonStandard by the mempool). KiiWORKS uses P2PK burn anchoring instead:
Script: [0x20][32-byte-hash][0xac]
OP_DATA_32 data_hash OP_CHECKSIG
This creates a valid P2PK script where the “public key” is actually the data hash. The output is computationally unspendable — functionally a burn output (no one has the private key for an arbitrary hash) — but the transaction is accepted by the network.
Anchor Flow
Data (DPP state, VC, etc.)
│
▼
SHA-256 hash (32 bytes)
│
▼
BurnAnchor::build_anchor_script(type, hash)
│
▼
TransactionBuilder
.add_input(utxo) ← From wallet
.add_output(burn_script, 1 KAS) ← Minimum 1 KAS (KIP-9)
.with_change_address(wallet)
.with_fee(30_000 sompi) ← Safe default
.build()
│
▼
SchnorrSigner::sign_transaction(tx, keys, entries)
│
▼
KaspaRpc::submit_transaction(signed_tx)
│
▼
TX confirmed in ~10 seconds (10 blocks)
Verification
TX ID
│
▼
KaspaRpc::get_transaction(tx_id)
│
▼
BurnAnchor::extract_anchor_hash(output_script)
│
▼
Compare hash with SHA-256(original_data)
│
▼
Match → data existed at block timestamp
Merkle Batch Anchoring
For high-volume anchoring (e.g., hundreds of DPPs per hour), use the kiiworks-anchor-tree crate:
- Collect N items to anchor
- Compute SHA-256 of each item → leaf hashes
- Build Merkle tree → single root hash (32 bytes)
- Anchor the root hash on Kaspa (1 transaction)
- Generate inclusion proof for each item
- Distribute proofs to item owners
This reduces blockchain cost from O(N) to O(1) transactions while maintaining individual verifiability.
Wallet Management
HD Derivation
KiiWORKS uses BIP-39/BIP-44 hierarchical deterministic wallets:
BIP-39 Mnemonic (12 or 24 words)
│
▼
512-bit Seed
│
▼
Master Key (BIP-32)
│
▼
m/44'/111111'/0'/0/0 → First address
m/44'/111111'/0'/0/1 → Second address
m/44'/111111'/0'/0/N → Nth address
- Coin type
111111is the Kaspa BIP-44 identifier - Each derived key is a secp256k1 keypair
- Addresses use Bech32 encoding:
kaspa:qr...(mainnet) orkaspatest:qr...(testnet)
Transaction Construction
// 1. Query UTXOs
let utxos = rpc.get_utxos_by_addresses(&[address]).await?;
// 2. Select inputs
let selected = UtxoSelector::select(&utxos, amount + fee)?;
// 3. Build transaction
let tx = TransactionBuilder::new()
.add_inputs(&selected)
.add_output(recipient, amount)
.with_change_address(address)
.with_fee(30_000) // sompi
.build()?;
// 4. Sign
let signed = SchnorrSigner::sign_transaction(tx, &[private_key], &selected)?;
// 5. Submit
let tx_id = rpc.submit_transaction(signed).await?;
KIP-9 Mass Considerations
Kaspa’s KIP-9 introduces storage mass — tiny outputs from large inputs trigger exponentially higher transaction mass (and thus fees). Always:
- Use outputs ≥ 1 KAS (100,000,000 sompi) to avoid mass penalty
- Use
minimum_fee_for_mass()to calculate the correct fee - Default fee of 30,000 sompi is safe for simple transactions
DID:Kaspa Identity
Format
did:kaspa:{network}:{hex_public_key}
Examples:
did:kaspa:mainnet:a1b2c3d4e5f6... (64 hex chars = 32-byte x-only pubkey)
did:kaspa:testnet-12:a1b2c3d4e5f6...
Resolution
DidKaspaMethod::resolve(did) produces a W3C DID Document:
{
"@context": ["https://www.w3.org/ns/did/v1"],
"id": "did:kaspa:testnet-12:a1b2c3...",
"verificationMethod": [{
"id": "did:kaspa:testnet-12:a1b2c3...#key-1",
"type": "SchnorrSecp256k1VerificationKey2024",
"controller": "did:kaspa:testnet-12:a1b2c3...",
"publicKeyMultibase": "z..."
}],
"authentication": ["did:kaspa:testnet-12:a1b2c3...#key-1"],
"assertionMethod": ["did:kaspa:testnet-12:a1b2c3...#key-1"]
}
Authentication Flow
- Client sends
POST /api/identity-auth/challengewith their DID - Server generates random nonce, stores with expiry
- Client signs nonce with their private key (Schnorr)
- Client sends
POST /api/identity-auth/verifywith DID + signature - Server resolves DID → extracts public key → verifies Schnorr signature
- If valid → issue JWT token
Cross-Chain Anchoring
The kiiworks-bridge crate provides a ChainAdapter trait for multi-chain anchoring:
#[async_trait]
pub trait ChainAdapter: Send + Sync {
fn chain_id(&self) -> ChainId;
async fn anchor(&self, request: AnchorRequest) -> Result<AnchorReceipt>;
async fn verify(&self, request: VerifyRequest) -> Result<VerifyResult>;
}
Supported chains:
- Kaspa (native, full implementation)
- EVM / EBSI (full implementation; see guides/ebsi-evm-anchor.md). One unified adapter covers:
- EBSI — the EU-sovereign permissioned Besu network (regulator-facing pilots)
- Public Ethereum L2s — Optimism, Arbitrum, Base, Linea, plus their Sepolia testnets
- Ethereum mainnet + Sepolia
The BridgeRegistry enables anchoring the same hash on multiple chains simultaneously for redundancy. The EVM adapter signs EIP-1559 type-2 transactions and supports two anchor modes: calldata-only (the 32-byte payload hash is the transaction’s data field, sent to the zero address — ~21,500 gas, cheap, retrievable via eth_getTransactionByHash) and contract-call (the adapter calls AnchorRegistry.anchor(bytes32) at an operator-deployed registry address — ~50,000 gas, queryable via the isAnchored(hash, anchorer) view and the AnchorAdded event). EBSI networks require contract-call mode per the EBSI ledger v4 data-anchor RFC; the adapter enforces this at construction. See the EBSI / EVM Anchor Guide for the AnchorRegistry deployment procedure, cost analysis per network, and operational notes.
Covenants (KIP-17, Toccata)
Target: Kaspa Toccata hardfork (~30 June 2026, DAA 474,165,565; KIP-16/17/20/21)
Covenants enable smart contract-like functionality on Kaspa. KiiWORKS uses them for:
Escrow
Lock: "Release 10 KAS to address X when:
1. DPP hash matches (proof of delivery), AND
2. 3-of-5 multisig approval, AND
3. Block time > 2026-04-01"
Time-Locked Disclosure
Lock: "Reveal encryption key when block time > target_date"
Supply Chain Gates
Lock: "Transfer ownership token when lifecycle stage >= 'shipped'"
The kiiworks-covenant-sdk compiles these conditions into Kaspa script and provides unlock proof construction.