-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Problem
BIP-360 P2MR (Pay-to-Merkle-Root) consensus support is merged to master, but there is no user-facing way to create or spend P2MR outputs. Today a user must:
- Build a Dilithium script tree manually (Python / custom code)
- Compute the Merkle root using
P2MRBuilderlogic - Derive the
bc1zaddress from the 32-byte witness program - Fund the address via
sendtoaddress(this part works) - Manually construct the witness stack (leaf script + control block + Dilithium signature) to spend
There is no RPC that automates steps 1-3 or 5. The goal is to let users create and spend P2MR outputs entirely through btq-cli / JSON-RPC without writing any custom code.
What exists today (on master)
| Layer | Status | Key locations |
|---|---|---|
| Consensus validation | Done | src/script/interpreter.cpp — SigVersion::P2MR_TAPSCRIPT, witness v2, Dilithium opcodes |
Address encoding (bc1z) |
Done | src/key_io.cpp — SegWit v2, bech32m encode/decode |
P2MRBuilder / P2MRSpendData |
Done | src/script/signingprovider.{h,cpp} — script tree → Merkle root |
CTxDestination variant |
Done | src/addresstype.h — WitnessV2P2MR in destination variant |
Sending TO a bc1z address |
Done | sendtoaddress / sendmany can fund bc1z outputs |
sign.cpp Schnorr signing |
Done | src/script/sign.cpp — handles SigVersion::P2MR_TAPSCRIPT |
| Policy / standardness | Done | src/policy/policy.cpp — P2MR witness stack validation |
| Functional tests | Done | test/functional/feature_p2mr.py — 15 test groups (uses MiniWallet, not wallet RPCs) |
Wallet IsMine |
Not done | src/wallet/scriptpubkeyman.cpp — WITNESS_V2_P2MR falls through to break (returns NO) |
| Wallet spend/sign | Not done | No P2MR witness construction in wallet signing path |
| Descriptor support | Not done | No p2mr() descriptor type in src/script/descriptor.cpp |
| P2MR-specific RPCs | Not done | No getnewp2mraddress, createp2mraddress, etc. |
What's missing
The wallet cannot generate P2MR addresses, recognize P2MR UTXOs as its own (IsMine returns false), or sign/spend P2MR outputs. All of these require new code.
Proposed work items
1. createp2mraddress RPC (stateless utility) — ~2-3 days
Create a P2MR address from externally-provided keys/scripts, without touching the wallet.
- Input: One or more Dilithium pubkeys + script structure (e.g., single-key, M-of-N, or raw leaf scripts as hex)
- Internally: Uses
P2MRBuilderto construct the script tree and compute the Merkle root - Output:
bc1z...address, Merkle root (hex), leaf scripts, control block templates - Analogy: Similar to Bitcoin Core's
createmultisig— a pure utility, no wallet state
2. getnewp2mraddress RPC (wallet-integrated) — ~2-3 days
Generate a new P2MR address backed by the wallet's Dilithium keys.
- Generates a fresh Dilithium keypair via
CDilithiumKey::MakeNewKey() - Wraps it in a single-leaf P2MR script tree:
OP_CHECKSIGDILITHIUMwith the pubkey - Computes Merkle root via
P2MRBuilder, returns thebc1zaddress - Stores the Dilithium key, leaf script, and
P2MRSpendDatain the wallet DB - Analogous to
getnewdilithiumaddressbut for the P2MR output type
Depends on: Wallet DB schema changes for P2MR spend data (item 3).
3. Wallet P2MR signing / spending support — ~4-5 days (largest item)
End-to-end wallet support for recognizing and spending P2MR UTXOs.
IsMinesupport: Updatescriptpubkeyman.cppsoWITNESS_V2_P2MRreturnsSPENDABLEwhen the wallet holds the corresponding Dilithium key + spend data- Wallet DB storage: Persist
P2MRSpendData(Merkle proofs, leaf scripts, control blocks) alongside Dilithium keys — new serialization inwalletdb.cpp - Signing: Teach
SignTransaction/ProduceSignatureto construct the full P2MR witness stack:[dilithium_signature] [leaf_script] [control_block] - PSBT support: Wire
walletprocesspsbtto handle P2MR inputs - Spend flow: Allow
sendtoaddress/sendmanyto select and spend P2MR UTXOs
4. P2MR descriptor support — ~2 days
- New descriptor syntax, e.g.,
p2mr(dilithium(<xpub>))for single-leaf orp2mr({script1,script2})for multi-leaf trees - Enables
importdescriptorsfor watch-only and signing-capable P2MR wallets - Required for descriptor wallets (the modern/default wallet type)
5. btq-cli convenience wrappers — ~1 day
sendtop2mr <amount>— generate a P2MR address + fund it in one callfundp2mr <address> <amount>— fund an existingbc1zaddress
6. Functional tests — ~2 days
- RPC-level tests for
createp2mraddressandgetnewp2mraddress - Wallet round-trip:
getnewp2mraddress→ fund →sendtoaddress(spend) → confirm - PSBT round-trip: create PSBT with P2MR input →
walletprocesspsbt→finalizepsbt→ broadcast - Multi-leaf P2MR tree: create 2-of-3 style tree, spend from different leaves
- Error cases: attempt to spend with wrong key, invalid control blocks
Phasing
| Phase | Items | Effort | Outcome |
|---|---|---|---|
| Phase 1 | 1 + 2 | ~5 days | Users can generate P2MR addresses via RPC |
| Phase 2 | 3 | ~5 days | Wallet can recognize, sign, and spend P2MR UTXOs |
| Phase 3 | 4 + 5 + 6 | ~5 days | Descriptor support, CLI convenience, full test coverage |
Total: ~15 days of dev work
Dependencies
- Phase 1 can ship independently — address generation is useful on its own
- Phase 2 depends on Phase 1 (wallet needs the address generation + storage from item 2)
- Phase 3 depends on Phase 2 (descriptors and tests need the signing infrastructure)
Quick win
Phase 1 alone (~5 days) gives users turnkey P2MR address creation. Spending would still require signtransactionwithdilithium with manually-provided witness data, but address generation would be fully automated.
Key files to modify
| File | Phase | Change |
|---|---|---|
src/wallet/rpc/dilithium.cpp |
1 | New RPC implementations |
src/rpc/dilithium.h |
1 | RPC declarations |
src/wallet/scriptpubkeyman.cpp |
2 | IsMine support for WITNESS_V2_P2MR |
src/wallet/walletdb.cpp |
2 | P2MRSpendData serialization |
src/wallet/spend.cpp |
2 | P2MR UTXO selection and spending |
src/script/sign.cpp |
2 | P2MR witness stack construction |
src/wallet/rpc/spend.cpp |
2 | Wire up spending RPCs for P2MR |
src/script/descriptor.cpp |
3 | p2mr() descriptor parsing and expansion |
test/functional/rpc_p2mr.py |
3 | New functional test file |
User workflow after completion
# Generate a P2MR address (wallet stores keys + spend data)
$ btq-cli getnewp2mraddress "savings"
bc1z...
# Send funds to it
$ btq-cli sendtoaddress "bc1z..." 10.0
# Later, spend from it (wallet handles witness construction automatically)
$ btq-cli sendtoaddress "<recipient>" 5.0
# Or use the stateless utility for multi-party P2MR
$ btq-cli createp2mraddress '["<dilithium_pubkey_1>", "<dilithium_pubkey_2>"]'
{
"address": "bc1z...",
"merkle_root": "abcdef...",
"leaf_scripts": [...],
"control_blocks": [...]
}Full scope document: docs/p2mr-rpc-scope.md