Changelog
All notable changes to this project are documented in this file. Format follows Keep a Changelog.
[Unreleased]
Fixed
- v0.3.8’s RubyGems publish (Ruby bindings,
dstu_core) actually completed - blocked by a pending PyPI/npm environment approval and a transient third-party (cargo-binstall/QuickInstall) outage on thearm64-darwinbuild, both unrelated to this repo’s own code. Live on RubyGems.org as of this entry, all four platform gems. Seedocs/DECISIONS.mdD-194.
Changed
-
bindings/ruby’s gemspec/Cargo.toml description and README, plus rootREADME.mdand the gh-pages landing page (both languages), updated to reflect RubyGems actually being live - same stale “not yet published” pattern already fixed once for PyPI/npm/crates.io (D-191). Seedocs/DECISIONS.mdD-194. -
Root
README.mdrestructured following real-world convention from libsodium/age/RustCrypto: badges row, a short plain-language pitch, one verified code example, everything else linked to dedicated docs instead of inlined. The full CLI walkthrough moved to a newdocs/CLI.md(published in the mdBook knowledge base); the repository-structure tree, dev-environment setup, and Windows-specific troubleshooting moved todocs/CONTRIBUTING.md. Seedocs/DECISIONS.mdD-192. -
gh-pages landing page (
index.html/uk/index.html, not a crate release): externalized ~287 KB/page of base64-inlined webfonts to real cacheablefonts/*.woff2files, dropped an IBM Plex Sans “600” face that turned out to be byte-identical to the 400 face (buttons were silently rendering non-bold glyphs under a false bold label), and fixed a second stale “v0.3.6 released” status blurb the README pass above hadn’t reached. Seedocs/DECISIONS.mdD-193.
[0.3.8] - 2026-08-13
Fixed
publish to RubyGemsfailed instantly on v0.3.7’s real release run (after all fourbuild-ruby-gemsplatforms passed):rubygems/configure-rubygems-credentials@v1doesn’t exist- that action only publishes full semver tags (
v1.0.0/v2.0.0/v2.1.0), no floatingv1/v2major alias. Pinned to the exact SHArubygems/release-gem’s ownaction.ymluses internally for this same step (v2.1.0). Seedocs/DECISIONS.mdD-190’s second update.
- that action only publishes full semver tags (
[0.3.7] - 2026-08-13
Fixed
- Root
README.md’s “Language bindings” table and the website’s “Bindings” section both still said “not published to any package registry” and linked only this repo’s own README, even though Python/Node.js were already live on PyPI/npm as of v0.3.6 - same class of bug as D-191, found while answering whether the site links registries as well as the repo. Both now link the README (full docs, every binding) and the registry page (the actual install command) side by side where a binding is published. Seedocs/DECISIONS.mdD-191’s addendum. build-ruby-gemsfailed on all four platforms on v0.3.6’s actual release run:magnus 0.7.1doesn’t support Ruby 4.0’s changed C ABI, andoxidize-rb/actions/cross-gem’s defaultruby-versionscross-compiled against Ruby 4.0 anyway. Pinnedruby-versionsexplicitly to3.1,3.2,3.3,3.4, matching whatbindings-ruby.yml’s own test job and the gemspec’srequired_ruby_versionalready cover. Seedocs/DECISIONS.mdD-190’s update.- The root
README.mdand website status banners had grown, release over release since v0.3.3, into a dense wall of text restating every past release’s own detail. Shortened both to a single current-state line, pointing todocs/CHANGELOG.mdfor what changed each release instead of re-narrating it in the banner itself.
[0.3.6] - 2026-08-13
Added
build-ruby-gems/publish-rubygemsjobs inrelease.yml(T-164): cross-compiled RubyGems native gem publishing fordstu_core(x86_64-linux,aarch64-linux,arm64-darwin,x64-mingw-ucrt) viaoxidize-rb/actions/cross-gem, OIDC Trusted Publishing against a pre-registered pending publisher. Dormant behind therubygemsGitHub Environment approval gate until the next tag. Seedocs/DECISIONS.mdD-190.
Fixed
- The already-live PyPI and npm package pages (
dstu-core) still had README/description text written pre-publish, claiming “provisional, not yet published” and offering only from-source build instructions - nopip install dstu-core/npm install dstu-coreanywhere. Fixed the README and every short description field (pyproject.toml,package.json, both crates’Cargo.toml, module doc comments) for both bindings, bumped both to0.1.1so the fix actually reaches the live registry page (PyPI/npm render metadata from the latest published version).uacrypt’s crates.io description (“CLI over dstu-core”) replaced with a real sentence. Ruby’s gemspec had a distinct, more serious bug caught in the same sweep:README.mdwas never inspec.filesat all, so the gem would have shipped with no description page content on its first publish - fixed ahead of that first publish. Seedocs/DECISIONS.mdD-191.
[0.3.5] - 2026-08-13
Added
dstu-core-linux-arm64-gnu- a new npm platform package for the Node.js bindings (aarch64-unknown-linux-gnu), built natively on GitHub’subuntu-24.04-armhosted runner (GA for public repos since 2025-08-07), no cross-compile toolchain needed.
Fixed
- v0.3.4’s npm publish (with the idempotency fix from that release) correctly skipped the two
already-published platform packages and reached
dstu-core-win32-x64-msvc, but that package hit npm’s own spam-detection block again - the same block from v0.3.3, confirmed not time-based (it reproduced identically after a 3+ hour wait). Deferred that one platform package explicitly (publish-npmnow skips it outright rather than attempting and tolerating the failure) so the rest of the publish - rootdstu-coreand the two working platform packages - isn’t blocked by it. Seedocs/DECISIONS.mdD-189 for the full incident, the research into what actually resolves this class of npm block (contacting npm support - renaming doesn’t, per a real precedent), and what un-defers it once support clears the name.
[0.3.4] - 2026-08-13
Fixed
- v0.3.3’s npm publish got past the provenance/access fix but hit npm’s own spam-detection
heuristic on the 3rd platform subpackage (
dstu-core-win32-x64-msvc) after two succeeded (dstu-core-linux-x64-gnu,dstu-core-darwin-arm64both published live). Retrying the same tag’s job after waiting failed differently:napi prepublish(theprepublishOnlyhook driving the whole publish) is not idempotent - its per-platform loop aborts the entire command, root package included, the instantnpm publishfails on any one platform, with no tolerance for “this version already exists”. The retry died on the 1st platform (already published from the prior attempt) and never reached the 3rd or the root package. Replaced the singlenapi prepublish-drivennpm publish --provenancewith explicit steps: set rootoptionalDependenciesdirectly (the one other thingnapi prepublishdid), then publish each platform subpackage and finally the root package each tolerating an “already published” error instead of failing the whole job - so a partial-failure retry (the normal case here, given both npm’s external spam heuristic and rate limits are outside this workflow’s control) picks up wherever the previous attempt stopped.
[0.3.3] - 2026-08-12
Fixed
publish-npm’snapi prepublishstep failed publishing every platform subpackage (Can't generate provenance for new or private package, you must set access to public) -npm publish --provenancerefuses to guess the intended access level for a package that’s never been published, even unscoped ones. Added"publishConfig": {"access": "public"}tobindings/nodejs/package.json-napi create-npm-diralready copies that field into every generated subpackage (confirmed by reading its source), so this one line covers the root package and all three platform packages. Also added--skip-gh-releaseto theprepublishOnlyscript -napi prepublishtries to create/update a GitHub Release itself by default, redundant with (and, lackingcontents: writein this job, failing 401 against)release.yml’s owncreate GitHub releasejob. Confirmed nothing had actually landed on the npm registry from the failed attempt before retrying (registry.npmjs.org/dstu-core*still 404).
[0.3.2] - 2026-08-12
Added
dstu-core(Python bindings) is now genuinely live on PyPI 0.1.0 - the 0.3.1 tag’spublish-pypirun actually went through (see Fixed, below), so this is the first release to reflect that as real instead of “prepared, not yet live.”
Fixed
environment: pypi/npminrelease.ymlwere referenced but never actually protected - GitHub auto-creates an environment with zero protection rules the first time a workflow references it, so the 0.3.1 tag’spublish-pypijob ran straight through with no approval pause (harmless here - it was the intended package - but not the safety behavior this project claimed). Fixed via the GitHub API (required_reviewersadded to both, project owner as reviewer) - not a file in this repo, recorded here so it isn’t lost.publish-npm’snpm install -g npm@latestfailed on Node 20 (EBADENGINE- npm’s own latest version now requires Node >=22) before ever attempting a publish. Bumped to Node 22.- npm (unlike PyPI) has no “pending trusted publisher” - a package that has never been published
can’t configure Trusted Publishing for itself at all (open upstream issue,
npm/cli#8544, confirmed live 2026-08-12), so OIDC alone can never reach a first npm publish here. Added a one-time bootstrap path:NODE_AUTH_TOKENfrom a repo secret (NPM_TOKEN, an npm token pasted directly into GitHub’s own secret UI, never into any chat/session) covers only the first publish; oncedstu-coreexists on npm, Trusted Publishing takes over and both the env var and the secret get removed. publish-pypihad noskip-existing, so any future tag whose Python binding hasn’t changed (its own version, 0.1.0, isn’t lockstepped with the Rust crates’ tag) would hard-fail re-uploading wheels PyPI already has, rather than skipping them.- The 0.3.1 tag itself is stuck on an older commit that predates all of the above - none of these
fixes could reach it by re-running its jobs (a git tag is a fixed pointer; re-running a job in
an existing workflow run replays the workflow file as it existed at that commit, not the
latest). This release exists specifically so npm’s first real publish attempt runs against a
workflow that has the fixes, not to add anything else on top of 0.3.1’s own -
crates.io/PyPI don’t need re-publishing, just
dstu-core/uacrypt’s version bumped to0.3.2socargo publishhas something new to accept.
[0.3.1] - 2026-08-12
Added
crypto_box512/crypto_sign257: wired intodstu-core-capiand all eight language bindings (Python/Node.js/Ruby/Java/PHP/.NET/Go/C++) - both landed indstu-coreitself already, in 0.3.0 above, without this wiring (docs/TASKS.mdT-204, closed 2026-08-09/10). This release’s Python wheel (and any other binding artifact attached to a GitHub Release) is the first to actually ship this surface outside the Rust crate itself.- CI infrastructure for publishing the Python (
dstu-core) and Node.js (dstu-core) bindings to PyPI and npm (docs/TASKS.mdT-164/T-203) - bothpublish-pypi/publish-npmjobs inrelease.ymlland dormant, gated behind their own GitHub Environment approval, and use OIDC Trusted Publishing exclusively (no token/secret stored anywhere) - the direct fix for a real crates.io token-leak incident T-203 records. Intended as prepared-but-dormant - see 0.3.2 above for what actually happened (the dormancy itself had a real gap) and what shipped since. Packagist is deliberately not part of this pass -bindings/phpis a compiledext-php-rsextension, and Packagist only distributes Composer (PHP-source) packages (docs/DECISIONS.mdD-144).
Fixed
docs/TASKS.mdT-17 andCLAUDE.md’s “MVP scope” both still read “not started” for the crates.io publish that actually happened at 0.3.0 above - corrected to reflect reality (docs/DECISIONS.mdD-159’s stale-doc failure shape: no task-ID string in either sentence for a grep sweep to have caught).
[0.3.0] - 2026-08-09
First crates.io publish (docs/TASKS.md T-17, docs/DECISIONS.md D-114) - both dstu-core
and uacrypt, completing the publish-crates CI job D-114 wired in for this exact tag. uacrypt
also stays available prebuilt via GitHub Releases as before - cargo install uacrypt is now an
additional option, not a replacement. This does not change the project’s own honesty posture:
still pre-1.0, still not independently audited, and the headline provisional gaps tracked in
docs/release-readiness.md (D-05’s Kalyna-alone AEAD assumption and Strumok’s vectors, both not
yet confirmed against their primary DSTU texts) are unchanged by this release - see that document
and docs/DECISIONS.md for the full standing caveats, repeated here exactly as prominently as
0.2.0’s own notes did below.
Added
hazmat::dstu9041: DSTU 9041:2020 hybrid (ECIES-style) asymmetric encryption over a twisted Edwards curve,l(p)=256/E256/1 only (D-47’s “ship the recommended curve first” precedent) -F_pbignum arithmetic, twisted-Edwards point arithmetic, and encrypt/decrypt composition, verified against the standard’s own worked example (docs/TASKS.mdT-177).crypto_box: public-key encryption overhazmat::dstu9041, hybrid via KDF (a random seed sealed asymmetrically, expanded viahazmat::kupyna_kdf, thencrypto_secretstreamencrypts the actual message) -seal/open/SecretKey/PublicKey(32-byte compressed,x-coordinate only,docs/TASKS.mdT-178,docs/DECISIONS.mdD-169).uacrypt box-keygen/box-pubkey/box-seal/box-openCLI surface.hazmat::dstu9041:l(p)=512/E512/1, the second curve size afterl(p)=256-message512/fp512/curve512/encryption512, same phased/test-first pattern, verified against the standard’s own worked example; bothl(p)=256security findings (an order-2 point, a cofactor-4 subgroup) independently re-derived and re-confirmed applicable, not assumed to carry over (docs/TASKS.mdT-192).crypto_box512: directl(p)=512sibling ofcrypto_box,PublicKey/SecretKeyat 64 bytes, seed deliberately fixed at 32 bytes/256 bits (notl(p)=512’s full KEM capacity, D-182) -seal/open/SecretKey/PublicKey,uacrypt box-keygen512/box-pubkey512/box-seal512/box-open512CLI surface. Not yet wired into any language binding ordstu-core-capi(separate future task, T-193’s own scope note) (docs/TASKS.mdT-193,docs/DECISIONS.mdD-182/D-183).hazmat::dstu4145: a second curve,m=257(gf2m257/curve257/scalar257/signature257) - what real Diia-issued qualified signatures actually use in production (confirmed from real issued certificates, not just the standard’s own curve table), alongside the existingm=163.crypto_sign257wraps it as a full sibling ofcrypto_sign(SigningKey/VerifyingKey/Signature, deterministic Kupyna-KMAC nonce).uacrypt sign-keygen257/sign-pubkey257/sign257CLI surface;uacrypt verifyreads a curve tag byte from--keyand handles bothm=163andm=257signatures through the one command (docs/TASKS.mdT-199,docs/DECISIONS.mdD-185/D-186).uacrypt: real binary-level (subprocess) smoke tests,crates/uacrypt/tests/- 75 tests spawning the actual compileduacryptbinary (exit codes, stdout/stderr, real files), covering every leaf command’s golden path plus targeted attack scenarios (T-199’s tagged-verifying-key format,crypto_secretstream’s wire-format tamper resistance, cross-key-type confusion between same-length key files,--in==--outin-place usage,--helptext checked as a pinned behavioral claim rather than prose, a constructed order-2/small-subgroup public key rejected byverify --keyfor both DSTU 4145 curves and bybox-openforcrypto_box/dstu9041(D-167 Finding 1’sr=p-1case), an exhaustive missing-required-flag sweep across all ~34 leaf command shapes). Previously the entire 140-test suite only ever called the library’srun()in-process (docs/TASKS.mdT-200).cargo xtask streaming-bounded: release-build proof, against a real 200 MiB file, thatencrypt/decrypt/kupyna-digest/strumok-cryptstay memory-bounded rather than buffering the whole input (D-42’s claim, previously asserted only in a doc comment) - samples the real subprocess’s OS-reported resident memory while it runs (crates/uacrypt/tests/support/mod.rs, one implementation per OS, no new dependency), with abox-sealcontrol case proving the measurement can actually detect unbounded growth.#[ignore]d by default in a plaincargo test(needs a release build for realistic timing - a debug-profile run of the same property took over 5 minutes and was killed before finishing); wired intocargo xtask ci’s optional layers and a new CI job matrixed across all three OSes (docs/TASKS.mdT-200).
Fixed
crypto_sign/hazmat::dstu4145(m=163):VerifyingKey::from_uncompressed_bytesaccepted a caller-supplied public key with no on-curve check, andverifynever validated its ownqparameter either - a signature could be forged againstPoint::Infinityor the curve’s one order-2 point (cofactorh=2, confirmed dual-source against Bouncy Castle) without ever needing the real private key. Found auditing T-183, fixed immediately as a real vulnerability, not deferred to backlog. Three tests actively forge working(r, s)pairs against all three attack points to confirm rejection, rather than trusting a walkthrough (docs/TASKS.mdT-189,docs/DECISIONS.mdD-172).uacrypt strumok-crypt:--in==--out(“apply the keystream to this file in place”) silently destroyed the input - exit code 0, 0-byte result, no error - instead of round-tripping. The streaming path opened--outviaFile::create(truncating it) before finishing reading--in. Found by T-200’s own--in==--outsmoke-test probe of the real binary, fixed with the same temp-file-then-rename disciplineencrypt/decryptalready use (docs/DECISIONS.mdD-187).
Changed
hazmat::gf2m_wide/hazmat::dstu4145::gf2m163:multiply()now dispatches to a hardware carry-less-multiply implementation (PCLMULQDQ/PMULL) at runtime when the CPU supports it and thestdfeature is enabled, falling back to the existing portable software path otherwise -no_std/embedded builds and CPUs without the instruction are unaffected. Real measured speedups: Kalyna-GCM 256-256 throughput up ~2.2-4.6x on top of the already-landed word-wisereducefix, DSTU 4145sign/verifyup ~26-32x on the dev machine (docs/TASKS.mdT-198,docs/DECISIONS.mdD-184).
[0.2.0] - 2026-08-02
Second tagged release - GitHub Releases only, no crates.io publish (docs/TASKS.md T-17 stays
separately gated, same posture as v0.1.0).
Added
crypto_sign/uacrypt: DSTU 4145 digital-signature CLI commands -sign-keygen,sign-pubkey,sign,verify(docs/TASKS.mdT-124).dstu-core:getrandomCargo feature - ano_std-compatible RNG path viagetrandom0.3’s link-time custom backend, for targets withoutstd(T-123,docs/DECISIONS.mdD-74).- Official Strumok-256/512 supplementary test vectors from two additional state-sourced supplements (beyond the existing UAPKI-attributed set), D-104.
- Kani bounded-model-check proofs for
gf2m163::reduce’s two previously hand-argued claims, checked exhaustively over all 2^384 possible inputs (T-145). - CodeQL advanced-setup CI migration, explicit least-privilege CI permissions (T-143); SonarCloud static analysis wired into CI (T-140).
Fixed
- DSTU 4145
scalar_multiplyreturned a wrong result for scalars at/near the curve’s own group order - reachable in-contract at exactly one boundary value (k == n-1). No forgery risk (confirmed via an independent Bouncy Castle cross-check), but a genuine correctness bug everysign/verifycall went through. Seedocs/DECISIONS.mdD-110.
Changed
- Performance: DSTU 4145
sign~2.6x faster,verify~4.4x faster (cumulative) - bit-interleave GF(2^163) squaring and an Itoh-Tsujii addition-chain field inversion, plus a projective/Shamir’s- trick fast path forverify’s public-scalar combine step. Narrows the gap to OpenSSL’snistb163from ~21-23x to ~5-8x slower. Seedocs/DECISIONS.mdD-108/D-109,docs/PERFORMANCE.md. - Kalyna: const-generic round functions close most of the block-cipher gap with the UAPKI reference
(T-128); the GCM/GMAC field-multiply bottleneck closed via a 4-bit comb multiply (T-125);
CMAC/GMAC/KW gain a cached-schedule API surface, XTS gains a faster
GF(2^m)doubling (T-126/T-127). - Kupyna gains a const-generic compression function (T-134); Strumok’s keystream generation is batched/fixed-index (T-135).
Notes
- No breaking changes in the public
crypto_*/hazmatAPI surface.uacrypt’s on-diskencrypt/decryptwire format was already changed pre-1.0 in a prior, unreleased state (the chunkedcrypto_secretstreamformat) - not part of this release specifically. - Language bindings (
bindings/) and the C ABI crate (crates/dstu-core-capi) are not part of this release - none of the eight bindings (Python/Node/Ruby/PHP/.NET/Java/Go/C++, all done as of 2026-08-03,docs/bindings-strategy.md) or the C ABI crate itself have ever shipped in a tagged GitHub Release; this file only records what actually releases (crates.io/GitHub Releases), not every landed change - per-binding status lives indocs/TASKS.md/docs/bindings-strategy.mdinstead. - Still pre-1.0, not audited, and not a claim of side-channel resistance.
[0.1.0] - 2026-07-26
First tagged release - GitHub Releases only (docs/TASKS.md T-18); not published to crates.io
(docs/TASKS.md T-17 stays separately gated on an explicit owner request). Everything below predates
this tag; there is no reconstructed per-commit history before it.
Added
dstu-core:hazmatprimitives for all three in-scope DSTU algorithms - Kupyna (DSTU 7564:2014, one-shot and streaming), Kalyna (DSTU 7624:2014, single-block encrypt/decrypt across all five key/block-size variants), and Strumok (DSTU 8845:2019, keystream generation).dstu-core: full DSTU 7624 mode-of-operation coverage over Kalyna - ECB, CBC, CFB, OFB, CTR, CMAC, KW, CCM, GCM/GMAC, and XTS.dstu-core: DSTU 4145-2002 digital signatures (hazmat::dstu4145, deterministic nonce derivation).dstu-core: libsodium-shaped high-levelcrypto_*frontend over the above -crypto_secretbox,crypto_secretstream(chunked/streaming AEAD),crypto_generichash,crypto_auth,crypto_kdf,crypto_stream,crypto_sign,crypto_pwhash(Argon2id, not a DSTU primitive),randombytes.dstu-core:no_std/alloc/stdfeature gating, plus an independentsmall-tablesresource profile for constrained targets. Cross-compilation confirmed forthumbv7em-none-eabihf(STM32 Cortex-M) andriscv32imc-unknown-none-elf(ESP32-C3-class RISC-V).uacrypt: CLI binary overdstu-core-keygen(fresh 32-byte key from the OS CSPRNG),encrypt/decrypt(overcrypto_secretstream, genuinely chunked disk I/O),hash(Kupyna-256), plushazmat-scoped multi-variant tools (kalyna-block,kalyna-ccm,kupyna-digest,strumok-crypt). Plain-language--help/-hfor every command,--version/-Vat the top level.- Official DSTU test vectors for Kalyna, Kupyna, and DSTU 4145; dual-oracle verification (Bouncy Castle Java/.NET harnesses) for Kalyna and Kupyna.
Changed
uacrypt encrypt/decrypt’s on-disk wire format changed twice pre-release: originally a single-shotcrypto_secretboxblob (255-byte cap), then migrated to uncappedcrypto_secretboxover Kalyna-GCM, then to the current genuinely chunkedcrypto_secretstreamformat. Each change is a breaking format change from the one before it - acceptable pre-1.0 and pre-publication, not covered by any compatibility guarantee.
Notes
- Kalyna-alone AEAD mode-of-operation (D-05) and the Strumok test vectors (D-15) are provisional:
adopted on corroborating evidence, not confirmed against the primary DSTU text. See
docs/SECURITY.md/docs/DECISIONS.mdfor the full provisional-status caveats. - No independent third-party security audit has been performed.
no_stdcompiling is not a side-channel-resistance claim.