Skip to content

Releases

qbit-libbitcoinpqc v0.3.2

Choose a tag to compare

@kiwidream kiwidream released this 02 Nov 15:40
v0.3.2
1965d0d

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

Choose a tag to compare

@kiwidream kiwidream released this 30 Oct 17:44
v0.3.1
ebd9092

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_keypair and crypto_sign_signature return through one cleanup path that wipes the stack spx_ctx (an exact SK_SEED copy) and optrand. 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_validate and slh_dsa_derandomize overwrite BITCOIN_PQC_BURN_STACK_BYTES of 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 knob SPX_FORS_THREADS) burn their own stacks.
  • Sanitizer-clean wipe loop (from #14). The secure_zero_local helpers count down without wrapping len, so -fsanitize=unsigned-integer-overflow builds do not trap.
  • crossbeam-epoch 0.9.20 (RUSTSEC-2026-0204, from #4). Lockfile only. The crate reaches the lockfile only through the criterion dev-dependency.
  • Version metadata is 0.3.1. docs/vendored-crypto-provenance.md lists 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, including build.rs) are byte-identical to v0.3.0. The library objects export two new internal symbols, slh_dsa_secret_key_validate_unburned and slh_dsa_derandomize_unburned, which no header declares.
  • Outputs unchanged. The golden vectors pass unmodified. A C harness linked against the v0.3.0 and 0.3.1 static libraries produces byte-identical keys and signatures (4 keypairs and 16 signatures, 59,264 bytes, sha256 5f0a2ba45b04406b…).

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 dev profile, CMake Debug), where the -O0 AVX2 transform frame alone is about 190 KiB.
  • 4 KiB under Emscripten.
  • Defining BITCOIN_PQC_BURN_STACK_BYTES at 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

Choose a tag to compare

@kiwidream kiwidream released this 24 Jun 14:18
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: 32 bytes
  • Secret key size: 64 bytes
  • Signature size: 3680 bytes

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 bitcoinpqc from Cargo package qbit-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/sphincsplus commit 7ec789ace6874d875f4bb84cb61b81155398167e.
  • 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.h and shake-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 --locked
  • cargo check --manifest-path fuzz/Cargo.toml --locked
  • cargo fmt -- --check
  • cargo audit --deny warnings
  • ruby scripts/check-workflow-action-pins.rb
  • scripts/check-source-inventory.sh
  • scripts/check-no-tracked-symlinks.sh
  • git diff --check