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
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