mineracks-ckbunker-hsm-sign/fixtures
mineracks 9d380f5013 Initial import: CKBunker HSM validation harness
WebSocket client + CLI harness + pytest suite that exercises each axis of
a CKBunker + Coldcard Mk4 policy and asserts the expected outcomes, including
the critical negative test that a large PSBT without TOTP is rejected with
a specific 'rule #1: need user(s) confirmation' reason.

Configuration via .env / YAML / CLI flags, two pre-crafted test PSBTs as
fixtures (generation guide in fixtures/README.md), dashboard counter
scraper as sanity check, design rationale in docs/.
2026-04-14 10:50:04 +10:00
..
README.md Initial import: CKBunker HSM validation harness 2026-04-14 10:50:04 +10:00

Test PSBTs — how to generate them

The harness needs two pre-crafted PSBTs:

Fixture Amount Policy path expected
small.psbt ≤ auto-approve cap (e.g. 9,000 sats if your Rule #2 cap is 10,000) Signs without TOTP
large.psbt > auto-approve cap, ≤ user-auth cap (e.g. 100,000 sats) Rejected without TOTP; signs with TOTP

Both PSBTs must:

  • be spendable by the Coldcard bound to your CKBunker (same seed / xpub)
  • spend to an address you control (or a burn address — they are test inputs, you never broadcast them)
  • use a real UTXO the Coldcard can see (watch-only wallet)

  1. In Sparrow, open or create a watch-only wallet loaded with your Coldcard's xpub. (The Coldcard's HSM-Mode QR or a coldcard.txt export works.)
  2. Send yourself a small amount on testnet or signet so you have a UTXO to spend without losing real sats. (For mainnet demos, 10k sats is ~AUD $1.)
  3. Build two transactions:
    • Small demo — pay 9,000 sats (or 90% of your Rule #2 per-txn cap) to any receive address in the same wallet. Sparrow → Send → Save PSBT → write to fixtures/small.psbt.
    • Large demo — pay 100,000 sats (or mid-range of your Rule #1 cap) the same way. Save as fixtures/large.psbt.
  4. Both PSBTs should show Coldcard as a required signer in Sparrow.

Do NOT broadcast these. The harness signs them, but you verify the signatures in Sparrow and then discard — there's no reason to spend real sats on a validation run.


Method 2 — bitcoind (CI / automation)

If you're wiring the harness into CI against a regtest or signet deployment, scripting PSBT generation is a one-off:

#!/usr/bin/env bash
# Requires bitcoin-cli on PATH, pointed at a node that sees your wallet.
set -euo pipefail

WALLET="ckbunker-watch"
FEE_RATE=10   # sat/vB

recipient=$(bitcoin-cli -rpcwallet=$WALLET getnewaddress)

small_raw=$(bitcoin-cli -rpcwallet=$WALLET walletcreatefundedpsbt \
  '[]' "[{\"$recipient\":0.00009000}]" 0 \
  "{\"fee_rate\":$FEE_RATE}" | jq -r '.psbt')
echo "$small_raw" | base64 -d > fixtures/small.psbt

large_raw=$(bitcoin-cli -rpcwallet=$WALLET walletcreatefundedpsbt \
  '[]' "[{\"$recipient\":0.00100000}]" 0 \
  "{\"fee_rate\":$FEE_RATE}" | jq -r '.psbt')
echo "$large_raw" | base64 -d > fixtures/large.psbt

Method 3 — use the same PSBT file over and over

Nothing in the harness requires the PSBT to be spendable right now for the reject-path test (test_04). The Coldcard rejects on amount, not on whether the UTXO is still unspent. So:

  • small.psbt can be reused until the UTXO is spent elsewhere.
  • large.psbt can be reused indefinitely — every validation run that tests Rule #1 rejection produces a rejection regardless of UTXO state.

If you run the full suite frequently, consider crafting large.psbt deliberately against an already-spent UTXO so the success path (test_05) fails at signature verification (not policy evaluation) — this is arguably safer than running with signable funds live.


File format

Either binary (psbt\xff... magic bytes) or base64-encoded text is accepted by the harness — it auto-detects via magic bytes. Sparrow exports binary by default; bitcoin-cli returns base64.


What NOT to do

  • Do not commit real PSBTs to git — .gitignore already blocks *.psbt in this directory.
  • Do not use a PSBT that spends a UTXO you can't afford to move. The harness does not broadcast, but a leaked signed PSBT can be broadcast by anyone.
  • Do not reuse production keys for generating fixtures — prefer testnet or signet.