Skip to content

pos: descriptor wallet staking with Taproot and SegWit kernel support#400

Merged
reddink merged 11 commits into
reddcoin-project:developfrom
reddink:pr/descriptor-staking-rebased
Jul 16, 2026
Merged

pos: descriptor wallet staking with Taproot and SegWit kernel support#400
reddink merged 11 commits into
reddcoin-project:developfrom
reddink:pr/descriptor-staking-rebased

Conversation

@reddink

@reddink reddink commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

Enables descriptor wallets to participate in Proof-of-Stake. Previously only
legacy wallets could produce coinstakes and sign PoS blocks; this PR extends the
staking path (CreateCoinStake, SignBlock, CheckBlockSignature) to work with
descriptor wallets and adds support for Taproot key-path and P2WPKH stake
kernels, with the safety gating and test coverage to match.

The change is purely additive and backward compatible: no coinstake output
type that this PR handles could be produced or accepted before, so validation of
all existing blocks is unchanged, and legacy-wallet staking behaves exactly as it
did. It remains interoperable with v4.22.9 followers.

What's included

Descriptor wallet staking (source)

  • CreateCoinStake now iterates all ScriptPubKeyMans and uses CanProvide /
    the wallet-appropriate GetKey to locate keys, covering PUBKEYHASH,
    WITNESS_V0_KEYHASH, PUBKEY, and WITNESS_V1_TAPROOT kernels. Signing is
    routed through the wallet's SignTransaction, which handles both wallet types.
  • SignBlock supports descriptor wallets: the coinstake output is P2PK while the
    descriptor wallet tracks P2PKH internally, so it reconstructs the P2PKH script
    from the keyid to resolve the signing key (plus a P2WPKH key-lookup path).

Taproot key-path staking

  • Adds WITNESS_V1_TAPROOT as a valid kernel type and preserves the taproot
    scriptPubKey in the coinstake output (key-path spend, no downgrade to a legacy
    type, privacy preserved). Script-path outputs naturally fail to sign, which is
    correct for autonomous PoS.
  • Adds a BIP340 Schnorr signing/verification path
    (DescriptorScriptPubKeyMan::SignBlockSchnorr + XOnlyPubKey::VerifySchnorr)
    so taproot-staked blocks can actually be signed and validated. Only descriptor
    wallets hold taproot keys, and taproot descriptors are off by default.

SegWit-activation safety

  • CreateCoinStake skips witness UTXOs (P2WPKH/P2WSH/taproot) as stake kernels
    while DEPLOYMENT_SEGWIT is not yet active, preventing blocks with unexpected
    witness data from being created during BIP9 STARTED/LOCKED_IN.

Testing

  • wallet_descriptor_staking.py — end-to-end PoS staking with a descriptor wallet.
  • feature_descriptor_legacy_staking.py — descriptor⇄legacy cross-validation, plus
    a UTXO-exhaustion case proving the descriptor wallet recognises and recycles P2PK
    coinstake outputs (100+ PoS blocks from 89 coinbase UTXOs).
  • wallet_descriptor_staking_witness.py — P2WPKH-skip before SegWit, then P2WPKH
    and tr() taproot kernel staking once SegWit/Taproot are active.
  • scriptpubkeyman_tests.cppDescriptorGetKey unit coverage, plus an
    importaddress guard that asserts on descriptor wallets.

Documentation

  • docs/wallet_descriptor_staking.md and docs/feature_descriptor_legacy_staking.md
    cover test structure, the P2PK fallback mechanics, UTXO-exhaustion validation, and
    cross-wallet interoperability.

Compatibility

Additive only — existing block validation is unchanged and legacy staking is
unaffected. Compatible with v4.22.9 followers.

Commits (11)

  • feat: add Taproot key-path staking support
  • pos: Add descriptor wallet support for coinstake creation
  • pos: Add descriptor wallet support for block signing
  • test: Add descriptor wallet tests and safety checks
  • test: Add functional test for descriptor wallet staking
  • test: Add descriptor-legacy wallet staking interop test
  • docs: Add descriptor wallet staking test documentation
  • fix: Support P2WPKH key lookup in SignBlock for descriptor wallets
  • pos: Skip witness UTXOs for staking before SegWit activation
  • pos: implement taproot key-path block signing for descriptor staking
  • test: cover P2WPKH and taproot kernel staking for descriptor wallets

Notes

Rebased onto the current develop. develop already registers
wallet_descriptor_staking.py and feature_descriptor_legacy_staking.py (they
landed via the test-framework / test-adaptations merges), so the branch's original
"Register descriptor staking tests in test runner" commit became redundant and was
dropped during the rebase (12 → 11 commits). The only test_runner.py change vs
develop now is registering the new wallet_descriptor_staking_witness.py.

@codacy-production

codacy-production Bot commented Jul 15, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 125 complexity · 5 duplication

Metric Results
Complexity 125
Duplication 5

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

reddink added 11 commits July 16, 2026 13:59
Enable Taproot outputs to participate in Proof-of-Stake by supporting
WITNESS_V1_TAPROOT as a valid kernel type. This implementation:

- Adds WITNESS_V1_TAPROOT to allowed output types for staking
- Preserves Taproot script in coinstake output (key-path spending)
- Maintains privacy by not converting to legacy output types
- Naturally rejects script-path outputs requiring coordination

Only single-key Taproot outputs (key-path) can stake autonomously.
Multi-party script-path outputs will fail to sign, which is correct
behavior for PoS that requires autonomous block generation.

This change is required for Taproot soft fork activation and enables
Taproot adopters to participate in network security through staking.

(cherry picked from commit 0a6c55d62c055949a4695c7c7e965007a63efd5d)
Update CreateCoinStake to support both legacy and descriptor wallets:

- PUBKEYHASH/WITNESS_V0_KEYHASH: Iterate all ScriptPubKeyMans to find
  keys, using CanProvide to check ownership and appropriate GetKey
  method based on wallet type

- PUBKEY: Add key ownership verification by extracting keyid from
  pubkey and checking all ScriptPubKeyMans

- WITNESS_V1_TAPROOT: Add key ownership verification using CanProvide
  on all ScriptPubKeyMans before preserving the taproot output

- Signing: Replace legacy SignSignature with wallet's SignTransaction
  which handles both wallet types internally

This enables PoS staking with descriptor wallets.

(cherry picked from commit 286a856cbce4ed4e2b4c23d2204ab2b4ff135cf6)
Update SignBlock to support both legacy and descriptor wallets by
iterating all ScriptPubKeyMans to find the signing key.

For descriptor wallets, the coinstake output uses P2PK format but the
wallet internally tracks P2PKH scripts. Construct a P2PKH script from
the keyid to query the descriptor wallet's GetKey method.

This enables PoS block signing with descriptor wallets.

(cherry picked from commit feb5c41789c50bea54f48e3af0f08e8ebc07cdcd)
- Add DescriptorGetKey unit test to verify DescriptorScriptPubKeyMan
  can retrieve private keys for owned scripts and fails appropriately
  for unknown scripts

- Add safety check in importaddress test utility to assert when called
  with a descriptor wallet, since legacy import is not supported

(cherry picked from commit 1237f6a1717cecdcb1e2222a043858e8b322094f)
Add wallet_descriptor_staking.py to verify end-to-end PoS staking
works with descriptor wallets:

- Creates a descriptor wallet
- Generates PoW blocks to fund the wallet
- Generates PoS blocks using the descriptor wallet for staking
- Verifies mature balance and block generation

Uses ReddCoin's regtest COINBASE_MATURITY of 60 blocks.

(cherry picked from commit e96ddbc5fe32b9620126f936fd4f2c004646ac77)
Verify that descriptor and legacy wallets can both stake PoS blocks
and that blocks produced by each wallet type are accepted by the other.

The test creates a 2-node network (descriptor wallet on node0, legacy
wallet on node1), generates PoW/PoS blocks, transfers coins between
nodes, and confirms cross-validation of staked blocks.

Includes a UTXO exhaustion test: the descriptor wallet stakes 100+
PoS blocks from only 89 original coinbase UTXOs, verifying that
P2PK coinstake outputs are correctly recognized and recycled by the
descriptor wallet's IsMine/GetSigningProvider P2PK fallback.

Key technical details:
- Connects nodes before setting mocktime to avoid InactivityCheck
  disconnections (nTimeConnected uses real time, InactivityCheck
  uses mocktime)
- Uses -peertimeout=999999999 to prevent P2P timeout disconnections
  during large mocktime advances
- Splits balance into 40 additional UTXOs at separate descriptor
  indices to bridge the 61-block coinstake maturity gap
- Sends 10x1,000,000 RDD to node1 at separate addresses to prevent
  coinstake combine from grouping them

(cherry picked from commit 1b24914eb28e13d3f153dee01056a6bcb86a9fd8)
Add documentation for wallet_descriptor_staking.py and
feature_descriptor_legacy_staking.py covering test structure,
P2PK fallback mechanics, UTXO exhaustion validation, and
cross-wallet interoperability.

(cherry picked from commit bf9febef89beaa07b6634f876179ba6c0cf274a1)
(cherry picked from commit 6aa1f5861d4cc0faa025aa6cbb59c58f7fa1eee4)
The staking code selects wallet UTXOs for coinstake without checking
whether SegWit is active. If a witness UTXO (P2WPKH, P2WSH, taproot)
is selected before activation, the resulting block contains witness
data and gets rejected with "unexpected witness data" during
ContextualCheckBlock.

Add a check at the start of the coin iteration loop in CreateCoinStake
to skip witness output types when DeploymentActiveAfter returns false
for DEPLOYMENT_SEGWIT. This prevents invalid block creation during the
BIP9 STARTED/LOCKED_IN phases.

(cherry picked from commit 2b85b7b8f7e05ac5d2b7597347ab0b394c6ff6d1)
CreateCoinStake can select a taproot (WITNESS_V1_TAPROOT) UTXO as a stake
kernel and preserves the taproot scriptPubKey as the coinstake output, but
SignBlock and CheckBlockSignature only handled P2PK coinstake outputs, so a
taproot-staked block could never be signed or validated (SignBlock returned
'Failed to sign PoS block').

Add a BIP340 Schnorr path. DescriptorScriptPubKeyMan::SignBlockSchnorr
resolves the taproot output key to its internal key and merkle root via the
descriptor signing provider and signs the block hash with the taproot tweak
applied, so the 64-byte signature verifies against the output key. SignBlock
dispatches taproot coinstake outputs to it; CheckBlockSignature verifies them
with XOnlyPubKey::VerifySchnorr.

Purely additive: no taproot-coinstake block could exist before (none could be
signed), so validation of existing blocks is unchanged. Only descriptor
wallets hold taproot keys, and taproot descriptors are not enabled by default.
The existing descriptor staking tests stake only from legacy (P2PKH)
addresses. Add wallet_descriptor_staking_witness.py covering the witness
kernel paths in CreateCoinStake:

  Phase 1 (SegWit inactive): a P2WPKH UTXO must be skipped as a stake kernel
  so the coinstake never embeds witness data rejected as unexpected-witness,
  while the wallet keeps staking from legacy UTXOs.

  Phase 2 (SegWit + Taproot active): drive BIP9 activation, then stake from
  wallets funded only with P2WPKH or (imported tr() descriptor) taproot UTXOs,
  exercising the WITNESS_V0_KEYHASH and WITNESS_V1_TAPROOT kernel branches and
  the taproot block-signing path.

Witness UTXOs are buried deeply before staking since freshly-confirmed coins
are not yet stakeable; success is asserted by a consumed kernel UTXO.
@reddink
reddink force-pushed the pr/descriptor-staking-rebased branch from 2c4e87f to 814a0fc Compare July 16, 2026 04:04
@reddink
reddink merged commit a899df7 into reddcoin-project:develop Jul 16, 2026
1 check passed
@reddink
reddink deleted the pr/descriptor-staking-rebased branch July 16, 2026 05:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant