Skip to main content

Post-Quantum Cryptography

BTX post-quantum signature algorithms, P2MR output construction, Merkle-tree spend paths, address format, and key generation.

Overview

BTX uses NIST-standardized post-quantum signature algorithms as its primary cryptographic primitives. All transaction outputs use P2MR (Pay-to-Merkle-Root), a witness version 2 output type that commits to a Merkle tree of spend paths. Each leaf in the tree can use a different signature algorithm, enabling flexible security policies including primary/recovery key separation and threshold multisig with mixed algorithm types.

Signature Algorithms

AlgorithmStandardPublic KeySecret KeySignatureSigop Weight
ML-DSA-44 FIPS 204 1,312 bytes 2,560 bytes 2,420 bytes 500
SLH-DSA-SHAKE-128s FIPS 205 32 bytes 64 bytes 7,856 bytes 5,000

ML-DSA-44 (lattice-based) is the primary signature scheme, offering compact public keys relative to its security level and fast verification. SLH-DSA-SHAKE-128s (hash-based) serves as the backup/recovery algorithm, providing a conservative security assumption that depends only on hash function security rather than lattice hardness.

P2MR Output Construction

Output Script

A P2MR output is a standard witness v2 script:

OP_2 <32-byte-merkle-root>

The 32-byte witness program is the root of a Merkle tree whose leaves are individual spend-path scripts.

Merkle Tree

  • Leaf hash: P2MRLeaf(leaf_version || script)
  • Branch hash: P2MRBranch(sort(h1, h2))
  • Sibling hashes are sorted before branching, ensuring canonical tree construction.

Control Block

The witness stack for spending includes a control block containing:

  • 1-byte leaf version (0xc2 masked with 0xfe)
  • Zero or more 32-byte sibling hashes for the Merkle proof

Leaf Script Types

ML-DSA Single-Sig Leaf

<1312-byte-pubkey> OP_CHECKSIG_MLDSA

SLH-DSA Single-Sig Leaf

<32-byte-pubkey> OP_CHECKSIG_SLHDSA

Multisig Leaf (m-of-n)

 OP_CHECKSIG_{algo1}
 OP_CHECKSIGADD_{algo2}
...
 OP_CHECKSIGADD_{algon}
 OP_NUMEQUAL

Algorithms are per-key and may mix ML-DSA and SLH-DSA within a single leaf. Maximum 8 public keys per multisig leaf (MAX_PQ_PUBKEYS_PER_MULTISIG). Leaf scripts may be up to 10,000 bytes (MAX_P2MR_SCRIPT_SIZE).

Default Leaf Roles

A typical P2MR tree contains leaves for distinct security purposes:

RoleLeaf TypePurpose
PrimaryML-DSA single-sigDay-to-day spending with fast verification
RecoverySLH-DSA single-sigBackup path using hash-based signatures (conservative assumption)
CTVOP_CHECKTEMPLATEVERIFYPre-committed transaction templates (vaults, covenants)
CSFSOP_CHECKSIGFROMSTACKDelegation and oracle-based spending conditions
Thresholdm-of-n multisigOrganizational custody with mixed PQ algorithms

PQ Opcodes

OpcodeValueStack EffectContext
OP_CHECKSIG_MLDSAPops sig + 1312-byte pubkey; pushes successP2MR only
OP_CHECKSIG_SLHDSAPops sig + 32-byte pubkey; pushes successP2MR only
OP_CHECKSIGADD_MLDSA0xbe(sig n pubkey → n+success)P2MR only
OP_CHECKSIGADD_SLHDSA0xbf(sig n pubkey → n+success)P2MR only

All PQ opcodes are valid only under SigVersion::P2MR. Empty signatures skip verification and charge no validation weight. Non-empty failing signatures are rejected under SCRIPT_VERIFY_NULLFAIL.

Sighash Construction

P2MR uses a BIP341-style digest structure with a distinct epoch byte for witness v2 script execution. The signed message is a 32-byte digest computed over:

  • Transaction context (version, locktime, inputs, outputs)
  • Leaf commitment context (leaf version, script, control block path)

Address Format

P2MR addresses use Bech32m encoding with witness version 2:

PropertyValue
EncodingBech32m (BIP350)
Witness version2
Mainnet prefixbtx1z...
Witness program32 bytes (Merkle root)

Key Generation and Derivation

HD Derivation

BTX wallet derivation uses purpose 87h for P2MR descriptors, following the path family m/87h/.... This provides deterministic PQ key derivation compatible with external signer workflows.

Descriptor Grammar

  • mr(...) — P2MR descriptor root
  • pk_slh(...) — SLH-DSA backup leaf
  • mr(multi_pq(m,key1,key2,...)) — threshold multisig
  • mr(sortedmulti_pq(m,key1,key2,...)) — sorted-key multisig

In descriptors, bare hex denotes an ML-DSA pubkey (1,312 bytes). SLH-DSA pubkeys use the pk_slh(hex) wrapper (32 bytes). sortedmulti_pq sorts keys by raw bytes before script construction.

Miniscript Support

The MiniscriptContext::P2MR context supports PQ-specific fragments:

  • pk_mldsa(KEY) / pk_slhdsa(KEY)
  • multi_mldsa(k, KEY, ...) / multi_slhdsa(k, KEY, ...)

PQ fragments are rejected in non-P2MR contexts via context gating.

PSBT Profile

  • Selected P2MR leaf script and control block are carried in input metadata.
  • Partial PQ signatures are keyed by (leaf_hash, pubkey).
  • Combine/finalize merges signer-contributed partial signatures and finalizes only when the threshold is met for the selected multisig leaf.

Constant-Time Requirements

  • Secret key buffers are zeroized on clear/destruction.
  • Sensitive comparisons use constant-time primitives.
  • Signature and verification timing does not depend on secret key material.

Forward Compatibility

  • P2MR supports OP_SUCCESS-style upgrade behavior through P2MR-specific checks.
  • Defined PQ opcodes are explicitly excluded from unconditional success handling.
  • P2MR annex data is parsed at consensus and remains non-standard under relay policy by default.