diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..485ffbc --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,11 @@ +* @qoosmo + +/.github/ @qoosmo +/LICENSE-MIT @qoosmo +/LICENSE-APACHE @qoosmo +/NOTICE @qoosmo +/CITATION.cff @qoosmo +/research-note/ @qoosmo +/docs/PROVENANCE.md @qoosmo +/docs/original-presentation.pdf @qoosmo +/legacy/ @qoosmo diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..ef0cc31 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,47 @@ +name: Correctness or implementation bug +description: Report a reproducible defect in Java, Rust, verification, or experiment tooling +title: "[bug] " +labels: [] +body: + - type: markdown + attributes: + value: "Please provide a minimal reproducible case." + - type: dropdown + id: component + attributes: + label: Component + options: + - Java reference + - Rust reference + - Experiment harness + - Historical-math verifier + - Paper/research note + - CI/reproducibility + - Other + validations: + required: true + - type: textarea + id: observed + attributes: + label: Observed behavior + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + description: What should have happened, and what evidence supports that expectation? + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: Minimal reproduction + description: Commands, parameters, seed, input, test vector, or mathematical counterexample. + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: OS, Java/JDK, Rust toolchain, commit SHA, and relevant versions. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..5f98d7a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Security policy + url: https://github.com/qoosmo/cryptanalytic-time-memory-tradeoffs/security/policy + about: Read the responsible-disclosure guidance before reporting sensitive issues. + - name: Algorizk Labs + url: https://algorizk.xyz + about: Research and engineering work by Algorizk Labs. diff --git a/.github/ISSUE_TEMPLATE/research_claim.yml b/.github/ISSUE_TEMPLATE/research_claim.yml new file mode 100644 index 0000000..95cd171 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/research_claim.yml @@ -0,0 +1,40 @@ +name: Research claim or experiment proposal +description: Propose a mathematical claim, falsification, experiment, or reproducibility improvement +title: "[research] " +labels: [] +body: + - type: dropdown + id: kind + attributes: + label: Type + options: + - Mathematical claim + - Counterexample / falsification + - Experiment proposal + - Reproducibility improvement + - Java/Rust equivalence + - Finite-field / AES representation + - TMTO / functional-graph analysis + validations: + required: true + - type: textarea + id: question + attributes: + label: Precise research question + description: State the claim or question in falsifiable form. + validations: + required: true + - type: textarea + id: evidence + attributes: + label: Current evidence + description: Paper reference, derivation, committed output, code path, or counterexample. + validations: + required: true + - type: textarea + id: acceptance + attributes: + label: Acceptance criterion + description: What result would establish, refute, or materially advance this question? + validations: + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a4f5831 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + + - package-ecosystem: "cargo" + directory: "/rust" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..5b8ac8b --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,27 @@ +## Summary + +Describe the research, implementation, documentation, or infrastructure change. + +## Evidence + +- [ ] Java verification passes (`mvn -B -ntp verify`) or is not affected. +- [ ] Rust format/tests/Clippy pass or are not affected. +- [ ] Historical finite-field verifier passes or is not affected. +- [ ] `git diff --check` passes. +- [ ] New numerical claims point to committed output and environment metadata. +- [ ] New mathematical claims are identified as proved, computationally checked, conjectural, or future work. + +## Historical integrity + +- [ ] `docs/original-presentation.pdf` is unchanged. +- [ ] Preserved files under `legacy/` are unchanged unless this PR is explicitly archival. + +## Cross-language semantics + +- [ ] Shared Java/Rust fixtures were updated if common semantics changed. +- [ ] No Java-vs-Rust speedup claim is made unless both implementations consume the same experiment plan. + +## Licensing / provenance + +- [ ] New files are compatible with the licensing boundary in `NOTICE`. +- [ ] Third-party material and historical authorship are attributed. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f855269..1810d14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,31 +12,46 @@ permissions: jobs: test: runs-on: ubuntu-latest - timeout-minutes: 12 + timeout-minutes: 15 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Set up Java 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4 with: distribution: temurin java-version: '17' cache: maven - name: Set up Rust - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable + with: + components: rustfmt, clippy + + - name: Verify historical presentation integrity + run: | + echo "4003194fdfe82e84bcc32876c8d9ba5042d12dc5f14da726a4bd6e3edc2e6bec docs/original-presentation.pdf" | sha256sum --check --strict - name: Verify historical finite-field mathematics run: python3 scripts/verify-historical-math.py - - name: Java tests - run: mvn -B -ntp test + - name: Java verify + run: mvn -B -ntp verify + + - name: Rust format + run: cargo fmt --manifest-path rust/Cargo.toml --all -- --check + + - name: Rust Clippy + run: cargo clippy --locked --manifest-path rust/Cargo.toml --all-targets -- -D warnings - name: Rust tests run: cargo test --locked --manifest-path rust/Cargo.toml + - name: Rust release build + run: cargo build --release --locked --manifest-path rust/Cargo.toml --bin tmto + - name: Rust smoke test run: cargo run --quiet --locked --manifest-path rust/Cargo.toml --bin tmto -- smoke diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..cce2959 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,32 @@ +cff-version: 1.2.0 +message: >- + If you use this repository, please cite the software artifact and the exact + commit you used. The future ePrint author list will be finalized separately + with the original research collaborators. +title: "Representation and Time-Memory Tradeoffs in Symmetric Cryptography" +type: software +authors: + - family-names: "Sakho" + given-names: "Abdourahmane" + - family-names: "Mkhida" + given-names: "Ali" + orcid: "https://orcid.org/0009-0009-2101-9070" + - family-names: "El Yadari" + given-names: "Maad" +repository-code: "https://github.com/qoosmo/cryptanalytic-time-memory-tradeoffs" +url: "https://algorizk.xyz" +abstract: >- + Research artifact on finite-field representation engineering for AES-like + arithmetic and reproducible cryptanalytic time-memory tradeoffs, with Java + and Rust reference implementations, exact coverage measurements, historical + reconstruction, and an evolving ePrint manuscript. +keywords: + - cryptography + - cryptanalysis + - time-memory tradeoff + - Hellman + - distinguished points + - finite fields + - AES + - Rust + - Java diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..c2f89e6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,31 @@ +# Code of Conduct + +## Our standard + +This is a research repository. Discussion should be technically rigorous, +professional, and respectful. Contributors are expected to: + +- critique claims, proofs, experiments, and code rather than people; +- distinguish evidence from speculation; +- disclose uncertainty, failed experiments, and limitations relevant to a claim; +- respect authorship, provenance, licensing, and responsible-disclosure boundaries; +- avoid harassment, discriminatory language, personal attacks, doxxing, or intimidation. + +## Research disagreements + +Strong disagreement is welcome when it improves the work. When challenging a +result, provide a reproducible counterexample, mathematical argument, failing +test, benchmark configuration, or precise source reference whenever possible. + +## Historical material + +Do not erase, rewrite, or misattribute the preserved historical project. Modern +corrections should be documented as corrections or new analysis rather than +silently substituted into the historical record. + +## Enforcement + +Maintainers may edit, hide, reject, or remove contributions that violate these +standards and may restrict participation for repeated or severe violations. +Concerns about repository conduct can be raised privately with the repository +owner through the GitHub account `@qoosmo`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 36d1bf9..8566139 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,36 +1,63 @@ # Contributing -Contributions are welcome when they preserve the repository's evidence discipline. +Contributions are welcome when they preserve the repository's evidence discipline, historical provenance, and reproducibility guarantees. -## Before changing an algorithm +## Workflow -- add or update tests; -- preserve the explicit reduced-state semantics; -- do not mix historical results with newly measured results; -- keep Java/Rust equivalence fixtures synchronized when shared semantics change. +1. Create a focused branch. +2. Keep historical artifacts untouched unless the change is explicitly archival. +3. Add tests/evidence before changing an algorithmic or mathematical claim. +4. Run the relevant quality gates. +5. Open a pull request using the repository template. +6. Do not merge a performance claim until output and environment metadata are committed. -## Before adding a paper claim +## Full check -Classify it as historical, verified, measured, conjectural, or future work. A numerical result must point to a committed experiment or verification path. +```bash +./scripts/check-all.sh +``` ## Java ```bash -mvn test +mvn -B -ntp verify ``` +Changes to state encoding, DES parity, reduction, table construction, or lookup require regression tests. + ## Rust ```bash -cargo test --manifest-path rust/Cargo.toml +cargo fmt --manifest-path rust/Cargo.toml --all -- --check +cargo clippy --locked --manifest-path rust/Cargo.toml --all-targets -- -D warnings +cargo test --locked --manifest-path rust/Cargo.toml +cargo build --release --locked --manifest-path rust/Cargo.toml --bin tmto ``` -## Historical finite-field verification +If shared Java/Rust semantics change, update `test-vectors/` and require both suites to pass. -```bash -python3 scripts/verify-historical-math.py -``` +## Paper claims + +Classify every new claim as historical reconstruction, mathematically proved, +computationally verified, measured, conjectural, or future work. + +Numerical claims must point to committed parameters, output, and environment +metadata. Direct Java/Rust performance comparisons require identical experiment +inputs. ## Historical assets -Do not modify `docs/original-presentation.pdf` or the preserved Eclipse prototype as part of modernization work. +Normal modernization work must not modify: + +- `docs/original-presentation.pdf`; +- `legacy/original-eclipse-prototype/`. + +The presentation hash is enforced in CI. + +## Licensing + +Modern contributions intended for the dual-licensed software portion are +submitted under **MIT OR Apache-2.0**, subject to the boundary in `NOTICE`. + +Do not copy third-party or historical material into the dual-licensed tree +unless rights and attribution are clear. diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..aee82c3 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Ali Mkhida, Algorizk Labs + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..c6ff7d3 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ali Mkhida, Algorizk Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the software and associated software documentation files (the "Software"), +to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..42b7406 --- /dev/null +++ b/NOTICE @@ -0,0 +1,28 @@ +Representation and Time-Memory Tradeoffs in Symmetric Cryptography +Copyright 2026 Ali Mkhida, Algorizk Labs + +LICENSING BOUNDARY + +The modern software, tests, build scripts, CI configuration, verification +utilities, and modern reproducibility code authored as part of the repository +modernization are offered under either the MIT License or Apache License 2.0, +at your option. + +The software-license grant does NOT relicense the following historical or +scholarly material: + + - docs/original-presentation.pdf + - docs/original-README.md + - legacy/original-eclipse-prototype/** + - research-note/** + +The preserved historical project was developed by Abdourahmane Sakho, +Ali Mkhida, and Maad El Yadari. Historical credit and existing rights are +retained regardless of the licensing of newly written modernization code. + +Generated experiment CSV files and environment metadata are published as +reproducibility evidence for this research repository. They should be cited +with the repository and the relevant commit when used in comparative work. + +The names "Algorizk" and "Algorizk Labs" are identifiers of the research +maintainer and are not granted as trademarks by the software licenses. diff --git a/README.md b/README.md index 3e567ee..d76d771 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,66 @@ # Representation and Time-Memory Tradeoffs in Symmetric Cryptography [![CI](https://github.com/qoosmo/cryptanalytic-time-memory-tradeoffs/actions/workflows/ci.yml/badge.svg)](https://github.com/qoosmo/cryptanalytic-time-memory-tradeoffs/actions/workflows/ci.yml) +[![Java 17](https://img.shields.io/badge/reference-Java%2017-blue.svg)](src/main/java/io/github/qoosmo/hpc) +[![Rust](https://img.shields.io/badge/reference-Rust-orange.svg)](rust/) +[![Research](https://img.shields.io/badge/status-active%20research-6f42c1.svg)](RESEARCH.md) +[![ePrint](https://img.shields.io/badge/paper-ePrint%20draft-informational.svg)](research-note/cryptanalytic-time-memory-tradeoffs.pdf) +[![License: MIT OR Apache-2.0](https://img.shields.io/badge/modern%20software-MIT%20OR%20Apache--2.0-blue.svg)](#license) -A reproducible research project in symmetric cryptography combining two themes: +**An [Algorizk Labs](https://algorizk.xyz) research artifact in cryptography, reproducible systems, and implementation-level verification.** -1. **finite-field representation engineering for AES-like arithmetic** — polynomial and normal bases, tower-field isomorphisms, composite-field inversion, and non-LUT S-box structure; -2. **cryptanalytic time-memory tradeoffs** — exhaustive search, Hellman chains, distinguished points, exact table coverage, and functional-graph coalescence over deliberately reduced DES state spaces. - -The project originated as an **M2P SCCI master's research project at Ensimag–UJF** by **Abdourahmane Sakho, Ali Mkhida, and Maad El Yadari**. The original research presentation and Eclipse prototype are preserved unchanged, while the modern repository adds a paper-oriented reconstruction, reproducible Java and Rust implementations, exact verification, and CI. - -> **Scope.** DES is obsolete and is used only as a compact reproducibility fixture. Nothing in this repository claims to break full-size DES or AES, and none of the code should be used as production cryptography. - -## Paper track - -The repository is being developed toward an **IACR ePrint submission**. - -**Working title** +This repository develops two connected research directions in symmetric cryptography: -> **Representation and Time-Memory Tradeoffs in Symmetric Cryptography: Finite-Field Basis Transformations for AES and Reproducible Hellman Experiments** - -- [ePrint-style PDF](research-note/cryptanalytic-time-memory-tradeoffs.pdf) -- [LaTeX source](research-note/main.tex) -- [historical-math reconstruction map](research-note/HISTORICAL-MATH-SOURCE.md) -- [research questions and evidence policy](RESEARCH.md) -- [project roadmap](ROADMAP.md) -- [authors and provenance](AUTHORS.md) - -The ePrint draft is an evolving research document. The authorship of a future submission will be finalized with the original project collaborators before submission. +1. **finite-field representation engineering for AES-like arithmetic** — polynomial and normal bases, tower-field isomorphisms, composite-field inversion, and non-LUT S-box structure; +2. **cryptanalytic time-memory tradeoffs** — exhaustive search, Hellman chains, distinguished points, exact coverage, collision handling, and functional-graph coalescence over deliberately reduced DES state spaces. -## Two PDFs, permanently separated +The project originated as an **M2P SCCI master's research project at Ensimag–UJF** by **Abdourahmane Sakho, Ali Mkhida, and Maad El Yadari**. The original presentation and Eclipse prototype are preserved unchanged. The modern repository adds a paper-oriented mathematical reconstruction, independent Java and Rust reference implementations, deterministic experiments, cross-language fixtures, exact verification, and CI. -| Artifact | Role | -| --- | --- | -| [`docs/original-presentation.pdf`](docs/original-presentation.pdf) | Original master's research presentation, preserved byte-for-byte with its original logos and layout | -| [`research-note/cryptanalytic-time-memory-tradeoffs.pdf`](research-note/cryptanalytic-time-memory-tradeoffs.pdf) | Current paper-oriented reconstruction and modern experimental report | +> **Scope.** DES is obsolete and is used here only as a compact reproducibility fixture. This repository does not claim to break full-size DES or AES and is not production cryptographic software. -The original presentation is not rewritten or replaced. +## What this repository demonstrates -Its preserved SHA-256 is: +A result should be traceable through: ```text -4003194fdfe82e84bcc32876c8d9ba5042d12dc5f14da726a4bd6e3edc2e6bec +mathematical statement + ↓ +reference semantics + ↓ +independent implementations + ↓ +committed evidence / experiment output ``` -## Historical mathematics reconstructed +That evidence chain is the core of the repository and reflects the research engineering approach used at Algorizk Labs. + +## Research highlights -The paper now reconstructs the recoverable mathematics from the original presentation, including: +### Historical finite-field mathematics -- the concrete `GF(16) = GF(2)[X]/(X^4 + X^3 + X^2 + X + 1)` model; -- the normal basis `{α^8, α^4, α^2, α}`; +The paper reconstructs the recoverable mathematics from the original presentation: + +- `GF(16) = GF(2)[X]/(X^4 + X^3 + X^2 + X + 1)`; +- normal basis `{α^8, α^4, α^2, α}`; - the slide-derived `GF(16)` multiplication formula; - Frobenius squaring as a coordinate permutation; - the generator-based tower-field isomorphism construction; -- the exact historical `8×8` binary isomorphism matrix and its inverse; -- Hamming-weight interpretation of those binary maps as parallel XOR networks; +- the exact historical `8×8` binary isomorphism matrix and inverse; +- Hamming-weight interpretation of the maps as XOR networks; - the second `GF(16) -> GF(4)` composite-field layer; -- the non-LUT identity `γ^{-1} = γ^2` for nonzero `γ ∈ GF(4)`. - -[`scripts/verify-historical-math.py`](scripts/verify-historical-math.py) independently checks the finite-field formulas and matrix identities. +- `γ^{-1}=γ^2` for nonzero `γ ∈ GF(4)`. -Slides whose derivations were written only on the board during the original research presentation are explicitly marked as unrecoverable from the preserved PDF; the paper does not invent those missing proofs. +[`scripts/verify-historical-math.py`](scripts/verify-historical-math.py) independently checks the recoverable finite-field identities and matrix inverses. -## Cryptanalytic model +### Cryptanalytic state model -For a reduced state `x`, a canonical DES key encoding `K(x)`, fixed plaintext `P`, encryption `E`, and reduction `R`, one chain transition is +For reduced state `x`, canonical DES key `K(x)`, fixed plaintext `P`, encryption `E`, and reduction `R`: ```math -F(x) = R(E_{K(x)}(P)). +F(x)=R(E_{K(x)}(P)). ``` -The modern state model uses **effective DES key bits**, not raw Java key-representation bytes: +The model uses effective DES key bits: ```math x \in \{0,\dots,2^b-1\}, \qquad 1 \le b \le 28. @@ -77,122 +68,168 @@ x \in \{0,\dots,2^b-1\}, \qquad 1 \le b \le 28. The four variable DES bytes contribute seven effective bits each; odd parity is inserted canonically. -## Implementations +### Independent Java and Rust implementations -### Java 17 reference +The committed 16-bit experiments use 30 trials, Hellman `(m,t)=(1024,64)`, and distinguished points with 1024 starts, `d=6`, maximum chain length 256. -The Java implementation under [`src/main/java/io/github/qoosmo/hpc`](src/main/java/io/github/qoosmo/hpc) contains: +| Implementation | Method | Exact recovery | Median exact coverage | +| --- | --- | ---: | ---: | +| Java | Exhaustive | 30/30 | 100.0% | +| Java | Hellman | 4/30 | 16.8% | +| Java | Distinguished points | 3/30 | 15.4% | +| Rust | Exhaustive | 30/30 | 100.0% | +| Rust | Hellman | 4/30 | 16.8% | +| Rust | Distinguished points | 3/30 | 15.4% | -- `ReducedDesKeySpace` -- `DesOracle` -- `LegacyReduction` -- `ExhaustiveSearch` -- `HellmanTable` -- `DistinguishedPointPredicate` -- `DistinguishedPointTable` -- `CoverageAnalyzer` -- `ExperimentRunner` -- `ExactCoverageRunner` +The matching recovery counts and coverage are a strong cross-implementation correctness signal. The current Java and Rust timing runs use different deterministic trial generators, so **no Java-vs-Rust speedup claim is made yet**. A shared experiment plan is the next comparison gate. -The Java suite remains the reference for the committed benchmark rows. +See [`docs/VERIFIED_RESULTS.md`](docs/VERIFIED_RESULTS.md) and [`docs/RUST_VERIFIED_RESULTS.md`](docs/RUST_VERIFIED_RESULTS.md). -### Rust reference — added 28 August 2026 +### Functional-graph coalescence -The Rust crate under [`rust/`](rust/) now implements the same core semantics: +With `mt/N = 1`, the independent-sample occupancy reference is -- canonical reduced DES state encoding and odd parity; -- DES/ECB with PKCS#5-compatible padding over the same plaintext fixture; -- the historical effective-bit reduction; -- exhaustive search; -- fixed-length Hellman chains; -- bounded distinguished-point chains; -- exact state-coverage analysis; -- shared Java/Rust reference vectors. +```math +1-e^{-mt/N} \approx 63.2\%. +``` -This first Rust implementation is a **correctness/equivalence layer**, not yet a speed claim. +The measured Hellman coverage is only about 16–17% in the committed configuration because the chains are iterates of one fixed map and therefore merge. -The shared vectors in [`test-vectors/java-rust-reference.csv`](test-vectors/java-rust-reference.csv) are checked by both languages. +## Paper track -## Verified Java benchmark +The repository is being developed toward an **IACR ePrint submission**. -The committed benchmark uses `b = 16`, `N = 65,536`, 30 deterministic seeded trials, Hellman `(m,t)=(1024,64)`, and distinguished points with 1024 starts, `d=6`, maximum chain length 256. +> **Working title:** *Representation and Time-Memory Tradeoffs in Symmetric Cryptography: Finite-Field Basis Transformations for AES and Reproducible Hellman Experiments* -| Method | Exact recovery | Median exact coverage | Median offline | Median online | -| --- | ---: | ---: | ---: | ---: | -| Exhaustive | 30/30 | 100.0% | 0.000 ms | 25.863 ms | -| Hellman | 4/30 | 16.8% | 70.222 ms | 3.399 ms | -| Distinguished points | 3/30 | 15.4% | 59.311 ms | 1.790 ms | +- [ePrint-style PDF](research-note/cryptanalytic-time-memory-tradeoffs.pdf) +- [LaTeX source](research-note/main.tex) +- [historical-math reconstruction map](research-note/HISTORICAL-MATH-SOURCE.md) +- [research questions and evidence policy](RESEARCH.md) +- [roadmap](ROADMAP.md) +- [authorship and provenance](AUTHORS.md) +- [citation metadata](CITATION.cff) -With `mt/N = 1`, the independent-sample occupancy reference is +The future ePrint author list will be finalized with the original project collaborators before submission. -```math -1-e^{-mt/N} \approx 63.2\%. -``` +## Historical preservation -The measured Hellman coverage is only about 16–17% because the chains are iterates of one fixed map and therefore coalesce. The exact functional graph, not independent sampling, governs the table. +| Artifact | Role | +| --- | --- | +| [`docs/original-presentation.pdf`](docs/original-presentation.pdf) | Original master's research presentation | +| [`legacy/original-eclipse-prototype/`](legacy/original-eclipse-prototype/) | Original Eclipse/Java prototype | -See [`docs/VERIFIED_RESULTS.md`](docs/VERIFIED_RESULTS.md). +Canonical presentation SHA-256: + +```text +4003194fdfe82e84bcc32876c8d9ba5042d12dc5f14da726a4bd6e3edc2e6bec +``` + +CI checks this hash. ## Reproduce +Full local research checks: + +```bash +./scripts/check-all.sh +``` + Java: ```bash -mvn test +mvn -B -ntp verify ./scripts/run-verified-small.sh ``` Rust: ```bash -cargo test --manifest-path rust/Cargo.toml -cargo run --manifest-path rust/Cargo.toml --bin tmto -- smoke +cargo fmt --manifest-path rust/Cargo.toml --all -- --check +cargo clippy --locked --manifest-path rust/Cargo.toml --all-targets -- -D warnings +cargo test --locked --manifest-path rust/Cargo.toml +cargo run --release --locked --manifest-path rust/Cargo.toml --bin tmto -- verified-small ``` -Historical finite-field reconstruction: +Historical mathematics: ```bash python3 scripts/verify-historical-math.py ``` -## Repository layout +## CI quality gates + +Every pull request is expected to pass: + +1. historical presentation SHA-256 integrity; +2. historical finite-field verification; +3. Java 17 Maven verification; +4. Rust formatting; +5. warning-free Rust Clippy; +6. Rust tests; +7. Rust release build; +8. Java and Rust smoke experiments; +9. diff hygiene. + +## Repository map ```text . ├── README.md ├── AUTHORS.md +├── CITATION.cff ├── RESEARCH.md ├── ROADMAP.md -├── research-note/ -│ ├── main.tex -│ ├── cryptanalytic-time-memory-tradeoffs.pdf -│ └── HISTORICAL-MATH-SOURCE.md +├── CONTRIBUTING.md +├── SECURITY.md +├── CODE_OF_CONDUCT.md +├── LICENSE-MIT +├── LICENSE-APACHE +├── NOTICE +├── research-note/ # evolving ePrint manuscript ├── src/ # Java 17 reference -├── rust/ # Rust reference implementation +├── rust/ # Rust reference + experiments ├── test-vectors/ # Java/Rust equivalence fixtures -├── experiments/results/ # curated benchmark evidence -├── scripts/ -├── docs/ -│ ├── original-presentation.pdf -│ ├── METHODOLOGY.md -│ ├── VERIFIED_RESULTS.md -│ ├── HISTORICAL_RESULTS.md -│ └── PROVENANCE.md -└── legacy/ - └── original-eclipse-prototype/ +├── experiments/results/ # committed benchmark evidence +├── scripts/ # reproduction / verification +├── docs/ # methodology, provenance, results, historical PDF +├── legacy/ # preserved original prototype +└── .github/ # CI, ownership, templates, Dependabot ``` ## Research discipline The repository distinguishes: -- **historical reconstruction** — statements directly recoverable from the original research presentation; -- **modern verification** — identities or semantics checked by committed tests; -- **reproduced experiments** — measurements generated by committed scripts; -- **future work** — results that still require implementation or new experiments. +- **historical reconstruction**; +- **mathematical verification**; +- **reference semantics**; +- **measured experiments**; +- **future work**. + +A benchmark or mathematical statement is not promoted into the paper without an identifiable evidence path. + +## Related Algorizk research + +This repository is part of a broader open R&D portfolio at **Algorizk Labs**: + +- [Boolean Kernel Basis Filtration](https://github.com/qoosmo/kernel-basis-filtration) — kernel coordinates, low-degree filtration, Rust checks, and Lean formalization; +- [Multilinear Sumcheck](https://github.com/qoosmo/multilinear-sumcheck) — reproducible Rust implementation and protocol-oriented research artifact. + +Algorizk Labs works at the boundary of **cryptography, mathematical algorithms, formal verification, arithmetic kernels, and hardware/software co-design**. See [algorizk.xyz](https://algorizk.xyz). + +## Contributing and security + +Read [`CONTRIBUTING.md`](CONTRIBUTING.md), [`SECURITY.md`](SECURITY.md), and [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). + +## Citation + +GitHub-readable citation metadata is provided in [`CITATION.cff`](CITATION.cff). + +**Maintainer:** Ali Mkhida — Algorizk Labs +**ORCID:** [0009-0009-2101-9070](https://orcid.org/0009-0009-2101-9070) -No benchmark or mathematical result is promoted into the paper without a reproducible source. +## License -## License and reuse +The **modern software, tests, build scripts, CI configuration, verification utilities, and modern reproducibility code** are available under either the MIT License or Apache License 2.0, at your option. See [`LICENSE-MIT`](LICENSE-MIT), [`LICENSE-APACHE`](LICENSE-APACHE), and [`NOTICE`](NOTICE). -A blanket repository license is intentionally not declared yet. The historical presentation and original prototype are coauthored research artifacts, so their reuse rights should not be inferred from the later modernization. Licensing of newly written Java/Rust code can be separated from the historical material after the original collaborators are contacted. +The software licenses **do not relicense** the preserved historical presentation, preserved historical Eclipse prototype, or the evolving scholarly manuscript under `research-note/`. diff --git a/RESEARCH.md b/RESEARCH.md index 84efe06..85d712b 100644 --- a/RESEARCH.md +++ b/RESEARCH.md @@ -52,6 +52,7 @@ Can Java and Rust reproduce identical state encodings, ciphertexts, reductions, - 30-trial Java benchmark and exact coverage results; - initial Rust reference implementation; - shared Java/Rust reference vectors. +- deterministic Rust experiment harness and measured 16-bit run; ## Evidence policy diff --git a/ROADMAP.md b/ROADMAP.md index 9794e34..4685ce2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -12,10 +12,13 @@ - [x] Create an ePrint-style LaTeX paper draft. - [x] Add the first Rust reference implementation. - [x] Add shared Java/Rust reference vectors. +- [x] Add a deterministic Rust experiment harness with exact coverage output. +- [x] Commit a first measured Rust 16-bit/30-trial benchmark with environment metadata. ## Immediate -- [ ] Get the Rust CI green on the canonical GitHub branch. +- [x] Get the Rust reference implementation CI green on the canonical GitHub branch. +- [ ] Get the new Rust experiment harness CI green on its PR. - [ ] Extend cross-language vectors to table lookup, collision, truncation, and coverage cases. - [ ] Implement the historical `GF(16)` and tower-field arithmetic as executable Rust modules. - [ ] Implement the AES non-LUT S-box path and compare against the standard S-box. diff --git a/SECURITY.md b/SECURITY.md index 7675070..afea78f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,11 +1,35 @@ -# Security +# Security Policy -This repository contains educational experiments using DES, an obsolete -cipher, over deliberately reduced key spaces. +## Scope -It must not be used to protect real data or as a production cryptographic -library. +This repository contains cryptographic research code and deliberately reduced +DES experiments. It is **not production cryptographic software**, has not been +security-audited, and must not be used to protect real data. -If you find a correctness issue that changes a documented experimental result, -please open a GitHub issue describing the affected algorithm, parameters, and -a minimal reproduction. +DES itself is obsolete. Reporting that DES is insecure is not a vulnerability +in this repository. + +## Supported code + +Security and correctness fixes target `main` and active pull requests. +Historical artifacts are preserved for provenance and are not maintained as +secure software. + +## Sensitive reports + +If an issue could expose credentials, private data, a supply-chain weakness, or +a vulnerability in reusable modern code, prefer GitHub private vulnerability +reporting / Security Advisories when available. + +Do not publish secrets, credentials, private exploit material, or personal data +in a public issue. If private reporting is unavailable, open a minimal public +issue requesting a private channel without including sensitive details. + +Ordinary correctness bugs, benchmark discrepancies, mathematical +counterexamples, and reproducibility failures should use the issue templates. + +## Useful report fields + +Include the affected commit SHA, toolchain versions, minimal reproduction, +expected/observed behavior, and whether a committed paper claim or benchmark is +affected. diff --git a/docs/RUST_VERIFIED_RESULTS.md b/docs/RUST_VERIFIED_RESULTS.md new file mode 100644 index 0000000..3a17cb5 --- /dev/null +++ b/docs/RUST_VERIFIED_RESULTS.md @@ -0,0 +1,41 @@ +# Verified Rust results + +This file records the first committed Rust benchmark run for the project. +The Rust implementation passed the shared Java/Rust semantic vectors before +these measurements were collected. + +Timing values are machine-dependent. The Rust benchmark currently uses a +deterministic SplitMix64 experiment generator; it does **not yet** consume +the exact Java `SplittableRandom` trial plan. Therefore the Java and Rust +timings below should not yet be treated as an apples-to-apples speedup claim. + +## Configuration + +- effective state dimension: **16 bits**, `N = 65,536`; +- trials: **30**; +- seed: `20260828`; +- Hellman: `m = 1024`, `t = 64`; +- distinguished points: 1024 starts, `d = 6`, maximum chain length 256; +- a separate warm-up run is executed before measurements; +- exact coverage is computed outside each timed build/lookup interval. + +## Results + +| Method | Exact recovery | Median exact coverage | Median offline | Median online | +| --- | ---: | ---: | ---: | ---: | +| Exhaustive | 30/30 | 100.0% | 0.000 ms | 67.226 ms | +| Hellman | 4/30 | 16.8% | 175.755 ms | 8.383 ms | +| Distinguished points | 3/30 | 15.4% | 149.456 ms | 4.089 ms | + +## Interpretation + +The algorithmic quantities to compare first are exact recovery, endpoint +statistics, and exact coverage. A direct Java-versus-Rust timing claim is +deferred until both implementations consume one committed shared experiment +plan (identical targets and identical Hellman/DP start sets). + +## Files + +- `experiments/results/rust-verified-small.csv` +- `experiments/results/rust-verified-small-coverage.csv` +- `experiments/results/rust-verified-small-environment.txt` diff --git a/experiments/results/rust-verified-small-coverage.csv b/experiments/results/rust-verified-small-coverage.csv new file mode 100644 index 0000000..cad2654 --- /dev/null +++ b/experiments/results/rust-verified-small-coverage.csv @@ -0,0 +1,91 @@ +trial,method,covered_states,state_space_size,coverage_fraction +0,exhaustive,65536,65536,1.000000000000 +0,hellman,11138,65536,0.169952392578 +0,distinguished_points,10073,65536,0.153701782227 +1,exhaustive,65536,65536,1.000000000000 +1,hellman,11291,65536,0.172286987305 +1,distinguished_points,10079,65536,0.153793334961 +2,exhaustive,65536,65536,1.000000000000 +2,hellman,11079,65536,0.169052124023 +2,distinguished_points,9987,65536,0.152389526367 +3,exhaustive,65536,65536,1.000000000000 +3,hellman,10847,65536,0.165512084961 +3,distinguished_points,10009,65536,0.152725219727 +4,exhaustive,65536,65536,1.000000000000 +4,hellman,11165,65536,0.170364379883 +4,distinguished_points,10304,65536,0.157226562500 +5,exhaustive,65536,65536,1.000000000000 +5,hellman,10973,65536,0.167434692383 +5,distinguished_points,10270,65536,0.156707763672 +6,exhaustive,65536,65536,1.000000000000 +6,hellman,11083,65536,0.169113159180 +6,distinguished_points,9970,65536,0.152130126953 +7,exhaustive,65536,65536,1.000000000000 +7,hellman,10797,65536,0.164749145508 +7,distinguished_points,10207,65536,0.155746459961 +8,exhaustive,65536,65536,1.000000000000 +8,hellman,11236,65536,0.171447753906 +8,distinguished_points,9890,65536,0.150909423828 +9,exhaustive,65536,65536,1.000000000000 +9,hellman,10928,65536,0.166748046875 +9,distinguished_points,10157,65536,0.154983520508 +10,exhaustive,65536,65536,1.000000000000 +10,hellman,11255,65536,0.171737670898 +10,distinguished_points,10120,65536,0.154418945312 +11,exhaustive,65536,65536,1.000000000000 +11,hellman,10945,65536,0.167007446289 +11,distinguished_points,10162,65536,0.155059814453 +12,exhaustive,65536,65536,1.000000000000 +12,hellman,11078,65536,0.169036865234 +12,distinguished_points,10076,65536,0.153747558594 +13,exhaustive,65536,65536,1.000000000000 +13,hellman,10820,65536,0.165100097656 +13,distinguished_points,10234,65536,0.156158447266 +14,exhaustive,65536,65536,1.000000000000 +14,hellman,11148,65536,0.170104980469 +14,distinguished_points,10317,65536,0.157424926758 +15,exhaustive,65536,65536,1.000000000000 +15,hellman,11065,65536,0.168838500977 +15,distinguished_points,10035,65536,0.153121948242 +16,exhaustive,65536,65536,1.000000000000 +16,hellman,10909,65536,0.166458129883 +16,distinguished_points,10359,65536,0.158065795898 +17,exhaustive,65536,65536,1.000000000000 +17,hellman,10872,65536,0.165893554688 +17,distinguished_points,10068,65536,0.153625488281 +18,exhaustive,65536,65536,1.000000000000 +18,hellman,11048,65536,0.168579101562 +18,distinguished_points,9935,65536,0.151596069336 +19,exhaustive,65536,65536,1.000000000000 +19,hellman,10905,65536,0.166397094727 +19,distinguished_points,10337,65536,0.157730102539 +20,exhaustive,65536,65536,1.000000000000 +20,hellman,11263,65536,0.171859741211 +20,distinguished_points,10106,65536,0.154205322266 +21,exhaustive,65536,65536,1.000000000000 +21,hellman,10777,65536,0.164443969727 +21,distinguished_points,10207,65536,0.155746459961 +22,exhaustive,65536,65536,1.000000000000 +22,hellman,11005,65536,0.167922973633 +22,distinguished_points,10066,65536,0.153594970703 +23,exhaustive,65536,65536,1.000000000000 +23,hellman,11146,65536,0.170074462891 +23,distinguished_points,10046,65536,0.153289794922 +24,exhaustive,65536,65536,1.000000000000 +24,hellman,11099,65536,0.169357299805 +24,distinguished_points,10095,65536,0.154037475586 +25,exhaustive,65536,65536,1.000000000000 +25,hellman,11385,65536,0.173721313477 +25,distinguished_points,10026,65536,0.152984619141 +26,exhaustive,65536,65536,1.000000000000 +26,hellman,10904,65536,0.166381835938 +26,distinguished_points,10368,65536,0.158203125000 +27,exhaustive,65536,65536,1.000000000000 +27,hellman,10843,65536,0.165451049805 +27,distinguished_points,10155,65536,0.154953002930 +28,exhaustive,65536,65536,1.000000000000 +28,hellman,10851,65536,0.165573120117 +28,distinguished_points,10332,65536,0.157653808594 +29,exhaustive,65536,65536,1.000000000000 +29,hellman,10963,65536,0.167282104492 +29,distinguished_points,10035,65536,0.153121948242 diff --git a/experiments/results/rust-verified-small-environment.txt b/experiments/results/rust-verified-small-environment.txt new file mode 100644 index 0000000..56b6801 --- /dev/null +++ b/experiments/results/rust-verified-small-environment.txt @@ -0,0 +1,17 @@ +generated_at_utc=2026-08-28T06:44:28Z + +Darwin Alis-MacBook-Pro-2.local 25.6.0 Darwin Kernel Version 25.6.0: Fri Jul 31 19:17:12 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T8103 arm64 + +ProductName: macOS +ProductVersion: 26.6.2 +BuildVersion: 25G83 + +rustc 1.96.0-nightly (55e86c996 2026-04-02) +binary: rustc +commit-hash: 55e86c996809902e8bbad512cfb4d2c18be446d9 +commit-date: 2026-04-02 +host: aarch64-apple-darwin +release: 1.96.0-nightly +LLVM version: 22.1.2 + +cargo 1.96.0-nightly (888f67534 2026-03-30) diff --git a/experiments/results/rust-verified-small.csv b/experiments/results/rust-verified-small.csv new file mode 100644 index 0000000..711f1d5 --- /dev/null +++ b/experiments/results/rust-verified-small.csv @@ -0,0 +1,91 @@ +trial,method,state_bits,target_state,configured_chains,chain_parameter,dp_difficulty_bits,stored_chains,distinct_endpoints,offline_ns,online_ns,exact_recovery,recovered_state,endpoint_matches_or_candidates,candidate_chains_checked,truncated_chains,projection_steps +0,exhaustive,16,57417,0,0,0,0,0,0,151507500,true,57417,0,0,0,0 +0,hellman,16,57417,1024,64,0,1024,684,175374083,15755000,false,-1,33,60,0,0 +0,distinguished_points,16,57417,1024,256,6,1015,139,151660375,28553375,false,-1,93,93,9,136 +1,exhaustive,16,59109,0,0,0,0,0,0,155018417,true,59109,0,0,0,0 +1,hellman,16,59109,1024,64,0,1024,736,176997416,8293208,false,-1,10,17,0,0 +1,distinguished_points,16,59109,1024,256,6,1022,140,145973458,8216333,false,-1,37,37,2,87 +2,exhaustive,16,21223,0,0,0,0,0,0,55699834,true,21223,0,0,0,0 +2,hellman,16,21223,1024,64,0,1024,697,176161250,8473750,true,21223,15,21,0,0 +2,distinguished_points,16,21223,1024,256,6,1023,141,166094250,6774208,false,-1,43,43,1,55 +3,exhaustive,16,18211,0,0,0,0,0,0,50736042,true,18211,0,0,0,0 +3,hellman,16,18211,1024,64,0,1024,693,174929167,5365292,false,-1,0,0,0,0 +3,distinguished_points,16,18211,1024,256,6,1017,134,154427416,40208,false,-1,1,1,7,9 +4,exhaustive,16,56041,0,0,0,0,0,0,147095500,true,56041,0,0,0,0 +4,hellman,16,56041,1024,64,0,1024,696,175310750,6253667,false,-1,5,5,0,0 +4,distinguished_points,16,56041,1024,256,6,1023,146,146866292,3630875,false,-1,33,33,1,65 +5,exhaustive,16,22121,0,0,0,0,0,0,58231041,true,22121,0,0,0,0 +5,hellman,16,22121,1024,64,0,1024,695,178895417,7440792,false,-1,11,12,0,0 +5,distinguished_points,16,22121,1024,256,6,1021,140,146345583,531750,false,-1,6,6,3,1 +6,exhaustive,16,7731,0,0,0,0,0,0,20298958,true,7731,0,0,0,0 +6,hellman,16,7731,1024,64,0,1024,689,176968458,7326000,false,-1,4,6,0,0 +6,distinguished_points,16,7731,1024,256,6,1023,151,163878041,39278000,false,-1,109,109,1,195 +7,exhaustive,16,55558,0,0,0,0,0,0,147327333,true,55558,0,0,0,0 +7,hellman,16,55558,1024,64,0,1024,678,176548208,6213834,false,-1,5,5,0,0 +7,distinguished_points,16,55558,1024,256,6,1021,147,155089041,8885125,false,-1,58,58,3,92 +8,exhaustive,16,58733,0,0,0,0,0,0,159543625,true,58733,0,0,0,0 +8,hellman,16,58733,1024,64,0,1024,691,191942375,12628083,false,-1,22,42,0,0 +8,distinguished_points,16,58733,1024,256,6,1021,153,145824209,29196709,false,-1,94,94,3,152 +9,exhaustive,16,12073,0,0,0,0,0,0,31959875,true,12073,0,0,0,0 +9,hellman,16,12073,1024,64,0,1024,710,307236042,13781166,false,-1,27,40,0,0 +9,distinguished_points,16,12073,1024,256,6,1018,141,166280375,2266458,false,-1,27,27,6,22 +10,exhaustive,16,5407,0,0,0,0,0,0,14124667,true,5407,0,0,0,0 +10,hellman,16,5407,1024,64,0,1024,676,179052500,5998334,false,-1,2,3,0,0 +10,distinguished_points,16,5407,1024,256,6,1020,144,151060500,2967917,false,-1,19,19,4,94 +11,exhaustive,16,14353,0,0,0,0,0,0,37695458,true,14353,0,0,0,0 +11,hellman,16,14353,1024,64,0,1024,698,177381167,10243084,false,-1,18,26,0,0 +11,distinguished_points,16,14353,1024,256,6,1021,132,146743333,8573666,false,-1,41,41,3,98 +12,exhaustive,16,27985,0,0,0,0,0,0,73672708,true,27985,0,0,0,0 +12,hellman,16,27985,1024,64,0,1024,672,175161375,14503209,false,-1,30,53,0,0 +12,distinguished_points,16,27985,1024,256,6,1021,159,146612416,510333,false,-1,8,8,3,22 +13,exhaustive,16,33658,0,0,0,0,0,0,88260083,true,33658,0,0,0,0 +13,hellman,16,33658,1024,64,0,1024,694,176200125,6048209,false,-1,3,4,0,0 +13,distinguished_points,16,33658,1024,256,6,1021,148,151227667,5032584,false,-1,27,27,3,145 +14,exhaustive,16,1963,0,0,0,0,0,0,5274792,true,1963,0,0,0,0 +14,hellman,16,1963,1024,64,0,1024,690,175226875,14664167,false,-1,27,54,0,0 +14,distinguished_points,16,1963,1024,256,6,1019,153,155274917,4546208,false,-1,26,26,5,38 +15,exhaustive,16,55563,0,0,0,0,0,0,149044375,true,55563,0,0,0,0 +15,hellman,16,55563,1024,64,0,1024,692,175557625,10276834,true,55563,23,37,0,0 +15,distinguished_points,16,55563,1024,256,6,1024,151,141775000,205292,false,-1,5,5,0,7 +16,exhaustive,16,15759,0,0,0,0,0,0,41482291,true,15759,0,0,0,0 +16,hellman,16,15759,1024,64,0,1024,692,251965542,6582375,false,-1,6,6,0,0 +16,distinguished_points,16,15759,1024,256,6,1020,149,147754542,2197542,true,15759,21,14,4,66 +17,exhaustive,16,5625,0,0,0,0,0,0,14914583,true,5625,0,0,0,0 +17,hellman,16,5625,1024,64,0,1024,680,175677334,22323334,false,-1,37,99,0,0 +17,distinguished_points,16,5625,1024,256,6,1020,144,149774709,18838334,false,-1,111,111,4,46 +18,exhaustive,16,25805,0,0,0,0,0,0,67740000,true,25805,0,0,0,0 +18,hellman,16,25805,1024,64,0,1024,674,175209334,21993625,false,-1,40,97,0,0 +18,distinguished_points,16,25805,1024,256,6,1022,149,151608500,15649125,false,-1,94,94,2,40 +19,exhaustive,16,49316,0,0,0,0,0,0,129164333,true,49316,0,0,0,0 +19,hellman,16,49316,1024,64,0,1024,694,175119041,17151875,false,-1,38,69,0,0 +19,distinguished_points,16,49316,1024,256,6,1021,165,144602584,85292,true,49316,3,3,3,5 +20,exhaustive,16,52222,0,0,0,0,0,0,136944709,true,52222,0,0,0,0 +20,hellman,16,52222,1024,64,0,1024,680,175619667,5916750,false,-1,3,3,0,0 +20,distinguished_points,16,52222,1024,256,6,1021,145,142884875,2725625,false,-1,25,25,3,78 +21,exhaustive,16,676,0,0,0,0,0,0,1773208,true,676,0,0,0,0 +21,hellman,16,676,1024,64,0,1024,693,175302000,7699041,true,676,23,27,0,0 +21,distinguished_points,16,676,1024,256,6,1018,153,149137292,725167,true,676,25,5,6,65 +22,exhaustive,16,17818,0,0,0,0,0,0,46777875,true,17818,0,0,0,0 +22,hellman,16,17818,1024,64,0,1024,686,176589458,14060458,false,-1,29,50,0,0 +22,distinguished_points,16,17818,1024,256,6,1022,143,149814875,6434167,false,-1,43,43,2,30 +23,exhaustive,16,20117,0,0,0,0,0,0,52733875,true,20117,0,0,0,0 +23,hellman,16,20117,1024,64,0,1024,688,176131458,13143792,false,-1,26,45,0,0 +23,distinguished_points,16,20117,1024,256,6,1022,139,173104041,6899666,false,-1,35,35,2,56 +24,exhaustive,16,25443,0,0,0,0,0,0,66712875,true,25443,0,0,0,0 +24,hellman,16,25443,1024,64,0,1024,695,174814333,7226667,false,-1,9,11,0,0 +24,distinguished_points,16,25443,1024,256,6,1022,146,147888875,471041,false,-1,6,6,2,34 +25,exhaustive,16,54129,0,0,0,0,0,0,141947000,true,54129,0,0,0,0 +25,hellman,16,54129,1024,64,0,1024,688,174991916,6281875,false,-1,3,5,0,0 +25,distinguished_points,16,54129,1024,256,6,1021,134,147703000,363916,false,-1,8,8,3,16 +26,exhaustive,16,54975,0,0,0,0,0,0,144332083,true,54975,0,0,0,0 +26,hellman,16,54975,1024,64,0,1024,685,182329834,6651125,false,-1,3,3,0,0 +26,distinguished_points,16,54975,1024,256,6,1020,141,171628375,3530208,false,-1,27,27,4,72 +27,exhaustive,16,21388,0,0,0,0,0,0,56155042,true,21388,0,0,0,0 +27,hellman,16,21388,1024,64,0,1024,693,175549792,10954584,false,-1,21,32,0,0 +27,distinguished_points,16,21388,1024,256,6,1019,132,153175375,8559625,false,-1,56,56,5,46 +28,exhaustive,16,64006,0,0,0,0,0,0,167729667,true,64006,0,0,0,0 +28,hellman,16,64006,1024,64,0,1024,680,175706958,6476500,true,64006,11,12,0,0 +28,distinguished_points,16,64006,1024,256,6,1021,136,148200459,5469583,false,-1,28,28,3,29 +29,exhaustive,16,39512,0,0,0,0,0,0,104071625,true,39512,0,0,0,0 +29,hellman,16,39512,1024,64,0,1024,684,175803125,9616083,false,-1,19,24,0,0 +29,distinguished_points,16,39512,1024,256,6,1020,158,148570792,154292,false,-1,2,2,4,11 diff --git a/pom.xml b/pom.xml index 5a06cdd..30d4c8c 100644 --- a/pom.xml +++ b/pom.xml @@ -11,9 +11,38 @@ Cryptanalytic Time-Memory Tradeoffs - Reproducible experiments for exhaustive search, Hellman time-memory - tradeoffs, and distinguished-point chains over a reduced DES keyspace. + Java reference implementation for reproducible exhaustive-search, Hellman, + distinguished-point, and exact-coverage research over reduced DES states. + https://github.com/qoosmo/cryptanalytic-time-memory-tradeoffs + + + + MIT License + https://github.com/qoosmo/cryptanalytic-time-memory-tradeoffs/blob/main/LICENSE-MIT + repo + + + Apache License 2.0 + https://github.com/qoosmo/cryptanalytic-time-memory-tradeoffs/blob/main/LICENSE-APACHE + repo + + + + + + Ali Mkhida + Algorizk Labs + https://algorizk.xyz + + + + + scm:git:https://github.com/qoosmo/cryptanalytic-time-memory-tradeoffs.git + scm:git:ssh://git@github.com/qoosmo/cryptanalytic-time-memory-tradeoffs.git + https://github.com/qoosmo/cryptanalytic-time-memory-tradeoffs + HEAD + 17 diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 5dcf54b..e709776 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -3,7 +3,13 @@ name = "cryptanalytic-time-memory-tradeoffs" version = "0.1.0" edition = "2021" publish = false -description = "Rust reference implementation for reduced-DES cryptanalytic time-memory tradeoff experiments" +description = "Rust reference implementation for reproducible cryptanalytic time-memory tradeoff research" +license = "MIT OR Apache-2.0" +repository = "https://github.com/qoosmo/cryptanalytic-time-memory-tradeoffs" +homepage = "https://algorizk.xyz" +authors = ["Ali Mkhida — Algorizk Labs"] +keywords = ["cryptanalysis", "hellman", "time-memory", "des", "research"] +categories = ["cryptography", "science"] [dependencies] des = "0.8.1" diff --git a/rust/README.md b/rust/README.md index 514e70b..68e9233 100644 --- a/rust/README.md +++ b/rust/README.md @@ -1,25 +1,44 @@ # Rust reference implementation -This crate is the first Rust port of the modern Java semantics. +This crate is the Rust implementation track for the research project. It currently implements: -- reduced DES state encoding with canonical odd parity; -- DES/ECB encryption with PKCS#5-compatible padding; +- reduced DES effective-state encoding with canonical odd parity; +- DES/ECB with PKCS#5-compatible padding over the shared plaintext fixture; - the historical effective-bit reduction; - exhaustive search; -- Hellman chains; -- distinguished-point chains; -- exact state coverage; -- Java/Rust shared reference-vector tests. +- fixed-length Hellman chains; +- bounded distinguished-point chains; +- exact coverage analysis; +- Java/Rust semantic reference vectors; +- a deterministic Rust experiment harness with CSV output. -This is a **correctness/equivalence implementation**, not yet a performance claim. +## Test -Run: +```bash +cargo test --locked --manifest-path rust/Cargo.toml +``` + +## Smoke + +```bash +cargo run --quiet --locked --manifest-path rust/Cargo.toml --bin tmto -- smoke +``` + +## Verified small run ```bash -cargo test --manifest-path rust/Cargo.toml -cargo run --manifest-path rust/Cargo.toml --bin tmto -- smoke +cargo run --release --locked --manifest-path rust/Cargo.toml --bin tmto -- verified-small +``` + +This writes: + +```text +experiments/results/rust-verified-small.csv +experiments/results/rust-verified-small-coverage.csv ``` -The shared fixtures are in `../test-vectors/java-rust-reference.csv`. +The current Rust benchmark uses deterministic SplitMix64 trial generation. +Java/Rust performance comparison is intentionally deferred until both languages +consume a single committed shared experiment plan. diff --git a/rust/src/bin/tmto.rs b/rust/src/bin/tmto.rs index a38a178..b132553 100644 --- a/rust/src/bin/tmto.rs +++ b/rust/src/bin/tmto.rs @@ -1,7 +1,11 @@ +use cryptanalytic_time_memory_tradeoffs::experiment::{ + run_experiment, write_run, ExperimentConfig, +}; use cryptanalytic_time_memory_tradeoffs::{ distinguished_coverage_states, hellman_coverage_states, DesOracle, DistinguishedPointPredicate, DistinguishedPointTable, HellmanTable, LegacyReduction, ReducedDesKeySpace, }; +use std::path::PathBuf; fn main() { if let Err(error) = run() { @@ -11,13 +15,17 @@ fn main() { } fn run() -> Result<(), Box> { - let command = std::env::args() - .nth(1) - .unwrap_or_else(|| "smoke".to_string()); + let args: Vec = std::env::args().skip(1).collect(); + let command = args.first().map(String::as_str).unwrap_or("smoke"); - match command.as_str() { + match command { "smoke" => smoke(), - other => Err(format!("unknown command: {other}; supported: smoke").into()), + "verified-small" => verified_small(), + "experiment" => experiment(&args[1..]), + other => Err(format!( + "unknown command: {other}; supported: smoke, verified-small, experiment" + ) + .into()), } } @@ -60,3 +68,69 @@ fn smoke() -> Result<(), Box> { Ok(()) } + +fn verified_small() -> Result<(), Box> { + // Warm up code paths separately from the measured run. + let warmup = ExperimentConfig::smoke( + PathBuf::from("/tmp/tmto-rust-warmup.csv"), + PathBuf::from("/tmp/tmto-rust-warmup-coverage.csv"), + ); + let _ = run_experiment(&warmup)?; + + let config = ExperimentConfig::verified_small(); + let run = run_experiment(&config)?; + write_run(&config, &run)?; + + println!("WROTE {}", config.output.display()); + println!("WROTE {}", config.coverage_output.display()); + println!("ROWS {}", run.results.len()); + println!("COVERAGE_ROWS {}", run.coverage.len()); + Ok(()) +} + +fn experiment(args: &[String]) -> Result<(), Box> { + let mut config = ExperimentConfig { + state_bits: 12, + trials: 5, + seed: 20260828, + hellman_chains: 256, + hellman_chain_length: 16, + dp_chains: 256, + dp_difficulty_bits: 4, + dp_max_chain_length: 64, + output: PathBuf::from("experiments/results/rust-smoke.csv"), + coverage_output: PathBuf::from("experiments/results/rust-smoke-coverage.csv"), + }; + + let mut index = 0; + while index < args.len() { + let flag = &args[index]; + index += 1; + let value = args + .get(index) + .ok_or_else(|| format!("missing value for {flag}"))?; + index += 1; + + match flag.as_str() { + "--state-bits" => config.state_bits = value.parse()?, + "--trials" => config.trials = value.parse()?, + "--seed" => config.seed = value.parse()?, + "--hellman-chains" => config.hellman_chains = value.parse()?, + "--hellman-chain-length" => config.hellman_chain_length = value.parse()?, + "--dp-chains" => config.dp_chains = value.parse()?, + "--dp-bits" => config.dp_difficulty_bits = value.parse()?, + "--dp-max-chain" => config.dp_max_chain_length = value.parse()?, + "--output" => config.output = PathBuf::from(value), + "--coverage-output" => config.coverage_output = PathBuf::from(value), + _ => return Err(format!("unknown argument: {flag}").into()), + } + } + + let run = run_experiment(&config)?; + write_run(&config, &run)?; + println!("WROTE {}", config.output.display()); + println!("WROTE {}", config.coverage_output.display()); + println!("ROWS {}", run.results.len()); + println!("COVERAGE_ROWS {}", run.coverage.len()); + Ok(()) +} diff --git a/rust/src/experiment.rs b/rust/src/experiment.rs new file mode 100644 index 0000000..0c31698 --- /dev/null +++ b/rust/src/experiment.rs @@ -0,0 +1,489 @@ +use crate::{ + distinguished_coverage_states, exhaustive_find, hellman_coverage_states, DesOracle, + DistinguishedPointPredicate, DistinguishedPointTable, HellmanTable, LegacyReduction, + ReducedDesKeySpace, TmtoError, +}; +use std::collections::HashSet; +use std::fmt::{Display, Formatter}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::time::Instant; + +#[derive(Debug)] +pub enum ExperimentError { + Core(TmtoError), + Io(std::io::Error), + InvalidConfig(String), +} + +impl Display for ExperimentError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Core(error) => Display::fmt(error, f), + Self::Io(error) => Display::fmt(error, f), + Self::InvalidConfig(message) => f.write_str(message), + } + } +} + +impl std::error::Error for ExperimentError {} + +impl From for ExperimentError { + fn from(value: TmtoError) -> Self { + Self::Core(value) + } +} + +impl From for ExperimentError { + fn from(value: std::io::Error) -> Self { + Self::Io(value) + } +} + +#[derive(Debug, Clone)] +pub struct ExperimentConfig { + pub state_bits: u8, + pub trials: usize, + pub seed: u64, + pub hellman_chains: usize, + pub hellman_chain_length: usize, + pub dp_chains: usize, + pub dp_difficulty_bits: u8, + pub dp_max_chain_length: usize, + pub output: PathBuf, + pub coverage_output: PathBuf, +} + +impl ExperimentConfig { + pub fn smoke(output: impl Into, coverage_output: impl Into) -> Self { + Self { + state_bits: 10, + trials: 2, + seed: 7, + hellman_chains: 64, + hellman_chain_length: 8, + dp_chains: 64, + dp_difficulty_bits: 3, + dp_max_chain_length: 32, + output: output.into(), + coverage_output: coverage_output.into(), + } + } + + pub fn verified_small() -> Self { + Self { + state_bits: 16, + trials: 30, + seed: 20260828, + hellman_chains: 1024, + hellman_chain_length: 64, + dp_chains: 1024, + dp_difficulty_bits: 6, + dp_max_chain_length: 256, + output: PathBuf::from("experiments/results/rust-verified-small.csv"), + coverage_output: PathBuf::from("experiments/results/rust-verified-small-coverage.csv"), + } + } + + fn validate(&self) -> Result { + let key_space = ReducedDesKeySpace::new(self.state_bits)?; + if self.trials == 0 { + return Err(ExperimentError::InvalidConfig( + "trials must be positive".to_string(), + )); + } + if self.hellman_chains == 0 || self.hellman_chains as u64 > key_space.size() { + return Err(ExperimentError::InvalidConfig( + "hellman_chains must be in [1, state-space size]".to_string(), + )); + } + if self.hellman_chain_length == 0 { + return Err(ExperimentError::InvalidConfig( + "hellman_chain_length must be positive".to_string(), + )); + } + if self.dp_chains == 0 || self.dp_chains as u64 > key_space.size() { + return Err(ExperimentError::InvalidConfig( + "dp_chains must be in [1, state-space size]".to_string(), + )); + } + DistinguishedPointPredicate::new(self.dp_difficulty_bits, key_space)?; + if self.dp_max_chain_length == 0 { + return Err(ExperimentError::InvalidConfig( + "dp_max_chain_length must be positive".to_string(), + )); + } + Ok(key_space) + } +} + +#[derive(Debug, Clone)] +pub struct ExperimentResult { + pub trial: usize, + pub method: &'static str, + pub state_bits: u8, + pub target_state: u64, + pub configured_chains: usize, + pub chain_parameter: usize, + pub dp_difficulty_bits: u8, + pub stored_chains: usize, + pub distinct_endpoints: usize, + pub offline_ns: u64, + pub online_ns: u64, + pub exact_recovery: bool, + pub recovered_state: Option, + pub endpoint_matches_or_candidates: usize, + pub candidate_chains_checked: usize, + pub truncated_chains: usize, + pub projection_steps: usize, +} + +impl ExperimentResult { + pub const fn csv_header() -> &'static str { + "trial,method,state_bits,target_state,configured_chains,chain_parameter,dp_difficulty_bits,stored_chains,distinct_endpoints,offline_ns,online_ns,exact_recovery,recovered_state,endpoint_matches_or_candidates,candidate_chains_checked,truncated_chains,projection_steps" + } + + fn to_csv(&self) -> String { + format!( + "{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}", + self.trial, + self.method, + self.state_bits, + self.target_state, + self.configured_chains, + self.chain_parameter, + self.dp_difficulty_bits, + self.stored_chains, + self.distinct_endpoints, + self.offline_ns, + self.online_ns, + self.exact_recovery, + self.recovered_state + .map(|value| value.to_string()) + .unwrap_or_else(|| "-1".to_string()), + self.endpoint_matches_or_candidates, + self.candidate_chains_checked, + self.truncated_chains, + self.projection_steps, + ) + } +} + +#[derive(Debug, Clone)] +pub struct CoverageResult { + pub trial: usize, + pub method: &'static str, + pub covered_states: usize, + pub state_space_size: u64, +} + +impl CoverageResult { + pub const fn csv_header() -> &'static str { + "trial,method,covered_states,state_space_size,coverage_fraction" + } + + fn to_csv(&self) -> String { + let fraction = self.covered_states as f64 / self.state_space_size as f64; + format!( + "{},{},{},{},{:.12}", + self.trial, self.method, self.covered_states, self.state_space_size, fraction + ) + } +} + +#[derive(Debug, Clone)] +pub struct ExperimentRun { + pub results: Vec, + pub coverage: Vec, +} + +#[derive(Debug, Clone, Copy)] +struct SplitMix64 { + state: u64, +} + +impl SplitMix64 { + const GAMMA: u64 = 0x9e3779b97f4a7c15; + + fn new(seed: u64) -> Self { + Self { state: seed } + } + + fn next_u64(&mut self) -> u64 { + self.state = self.state.wrapping_add(Self::GAMMA); + let mut z = self.state; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb); + z ^ (z >> 31) + } + + fn next_state(&mut self, key_space: ReducedDesKeySpace) -> u64 { + self.next_u64() & key_space.mask() + } +} + +pub fn run_experiment(config: &ExperimentConfig) -> Result { + let key_space = config.validate()?; + let oracle = DesOracle::standard(key_space); + let reduction = LegacyReduction::new(key_space); + let predicate = DistinguishedPointPredicate::new(config.dp_difficulty_bits, key_space)?; + let mut rng = SplitMix64::new(config.seed); + + let mut results = Vec::with_capacity(config.trials * 3); + let mut coverage = Vec::with_capacity(config.trials * 3); + + for trial in 0..config.trials { + let target_state = rng.next_state(key_space); + let target_ciphertext = oracle.encrypt_state(target_state)?; + + let online_start = Instant::now(); + let exhaustive = exhaustive_find(key_space, &oracle, &target_ciphertext)?; + let exhaustive_online_ns = elapsed_ns(online_start); + + results.push(ExperimentResult { + trial, + method: "exhaustive", + state_bits: config.state_bits, + target_state, + configured_chains: 0, + chain_parameter: 0, + dp_difficulty_bits: 0, + stored_chains: 0, + distinct_endpoints: 0, + offline_ns: 0, + online_ns: exhaustive_online_ns, + exact_recovery: exhaustive == Some(target_state), + recovered_state: exhaustive, + endpoint_matches_or_candidates: 0, + candidate_chains_checked: 0, + truncated_chains: 0, + projection_steps: 0, + }); + coverage.push(CoverageResult { + trial, + method: "exhaustive", + covered_states: key_space.size() as usize, + state_space_size: key_space.size(), + }); + + let hellman_starts = unique_random_states(&mut rng, key_space, config.hellman_chains)?; + + let offline_start = Instant::now(); + let hellman = HellmanTable::build( + key_space, + oracle.clone(), + reduction, + &hellman_starts, + config.hellman_chain_length, + )?; + let hellman_offline_ns = elapsed_ns(offline_start); + + let online_start = Instant::now(); + let hellman_lookup = hellman.lookup(&target_ciphertext)?; + let hellman_online_ns = elapsed_ns(online_start); + + results.push(ExperimentResult { + trial, + method: "hellman", + state_bits: config.state_bits, + target_state, + configured_chains: config.hellman_chains, + chain_parameter: config.hellman_chain_length, + dp_difficulty_bits: 0, + stored_chains: hellman.chain_count(), + distinct_endpoints: hellman.distinct_endpoint_count(), + offline_ns: hellman_offline_ns, + online_ns: hellman_online_ns, + exact_recovery: hellman_lookup.state == Some(target_state), + recovered_state: hellman_lookup.state, + endpoint_matches_or_candidates: hellman_lookup.endpoint_matches, + candidate_chains_checked: hellman_lookup.candidate_chains_checked, + truncated_chains: 0, + projection_steps: 0, + }); + + let hellman_covered = hellman_coverage_states( + key_space, + &oracle, + reduction, + &hellman_starts, + config.hellman_chain_length, + )?; + coverage.push(CoverageResult { + trial, + method: "hellman", + covered_states: hellman_covered, + state_space_size: key_space.size(), + }); + + let dp_starts = unique_random_states(&mut rng, key_space, config.dp_chains)?; + + let offline_start = Instant::now(); + let dp_table = DistinguishedPointTable::build( + key_space, + oracle.clone(), + reduction, + predicate, + &dp_starts, + config.dp_max_chain_length, + )?; + let dp_offline_ns = elapsed_ns(offline_start); + + let online_start = Instant::now(); + let dp_lookup = dp_table.lookup(&target_ciphertext)?; + let dp_online_ns = elapsed_ns(online_start); + + results.push(ExperimentResult { + trial, + method: "distinguished_points", + state_bits: config.state_bits, + target_state, + configured_chains: config.dp_chains, + chain_parameter: config.dp_max_chain_length, + dp_difficulty_bits: config.dp_difficulty_bits, + stored_chains: dp_table.stored_chains(), + distinct_endpoints: dp_table.distinct_endpoint_count(), + offline_ns: dp_offline_ns, + online_ns: dp_online_ns, + exact_recovery: dp_lookup.state == Some(target_state), + recovered_state: dp_lookup.state, + endpoint_matches_or_candidates: dp_lookup.endpoint_candidates, + candidate_chains_checked: dp_lookup.candidate_chains_checked, + truncated_chains: dp_table.truncated_chains(), + projection_steps: dp_lookup.projection_steps, + }); + + let dp_covered = distinguished_coverage_states( + key_space, + &oracle, + reduction, + predicate, + &dp_starts, + config.dp_max_chain_length, + )?; + coverage.push(CoverageResult { + trial, + method: "distinguished_points", + covered_states: dp_covered, + state_space_size: key_space.size(), + }); + } + + Ok(ExperimentRun { results, coverage }) +} + +pub fn write_run(config: &ExperimentConfig, run: &ExperimentRun) -> Result<(), ExperimentError> { + write_results(&config.output, &run.results)?; + write_coverage(&config.coverage_output, &run.coverage)?; + Ok(()) +} + +fn write_results(path: &Path, rows: &[ExperimentResult]) -> Result<(), ExperimentError> { + ensure_parent(path)?; + let mut csv = String::new(); + csv.push_str(ExperimentResult::csv_header()); + csv.push('\n'); + for row in rows { + csv.push_str(&row.to_csv()); + csv.push('\n'); + } + fs::write(path, csv)?; + Ok(()) +} + +fn write_coverage(path: &Path, rows: &[CoverageResult]) -> Result<(), ExperimentError> { + ensure_parent(path)?; + let mut csv = String::new(); + csv.push_str(CoverageResult::csv_header()); + csv.push('\n'); + for row in rows { + csv.push_str(&row.to_csv()); + csv.push('\n'); + } + fs::write(path, csv)?; + Ok(()) +} + +fn ensure_parent(path: &Path) -> Result<(), ExperimentError> { + if let Some(parent) = path.parent() { + if !parent.as_os_str().is_empty() { + fs::create_dir_all(parent)?; + } + } + Ok(()) +} + +fn unique_random_states( + rng: &mut SplitMix64, + key_space: ReducedDesKeySpace, + count: usize, +) -> Result, ExperimentError> { + if count as u64 > key_space.size() { + return Err(ExperimentError::InvalidConfig( + "requested more unique starts than states".to_string(), + )); + } + + let mut states = HashSet::with_capacity(count.saturating_mul(2)); + while states.len() < count { + states.insert(rng.next_state(key_space)); + } + + let mut states: Vec = states.into_iter().collect(); + states.sort_unstable(); + Ok(states) +} + +fn elapsed_ns(start: Instant) -> u64 { + u64::try_from(start.elapsed().as_nanos()).unwrap_or(u64::MAX) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deterministic_experiment_repeats_algorithmic_outputs() { + let config_a = ExperimentConfig::smoke( + PathBuf::from("/tmp/tmto-rust-test-a.csv"), + PathBuf::from("/tmp/tmto-rust-test-a-coverage.csv"), + ); + let config_b = config_a.clone(); + + let run_a = run_experiment(&config_a).expect("first run"); + let run_b = run_experiment(&config_b).expect("second run"); + + assert_eq!(run_a.results.len(), run_b.results.len()); + assert_eq!(run_a.coverage.len(), run_b.coverage.len()); + + for (left, right) in run_a.results.iter().zip(&run_b.results) { + assert_eq!(left.trial, right.trial); + assert_eq!(left.method, right.method); + assert_eq!(left.target_state, right.target_state); + assert_eq!(left.configured_chains, right.configured_chains); + assert_eq!(left.chain_parameter, right.chain_parameter); + assert_eq!(left.dp_difficulty_bits, right.dp_difficulty_bits); + assert_eq!(left.stored_chains, right.stored_chains); + assert_eq!(left.distinct_endpoints, right.distinct_endpoints); + assert_eq!(left.exact_recovery, right.exact_recovery); + assert_eq!(left.recovered_state, right.recovered_state); + assert_eq!( + left.endpoint_matches_or_candidates, + right.endpoint_matches_or_candidates + ); + assert_eq!( + left.candidate_chains_checked, + right.candidate_chains_checked + ); + assert_eq!(left.truncated_chains, right.truncated_chains); + assert_eq!(left.projection_steps, right.projection_steps); + } + + for (left, right) in run_a.coverage.iter().zip(&run_b.coverage) { + assert_eq!(left.trial, right.trial); + assert_eq!(left.method, right.method); + assert_eq!(left.covered_states, right.covered_states); + assert_eq!(left.state_space_size, right.state_space_size); + } + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 5802017..2180f84 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -8,6 +8,8 @@ use des::Des; use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Formatter}; +pub mod experiment; + pub const MAX_STATE_BITS: u8 = 28; pub const DEFAULT_PLAINTEXT: &[u8] = b"HPC reproducibility fixture"; @@ -127,7 +129,7 @@ impl ReducedDesKeySpace { fn with_odd_parity(seven_bits: u8) -> u8 { let seven_bits = seven_bits & 0x7f; let data = seven_bits << 1; - let parity = if seven_bits.count_ones() % 2 == 0 { + let parity = if seven_bits.count_ones().is_multiple_of(2) { 1 } else { 0 @@ -172,7 +174,7 @@ impl DesOracle { let pad_len = block_size - (self.plaintext.len() % block_size); let mut output = Vec::with_capacity(self.plaintext.len() + pad_len); output.extend_from_slice(&self.plaintext); - output.extend(std::iter::repeat(pad_len as u8).take(pad_len)); + output.extend(std::iter::repeat_n(pad_len as u8, pad_len)); for chunk in output.chunks_exact_mut(block_size) { let block = GenericArray::from_mut_slice(chunk); @@ -631,7 +633,7 @@ mod tests { use super::*; fn decode_hex(text: &str) -> Vec { - assert!(text.len() % 2 == 0); + assert!(text.len().is_multiple_of(2)); (0..text.len()) .step_by(2) .map(|index| u8::from_str_radix(&text[index..index + 2], 16).unwrap()) diff --git a/scripts/run-rust-verified-small.sh b/scripts/run-rust-verified-small.sh new file mode 100755 index 0000000..8ba10f1 --- /dev/null +++ b/scripts/run-rust-verified-small.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +cargo run --release --quiet --locked \ + --manifest-path rust/Cargo.toml \ + --bin tmto -- verified-small + +{ + echo "generated_at_utc=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" + echo + uname -a + echo + if command -v sw_vers >/dev/null 2>&1; then + sw_vers + echo + fi + rustc -Vv + echo + cargo -V +} > experiments/results/rust-verified-small-environment.txt + +python3 scripts/summarize_rust_results.py \ + --results experiments/results/rust-verified-small.csv \ + --coverage experiments/results/rust-verified-small-coverage.csv \ + --environment experiments/results/rust-verified-small-environment.txt \ + --output docs/RUST_VERIFIED_RESULTS.md + +echo "RUST_VERIFIED_SMALL=PASS" diff --git a/scripts/summarize_rust_results.py b/scripts/summarize_rust_results.py new file mode 100755 index 0000000..690c411 --- /dev/null +++ b/scripts/summarize_rust_results.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +import argparse +import csv +import statistics +from collections import defaultdict +from pathlib import Path + +def read_csv(path): + with open(path, newline="", encoding="utf-8") as f: + return list(csv.DictReader(f)) + +def median_ms(rows, key): + return statistics.median(int(row[key]) for row in rows) / 1_000_000.0 + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--results", required=True) + parser.add_argument("--coverage", required=True) + parser.add_argument("--environment", required=True) + parser.add_argument("--output", required=True) + args = parser.parse_args() + + results = read_csv(args.results) + coverage = read_csv(args.coverage) + + by_method = defaultdict(list) + for row in results: + by_method[row["method"]].append(row) + + cov_by_method = defaultdict(list) + for row in coverage: + cov_by_method[row["method"]].append(row) + + order = ["exhaustive", "hellman", "distinguished_points"] + labels = { + "exhaustive": "Exhaustive", + "hellman": "Hellman", + "distinguished_points": "Distinguished points", + } + + lines = [ + "# Verified Rust results", + "", + "This file records the first committed Rust benchmark run for the project.", + "The Rust implementation passed the shared Java/Rust semantic vectors before", + "these measurements were collected.", + "", + "Timing values are machine-dependent. The Rust benchmark currently uses a", + "deterministic SplitMix64 experiment generator; it does **not yet** consume", + "the exact Java `SplittableRandom` trial plan. Therefore the Java and Rust", + "timings below should not yet be treated as an apples-to-apples speedup claim.", + "", + "## Configuration", + "", + "- effective state dimension: **16 bits**, `N = 65,536`;", + "- trials: **30**;", + "- seed: `20260828`;", + "- Hellman: `m = 1024`, `t = 64`;", + "- distinguished points: 1024 starts, `d = 6`, maximum chain length 256;", + "- a separate warm-up run is executed before measurements;", + "- exact coverage is computed outside each timed build/lookup interval.", + "", + "## Results", + "", + "| Method | Exact recovery | Median exact coverage | Median offline | Median online |", + "| --- | ---: | ---: | ---: | ---: |", + ] + + for method in order: + rows = by_method[method] + cov_rows = cov_by_method[method] + exact = sum(row["exact_recovery"].lower() == "true" for row in rows) + median_cov = statistics.median( + float(row["coverage_fraction"]) for row in cov_rows + ) * 100.0 + offline = median_ms(rows, "offline_ns") + online = median_ms(rows, "online_ns") + lines.append( + f"| {labels[method]} | {exact}/{len(rows)} | {median_cov:.1f}% | " + f"{offline:.3f} ms | {online:.3f} ms |" + ) + + lines.extend([ + "", + "## Interpretation", + "", + "The algorithmic quantities to compare first are exact recovery, endpoint", + "statistics, and exact coverage. A direct Java-versus-Rust timing claim is", + "deferred until both implementations consume one committed shared experiment", + "plan (identical targets and identical Hellman/DP start sets).", + "", + "## Files", + "", + f"- `{args.results}`", + f"- `{args.coverage}`", + f"- `{args.environment}`", + "", + ]) + + Path(args.output).write_text("\n".join(lines), encoding="utf-8") + +if __name__ == "__main__": + main()