Skip to contentQbit Wallet is live — add it to Chrome
← All releases
Libraries

qbit-libbitcoinpqc v0.3.2

Release notes

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_sign and bitcoin_pqc_sign_with_stats return BITCOIN_PQC_ERROR_BAD_KEY (-2) for a secret key that fails bitcoin_pqc_secret_key_validate, instead of BITCOIN_PQC_ERROR_BAD_SIGNATURE (-3). This includes a corrupted SK_SEED, PUB_SEED or root. The Rust sign and sign_with_stats return PqcError::BadKey instead of PqcError::BadSignature. The facade slh_dsa_sign returns SLH_DSA_SIGN_ERROR_BAD_KEY (-101) instead of -1. BAD_SIGNATURE from signing now means only that the signing operation itself failed.
  • Allocation failures are reported as OUT_OF_MEMORY (#10). The new append-only BITCOIN_PQC_ERROR_OUT_OF_MEMORY (-6) replaces BAD_ARG for a failed key or signature output buffer, and BAD_KEY for a failed keygen seed-derivation buffer, which grows with the entropy size. The output struct stays empty. slh_dsa_keygen returns SLH_DSA_KEYGEN_ERROR_OUT_OF_MEMORY (-102). In Rust, -6 reaches callers as PqcError::Other(-6), displayed as Unexpected error code: -6. A dedicated variant is planned for 0.4.0.
  • slh_dsa_sign cleans its outputs on rejected arguments too (#10). For m == NULL with mlen > 0, or a NULL sig, siglen or sk, it zeroes sig and sets *siglen = 0 for 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 -O0 keygen, 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_test now fails when a call runs deeper than the burn.
  • Test and build changes with no effect on the library:
    • #4: backend-selection observers, compiled only with BITCOINPQC_ENABLE_TEST_HELPERS, and the differential test reporting requested and effective SHA backends
    • #13: allocator_may_return_null in the ASan smoke
    • #14: build.rs reruns when CC, CFLAGS or LDFLAGS change; UBSan CI steps
  • Version metadata is 0.3.2. docs/vendored-crypto-provenance.md lists the 0.3.2 changes.

Compatibility

  • Rust API unchanged from 0.3.0 and 0.3.1. PqcError keeps exactly the 0.3.0 variant set; a guard test fails to compile if a variant is added. cargo semver-checks reports no semver update required against v0.3.0 and v0.3.1.
  • C API and ABI: additive only.
    • bitcoin_pqc_error_t gains BITCOIN_PQC_ERROR_OUT_OF_MEMORY = -6; existing values are unchanged.
    • slh_dsa.h gains SLH_DSA_SIGN_ERROR_BAD_KEY and SLH_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/BadSignature from signing as "corrupt key" should also handle BAD_KEY/BadKey. Callers that check allocation failure as BAD_ARG/BadArgument (or BAD_KEY from keygen) should also handle -6 or Other(-6). A C switch over bitcoin_pqc_error_t with no default label now gets a -Wswitch warning 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, -O1 to -O3, -Og, -Os).
  • 288 KiB in unoptimized GCC/Clang builds (Cargo dev profile, CMake Debug), 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 -O0 UBSan uses about 390 KiB. Define BITCOIN_PQC_BURN_STACK_BYTES at 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.lock that pins qbit-libbitcoinpqc 0.3.1 needs the 0.3.2 bump.

Validation

See the evidence table and the independent review in the release PR (Qbit-Org/qbit-libbitcoinpqc#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 Qbit-Org/qbit-libbitcoinpqc#18.