Repository navigation
Releases
qbit-libbitcoinpqc v0.3.2
Patch release of qbit-libbitcoinpqc from main. It reports inconsistent secret keys and allocation failures from the C API with specific error codes, and extends the unoptimized-build stack burn to cover Clang. Keys, signatures and verification results are byte-identical to 0.3.1 and 0.3.0.
Changes
- Signing reports an inconsistent secret key as
BAD_KEY(#10).bitcoin_pqc_signandbitcoin_pqc_sign_with_statsreturnBITCOIN_PQC_ERROR_BAD_KEY(-2) for a secret key that failsbitcoin_pqc_secret_key_validate, instead ofBITCOIN_PQC_ERROR_BAD_SIGNATURE(-3). This includes a corrupted SK_SEED, PUB_SEED or root. The Rustsignandsign_with_statsreturnPqcError::BadKeyinstead ofPqcError::BadSignature. The facadeslh_dsa_signreturnsSLH_DSA_SIGN_ERROR_BAD_KEY(-101) instead of -1.BAD_SIGNATUREfrom signing now means only that the signing operation itself failed. - Allocation failures are reported as
OUT_OF_MEMORY(#10). The new append-onlyBITCOIN_PQC_ERROR_OUT_OF_MEMORY(-6) replacesBAD_ARGfor a failed key or signature output buffer, andBAD_KEYfor a failed keygen seed-derivation buffer, which grows with the entropy size. The output struct stays empty.slh_dsa_keygenreturnsSLH_DSA_KEYGEN_ERROR_OUT_OF_MEMORY(-102). In Rust, -6 reaches callers asPqcError::Other(-6), displayed asUnexpected error code: -6. A dedicated variant is planned for 0.4.0. slh_dsa_signcleans its outputs on rejected arguments too (#10). Form == NULLwithmlen > 0, or a NULLsig,siglenorsk, it zeroessigand sets*siglen = 0for whichever pointer is non-NULL. It already did this on every other failure.- Larger stack burn in unoptimized builds (N1 from the 0.3.1 review). Unoptimized GCC/Clang builds burn 288 KiB, up from 208 KiB. Clang
-O0keygen, validation and signing use about 242 KiB, so about 34 KiB of their frames stayed unburned in 0.3.1. Optimized builds are unchanged.slh_dsa_stack_scrub_testnow fails when a call runs deeper than the burn. - Test and build changes with no effect on the library:
- Version metadata is
0.3.2.docs/vendored-crypto-provenance.mdlists the 0.3.2 changes.
Compatibility
- Rust API unchanged from 0.3.0 and 0.3.1.
PqcErrorkeeps exactly the 0.3.0 variant set; a guard test fails to compile if a variant is added.cargo semver-checksreports no semver update required against v0.3.0 and v0.3.1. - C API and ABI: additive only.
bitcoin_pqc_error_tgainsBITCOIN_PQC_ERROR_OUT_OF_MEMORY = -6; existing values are unchanged.slh_dsa.hgainsSLH_DSA_SIGN_ERROR_BAD_KEYandSLH_DSA_KEYGEN_ERROR_OUT_OF_MEMORY.- No function is added, removed or changed.
- The production static library exports the same symbols as 0.3.1.
- Error-code changes for callers that branch on codes. Callers that treat
BAD_SIGNATURE/BadSignaturefrom signing as "corrupt key" should also handleBAD_KEY/BadKey. Callers that check allocation failure asBAD_ARG/BadArgument(orBAD_KEYfrom keygen) should also handle -6 orOther(-6). A Cswitchoverbitcoin_pqc_error_twith nodefaultlabel now gets a-Wswitchwarning for the new value. - Outputs unchanged. The golden vectors pass unmodified. A public-API corpus produces byte-identical output against v0.3.0, v0.3.1 and 0.3.2, with clang and gcc. It covers 8 keypairs, 96 signatures with sign stats, and 2,968 verify results over bit-flipped, truncated, extended, wrong-key, wrong-message and corrupted-key inputs.
Stack Requirements
Each secret-key call overwrites BITCOIN_PQC_BURN_STACK_BYTES of stack below its caller before returning:
- 16 KiB in optimized native builds (
__OPTIMIZE__), unchanged. Measured optimized keygen and sign use at most 13.1 KiB below the entry point (GCC 15 and Clang 21,-O1to-O3,-Og,-Os). - 288 KiB in unoptimized GCC/Clang builds (Cargo
devprofile, CMakeDebug), up from 208 KiB. Unoptimized calls use about 200 KiB with GCC and 242 KiB with Clang. A call needs about 289 KiB of free stack. - 4 KiB under Emscripten, unchanged.
- Sanitizer-instrumented unoptimized builds can run deeper than the burn: Clang
-O0UBSan uses about 390 KiB. DefineBITCOIN_PQC_BURN_STACK_BYTESat build time to cover a measured depth.
Verification does not burn.
Performance
Measured with clang -O3 (CMake Release) on a shared x86-64 Linux host: 6 runs of 20 iterations each, 3 with v0.3.1 first and 3 with 0.3.2 first. Each figure is a per-run median.
| Call | 0.3.1 | 0.3.2 |
|---|---|---|
| Sign | 144.2–148.9 ms | 145.9–149.6 ms |
| Keygen | 10.25–10.40 ms | 10.41–10.96 ms |
| Secret-key validation | 10.22–10.45 ms | 10.39–10.55 ms |
| Verify | 1.85–1.90 ms | 1.84–1.95 ms |
No crypto code changes. Of the 26 library objects, only src/bitcoinpqc.c, src/slh_dsa/keygen.c and src/slh_dsa/sign.c differ in machine code, all in #10's error-code handling. Every vendored SPHINCS+ object, validate.c and verify.c have byte-identical .text, so the 1–5% keygen difference is host noise and code layout, not added work. Optimized builds burn the same 16 KiB as 0.3.1. Unoptimized builds burn 80 KiB more per secret-key call.
Security And Signing Posture
The wipes and the stack burn reduce accidental disclosure of secret-key material through released stack memory. They are not side-channel hardening.
Signing remains deterministic, and deterministic signing is not side-channel hardening. Randomized or hedged signing is not available. Production signing should be limited to trusted local wallet processes, dedicated signing hosts, or comparably isolated key-custody infrastructure. This release does not claim signing support on hostile shared hardware, or resistance to physical side-channel attackers or fault injection. See docs/side-channel-policy.md.
Downstream Notes
- Importers on 0.3.1 get the error-code changes above. Re-run any test that asserts sign or keygen error codes.
- Debug builds of the library need up to 80 KiB more stack per secret-key call than 0.3.1 (about 47 KiB more with Clang, whose calls already used 242 KiB).
- Any
contrib/testgen/*/Cargo.lockthat pinsqbit-libbitcoinpqc0.3.1 needs the 0.3.2 bump.
Validation
See the evidence table and the independent review in the release PR (#17). Upstream CI passed on the release PR. The release commit 1965d0da1e has the same tree as the reviewed head c015787b47. Attached: a CycloneDX SBOM and the cargo-semver-checks log. Review follow-ups are tracked in #18.
qbit-libbitcoinpqc v0.3.1
Patch release of qbit-libbitcoinpqc, cut from v0.3.0. It wipes SLH-DSA secret-key material from the stack before key generation and signing return. Keys, signatures and verification results are byte-identical to v0.3.0.
Changes
- Secret-key scratch is zeroed before return (#5).
crypto_sign_seed_keypairandcrypto_sign_signaturereturn through one cleanup path that wipes the stackspx_ctx(an exact SK_SEED copy) andoptrand.prf_addr,gen_message_random, the SHA-2 finalize helpers,sha256()/sha512()and the AVX2 x8 helpers wipe their local scratch. FORS+C salt-counter exhaustion, which cannot be reached under the signing cap, now also resets*siglen. - The released stack is burned once per secret-key call (#6).
slh_dsa_keygen,slh_dsa_sign/slh_dsa_sign_with_stats,slh_dsa_secret_key_validateandslh_dsa_derandomizeoverwriteBITCOIN_PQC_BURN_STACK_BYTESof the stack they released, on success and failure paths. This clears SHA-2 message schedules that compiler spills leave behind, which can be inverted to SK_SEED, SK_PRF and derived seeds. Threaded FORS workers (test/benchmark knobSPX_FORS_THREADS) burn their own stacks. - Sanitizer-clean wipe loop (from #14). The
secure_zero_localhelpers count down without wrappinglen, so-fsanitize=unsigned-integer-overflowbuilds do not trap. - crossbeam-epoch 0.9.20 (RUSTSEC-2026-0204, from #4). Lockfile only. The crate reaches the lockfile only through the
criteriondev-dependency. - Version metadata is
0.3.1.docs/vendored-crypto-provenance.mdlists the vendored SPHINCS+ files this release changes.
Not included: #10 (new PqcError::OutOfMemory variant and changed C error codes; this needs a minor release), #4's backend-selection test helpers, #13, and the CI and ruleset changes on main.
Compatibility
- API and ABI unchanged. No public header declares a new or changed function. The only header change is a comment documenting the stack requirement. The Rust sources (
*.rs, includingbuild.rs) are byte-identical tov0.3.0. The library objects export two new internal symbols,slh_dsa_secret_key_validate_unburnedandslh_dsa_derandomize_unburned, which no header declares. - Outputs unchanged. The golden vectors pass unmodified. A C harness linked against the
v0.3.0and0.3.1static libraries produces byte-identical keys and signatures (4 keypairs and 16 signatures, 59,264 bytes, sha2565f0a2ba45b04406b…).
Stack Requirements
Each secret-key call now overwrites BITCOIN_PQC_BURN_STACK_BYTES of stack below its caller before returning:
- 16 KiB in optimized native builds (
__OPTIMIZE__). Optimized keygen and sign use about 13 KiB below the entry point, so a call needs about 17 KiB of free stack. - 208 KiB in unoptimized GCC/Clang builds (Cargo
devprofile, CMakeDebug), where the-O0AVX2 transform frame alone is about 190 KiB. - 4 KiB under Emscripten.
- Defining
BITCOIN_PQC_BURN_STACK_BYTESat build time overrides the size.
Optimized keygen, validation and signing run on a 64 KiB thread stack (checked with a pthread_attr_setstacksize(65536) thread on x86-64 Linux, clang -O3). Unoptimized builds need more than 208 KiB. Verification does not burn.
Performance
Measured with clang -O3 (CMake Release) on a shared x86-64 Linux host: 3 interleaved runs of 20 iterations, v0.3.0 against 0.3.1.
| Call | 0.3.0 | 0.3.1 |
|---|---|---|
| Sign | 144–147 ms | 158–164 ms (about +10%) |
| Keygen | 9.5–9.8 ms | 10.6–11.1 ms (about +12%) |
| Secret-key validation | 9.6–9.7 ms | 10.7–11.1 ms (about +12%) |
| Verify | 1.80–1.86 ms | 1.82–1.88 ms (about +1%) |
Sign-time key validation. slh_dsa_sign and slh_dsa_sign_with_stats validate the secret key before signing by re-deriving its public root. That costs about 10.7 ms, about 6.7% of a sign. The check was already in 0.3.0. 0.3.1 only routes it through an internal variant that does not burn, so a sign burns the stack once. Callers who validate a key on import and then sign pay for validation twice.
Verification gains only the SHA-2 helper wipes, because it shares those helpers with signing. It does not burn the stack.
Security And Signing Posture
The wipes and the stack burn reduce accidental disclosure of secret-key material through released stack memory. They are not side-channel hardening.
Signing remains deterministic, and deterministic signing is not side-channel hardening. Randomized or hedged signing is not available. Production signing should be limited to trusted local wallet processes, dedicated signing hosts, or comparably isolated key-custody infrastructure. This release does not claim signing support on hostile shared hardware, or resistance to physical side-channel attackers or fault injection. See docs/side-channel-policy.md.
Validation
See the evidence table and the independent review in the release PR (#15). Upstream CI ran on the release commit on a temporary develop branch.
qbit-libbitcoinpqc v0.3.0
Initial public Qbit release of qbit-libbitcoinpqc.
This release preserves the upstream pre-fork libbitcoinpqc history through baseline commit 09cdb3e2426c5367ced613751ddcbc8c6f9fbcf2, then squashes Qbit-specific development into a single release commit.
Highlights
- Renames the Cargo package to
qbit-libbitcoinpqc. - Keeps the exported Rust crate name as
bitcoinpqc. - Keeps the C library target, headers, and
bitcoin_pqc_*ABI namespace unchanged. - Sets the core library version to
0.3.0. - Narrows the public release surface to the Qbit-supported C and Rust APIs.
- Removes non-production bindings and unused crypto payload from the public release tree.
Supported Profile
This release supports the Qbit bounded SLH-DSA profile only:
SLH-DSA-SHA2-128s-bounded30- Public key size:
32bytes - Secret key size:
64bytes - Signature size:
3680bytes
The public API is intentionally single-profile and does not expose algorithm selection.
Production Surface
Supported release surfaces:
- Native C API headers under
include/libbitcoinpqc/ - CMake target
bitcoinpqc - Rust crate
bitcoinpqcfrom Cargo packageqbit-libbitcoinpqc
Python and Node.js bindings are not part of this public production release.
Security And Signing Posture
This release includes hardening around malformed input rejection, fail-closed signing behavior, secret-key validation, secret cleanup, fuzz/sanitizer evidence, and deterministic verification behavior.
Signing remains deterministic and is not a side-channel-hardening guarantee. Production signing should be limited to trusted local wallet processes, dedicated signing hosts, or comparably isolated key-custody environments.
Supply Chain Notes
- GitHub Actions external action refs are SHA-pinned.
- Rust advisory audit passes with
cargo audit --deny warnings. - Vendored SPHINCS+ source is based on upstream
sphincs/sphincspluscommit7ec789ace6874d875f4bb84cb61b81155398167e. - The initial vendored SPHINCS+ snapshot differs from that upstream commit by one local entropy adapter,
ref/randombytes_custom.c, and by omitting two upstream test-only headers:ref/test/cycles.handshake-a64/test/cycles.h. - Current Qbit sources then apply the documented bounded-profile, hardening, and public release-surface changes.
Validation
Release-prep validation included:
cargo check --lockedcargo check --manifest-path fuzz/Cargo.toml --lockedcargo fmt -- --checkcargo audit --deny warningsruby scripts/check-workflow-action-pins.rbscripts/check-source-inventory.shscripts/check-no-tracked-symlinks.shgit diff --check