Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

docs/ORACLES.md

Which sources this project trusts for verifying correctness, how much, and why — and where test vectors will come from once primitives exist. Canonical owner of the oracle trust matrix and test-vector convention (oracles/README.md links here instead of duplicating this content).

Two axes that don’t line up

Every oracle sits on two independent scales, and for this project they’re inverted rather than correlated — that inversion is the main thing this document has to make explicit.

Verification authority (how much do we trust its numbers are algorithmically correct):

  1. The standard’s own author’s reference implementation (Roman Oliynykov for Kalyna/Kupyna).
  2. The official standard text / designers’ published paper itself.
  3. A mature, independently audited library (Bouncy Castle).
  4. A production library whose audit has lapsed (cryptonite — certified 2016–2021, nothing since) — UAPKI (added 2026-07-22) is a fork of this same lineage, with an additional cited Ukrainian state crypto-expertise conclusion for the UAPKI project specifically (2021; see docs/DECISIONS.md D-16 for exactly what that does and doesn’t certify — the conclusion predates and doesn’t cover this project’s pinned commit). Treat it as sitting at this tier for Kalyna/Kupyna/DSTU 4145 (same underlying lineage as cryptonite), except for Strumok, where it’s the only source found at all — no cryptonite equivalent exists to compare it against, so its self-declared // ДСТУ 8845:2019 attribution is taken on the library’s word, not cross-tiered against anything above it.
  5. An unofficial, single-maintainer, unaudited implementation (outspace/dstu8845).
  6. Excluded — untrusted provenance (li0ard, see D-07 in docs/DECISIONS.md).

Legal portability (can code be copied/ported, or only used to check numbers):

  • MIT / BSD-2-Clause (Bouncy Castle, cryptonite, UAPKI) — portable with attribution.
  • No LICENSE file (Roman Oliynykov’s repos, outspace/dstu8845) — full copyright, no permission granted, verification-only, copying is not legally available regardless of code quality.

The inversion: the highest-verification-authority sources — the standard authors’ own code — are exactly the ones with zero legal portability. The one source that’s both audited and portable, Bouncy Castle, is Java/C#, so using it means re-deriving the algorithm’s logic in Rust, not a mechanical port, and it only covers DSTU 4145 (plus Kalyna/Kupyna, which turned out to also be implemented there).

Committed development model

This project’s own docs/SECURITY.md and docs/DECISIONS.md (D-06) already settled how oracles get used: implement each primitive from the official DSTU spec text, citing the clause, then verify against oracles. Never port or copy oracle source into crates/, regardless of the oracle’s license. Everything below assumes that model — oracles here answer “who do we check our numbers against,” not “what do we translate into Rust.”

Official DSTU text — purchase cost (checked 2026-07-21)

Official texts are sold per-page via fnd-store.uas.gov.ua (see also the free catalog at uas.gov.ua/natsionalnyi-fond-nd/kataloh-natsionalnykh-standartiv-ta-k to confirm validity before paying). Checked listings for the three standards this project would most benefit from:

StandardPagesPrice (UAH)Listing
ДСТУ 9041:2020405,304.00fnd-store.uas.gov.ua/documents/42241
ДСТУ 8845:2019 (Strumok)537,027.80fnd-store.uas.gov.ua/documents/39053
ДСТУ 7624:2014 (Kalyna, incl. Amendment No. 1:2016)22729,967.60fnd-store.uas.gov.ua/documents/4228

All three land at roughly the same ≈132.6 UAH/page rate (the state-set per-page tariff for official reproduction) — Kalyna’s total is simply large because the document is large (227 pages, folding in its 2016 amendment), not a different rate. The store’s “40 pages” figure for DSTU 9041 doesn’t match the physically obtained document, confirmed 36 pages total (2026-08-06, owner-supplied photos of the final page) — likely a cover/title-page count difference on the store’s side, not a sign of unpurchased content; see the DSTU 9041 subsection below. Verdict: cost-prohibitive for this project at this time — combined total is 42,300 UAH ($1,000 USD) for all three, against a volunteer open-source project’s budget. Not pursued for now; each per-algorithm section below notes what specifically the official text would have resolved, so this can be revisited if project funding changes rather than re-researched from scratch.

Checked 2026-08-06, for context only — these two are explicitly out of scope (D-08), not part of the five in-scope algorithms above:

StandardPagesPrice (UAH)Listing
ДСТУ 8961:2019 “Скеля” (post-quantum KEM/asymmetric encryption)24532,487.00fnd-store.uas.gov.ua catalog
ДСТУ 9212:2023 “Вершина” (post-quantum signature)25433,680.40fnd-store.uas.gov.ua catalog

Same ≈132.6 UAH/page rate holds. Both are far larger documents than any in-scope standard (Kalyna’s 227 pages was previously the biggest); combined cost (~66,167 UAH, ~$1,600 USD) exceeds all three in-scope standards above combined. Purchasability alone doesn’t lift D-08’s out-of-scope decision — see D-08/docs/dstu-crypto-project.md’s “Post-quantum track” for the other three reasons (math class, implementation complexity, cryptanalysis maturity) that stand independent of source-text cost.

For scale, checked the same day against their closest NIST equivalents (page count via pdfinfo on the official PDF, not a WebFetch summary — see this file’s own PDF-extraction reliability note): FIPS 203 (ML-KEM, ex-Kyber, nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.203.pdf) is 56 pages; FIPS 204 (ML-DSA, ex-Dilithium, nvlpubs.nist.gov/nistpubs/fips/nist.fips.204.pdf) is 65 pages. Skelya/Vershyna are each roughly 4x the length of their NIST counterpart — consistent with D-08’s own “implementation complexity comparable to all five other in-scope algorithms combined” framing, not just a pricing quirk.

Per-algorithm oracle map

Kalyna (DSTU 7624)

  • Pseudocode: docs/pseudocode/kalyna.md — transcribed from the paper below, cross-checked against the reference C oracle. Its k=2l key-schedule branch (originally ambiguous from the paper’s own notation) is read as a word-rotation rather than arithmetic addition, corroborated by bouncycastle-java’s DSTU7624Engine.javanote this is not a second independent reading: that file’s own header credits Oliynykov’s C code as its source, so it’s a faithful port, not an independent implementation (same is true of DSTU7564Digest.java for Kupyna below). See the “Correction on provenance” note in docs/pseudocode/kalyna.md for why this still has some value (rules out a C-specific transcription slip) without being the strong cross-check it was first described as.
  • Highest-trust source: docs/papers/Kalyna.pdf, Appendix B — “A New Encryption Standard of Ukraine: The Kalyna Block Cipher” (Oliynykov et al.), the designers’ own published paper. Ranks above the reference-implementation oracles below: it’s the formal specification itself, not a third-party implementation of it. Test vectors extracted and verified (full round traces cross-checked for hex validity and expected byte length) into crates/dstu-core/tests/vectors/kalyna/{128-128,128-256,256-256,256-512,512-512}.json — encryption and decryption KEY/PLAINTEXT/CIPHERTEXT triples for all five variants.
  • Secondary code oracle: oracles/kalyna-reference/ (Roman Oliynykov, same author) — same vectors re-derivable from main.c, verify-only, no license.
  • Tertiary: oracles/cryptonite/ (BSD-2-Clause). Also the original source of the D-05 question (its native CCM/GCM encrypt_mac API on Kalyna alone) — D-05 was later resolved on assumption in this same direction, see below and docs/DECISIONS.md D-05.
  • Quaternary: oracles/bouncycastle-{java,dotnet}/ (MIT, actively maintained, audited) — good cross-check on modes and wrap behavior.
  • Added 2026-07-22: oracles/uapki/ (fork of Cryptonite, state-expertise pedigree — see oracles/README.md for the exact caveat on what that does and doesn’t certify). Its dstu7624_self_test covers ECB/CBC/OFB/CFB/CTR/CMAC/XTS/KW/CCM/GMAC/GCM. ECB cross-checked (same day): all 10 dstu7624_ecb_self_test cases run ECB with data_len == block_size, i.e. plain single-block encryption — diffed byte-for-byte (script) against {128-128,128-256,256-256,256-512,512-512}.json’s encryption/decryption cases and all 10 match exactly. Same official vector set as Kalyna.pdf (not independent new data), but confirms UAPKI reproduces it correctly. CBC/OFB/CFB/CTR/XTS remain unchecked — genuine new data, since no Rust mode-of-operation exists yet to check them against; GCM/GMAC specifically were directly relevant to D-05 (resolved on assumption 2026-07-24, still not primary-confirmed) and have since been checked (below) as those modes were built (see docs/DECISIONS.md D-16 update, docs/TASKS.md).
  • GCM checked, 2026-07-24 (docs/DECISIONS.md D-56, hazmat::kalyna_gcm, docs/TASKS.md T-95) — uapki’s 6 dstu7624_gcm_self_test vectors plus a vector-only cross-check against bouncycastle-java’s DSTU7624Test.java GCM tests (construction source not vendored — same weaker-claim caveat as CCM above). BC-.NET has no GCM class at all.
  • GMAC checked, 2026-07-24 (docs/DECISIONS.md D-57, hazmat::kalyna_gmac, docs/TASKS.md T-95) — uapki-only, 5 dstu7624_gmac_self_test vectors covering 4 of 5 Kalyna variants (Kalyna128_128 has none). No Bouncy Castle standalone GMAC class exists in either port (confirmed by search — BC-Java’s “GCM/GMAC test N” cases configure KGCMBlockCipher for AEAD, not this AAD-less single-stream construction, so they don’t corroborate it even as a vector-only check). Weaker coverage than GCM’s, stated plainly in D-57 rather than implied to be equal by proximity. Also: dstu7624.c itself has two disagreeing GMAC code paths (D-57) — the streaming gmac_update/gmac_final pair has a confirmed bug on multi-block single-call input, not ported; hazmat::kalyna_gmac ports the coherent one-shot encrypt_gmac instead.
  • CCM checked, 2026-07-23 (docs/DECISIONS.md D-41, hazmat::kalyna_ccm, docs/TASKS.md T-81) — a genuine dual-oracle case, not a same-vendor recheck: dstu7624_ccm_self_test’s 5 vectors and bouncycastle-java’s DSTU7624Test.java CCMModeTests’s 4 vectors were compared directly (not just each independently against this project’s Rust port) and 4 of the 5 UAPKI cases matched a BC case byte-for-byte (128/128, 256/256, 256/512, 512/512) — independent-lineage agreement. The 5th (128/256) has no BC vector at all (BC’s CCMModeTests doesn’t cover that variant), so that one case is UAPKI-only, flagged as such in its vector file. Still provisional — this cross-oracle agreement is reference-implementation evidence, not a reading of the primary DSTU 7624:2014 text; see D-41 and the “not yet confirmed against primary text” caveat repeated in hazmat::kalyna_ccm’s module doc comment and every extracted vector file’s source field. BC’s own KCCMBlockCipher/KGCMBlockCipher construction source is not present in this project’s vendored sparse checkout of oracles/bouncycastle-java (only the test file importing them is) — the cross-check above is against BC’s vector outputs only, not a second reading of BC’s construction code, weaker than “read both implementations.”
  • KW checked 2026-07-24 (docs/DECISIONS.md D-55, hazmat::kalyna_kw, docs/TASKS.md T-94) — read DSTU7624WrapEngine.java and Dstu7624WrapEngine.cs in full, not just their test vectors. Correction to this file’s own earlier “quaternary … good cross-check” framing above: the .NET port is a structural port of the Java one (identical method shapes, matching commented-out debug lines carried across) — one construction lineage, not two independent readings, for KW specifically (and likely for the base engine too, though that wasn’t re-audited here). This reading also surfaced a real fork uapki’s C doesn’t share with either BC port (round-counter tweak width: uapki XORs 1 byte, BC XORs 4 little-endian bytes) — provably unobservable in every existing vector (v <= 255 in all cases) and resolved in the Rust port by hard-bounding input so the fork can never be reached, not by picking a side. See D-55 for the full account, including a second, independent finding (a latent length-recovery fragility in uapki’s own non-block-aligned KW branch) that led to a deliberate scope-cut, not just an oracle-strength correction.
  • Supplementary, not authoritative: docs/papers/Dolgov_5-22.pdf contains a C-like pseudocode description of Kalyna (Kalyna_Cipher, Kalyna_InvCipher, Kalyna_S_boxes, Kalyna_KeyExpansion_Ksigma). Correction, 2026-07-31: this bullet previously claimed its surrounding Ukrainian prose doesn’t extract cleanly via pdftotext (font-encoding issue, no ToUnicode CMap) — checked again directly and that was false; pdftotext -layout extracts it cleanly (see the general PDF extraction note below). Kalyna.pdf remains the reference and this one still isn’t transcribed here — Kalyna already has a confirmed dual-oracle implementation, so there’s no gap for this secondary source to fill, not because it’s unreadable.
  • Checked 2026-07-24, ruled out for the D-05 mode-of-operation question: docs/papers/Kalyna_construction_principles_ZI_2015.pdf (Горбенко/Олійников/Казимиров et al., “Принципи побудови і основні властивості нового національного стандарту блокового шифрування України”, Захист інформації 17(2), 2015 — same author group as Kalyna.pdf) and docs/papers/Kalyna_vs_international_standards_2018.pdf (Єфіменко/Байлюк/Покотило, 2018, comparison against AES/RC4/3DES). Both read in full — both are exclusively about the block cipher’s internal SPN structure (S-box/MDS-matrix design choices, speed comparisons), neither mentions modes of operation or Kupyna combination anywhere. Kept in docs/papers/ as legitimate secondary sources for the cipher’s design rationale, not for D-05. (The original note that reading these required rendering to PNG due to a font-encoding gap was also corrected 2026-07-31 — both extract cleanly via plain pdftotext -layout.)
  • The actual D-05 mode-of-operation evidence found 2026-07-24: Ukrainian Wikipedia’s “Калина (шифр)” article publishes a ten-mode table (ECB/CTR/CFB/CMAC/CBC/OFB/GCM+GMAC/CCM/XTS/KW, each with its security service) that matches — mode-for-mode — this project’s own oracles/uapki/-derived note above (dstu7624_self_test’s ten-mode coverage), independently arrived at from a different source. See docs/DECISIONS.md D-05’s 2026-07-24 revision for the full table, the sourcing caveats, and why this was adopted as a working assumption rather than treated as a primary-text reading.
  • Candidate added 2026-08-03, see docs/DECISIONS.md D-154: cppcrypto 0.20 (kerukuro, SourceForge, BSD per project page / public-domain per file header — discrepancy observed, not resolved). Implements all 5 Kalyna variants; all 10 official Kalyna.pdf vectors matched byte-for-byte against a standalone harness calling its kalyna*::init/encrypt_block/ decrypt_block directly. Independence check, done deliberately (this file’s own “three prior false starts” pattern above): kalyna.cpp’s monolithic fused-table encrypt_block shares no function/table name or step-decomposition with oracles/kalyna-reference/kalyna.c’s granular SubBytes/ShiftRows/MixColumns/… style, or with either Bouncy Castle port — a materially stronger independence signal than BC/cryptonite/outspace’s provable shared lineage, but not a provable clean-room claim (a fused-table SPN is the natural shape for any fast Kalyna, related or not). Recorded as “independence not established, not refuted” — sits between tier 3 (audited) and tier 5 (unofficial single-maintainer) on the verification-authority scale above, not slotted formally until/unless independence gets stronger evidence. Binary-level performance (Ryzen dev machine): beats uacrypt on all 10 measured cells (5 variants × encrypt/decrypt), ~1.3–1.9x — see docs/PERFORMANCE.md’s Kalyna section for the full table. Not built on the Raspberry Pi yet (D-154’s own caveat: don’t assume the Ryzen-favors-cppcrypto result holds cross-architecture, per D-33’s standing pattern).

Kupyna (DSTU 7564)

  • Pseudocode: docs/pseudocode/kupyna.md — transcribed from the paper below, cross-checked against the reference C oracle; one extraction gap (the IV formula) resolved from the oracle and flagged as such. Additionally checked (2026-07-21) against bouncycastle-java/.../DSTU7564Digest.java — same structure confirmed (state[0] = blockSize for the IV; P/Q constant-addition and the fused S-box/shift/mix T-tables match), but this is the same “not independent” caveat as Kalyna: that file’s header also credits Oliynykov’s Kupyna-reference C code as its source. Treat as corroboration of a faithful port, not a second independent reading.
  • Highest-trust source: docs/papers/Kupyna.pdf, Appendix B — “A New Standard of Ukraine: The Kupyna Hash Function” (Oliynykov et al.), same standing as the Kalyna paper above. Test vectors extracted and verified into crates/dstu-core/tests/vectors/kupyna/{kupyna-256,kupyna-512}.json — six byte-aligned message-length cases each (0, 8, 512, 760/1536, 1024, 2048 bits). The paper also publishes bit-level (non-byte-aligned) cases at N=510/655 (both variants) and N=33/1 (Kupyna-512 only); deliberately not transcribed — see the note field in those JSON files for why.
  • Secondary code oracle: oracles/kupyna-reference/ (Roman Oliynykov, author) — verify-only, no license.
  • Tertiary: oracles/cryptonite/, oracles/bouncycastle-{java,dotnet}/.
  • Added 2026-07-22: oracles/uapki/’s dstu7564_self_test_hash — diffed byte-for-byte (script, not eyeball) against kupyna-{256,512}.json: all 12 cases match exactly. Confirms UAPKI reproduces the same official Kupyna.pdf vector set already used by cargo test, not a second independent reading (see oracles/README.md for the state-expertise pedigree caveat). The same file’s dstu7564_self_test_kmac (3 cases, KMAC-256/384/512) is separate data, now implemented (hazmat::kupyna_kmac, docs/TASKS.md T-38, docs/DECISIONS.md D-44): its construction (dstu7564_init_kmac/_update_kmac/_final_kmac) was read directly, not just its vectors, and cross-checked against oracles/bouncycastle-java/.../macs/DSTU7564Mac.java - a fully independent Java implementation whose own construction was also read - byte-for-byte matching on all three self-test vectors. See docs/pseudocode/kupyna-kmac.md for the full citation.
  • Candidate added 2026-08-03, see docs/DECISIONS.md D-154: cppcrypto 0.20 (kerukuro, same source as the Kalyna candidate above). Implements Kupyna-256/512 only (matches this project’s own scope). All 10 byte-aligned official Kupyna.pdf vectors matched byte-for-byte against its kupyna(256|512)::init/update/final API, called directly by a standalone harness. Same “independence not established, not refuted” framing as the Kalyna entry above — not re-derived separately here, see that entry’s reasoning. Binary-level performance (Ryzen dev machine): beats uacrypt at every message size measured, but by only ~5–9% (near parity, unlike Kalyna’s ~1.3–1.9x gap) — see docs/PERFORMANCE.md’s Kupyna section.

Strumok (DSTU 8845)

  • Pseudocode: docs/pseudocode/strumok.md — transcribed from Strumok.pdf, cross-checked structurally against the outspace oracle; one FSM-update ambiguity in the paper’s extraction resolved from the oracle and flagged as such.
  • No test vectors in docs/papers/. Checked directly (not assumed): docs/papers/Strumok.pdf (the designers’ paper, Gorbenko/Kuznetsov et al.) gives the full algorithmic description but contains zero test vectors (confirmed by scanning for hex runs of 16+ characters — the only hit is a bitmask constant, not a vector). docs/papers/Strumok_verilog.pdf (Verilog HDL writeup, re-checked 2026-07-22 specifically for a hardware testbench KAT — has a real text layer, searched for test-vector/testbench/keystream sections, nothing beyond module signal declarations) and docs/papers/Speed_of_modern_stream_ciphers.pdf were also scanned — no hex runs in either. The official standard text itself was priced (2026-07-21) at 7,027.80 UAH for 53 pages — see “Official DSTU text — purchase cost” above; not purchased.
  • Test vectors found 2026-07-22 in oracles/uapki/ (D-15): crates/dstu-core/tests/vectors/strumok/keystream-{256,512}.json, transcribed from library/uapkic/src/dstu8845.c’s dstu8845_self_test(), whose source comments the block // ДСТУ 8845:2019 — i.e. attributed by UAPKI’s own authors to the standard, not invented by this project. Not the same as having the official text: this project has not independently confirmed these values against the paid DSTU 8845:2019 document itself, only against UAPKI’s claim about them. Every vector file’s "status" field says so plainly. See oracles/README.md for UAPKI’s pedigree (state-expertise conclusion, fork of Cryptonite) and its exact limits (tied to a specific certified build, not this cloned commit).
  • Cross-check, not independent confirmation: the UAPKI values were reproduced byte-for-byte by running oracles/strumok-dstu8845/ (outspace, unofficial, no license) on the same inputs (tests/oracle-harness/strumok-cross-check/cross_check_against_uapki.c). This is not treated as two independent implementations agreeing — outspace’s strumok.c and UAPKI’s dstu8845.c share identical internal function/table names (dstu8845_init, dstu8845_crypt, T0..T7), which reads as shared lineage rather than independent authorship from the spec (the same trap D-07/the Kalyna provenance correction caught elsewhere in this file). Treat it as a consistency bonus on top of the UAPKI attribution, not a second oracle. li0ard/strumok remains excluded outright (D-07).
  • Status, stated plainly: better than “no vectors, self-invented gray inputs” (this project’s own 2026-07-22 earlier attempt, since superseded), still short of “official.” Locating the standard text itself, or a source that independently transcribes its own annexed vectors the way DSTU_4145-2002.pdf Annex Б does, remains open — see docs/TASKS.md.
  • Official supplementary vectors received (docs/DECISIONS.md D-104): a public-information response from Держспецзв’язку (Адміністрація Держспецзв’язку — the request/response’s own reference number, filing/response dates, and signatory are deliberately not recorded, in this file or anywhere else in this public repository, for the de-anonymization reason D-104 states) says that the standard’s own Annex Д (Annex D) test-with-known-answer values are in the standard text itself (still not purchased — see above), but attaches two additional Strumok-256/512 test examples used by ДНДІ ТКЗІ (the State Research Institute of Cybersecurity Technologies and Information Protection) during real conformance expert examinations of implementations — a genuinely independent source from UAPKI/outspace, sourced directly from the state body that owns the standard rather than from a third-party reference implementation’s own self-test. Transcribed and verified in crates/dstu-core/tests/strumok.rs’s official_letter_vectors module — both variants pass. Two byte-order conventions had to be derived (not assumed) from the letter’s own notation before they matched; see that module’s doc comment and D-104 for the full derivation. This upgrades, but does not close, the “UAPKI-attributed, not confirmed against the official text” status above: it is now independently confirmed against a second, state-sourced oracle, but still not against Annex Д of the standard text itself (unpurchased) — D-15/D-16 stay open on that specific, narrower point.

DSTU 4145 (signature)

  • Official text now in hand (docs/papers/DSTU_4145-2002.pdf, added 2026-07-22) — corrects the earlier “no spec paper exists” claim below and in docs/pseudocode/dstu4145.md’s header; this algorithm is no longer the BC-only exception to the “cited spec section” hard constraint. Sections 1-13 are the algorithm text (data representation, computational algorithms, parameter/key generation and verification, pre-signature/signature computation and verification); Annex B (Додаток Б, pages 18-21) is a full worked example with real numbers in both polynomial basis (GF(2^163)) and optimal normal basis (GF(2^173)); Annex D (Додаток Г) lists recommended curves. The PDF is a scan with no text layer (pdftotext yields nothing) — rendered to PNG via pdftoppm (poppler, installed 2026-07-22, see .claude.local.md) and read visually page by page.
  • Test vector: crates/dstu-core/tests/vectors/dstu4145/gf2m163.json — the Annex B.1 (GF(2^163), polynomial basis) worked example, transcribed from the scan and independently cross-checked byte-for-byte against oracles/bouncycastle-java/.../DSTU4145Test.java’s test163() (a hardcoded KAT that does not derive from this PDF) — every field matches exactly, which is what makes this a genuinely dual-sourced vector rather than a single by-eye transcription off a 150 DPI scan. This also upgrades Bouncy Castle’s own standing for this one algorithm: test163() passing is now confirmed DSTU-conformant against the standard’s own example, not just an internally-consistent BC fixture. Triple-checked 2026-07-22: oracles/uapki/library/uapkic/src/dstu4145.c’s dstu4145_self_test() (explicitly commented // ДСТУ 4145-2002. Додаток Б in its own source) carries the same d/Q/r/s values — byte-identical once UAPKI’s little-endian storage is reversed. Three sources (the standard text itself, Bouncy Castle, and a state-expertise-pedigreed library) now agree on this one example — about as solid as a single test vector gets without running the paid official text through this project’s own eyes. The Annex B.2 (optimal normal basis, GF(2^173)) example was not cross-checked this way — BC’s test173() uses different curve parameters (a separate, unrelated KAT), so that example currently rests on the scan transcription alone; treat it as unverified-transcription if it’s ever extracted.
  • Pseudocode: docs/pseudocode/dstu4145.md — still transcribed from the Bouncy Castle Java signer as of this writing; re-deriving it against the official spec sections above (now that they exist) is a follow-up, not yet done — see docs/TASKS.md.
  • Primary: oracles/bouncycastle-{java,dotnet}/ (MIT, audited, decades in production) — the best-supported algorithm in this project by oracle quality, and per the vector cross-check above, the one primitive here with genuine double confirmation (official worked example + independent hardcoded KAT) rather than a single source.
  • Secondary: oracles/cryptonite/dstu4145* (BSD-2-Clause, stale since 2016).
  • Superseded 2026-08-02, see D-115: D-02 originally said Bouncy Castle would also be the actual dependency wrapped for the Java/.NET bindings, not just an oracle — that’s no longer the plan now that hazmat::dstu4145/crypto_sign exist and are dual-oracle-verified. Bouncy Castle’s role here stays oracle-only, same as every other language’s bindings. Original line kept for the historical record, not deleted.

DSTU 9041 (asymmetric encryption, twisted Edwards curves)

  • No oracle exists anywhere. The standard is from 2020, newer than every reference implementation surveyed (Kalyna/Kupyna-reference predate it, cryptonite is 2016, Bouncy Castle doesn’t implement it). When this algorithm is reached, it starts from spec text alone with no cross-check available, unless one is found or built first.
  • Web search performed (2026-07-21), no GitHub implementation found for DSTU 9041:2020 in any language — confirms the above is not just an unsearched gap.
  • Two candidate papers checked for pseudocode:
    • Skorobahatko, bachelor’s thesis, KPI, 2023 (“Аналіз стійкості алгоритму гібридного шифрування за ДСТУ 9041:2020 та його модифікацій до розрізнювальних атак”) — the one paper found that actually analyzes this algorithm’s steps (its abstract explicitly frames it as chosen- plaintext/chosen-ciphertext resistance analysis of the DSTU 9041:2020 hybrid encryption scheme, referencing DSTU 7624:2014 too). Downloaded from https://ela.kpi.ua/server/api/core/bitstreams/12932ea1-d36a-468a-b4a0-504309a90fbd/content. Correction, 2026-07-31: an earlier pass here claimed pdftotext -layout failed on this file with the same “no ToUnicode CMap” gap as Dolgov_5-22.pdf/Strumok_verilog.pdf - checked again directly and that claim was false, for all three files (see the general PDF extraction note below). pdftotext -layout -f 12 -l 18 on this thesis extracts clean, complete Ukrainian prose, including §1.2’s full encryption algorithm (15 numbered steps) and decryption algorithm (19 numbered steps), with notation (M, l(M), base point P, order n, private key e, ephemeral key ε, public key Q), confirming it uses Kalyna-l/k-KW or Kalyna-l/k-KW-p (DSTU 7624:2014) as the symmetric stage in “KIVREP” mode and Kupyna as the recommended hash. This is real, previously-missed source material - not a dead end. What it is not: a primary source. It’s a single secondary transcription citing the standard as its own [15], with no oracle or reference implementation anywhere to cross-check against (unlike every other algorithm in this project). At least four transcription-level defects are visible in the extracted text itself (e.g. decrypt step 7 computes T' = er - scalar times a field element, not a point, almost certainly meant eR'; step 8’s exponent notation disagrees with encrypt step 13’s; a typo’d subscript in step 13; “винується” for “виконується”) - each is a plausible bachelor’s-thesis typo, but without the primary text there is no way to tell a typo from a real spec ambiguity. This source can responsibly support a docs/pseudocode/dstu9041.md draft with every such point flagged inline as unresolved (D-15’s own pattern for exactly this situation) - it cannot support writing hazmat::dstu9041 itself, which still needs the dual-oracle bar every other primitive here meets. Written up 2026-07-31 - docs/pseudocode/dstu9041.md now exists, both algorithm forms transcribed (§1.2’s full 15/19-step form and §2.1.1’s simplified restatement), every defect above flagged inline, plus a fifth found while writing it (§2.1.1’s own decrypt step independently repeats the same “scalar times the wrong operand” slip as §1.2’s step 7 - two independently-phrased sections making the same mistake reads as a genuine authorial error, not a transcription artifact, though that inference isn’t itself a citable confirmation). Four further gaps this source cannot answer (no l_max(p) formula, no concrete curve parameters, no KIVREP definition beyond its acronym, no hash-identifier/user-group registry) are recorded in the pseudocode doc’s own “Open gaps” section.
    • Ivanov/Kuznetsov et al., ITCE 2020 №1 (itce.vntu.edu.ua, downloaded and extracted cleanly — English abstract intact) — topically adjacent but not this algorithm: it’s about base-point selection algorithms for Edwards curves in the context of the DSTU 4145-2002 signature standard, not the DSTU 9041:2020 hybrid short-message-encryption algorithm. Useful background on Edwards-curve arithmetic in the Ukrainian standards ecosystem, not a source to transcribe pseudocode from for this algorithm.
  • No docs/pseudocode/dstu9041.md exists yet. A real, usable secondary source now exists (above); writing the doc from it (with defects flagged, no oracle to verify against) is a possible next step, not yet done. Obtaining the actual DSTU 9041:2020 standard text remains the way to upgrade past “single uncross-checked secondary source.”
  • The official text was priced (2026-07-21): 5,304.00 UAH for 40 pages — see “Official DSTU text — purchase cost” above. This is still the only algorithm in this project with no oracle of any kind (dual-oracle verification, this project’s own hard constraint, has nothing to check against here even with the thesis in hand) — the strongest case for revisiting the purchase if budget ever allows, but deemed cost-prohibitive for now, same as the other two.
  • Update, 2026-08-05/06 (T-173/T-174/T-176/T-177): the primary text was obtained after all — a partial scan (T-173) plus a targeted supplement (T-176) together cover the recommended l(p)=256 curve (E256/1) end-to-end, including Додаток Г’s own worked example (d, Q, ephemeral ε/R, T, the full ciphertext C). hazmat::dstu9041 (T-177) is verified against that worked example directly — the sole oracle for this primitive, same single-source caveat as the rest of this section (no independent reference implementation exists anywhere, confirmed again as part of T-177’s own closure, not just the 2026-07-21 search above), but now a primary-source vector rather than a secondary thesis transcription. The l(p)=384/512/768 cases remain unimplemented. Confirmed 2026-08-06 (owner-supplied photos of the document’s final pages): the standard is genuinely 36 pages total, not the 40 the store listing states — page 36 is the last page, finishing Додаток Г.3’s l(p)=512 decryption steps and then Додаток Д’s bibliography. Table В.4’s curve parameters for l(p)=768 exist, but no fourth worked example was ever published for it — this is not a purchasing gap (there are no more pages to buy), it’s a permanent absence of a vector oracle for that security level. See docs/pseudocode/dstu9041.md’s “Open gaps”/ “Implementation status” sections and docs/TASKS.md T-182.

Test-vector convention

Populated for Kalyna and Kupyna. The Rust loader exists now (crates/dstu-core/tests/kupyna.rs, per D-10) — the earlier “waits for the first primitive” caveat no longer applies to Kupyna.

  • Vectors live at crates/dstu-core/tests/vectors/<algorithm>/<case>.json — one file per block/key-size or hash-size variant, plain hex fields, human-diffable, not a binary blob.
  • Every vector file records its source (which paper/oracle, down to the appendix section) — an unattributed vector is not admissible, by the same logic as docs/SECURITY.md’s “no primitive without a cited spec section.” Every hex field has been length/validity-checked programmatically against its declared bit size before being committed here — see the PDF extraction notes below.
  • Integration tests in crates/dstu-core/tests/<algorithm>.rs load these files and assert against the Rust implementation — black-box, per docs/rust_ai_ruleset.md §11.
  • Same files, consumed cross-language too: tests/oracle-harness/{java,dotnet}/ run these vectors against real Bouncy Castle directly (not the Rust port), via the published Maven/NuGet packages — one vector format, multiple independent consumers. Both actually run and pass (all 10 Kalyna + all 12 Kupyna cases). No cryptonite/C harness — tried on 2026-07-22 with a real local GCC and dropped: cryptonite’s own source doesn’t compile clean on a modern compiler (unrelated to Kalyna/Kupyna — an error in dstu4145_prng_internal.c), and the added value was already modest given the two harnesses above already independently confirm these vectors. See docs/TASKS.md “Infrastructure” for the full note; cryptonite is still used as a read-only reference (e.g. the D-05 CCM/GCM finding below), just not a runnable harness.
  • Real shape, from crates/dstu-core/tests/vectors/kalyna/128-128.json:
    {
      "algorithm": "Kalyna-128/128",
      "block_bits": 128,
      "key_bits": 128,
      "source": "docs/papers/Kalyna.pdf, Appendix B.2.6 (...)",
      "cases": [
        { "name": "encryption", "key_hex": "...", "plaintext_hex": "...", "ciphertext_hex": "..." }
      ]
    }
    

PDF extraction notes (for re-deriving or extending these): docs/papers/*.pdf were converted with pdftotext -layout. Correction, 2026-07-31: this file previously claimed Cyrillic-only PDFs (Dolgov_5-22.pdf, Strumok_verilog.pdf, and by extension Kalyna_construction_principles_ZI_2015.pdf/Kalyna_vs_international_standards_2018.pdf) lose their prose to a font-encoding issue with no ToUnicode CMap. That claim was checked directly and is false for all four — re-run 2026-07-31 while investigating a fifth PDF (the Skorobahatko DSTU 9041 thesis, see below): pdftotext -layout on each extracts clean, complete Ukrainian prose. The only actual defect found is cosmetic: Cyrillic і (U+0456) sometimes extracts as Latin i (U+0069) — a common LaTeX/T2A-encoding glyph-sharing quirk, not a missing-CMap failure, and not a blocker for reading or transcribing prose (just don’t grep/match on і expecting the Unicode Cyrillic codepoint). Scans with a genuinely empty text layer (DSTU_4145-2002.pdf) are a different, real failure mode — that one still needs the render-to-PNG workflow; don’t conflate the two. Page-footer numbers routinely get injected mid-hex-block by pdftotext (observed and corrected during extraction: stray "64", "96", "36", "18", "34" splitting what should have been one contiguous hex run) — always re-verify against a wide context window around each value, and length-check every field against its declared bit size before trusting it; a plausible-looking but truncated or corrupted vector is worse than no vector, since it fails a correct implementation silently.