TYPE 0x0A03 · QABIO FAMILY

PQ_BATCH

Lightweight post-quantum batch primitive. Conditions commit only SHA256(falcon_pubkey). In a spending tx, exactly one input per commit group is the anchor: its witness reveals the canonical PQ pubkey + a PQ signature over the per-input ladder sighash. Every other input gated by the same hash carries an empty witness and short-circuits via a tx-local cache the anchor populates after its first verify. No coordinator, no priming round, no output-set binding — the simple "key-sharing pool" complement to QABIO.

QABIO Non-Invertible
PQ_BATCH (anchor input) witness = [PUBKEY, SIGNATURE] verify FALCON sig → populate PQBatchCache[commit] PQ_BATCH (non-anchor input) witness = [] (empty) read PQBatchCache[commit] → SATISFIED
FieldData TypeSizeSideDescription
pubkey_hash HASH256 32 B Conditions SHA256(canonical_falcon_pubkey) — the only thing committed at fund time
pubkey
(anchor only)
PUBKEY 32 / 33 / 897 / 1,793 / 1,952 B Witness Canonical PQ pubkey (or Schnorr/ECDSA carve-out). Scheme derived from pubkey size: 32-33 → SCHNORR (rejected by PQ_BATCH — classical sigs handled by SIG with merkle_pub_key gating), 897 → FALCON-512, 1,793 → FALCON-1024, 1,952 → Dilithium3. Any other size → UNSATISFIED. SPHINCS+ is not supported — its ~13 KB sig makes the amortisation benefit marginal.
signature
(anchor only)
SIGNATURE scheme-dependent Witness PQ signature over the per-input ladder sighash (FetchLadderSighash(SIGHASH_DEFAULT)). FALCON-512 sigs are variable up to ~666 B; OQS_SIG_verify validates the encoded length internally.

The merged conditions+witness block has 1 field for non-anchor inputs (just HASH256) or 3 fields for the anchor input (HASH256, PUBKEY, SIGNATURE). Audit #3 fix E-020/E-021: the evaluator pins these per-slot types exactly — the cardinality check alone was bypassable (witness=[SIG, SIG] gave a 3-field merged block with no PUBKEY, falling through to the non-anchor cache lookup with up to ~99 KB of attacker bytes silently embedded). PQ_BATCH_WITNESS is declared nullptr in BlockTypeInfo precisely because the merged shape is what's pinned.

1. Strict shape check on the merged conditions+witness block: must be either 1 field [HASH256] (non-anchor) or 3 fields [HASH256, PUBKEY, SIGNATURE] in that exact order. Anything else → ERROR
2. HASH256 must be exactly 32 B. Convert to a uint256 commit_key for the cache.
3. Non-anchor path (1-field merged block): take the mutex on ctx.pq_batch_cache_mutex (when provided), look up commit_key; cached & true → SATISFIED; absent or false → UNSATISFIED (anchor must precede this input in script-check order).
4. Anchor path (3-field merged block): compute SHA256(PUBKEY) and memcmp against the committed HASH256. Mismatch → UNSATISFIED (failures are not cached, so a later input with a different commit can still anchor).
5. Derive scheme from pubkey.size(): 32 or 33 → SCHNORR; 897 → FALCON512; 1793 → FALCON1024; 1952 → DILITHIUM3; anything else → UNSATISFIED. Then FetchLadderSighash(ctx, SIGHASH_DEFAULT) → 32-byte message; failure → ERROR
6. If IsPQScheme(scheme): VerifyPQSignature(scheme, sig, sighash, pubkey) → verified flag. Schnorr/ECDSA carve-out: verified stays false — PQ_BATCH's value-add is PQ-specific (use SIG with merkle_pub_key for classical paths). On verified: take the mutex and write (*ctx.pq_batch_cache)[commit_key] = true so subsequent non-anchor inputs (and concurrent workers) see the verification immediately. v0.13 fix.
7. verifiedSATISFIED; else → UNSATISFIED

Script verification runs in input order (0, 1, 2, ...). The anchor input must be at the lowest-index PQ_BATCH input per commit group — non-anchor inputs at lower indices return UNSATISFIED because the cache entry doesn't exist yet. This is acceptable: the cost model of "one anchor input per commit group per tx" still delivers ~10× amortisation for N=100. Signers must respect this ordering.

ConditionResult
Merged block field count outside {1, 3}, or per-slot types don't match the strict [HASH256] / [HASH256, PUBKEY, SIGNATURE] shapeERROR
Anchor input: SHA256(pubkey) ≠ pubkey_hashUNSATISFIED
Anchor input: PQ signature invalidUNSATISFIED
Non-anchor input: cache empty (anchor not yet seen)UNSATISFIED
Anchor input: PQ sig valid — populates cacheSATISFIED
Non-anchor input: cache reads true for commitSATISFIED
Conditions (committed at fund time)
{
  "type": "PQ_BATCH",
  "inverted": false,
  "fields": [
    { "type": "HASH256", "hex": "a1b2c3...32 bytes (SHA256(falcon_pubkey))" }
  ]
}
Witness — anchor input
{
  "type": "PQ_BATCH",
  "fields": [
    { "type": "PUBKEY", "hex": "...897 bytes (FALCON-512)" },
    { "type": "SIGNATURE", "hex": "...666 bytes (FALCON-512 sig)" }
  ]
}
Witness — non-anchor inputs (cache reads)
{
  "type": "PQ_BATCH",
  "fields": []
}

For the typical "drain N UTXOs to one sink" shape (FALCON-512). Per doc/ladder-script/PQ_BATCH_SPEC.md: anchor input ~392 vB (FALCON-512 witness, SegWit-discounted), non-anchor input ~14 vB (just the MLSC proof path).

NTotal vsizePer-input vsizevs N × SIG(FALCON-512) at ~400 vB
1~392 vB392 vBparity
10~518 vB~52 vB~8× cheaper
100~1,778 vB~17.8 vB~22× cheaper
500~7,378 vB~15 vB~27× cheaper
1,000~14,378 vB~14 vB~28× cheaper

Asymptote: ~14 vB per non-anchor input. The anchor input pays the FALCON witness cost once (~392 vB after the SegWit 4× discount on 666 B sig + 897 B pubkey + scaffolding); every cache-read input pays only the PQ_BATCH micro-header + the rung's MLSC proof scaffolding (~14 vB). At N=100 the per-input mean is 17.8 vB; the BIP cites this figure (§Q11). Both numbers measure pure witness cost; the per-input tx-input baseline (32 B prevout + 4 B vout + 4 B sequence + 1 B scriptsig-length = 41 vB) sits on top, identical to every other input type.

Exchange Sweeps
A custodian holds many UTXOs all spendable by the same FALCON key. PQ_BATCH collapses the consolidation cost — one signature, one verify, N inputs.
Co-owned UTXO Pools
Multiple addresses derived from the same PQ master key share a single batch authorisation primitive without coordination.
Recurring Subscription Drains
A service that periodically batches small UTXOs into a single PQ-authorised sweep. The subscription model gets PQ safety for almost the cost of a single Schnorr spend.
Comparison with QABIO
PQ_BATCH is the single-key complement to QABIO. Use PQ_BATCH for "many UTXOs, one signer" (no coordinator, no output-set binding). Use QABIO for "many parties, one atomic batch" (coordinator-driven, output-set bound, escape-rung-protected). They can coexist in the same tx — PQ_BATCH gates the inputs, QABIO carries the coordinator's tx-level signature.