Post-Quantum Multisig
Tutorial for creating and spending from PQ multisig wallets using ML-DSA-44 keys and P2MR Merkle trees.
Start from the workflow that matches why you are here.
The docs are broad. These two paths jump straight into the operator and builder material behind the new microsites.
Operators, miners, and power sites
Go to the Mine microsite for the operator thesis, fleet playbook, and mining-focused doc bundles.
Open Path DevelopAPI teams, agent runtimes, and gateways
Go to the Develop microsite for service challenges, PQ identity framing, and builder launch kits.
Open PathBTX supports post-quantum M-of-N multisignature spending using ML-DSA-44 (Dilithium) keys in P2MR (Pay-to-Merkle-Root) witness v2 outputs. This guide walks through a complete 2-of-3 multisig workflow using PSBT.
How PQ Multisig Works
BTX PQ multisig uses a Merkle tree of leaf scripts rather than legacy OP_CHECKMULTISIG. Each leaf contains a threshold script with the new OP_CHECKSIGADD_MLDSA or OP_CHECKSIGADD_SLHDSA opcodes.
Key facts:
- Key type: ML-DSA-44 (1312-byte public keys) by default
- Address type: P2MR (witness v2)
- Max keys per leaf: 8 (
MAX_PQ_PUBKEYS_PER_MULTISIG) - Mixed algorithms: ML-DSA and SLH-DSA keys can coexist in a single leaf
- Signing: PSBT-based partial signing with union merge
Prerequisites
- Descriptor wallets enabled (BTX default)
- Three signer wallets:
signerA,signerB,signerC - One coordinator/watch-only wallet:
coordinator
btx-cli createwallet signerA
btx-cli createwallet signerB
btx-cli createwallet signerC
btx-cli createwallet coordinator true # watch-only Step 1: Export PQ Keys
Use deterministic key export from each signer wallet. This avoids manual witness/script parsing.
# Generate addresses and export PQ keys
A_ADDR=$(btx-cli -rpcwallet=signerA getnewaddress)
B_ADDR=$(btx-cli -rpcwallet=signerB getnewaddress)
C_ADDR=$(btx-cli -rpcwallet=signerC getnewaddress)
PK1=$(btx-cli -rpcwallet=signerA exportpqkey "$A_ADDR" | jq -r '.key')
PK2=$(btx-cli -rpcwallet=signerB exportpqkey "$B_ADDR" | jq -r '.key')
PK3=$(btx-cli -rpcwallet=signerC exportpqkey "$C_ADDR" | jq -r '.key') Fresh BTX descriptor wallets export ml-dsa-44 keys by default. For SLH-DSA backup keys, use the pk_slh(<32-byte-hex>) syntax.
Step 2: Create a PQ Multisig Address
Option A: Utility RPC (no wallet import)
Use createmultisig to generate the address without importing into a wallet:
btx-cli createmultisig 2 "[\"$PK1\",\"$PK2\",\"$PK3\"]" \
'{"address_type":"p2mr","sort":true}' Returns:
address— the P2MR multisig addressredeemScript— the P2MR leaf scriptdescriptor— e.g.mr(sortedmulti_pq(2,...))
Option B: Wallet RPC (create + import)
Use addpqmultisigaddress to create and import in one step:
btx-cli -rpcwallet=coordinator addpqmultisigaddress 2 \
"[\"$PK1\",\"$PK2\",\"$PK3\"]" "team-safe" true The true parameter enables sorted key ordering (sortedmulti_pq) for deterministic descriptor construction.
Step 3: Fund the Multisig
btx-cli -rpcwallet=funder sendtoaddress 3.0
btx-cli -rpcwallet=funder generatetoaddress 1 Step 4: Create an Unsigned PSBT
Use a fee rate suitable for large PQ witnesses. PQ signatures are significantly larger than classical signatures.
btx-cli -rpcwallet=coordinator walletcreatefundedpsbt \
'[{"txid":"","vout":}]' \
'[{"":1.0}]' \
0 \
'{"add_inputs":false,"changeAddress":"","fee_rate":25}' Take .psbt from the result.
Step 5: Add Metadata (Updater)
btx-cli -rpcwallet=coordinator walletprocesspsbt false "ALL" true false Use the returned .psbt as input for each signer.
Step 6: Sign on Two Independent Signers
Each signer independently processes the PSBT, adding their partial signature:
# Signer A
btx-cli -rpcwallet=signerA walletprocesspsbt
# Signer B
btx-cli -rpcwallet=signerB walletprocesspsbt Each response contains a partially signed PSBT with that signer's PQ signature included.
Step 7: Combine, Finalize, and Broadcast
# Combine partial signatures
btx-cli combinepsbt '["",""]'
# Finalize (assembles witness stack)
btx-cli finalizepsbt
# Broadcast
btx-cli sendrawtransaction
# Verify
btx-cli gettransaction The finalized witness stack layout is:
[sig_3_or_empty] [sig_2_or_empty] [sig_1_or_empty] [leaf_script] [control_block] Finalization succeeds only when the threshold number of valid non-empty signatures is present.
Mixed ML-DSA + SLH-DSA Keys
You can mix ML-DSA and SLH-DSA keys in a single multisig leaf for defense in depth. Use the pk_slh() prefix for SLH-DSA keys:
btx-cli createmultisig 2 \
'["","","pk_slh()"]' \
'{"address_type":"p2mr","sort":true}' The resulting leaf script uses the appropriate opcode for each key:
OP_CHECKSIG_MLDSA
OP_CHECKSIGADD_MLDSA
OP_CHECKSIGADD_SLHDSA
OP_2 OP_NUMEQUAL Validation weight per signature: 500 for ML-DSA, 5000 for SLH-DSA.
Recovery Paths
SLH-DSA Backup Key
For high-security wallets, include an SLH-DSA (SHAKE-128s) key as one of the signers. SLH-DSA is hash-based and believed to be conservative against quantum attacks even beyond lattice assumptions. A 2-of-3 with two ML-DSA keys and one SLH-DSA backup provides defense in depth.
Descriptor Backup
Always back up the full descriptor string (e.g. mr(sortedmulti_pq(2,...))) and all public keys. The descriptor is sufficient to reconstruct the address and spending conditions on any BTX node.
Large Quorums
For quorums larger than 8 keys, split policy across multiple P2MR leaves instead of a single oversized leaf. Each leaf can hold up to MAX_PQ_PUBKEYS_PER_MULTISIG = 8 keys.
Script Inspection
Decode and inspect a PQ multisig leaf script:
btx-cli decodescript For PQ multisig leaves, the output includes:
pq_multisig.threshold— the M valuepq_multisig.keys— list of public keyspq_multisig.algorithms— algorithm per key (ml-dsa-44 or slh-dsa-shake-128s)
Operational Security
- Use
sortedmulti_pq(orsort=true) for deterministic descriptor construction across all signers - Keep signer wallets on separate machines or hardware — never share private key material
- Transfer PSBTs between signers via secure channels (encrypted file, air-gapped media)
- Test the full sign/combine/finalize cycle on regtest before committing funds on mainnet
- Back up descriptor strings and wallet metadata for disaster recovery
- Consider geographic distribution of signer keys for resilience
Common Errors
| Error | Cause | Fix |
|---|---|---|
Only address type 'p2mr' is supported for PQ multisig | Wrong address type | Use {"address_type":"p2mr"} |
nrequired cannot exceed number of keys | Threshold/key count mismatch | Ensure M <= N |
Unable to build PQ multisig leaf script | Invalid key sizes or too many keys | Verify key sizes and stay under 8 keys per leaf |
| Relay fee rejection after finalize | Fee too low for large PQ witness | Increase fee_rate in walletcreatefundedpsbt |