Skip to content

doc: document bounded30 usage-dependent security margin and raw-key counter limits - #264

Merged
kiwidream merged 2 commits into
1.x.xfrom
kiwidream/rg-261-bounded30-security-docs
Oct 30, 2026
Merged

kiwidream merged 2 commits into
1.x.xfrom
kiwidream/rg-261-bounded30-security-docs

Conversation

@kiwidream

@kiwidream kiwidream commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Summary

Refs #261.

Targets the v1.0.1 release line (1.x.x). 1.x.x is frozen for v1.0.1 (#181), so a maintainer must lift the freeze before merging, and the v1.0.1 release notes in #181 need this entry (fragment: doc/release-notes-264.md).

Docs and RPC help only. No consensus, signature-format, or counter-behaviour change.

1. Usage-dependent security margin. New non-normative note doc/reference/p2mr-signature-security.md:

  • Corrects the "7 × 16 = 112 bits" reading of FORS+C: crypto_sign_verify rejects any digest whose compressed (8th) tree index is not 0, so a forger must also meet that 2^-16 condition. Single-use bound is k·a = 128 bits.
  • Gives the standard SPHINCS+ few-time FORS bound per H_msg query, Σ_r Poisson(r; q/2^30) · 2^-16 · (1 − (1 − 2^-16)^r)^7, with a stdlib-only Python script and its output (reproduced below).
  • Plain guidance: the full 128-bit classical level up to about 2^26 signatures per key (exact crossover 2^26.64; 2^26 is the rounded-down planning figure), about 118 bits at the 2^30 cap; rotate high-volume keys well before 2^30.
  • Explains that shared (tree, leaf) pairs are expected (50% at ~38.6k signatures) and share a hypertree path by design; maps the wallet warning/critical thresholds onto the curve; tabulates which signing paths track the 2^30 counter (wallet-held keys, signrawtransactionwithkey, descriptorprocesspsbt, copies/backups, external signers).

Short cross-references were added to doc/user/bitcoin-core-differences.md (which now also states that the 2^30 limit is a signer-side contract that consensus does not enforce), doc/user/wallet/p2mr-wallets.md, doc/user/wallet/psbt.md, doc/user/wallet/descriptors.md, doc/integration/rpc-delta-reference.md (Raw Transaction Signing and PSBT Handling), and doc/reference/README.md.

2. Raw-key RPC help.

  • signrawtransactionwithkey and descriptorprocesspsbt now say PQC signature counts for keys passed to them are not tracked across calls (each call treats every key as unused) and the caller is responsible for keeping each key within the 1073741824-signature limit.
  • importdescriptors now says imported keys' counters do not include signatures the same keys made outside this wallet.
  • These are the only RPCs that sign with caller-supplied PQC secrets (utxoupdatepsbt hides secrets; signmessagewithprivkey is legacy-only and disabled on P2MR-only chains).
  • The task brief suggested "…unless supplied as descriptor counters". That clause was not added because it would be false: descriptor syntax carries no counter. ParseDescriptorPQCPrivateKey merges expanded_keys.pqc_sig_counters, but ExpandPrivate never populates it, so every raw-key call starts from 0.
  • RPC docs (Build versioned RPC docs pages #148): rpc-docs.json is generated from the binary by the rpcdocs-json target; there are no checked-in per-release help snapshots or annotations for these RPCs, so nothing needed regenerating. I built rpcdocs-json and confirmed the new text is present.

3. doc/consensus/p2mr-v1.md §9. The line "qbit wallet signing counters enforce the 2^30 contract operationally" now distinguishes wallet-held keys (enforced by wallet counters) from caller-supplied keys signed outside a wallet (caller's responsibility), without naming specific RPCs in the normative profile, and a one-paragraph pointer to the non-normative note was added. No MUST/SHOULD text and no §10 text changed (§10 belongs to #257). Binding check: p2mr_v1_manifest.json names the spec by path only; ci/release/verify_p2mr_v1_conformance.py reads the spec blob from the signed tag target and requires it to be unchanged since the reviewed specification_commit. Edits are therefore allowed on this line, but release review evidence must cover the final §9 text (as it must for #252/#257). The analysis itself lives outside the normative spec.

4. Upstream note (no qbit file changed). The vendored parameter explorer src/libbitcoinpqc/docs/sphincs-parameter-explorer.html scores security as k·a only, ignoring the usage-dependent FORS term. That fix belongs upstream in Qbit-Org/qbit-libbitcoinpqc. src/libbitcoinpqc is untouched.

Reproduced table

Model: per-key, per-H_msg-query forgery probability eps(q) = Σ_{r≥1} Poisson(r; q/2^30) · 2^-16 · (1 − (1 − 2^-16)^r)^7; bits = −log2 eps(q). Poisson matches exact Binomial(q, 2^-30) to 4 decimal places at every row.

SLH-DSA-SHA2-128s-bounded30 (h=30, a=16, k=8 with FORS+C)
signatures per key   classical bits
2^20                  137.9
2^25                  131.3
2^26                  129.4
2^27                  127.2
2^28                  124.6
2^29                  121.7
2^30                  118.2
2^31                  114.2
2^32                  109.6
128-bit crossover    2^26.64 signatures
Reference: SPHINCS+-128s (h=63, a=12, k=14) at 2^64 signatures: 133.7 bits

The 16-byte hash output still caps the overall level at about 128 bits, so figures above 128 are model output only.

Testing

  • Built locally (cmake --build build -j12, RelWithDebInfo, wallet, tests, bench).
  • Ran focused unit or functional tests for the changed area.
  • Ran lint or formatting checks relevant to this change.
  • Not run. Reason:

Exact checks:

  • build/bin/test_qbit (full unit suite): passed, "No errors detected".
  • test/functional/test_runner.py rpc_help.py rpc_signrawtransactionwithkey.py wallet_importdescriptors.py rpc_psbt.py: 4/4 passed.
  • test/lint/lint-qbit-public-docs.py: passed. test/lint/check-doc.py, test/lint/lint-files.py: passed.
  • python3 -m unittest ci.release.test_verify_p2mr_v1_conformance: 31 tests OK (includes CheckedInP2MRIntegrationMatrixTest from the public-docs-lint job).
  • cmake --build build --target rpcdocs-json, then confirmed the new help text appears in build/doc/rpc/rpc-docs.json. test/rpc_docs/test_*.py: all OK (1 skipped: mkdocs is not installed locally).
  • Ran the embedded script from the new doc and diffed its output against the documented output: identical. Cross-checked with exact binomial, the critical threshold (2^30 − 2^24 → 118.3 bits), and the birthday figure (38,581).
  • Manually rendered help signrawtransactionwithkey, help descriptorprocesspsbt, and help importdescriptors on regtest.
  • Not run: lint-spelling.py (codespell is not installed on the host); ctest (host-memory restriction, see release-gate rules).

Target Branch

  • This PR targets main or a maintainer-requested release branch such as 0.1.x.

Targets 1.x.x (v1.0.1 release line), base 4fb9dbeac3.

Risk / Review Notes

  • Consensus, script, crypto, wallet, P2P, release, CI, or security-sensitive behavior changed.
  • No consensus, script, crypto, wallet, P2P, release, CI, or security-sensitive behavior changed.

Notes: The only code changes are RPC help strings. The security statement is a judgment call, so reviewers should check the model, not just the arithmetic.

Engineering practices applied:

  • EP-COMPAT: traced every RPC that accepts PQC secrets and every counter source (ParseExplicitPQCSecret, ParseDescriptorPQCPrivateKey, FlatSigningProvider::SignPQC, wallet m_map_pqc_sig_counters). The help now matches the contract all of those paths actually implement. No serialized or RPC result format changed.
  • EP-VALIDATION / EP-STATE / EP-ERRORS: not applicable (no input handling, cache, or failure path changed).

Docs / Process Impact

  • I updated public docs because this PR changes user-visible behavior, integration guidance, release/process guidance, or expected validation.
  • No public docs update needed. Reason:

libbitcoinpqc Subtree Checklist (if src/libbitcoinpqc changed)

Not applicable: src/libbitcoinpqc is unchanged.

🤖 Generated with Claude Code

kiwidream and others added 2 commits October 30, 2026 10:29
…mits

Add a non-normative note, doc/reference/p2mr-signature-security.md, on
how the SLH-DSA-SHA2-128s-bounded30 security margin depends on the
number of signatures per key. FORS+C still gives k*a = 128 bits for a
single use, because verification rejects a nonzero compressed index.
The standard SPHINCS+ few-time FORS bound gives the full 128-bit level
up to about 2^26.6 signatures and about 118 bits at the 2^30 limit.
The note includes a script that reproduces these figures.

Make the signrawtransactionwithkey, descriptorprocesspsbt and
importdescriptors help say that PQC signature counts for caller-supplied
keys are not tracked across calls, so the caller is responsible for the
2^30-per-key limit. In P2MR v1 section 9, separate wallet-held keys,
whose counters enforce the contract, from caller-supplied keys. Add
cross-references from the user, wallet, PSBT, descriptor and RPC-delta
docs.

No consensus, signature-format or counter-behaviour change.

Refs #261

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T14:34:49.989410Z b0bc0e2 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@kiwidream

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: b0bc0e2d72

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kiwidream
kiwidream merged commit d38cbd8 into 1.x.x Oct 30, 2026
44 checks passed
@kiwidream kiwidream mentioned this pull request Nov 2, 2026
5 of 9 tasks
kiwidream added a commit that referenced this pull request Nov 2, 2026
Fold the release-notes fragments of #264, #265, #266 and #268 into
doc/release-notes-1.0.1.md and delete them, and write the #263 entry
from its pull request. #263's new P2MR stack-byte standardness rule is
also listed under the behavior changes.

Correct claims these merges made stale:
- the fuzz-evidence limitation said later changes touch no fuzzed code;
  #263 and #265 change the interpreter that p2mr_script runs, and #268
  replaces the library that pqc and p2mr_script call, so fresh evidence
  on the release source is now required before tagging;
- the #162 entry said data signatures are unchanged (#265 caches them);
- the no-measurement line now points to #268's library cost figures;
- the #252 entry now notes the later #264 and #263 profile edits.

Also correct the #183 entry, which said every other encryptwallet
failure returns -16; some return -4 or -8.

Record the deferred items under Known limitations: the consensus
stack-byte cap (#257), uncached failed PQC signature checks (#259), and
the remaining AuxPoW limits and open review notes from #266 (#258).

Refs #174.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kiwidream kiwidream mentioned this pull request Nov 2, 2026
8 of 24 tasks
kiwidream added a commit that referenced this pull request Nov 2, 2026
Release notes:
- add an Updated RPCs section: bumpfee rejects a replacement whose
  signer changed anything but input signatures, and revalidates the
  replacement at commit time (#167, which shipped the unmerged #146);
  send entries can omit vout, address and label (#150); list the new
  bumpfee failures under the behavior changes as well;
- restate #268's cost from the release review's measurements, with the
  SHA backends they cover;
- add Known limitations for per-descriptor PQC signature counters, the
  qbit-qt Send dialog signing while it prepares, and the platforms on
  which the libbitcoinpqc stack-wipe depth is verified.

doc/consensus/p2mr-v1.md: correct the non-normative section 9 sentence
from #264, which implied wallet-wide per-key counting. Wallet counters
enforce the 2^30 contract per descriptor and per wallet copy. No
normative text changes, but the specification bytes do, so the P2MR v1
consensus review must cite a commit that contains this change.

share/examples/qbit.conf: show the -fallbackfee default from #151, as
contrib/devtools/gen-qbit-conf.sh generates it. The generator's other
difference, the lock debug category, appears only in Debug builds and
is left out.

Refs #174.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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