Skip to content

pocx: gate nonce submissions on actual privkey availability#3

Merged
JohnnyFFM merged 2 commits into
pocx-nightlyfrom
fix/havekey-watchonly-check
May 13, 2026
Merged

pocx: gate nonce submissions on actual privkey availability#3
JohnnyFFM merged 2 commits into
pocx-nightlyfrom
fix/havekey-watchonly-check

Conversation

@JohnnyFFM
Copy link
Copy Markdown
Collaborator

Summary

  • The submit_nonce receive-path gate (pocx::mining::HaveAccountKey) treated IsMine-equivalent script membership as proof of private-key ownership. Wallets loaded with a pubkey-only descriptor (importdescriptors with an xpub, or any watch-only setup) passed the gate; the scheduler then built unsigned blocks that signing dropped seconds later.
  • Replace isSpendable probe with the predicate the signer actually executes (CanProvide + GetPoCXPubKey under cs_wallet), mirroring CWallet::SignMessage for both loop shape and lock-ordering (cs_wallet -> cs_desc_man, see wallet/wallet.cpp:2215).
  • Return AccountKeyAvailability (Available / Locked / Absent) instead of bool so the RPC triage at pocx/rpc/mining.cpp keeps producing RPC_WALLET_UNLOCK_NEEDED for encrypted+locked wallets that would be able to sign once unlocked, rather than regressing them to the generic "no private key" error.
  • Apply the same locking idiom to SignPoCXBlock to close the matching latent deadlock window.
  • Correct the misleading "ScriptPubKeyMan failed to get public key" log line (the actual failure mode is missing private key material).

Refs PoC-Consortium/bitcoin-pocx#4

Test plan

  • Load a descriptor wallet with only wpkh(<xpub>/0/*) for a known PoCX account; point a plotter at the same account and submit a nonce. Expected: submitnonce rejects with RPC_INVALID_ADDRESS_OR_KEY before proof validation. Before this change: ACKs, then [Scheduler] Block signing failed at deadline.
  • Encrypt a wallet that holds the matching xprv, leave it locked, submit a nonce. Expected: submitnonce rejects with RPC_WALLET_UNLOCK_NEEDED ("unlock with walletpassphrase first"). Confirms the tri-state path preserves the operator-friendly error.
  • Unlock the same wallet and resubmit; nonce ACKs and the block forges/signs through to acceptance.
  • Regression smoke: run an existing mining sequence with a normal HD descriptor wallet end-to-end (nonce → forge → broadcast) on regtest.

JohnnyFFM and others added 2 commits May 12, 2026 22:18
…pt membership

The receive-path check in submit_nonce ultimately resolved to
DescriptorScriptPubKeyMan::IsMine(script), which is pure script-map
membership. Wallets loaded with pubkey-only descriptors (importdescriptors
with an xpub, watch-only setups) passed the gate and the scheduler then
built unsigned blocks that signing dropped on the floor seconds later.

Replace HaveAccountKey's isSpendable probe with the same predicate the
signer actually executes: iterate ScriptPubKeyMans, CanProvide as a cheap
script-only pre-filter, then GetPoCXPubKey under cs_wallet to verify a
private key is loadable. CWallet::SignMessage is the established Core
precedent for both the loop shape and the lock placement (cs_wallet ->
cs_desc_man order, see comment at wallet/wallet.cpp:2215).

Return AccountKeyAvailability instead of bool so the locked-vs-absent
triage in pocx/rpc/mining.cpp keeps producing RPC_WALLET_UNLOCK_NEEDED
for encrypted-and-locked wallets instead of regressing them to the
generic "no private key" error. SignPoCXBlock adopts the same idiom for
consistency and to close the matching latent deadlock window. Misleading
"failed to get public key" log line corrected to mention the actual
cause.

Refs PoC-Consortium/bitcoin-pocx#4
Drop the dead has_key flag from submit_nonce and replace the if/else if/else
chain with two unconditional throws (Locked, then Absent); Available falls
through. Same semantics, no intermediate state to track.

Add a one-line note on the AccountKeyAvailability forward declaration in
interfaces/wallet.h pointing at the underlying-type definition in
pocx/mining/wallet_signing.h, since the two must stay in sync for the
forward decl to remain valid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JohnnyFFM JohnnyFFM merged commit c207c27 into pocx-nightly May 13, 2026
11 of 28 checks passed
JohnnyFFM added a commit to PoC-Consortium/bitcoin-pocx that referenced this pull request May 15, 2026
Add scripts/mining/test-regtest-submitnonce-key-gate-v2.sh covering every
branch of the receive-path probe (HaveAccountKey) plus the multi-wallet
iteration semantics:

  A  Absent     watch-only descriptor; expect -5 + bech32 + no raw-hex regression
  B  Locked     encrypted+locked privkey; expect -13 + walletpassphrase hint
  C  Available  unlocked privkey; expect exit 0 + raw_quality/poc_time
  D  Multi      Locked + Available, Locked loaded first
                (probe must traverse past Locked to pick Available)
  E  Multi      Available + Locked, Available loaded first
                (later Locked must not downgrade an earlier Available)

D + E together prove the {Locked, Available} probe outcome is order-
independent, ruling out "break on first Locked" and "Locked overrides
later" regressions. Each case loads its wallets in isolation and unloads
between cases so the probe state under test is unambiguous.

Regression guards for:
  #4 (PR PoC-Consortium/bitcoin#3) - Absent/Locked split
  #3 (PR PoC-Consortium/bitcoin#2) - bech32 rendering

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JohnnyFFM JohnnyFFM deleted the fix/havekey-watchonly-check branch May 16, 2026 19:25
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