Skip to content

rpc: Add user-friendly RPCs for BIP-360 P2MR address creation and spending #32

@augchan42

Description

@augchan42

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:

  1. Build a Dilithium script tree manually (Python / custom code)
  2. Compute the Merkle root using P2MRBuilder logic
  3. Derive the bc1z address from the 32-byte witness program
  4. Fund the address via sendtoaddress (this part works)
  5. 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.cppSigVersion::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.hWitnessV2P2MR 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.cppWITNESS_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 P2MRBuilder to 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_CHECKSIGDILITHIUM with the pubkey
  • Computes Merkle root via P2MRBuilder, returns the bc1z address
  • Stores the Dilithium key, leaf script, and P2MRSpendData in the wallet DB
  • Analogous to getnewdilithiumaddress but 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.

  • IsMine support: Update scriptpubkeyman.cpp so WITNESS_V2_P2MR returns SPENDABLE when 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 in walletdb.cpp
  • Signing: Teach SignTransaction / ProduceSignature to construct the full P2MR witness stack: [dilithium_signature] [leaf_script] [control_block]
  • PSBT support: Wire walletprocesspsbt to handle P2MR inputs
  • Spend flow: Allow sendtoaddress / sendmany to select and spend P2MR UTXOs

4. P2MR descriptor support — ~2 days

  • New descriptor syntax, e.g., p2mr(dilithium(<xpub>)) for single-leaf or p2mr({script1,script2}) for multi-leaf trees
  • Enables importdescriptors for 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 call
  • fundp2mr <address> <amount> — fund an existing bc1z address

6. Functional tests — ~2 days

  • RPC-level tests for createp2mraddress and getnewp2mraddress
  • Wallet round-trip: getnewp2mraddress → fund → sendtoaddress (spend) → confirm
  • PSBT round-trip: create PSBT with P2MR input → walletprocesspsbtfinalizepsbt → 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions