diff --git a/.changes/checksum-diagnostics.md b/.changes/checksum-diagnostics.md new file mode 100644 index 00000000..265c0c54 --- /dev/null +++ b/.changes/checksum-diagnostics.md @@ -0,0 +1,10 @@ +--- +"rscrypto" = "minor" +--- + +Reduce CRC-32 and CRC-64 selection diagnostics to the polynomial, input length, architecture, +selection reason, effective force setting, and selected kernel. Remove obsolete policy thresholds, +stream counts, capability flags, and placeholder values that no longer described active dispatch. +Remove the unused `SelectionReason::BelowSmallThreshold` and `BelowSimdThreshold` variants. +The numeric discriminants of the remaining selection reasons change. +Checksum computation and backend selection are unchanged. diff --git a/.changes/docs-cleanup.md b/.changes/docs-cleanup.md index bcf23d5b..ebc5235a 100644 --- a/.changes/docs-cleanup.md +++ b/.changes/docs-cleanup.md @@ -2,4 +2,7 @@ "rscrypto" = "patch" --- -Consolidate user documentation, migration guidance, and README assets. +Remove the migration guide and broad API tours. Keep current API contracts, feature selection, +secret ownership, and focused executable examples in their owning documentation. +Correct platform coverage and the limits of retained evidence. +Update installation examples for v0.10 and remove temporary development-version notes. diff --git a/.changes/ecdsa-secret-selection.md b/.changes/ecdsa-secret-selection.md new file mode 100644 index 00000000..9b5ea85c --- /dev/null +++ b/.changes/ecdsa-secret-selection.md @@ -0,0 +1,9 @@ +--- +"rscrypto" = "patch" +--- + +Preserve masked ECDSA point selection on AArch64 and Windows +and masked secret selection in portable P-256. +Use fixed-bound ECDSA table traversal +and retain unconditional RISC-V generator-table loads under LLVM optimization without changing +signature semantics. diff --git a/.changes/edwards-msrv-build.md b/.changes/edwards-msrv-build.md deleted file mode 100644 index 857a9907..00000000 --- a/.changes/edwards-msrv-build.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"rscrypto" = "patch" ---- - -Restore Rust 1.91 builds of Ed25519 and X25519 on x86-64 Linux by retaining explicit compile-time references to the Rust fixed-base kernels when assembly owns dispatch. diff --git a/.changes/examples-workflows.md b/.changes/examples-workflows.md deleted file mode 100644 index dcdde7df..00000000 --- a/.changes/examples-workflows.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"rscrypto" = "patch" ---- - -Replace broad API tours with focused, locally executable cryptographic workflow examples. diff --git a/.changes/fallible-ecdsa-secret-owners.md b/.changes/fallible-ecdsa-secret-owners.md index 4bbd5a7a..776c7c60 100644 --- a/.changes/fallible-ecdsa-secret-owners.md +++ b/.changes/fallible-ecdsa-secret-owners.md @@ -1,5 +1,9 @@ --- -"rscrypto" = "patch" +"rscrypto" = "minor" --- -Add fallible blinded P-256/P-384 signing and public derivation, plus zero-copy and in-place construction for rscrypto-owned secret buffers and UTF-8 strings. +Expose fallible P-256/P-384 blinded signing and public-key derivation with caller-provided entropy. +Remove `public_key_blinded` and `try_sign_blinded`; the supported entry points are +`try_public_key_blinded_with` and `try_sign_blinded_with`, which report entropy failure before private arithmetic. +Add direct-fill constructors for SecretBytes and SecretVec, +plus allocation-preserving ownership transfer from Vec and String. diff --git a/.changes/feature-target-builds.md b/.changes/feature-target-builds.md new file mode 100644 index 00000000..f10f8ab4 --- /dev/null +++ b/.changes/feature-target-builds.md @@ -0,0 +1,8 @@ +--- +"rscrypto" = "patch" +--- + +Restore Rust 1.91 Ed25519 and X25519 builds on x86-64 Linux when assembly owns fixed-base dispatch, +and keep standalone AEAD features lint-clean on Linux. +Exclude SIMD backends from scalar WebAssembly hash, AEAD, +and Argon2 dispatch when SIMD128 is disabled. diff --git a/.changes/internal-evidence-boundary.md b/.changes/internal-evidence-boundary.md new file mode 100644 index 00000000..b5a6ef87 --- /dev/null +++ b/.changes/internal-evidence-boundary.md @@ -0,0 +1,14 @@ +--- +"rscrypto" = "minor" +--- + +Limit ordinary `diag` builds to capability and backend-selection introspection. Remove benchmark, +forced-kernel, constant-time, zeroization, and component operations from ordinary public module paths, +re-exports, and associated methods across AEADs, MACs, KDFs, password hashing, signatures, key agreement, +RSA, ML-KEM, and cryptographic hashes. This includes RSA seeded diagnostic encryption methods, +BLAKE3 diagnostic selectors, the SHA-256 benchmark compression helper, and the diagnostic-only +`Argon2Error::BackendUnavailable` variant. Removing that variant changes the numeric discriminant of +`VerificationLimitTooLow` in ordinary `diag` builds with `phc-strings`. +Repository evidence tools retain explicit internal access. Internal PBKDF2 verification probes exercise +the primitive instead of rejecting their fixed parameters through the application password policy. +Application cryptographic operations are unchanged by this boundary cleanup. diff --git a/.changes/keep-aarch64-ecdsa-point-e959.md b/.changes/keep-aarch64-ecdsa-point-e959.md deleted file mode 100644 index c7427fa4..00000000 --- a/.changes/keep-aarch64-ecdsa-point-e959.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"rscrypto" = "patch" ---- - -Keep AArch64 ECDSA point selection masked after compiler optimization. diff --git a/.changes/keep-portable-p-256-4870.md b/.changes/keep-portable-p-256-4870.md deleted file mode 100644 index 30caf0a2..00000000 --- a/.changes/keep-portable-p-256-4870.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"rscrypto" = "patch" ---- - -Preserve masked secret selection in portable P-256 and Windows ECDSA, and use fixed-bound ECDSA table traversal. diff --git a/.changes/keep-standalone-aead-feature-c4b6.md b/.changes/keep-standalone-aead-feature-c4b6.md deleted file mode 100644 index 6dc30ccc..00000000 --- a/.changes/keep-standalone-aead-feature-c4b6.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"rscrypto" = "patch" ---- - -Keep standalone AEAD feature builds lint-clean on Linux. diff --git a/.changes/markdown-accuracy.md b/.changes/markdown-accuracy.md deleted file mode 100644 index 0646c74c..00000000 --- a/.changes/markdown-accuracy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"rscrypto" = "patch" ---- - -Fix the blinded-signing migration example and clarify feature selection, secret-owner duplication, migration prerequisites, and platform validation coverage. diff --git a/.changes/preserve-unconditional-risc-v-1146.md b/.changes/preserve-unconditional-risc-v-1146.md deleted file mode 100644 index 4910c33a..00000000 --- a/.changes/preserve-unconditional-risc-v-1146.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"rscrypto" = "patch" ---- - -Preserve unconditional RISC-V ECDSA generator-table loads when LLVM optimizes masked selection. diff --git a/.changes/release-lockfiles.md b/.changes/release-lockfiles.md new file mode 100644 index 00000000..64d9f1da --- /dev/null +++ b/.changes/release-lockfiles.md @@ -0,0 +1,5 @@ +--- +"rscrypto" = "patch" +--- + +Synchronize standalone fuzz and evidence-tool lockfiles with the rscrypto version during release preparation. diff --git a/.changes/remove-unbounded-pbkdf2-verification.md b/.changes/remove-unbounded-pbkdf2-verification.md new file mode 100644 index 00000000..95e356d8 --- /dev/null +++ b/.changes/remove-unbounded-pbkdf2-verification.md @@ -0,0 +1,7 @@ +--- +"rscrypto" = "minor" +--- + +Remove `verify_with_policy` and `verify_password_with_policy` from PBKDF2-SHA256 and PBKDF2-SHA512. +Explicit password policies use `verify_with_policy_bounded` and `verify_password_with_policy_bounded`, +which require an upper iteration limit. Default password verification and explicit primitive operations remain. diff --git a/.changes/remove-the-retired-performance-e70c.md b/.changes/retired-performance-chart.md similarity index 100% rename from .changes/remove-the-retired-performance-e70c.md rename to .changes/retired-performance-chart.md diff --git a/.changes/require-sha-and-sse4-53de.md b/.changes/sha2-dispatch.md similarity index 100% rename from .changes/require-sha-and-sse4-53de.md rename to .changes/sha2-dispatch.md diff --git a/.changes/wasm-scalar-artifacts.md b/.changes/wasm-scalar-artifacts.md deleted file mode 100644 index 0a855e41..00000000 --- a/.changes/wasm-scalar-artifacts.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"rscrypto" = "patch" ---- - -Keep scalar WebAssembly hash, AEAD, and Argon2 dispatch from retaining SIMD backends when SIMD128 is disabled. diff --git a/.config/rail.toml b/.config/rail.toml index f463fdda..94bdd4c1 100644 --- a/.config/rail.toml +++ b/.config/rail.toml @@ -32,3 +32,48 @@ msrv_policy = { mode = "disabled" } # Cargo.toml owns the declared package MSRV. # Cargo Rail 0.25 cannot distinguish expected `compile_fail` doctest invocations from compiler failures. enabled = false consumer_scope = "open" # Published library: external consumers remain part of the visibility boundary. + +[release] +# Keep standalone fuzz and evidence-tool lockfiles on the released package version. +auxiliary_cargo_manifests = [ + "fuzz-packages/aead-aegis256/Cargo.toml", + "fuzz-packages/aead-aes-siv-cmac256/Cargo.toml", + "fuzz-packages/aead-aes128gcm/Cargo.toml", + "fuzz-packages/aead-aes128gcmsiv/Cargo.toml", + "fuzz-packages/aead-aes256gcm/Cargo.toml", + "fuzz-packages/aead-aes256gcmsiv/Cargo.toml", + "fuzz-packages/aead-ascon128/Cargo.toml", + "fuzz-packages/aead-chacha20poly1305/Cargo.toml", + "fuzz-packages/aead-header-protection/Cargo.toml", + "fuzz-packages/aead-nonce-counter/Cargo.toml", + "fuzz-packages/aead-xchacha20poly1305/Cargo.toml", + "fuzz-packages/auth-argon2/Cargo.toml", + "fuzz-packages/auth-ed25519/Cargo.toml", + "fuzz-packages/auth-hkdf/Cargo.toml", + "fuzz-packages/auth-hmac/Cargo.toml", + "fuzz-packages/auth-kmac256/Cargo.toml", + "fuzz-packages/auth-p256-ecdh/Cargo.toml", + "fuzz-packages/auth-pbkdf2/Cargo.toml", + "fuzz-packages/auth-phc/Cargo.toml", + "fuzz-packages/auth-scrypt/Cargo.toml", + "fuzz-packages/auth-x25519/Cargo.toml", + "fuzz-packages/checksums-crc16/Cargo.toml", + "fuzz-packages/checksums-crc24/Cargo.toml", + "fuzz-packages/checksums-crc32/Cargo.toml", + "fuzz-packages/checksums-crc64/Cargo.toml", + "fuzz-packages/fast-rapidhash/Cargo.toml", + "fuzz-packages/fast-xxh3/Cargo.toml", + "fuzz-packages/hash-ascon/Cargo.toml", + "fuzz-packages/hash-blake2/Cargo.toml", + "fuzz-packages/hash-blake3/Cargo.toml", + "fuzz-packages/hash-sha2/Cargo.toml", + "fuzz-packages/hash-sha3/Cargo.toml", + "fuzz-packages/hash-websocket-sha1/Cargo.toml", + "fuzz-packages/surface-hex-parse/Cargo.toml", + "fuzz-packages/traits-io/Cargo.toml", + "fuzz/Cargo.toml", + "tools/ct-binsec-harness/Cargo.toml", + "tools/ct-dudect/Cargo.toml", + "tools/ct-harness/Cargo.toml", + "tools/wasm-runtime-vectors/Cargo.toml", +] diff --git a/.config/tooling.toml b/.config/tooling.toml index 188ded65..70eb5bc1 100644 --- a/.config/tooling.toml +++ b/.config/tooling.toml @@ -73,7 +73,7 @@ nightly-components = ["miri", "rust-src"] packages = ["build-essential", "ca-certificates", "curl", "git", "python3"] [ci-ct] -cargo = ["just"] +cargo = ["just", "cargo-nextest"] components = ["llvm-tools"] packages = ["build-essential", "ca-certificates", "curl", "git", "python3"] diff --git a/.github/runs-on.yml b/.github/runs-on.yml index d586b786..6cba60c6 100644 --- a/.github/runs-on.yml +++ b/.github/runs-on.yml @@ -1,5 +1,5 @@ -# AWS runner profiles. Public repositories resolve this catalog from the default branch. -# Workflows reference these names; other providers keep their own labels. +# AWS runner profiles. Keep sizing here; workflows select names without shape overrides. +# Public repositories resolve this catalog from the default branch. runners: plan: family: [c7a.large, c7i.large, c8a.large, c8i.large] @@ -8,14 +8,14 @@ runners: spot: price-capacity-optimized volume: 40gb:gp3 miri: - family: [c7a.xlarge, c7i.xlarge, c8a.xlarge, c8i.xlarge] - cpu: 4 + family: [c7a.large, c8a.large] + cpu: 2 image: ubuntu24-minimal-x64 spot: price-capacity-optimized volume: 150gb:gp3 package: - family: [c7a.2xlarge, c7i.2xlarge, c8a.2xlarge, c8i.2xlarge] - cpu: 8 + family: [c7a.xlarge, c7i.xlarge, c8a.xlarge, c8i.xlarge] + cpu: 4 image: ubuntu24-minimal-x64 spot: price-capacity-optimized volume: 100gb:gp3 @@ -25,12 +25,6 @@ runners: image: ubuntu24-minimal-x64 spot: price-capacity-optimized volume: 150gb:gp3 - cross-build: - family: [c7a.8xlarge, c7i.8xlarge, c8a.8xlarge, c8i.8xlarge] - cpu: 32 - image: ubuntu24-minimal-x64 - spot: price-capacity-optimized - volume: 150gb:gp3 ci-cross-build: family: [c7a.4xlarge, c7i.4xlarge, c8a.4xlarge, c8i.4xlarge] cpu: 16 @@ -62,11 +56,12 @@ runners: spot: price-capacity-optimized volume: 150gb:gp3 x86_64-win: - family: [c7a.4xlarge, c7i.4xlarge, c8a.4xlarge, c8i.4xlarge] - cpu: 16 + family: [c7a.2xlarge, c8a.2xlarge] + cpu: 8 image: windows25-full-x64 spot: price-capacity-optimized volume: 200gb:gp3 + # Eight concurrent fuzz targets need eight physical cores, including on x86. fuzz-x86_64-linux: family: [c7a.2xlarge, c8a.2xlarge] cpu: 8 @@ -79,6 +74,7 @@ runners: image: ubuntu24-minimal-arm64 spot: price-capacity-optimized volume: 150gb:gp3 + # Fixed, non-Flex, On-Demand CT hardware; sized independently of benchmarks. ct-x86_64-linux-intel: family: [c8i.2xlarge] cpu: 8 @@ -97,7 +93,7 @@ runners: image: ubuntu24-minimal-arm64 spot: false volume: 100gb:gp3 - # Fixed, non-Flex, On-Demand hardware for timing and benchmark evidence. + # Fixed benchmark hardware preserves the existing measurement baseline. measure-x86_64-linux-intel: family: [c8i.2xlarge] cpu: 8 diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index ffd0babf..4d42fc38 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -66,7 +66,7 @@ jobs: needs: plan if: needs.plan.outputs.cross == 'true' name: Prepare ${{ matrix.target }} benchmarks - runs-on: runs-on=${{ github.run_id }}/runner=cross-build/env=production + runs-on: runs-on=${{ github.run_id }}-${{ matrix.target }}/runner=bench-cross-build/env=production timeout-minutes: 120 strategy: fail-fast: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b96ee5f..4a03414a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ defaults: jobs: cross-build: name: Prepare ${{ matrix.target }} tests - runs-on: runs-on=${{ github.run_id }}/runner=cross-build/env=production + runs-on: runs-on=${{ github.run_id }}-${{ matrix.target }}/runner=ci-cross-build/env=production timeout-minutes: 45 strategy: fail-fast: true @@ -59,7 +59,7 @@ jobs: matrix: include: - platform: compatibility - runner: runs-on=${{ github.run_id }}/runner=cross-build/env=production + runner: runs-on=${{ github.run_id }}/runner=compatibility/env=production timeout: 60 - platform: package runner: runs-on=${{ github.run_id }}/runner=package/env=production @@ -109,6 +109,7 @@ jobs: just ci-check just test --all --release just test --all --release --portable + if [[ "$PLATFORM" != x86_64-linux ]]; then just test-evidence; fi if [[ "$PLATFORM" == x86_64-linux || "$PLATFORM" == aarch64-linux ]]; then just test-musl; fi git diff --exit-code HEAD @@ -169,5 +170,7 @@ jobs: if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } just test --all --release --portable if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + just test-evidence + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } git diff --exit-code HEAD if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/.github/workflows/ct.yml b/.github/workflows/ct.yml index 3780d5e2..6ac6eb30 100644 --- a/.github/workflows/ct.yml +++ b/.github/workflows/ct.yml @@ -84,8 +84,7 @@ jobs: select: if: ${{ !inputs.replay_p384 }} - # Public-repository named runners resolve from main, not this workflow's ref. - runs-on: runs-on=${{ github.run_id }}/cpu=2/family=c7a.large+c7i.large+c8a.large+c8i.large/image=ubuntu24-minimal-x64/spot=price-capacity-optimized/volume=40gb:gp3/env=production + runs-on: runs-on=${{ github.run_id }}/runner=plan/env=production timeout-minutes: 5 outputs: matrix: ${{ steps.select.outputs.matrix }} @@ -101,7 +100,7 @@ jobs: cross-build: needs: select if: needs.select.outputs.cross == 'true' - runs-on: runs-on=${{ github.run_id }}-${{ matrix.target }}/cpu=32/family=c7a.8xlarge+c7i.8xlarge+c8a.8xlarge+c8i.8xlarge/image=ubuntu24-minimal-x64/spot=price-capacity-optimized/volume=150gb:gp3/env=production + runs-on: runs-on=${{ github.run_id }}-${{ matrix.target }}/runner=ct-cross-build/env=production timeout-minutes: 45 name: Prepare ${{ matrix.target }} CT strategy: @@ -185,6 +184,5 @@ jobs: uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1 with: path: target/ct-${{ matrix.platform }}-full.tar.gz - archive: false if-no-files-found: warn retention-days: 7 diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index e5fec7b3..2a2aa2dc 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -58,7 +58,7 @@ jobs: include: - platform: x86_64-linux task: miri - runs-on: runs-on=${{ github.run_id }}/runner=${{ matrix.task == 'miri' && 'miri' || matrix.platform }}/env=production + runs-on: runs-on=${{ github.run_id }}/runner=${{ matrix.task == 'miri' && 'miri' || format('fuzz-{0}', matrix.platform) }}/env=production timeout-minutes: ${{ (matrix.task == 'miri' || github.event_name == 'pull_request') && 60 || 90 }} steps: - uses: "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1" # v7.0.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ebfa5fb8..e9a9c344 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,7 @@ # Contributing -Develop every change on a short-lived branch and merge it through a pull -request. The protected `main` branch is releasable history, not a working -branch. +Develop every change on a short-lived branch and merge it through a pull request. +The protected `main` branch is releasable history, not a working branch. ## Start a change @@ -15,29 +14,35 @@ git pull --ff-only git switch -c ``` -Do not discard unrelated work to make the worktree clean. Preserve it or move -it to its own branch first. +Do not discard unrelated work to make the worktree clean. +Preserve it or move it to its own branch first. ## Record release intent -Add a `.changes/*.md` file when crate users will observe an API, behavior, -security, performance, compatibility, or release-artifact change: +Add a `.changes/*.md` file when crate users will observe an API, behavior, security, performance, compatibility, +or release-artifact change: ```bash cargo rail change add rscrypto --bump patch --message "Describe the user-visible result." ``` -Use `minor` or `major` when compatibility requires it. Internal tooling and -maintainer-only documentation normally need no change file. Review release -intent manually before committing. +Use `minor` or `major` when compatibility requires it. +Internal tooling and maintainer-only documentation normally need no change file. +Review release intent manually before committing. + +Keep pending notes focused on user-visible results. +Consolidate overlapping notes while retaining distinct changes and their bump levels; +maintainer-only runner adjustments belong in the runner catalog and tooling guide. +`cargo rail change status` validates and lists the pending intent. +The release command consumes it into `CHANGELOG.md`; +do not add unreleased work to a published version's changelog section. ## Configure compiler reuse -Cargo Rail can reuse compiler results across Cargo, Nextest, Just, and IDE -invocations. For the first-party development fleet, choose a configured -rscrypto target from `~/dev-machines/dev-machine list rscrypto`, acquire a -short-lived credential, and install the canonical remapped policy into the -active Cargo home: +Cargo Rail can reuse compiler results across Cargo, Nextest, Just, and IDE invocations. +For the first-party development fleet, choose a configured rscrypto target from `~/dev-machines/dev-machine list rscrypto`, +acquire a short-lived credential, +and install the canonical remapped policy into the active Cargo home: ```bash eval "$("$HOME/dev-machines/dev-machine" cache-env rscrypto )" @@ -45,54 +50,62 @@ just rail-cache-setup --max-size 10GiB just cache-status ``` -Run `cache-env` again when its short-lived R2 lease expires. `dev-machine ssh` -and `dev-machine just` refresh the corresponding remote-machine lease before -execution. Keep cache credentials outside repository configuration. Use -`CARGO_RAIL_CACHE=off` only when a check requires a cold compiler process, +Run `cache-env` again when its short-lived R2 lease expires. +`dev-machine ssh` and `dev-machine just` refresh the corresponding remote-machine lease before execution. +Keep cache credentials outside repository configuration. +Use `CARGO_RAIL_CACHE=off` only when a check requires a cold compiler process, including Miri and machine-code zeroization evidence. ## Validate -macOS ARM64 qualification runs on the maintainer's physical Apple Silicon Mac, -before every commit, including documentation and tooling changes. Run -`just install-hooks` once per checkout. The pre-commit and pre-merge-commit hooks -run `just check-macos`: native checks, complete release tests with native and -portable dispatch (including doctests), and the Apple Silicon RSA assembly gate. +macOS ARM64 qualification runs on the maintainer's physical Apple Silicon Mac, before every commit, +including documentation and tooling changes. +Run `just install-hooks` once per checkout. +The pre-commit and pre-merge-commit hooks run `just check-macos`: native checks, +complete release tests with native and portable dispatch +(including doctests), internal evidence regressions, and the Apple Silicon RSA assembly gate. Install prerequisites with `scripts/tooling/aarch64-macos.sh` when needed. -The hooks reject unstaged tracked changes and untracked files so the tested -source matches the staged commit. Do not bypass the hooks. Git hooks are local; -GitHub does not enforce this qualification and remote-created commits must not -replace the locally validated submission path. +The hooks reject unstaged tracked changes and untracked files +so the tested source matches the staged commit. +Do not bypass the hooks. +Git hooks are local; GitHub does not enforce this qualification +and remote-created commits must not replace the locally validated submission path. -Run `just --list` to discover the current recipes. Start with: +Run `just --list` to discover the current recipes. +Start with: ```bash just check just test ``` -`just check` repairs sources before validation. It covers the host and every -entry in `.config/target-matrix.json`; missing target libraries or Clippy -components fail before repairs start. The repair pass applies rustfmt and Clippy -suggestions, including in a dirty or staged worktree. Review the resulting diff. +`just check` repairs sources before validation. +It covers the host and every entry in `.config/target-matrix.json`; +missing target libraries or Clippy components fail before repairs start. +The repair pass applies rustfmt and Clippy suggestions, including in a dirty or staged worktree. +Review the resulting diff. `just ci-check` validates only the native host without source fixes. `just ci-policy` checks dependencies across the full supported target graph. Neither command uses affected-work selection. -Every target receives release/native and debug/portable Clippy passes. The host -checks all Cargo targets; cross checks compile the library without foreign test -or benchmark C dependencies. Bare-metal and browser WASM use `full` plus -applicable serialization features without std, threads, or OS -entropy. WASI adds std and entropy, without threads. POWER, IBM Z, and RISC-V -use the repository-pinned nightly; other targets use the development toolchain. +Every target receives release/native and debug/portable Clippy passes. +The host checks all Cargo targets; +cross checks compile the library without foreign test or benchmark C dependencies. +Bare-metal and browser WASM use `full` plus applicable serialization features without std, threads, +or OS entropy. +WASI adds std and entropy, without threads. +POWER, IBM Z, and RISC-V use the repository-pinned nightly; +other targets use the development toolchain. Validation also checks independent workspaces, dependencies, and docs. -`just test` enables every crate feature except `portable-only`, so runtime -capability detection selects native backends where supported. Use -`just test --portable` to test forced portable dispatch. Both modes print their -dispatch profile; `--all` widens test scope independently of that choice. -ChaCha20 differential tests report accelerated backend and kernel execution -counts, including an explicit message when no accelerated backend ran. +`just test` enables every crate feature except `portable-only`, +so runtime capability detection selects native backends where supported. +Use `just test --portable` to test forced portable dispatch. +Both modes print their dispatch profile; `--all` widens test scope independently of that choice. +Run `just test-evidence` for changes to internal evidence hooks or forced-kernel tests. It executes their +native and portable regressions through the internal build boundary; ordinary test builds keep that boundary closed. +ChaCha20 differential tests report accelerated backend and kernel execution counts, +including an explicit message when no accelerated backend ran. Use the same command for a focused loop: @@ -102,11 +115,11 @@ just test --test aead_kernel_equivalence chacha20 just test -- --lib -- --exact checksum::crc16::tests::test_vectors_crc16_ccitt_x25 --nocapture ``` -`just test` uses the pinned Nextest runner; it requires `cargo-nextest` and has -no Cargo-test fallback. Put repository options (`--all`, `--release`, `--native`, -`--portable`) first. `--release` selects optimized builds for both Nextest and doctests. -The first runner argument, or an explicit `--`, starts verbatim forwarding -to `cargo nextest run`. For example: +`just test` uses the pinned Nextest runner; it requires `cargo-nextest` and has no Cargo-test fallback. +Put repository options (`--all`, `--release`, `--native`, `--portable`) first. +`--release` selects optimized builds for both Nextest and doctests. +The first runner argument, or an explicit `--`, starts verbatim forwarding to `cargo nextest run`. +For example: ```bash just test --portable -- --release --lib @@ -114,47 +127,44 @@ just test -- --no-run just test -- --lib -- --skip slow_test ``` -The wrapper consumes the first `--`; a second one reaches Nextest for its -libtest-compatible arguments such as `--skip` and `--exact`. `--test` selects an -integration binary, `--lib` selects library tests, and a name filters tests. -Runner arguments select explicit work regardless of affected scope and skip -doctests. Runs without runner arguments retain the separate Cargo doctest step. -`RSCRYPTO_TEST_THREADS` sets `NEXTEST_TEST_THREADS`; Nextest's explicit -`--test-threads` option takes precedence. - -Run `just test-coverage` when you need source coverage. It runs the complete -native and portable test suites plus committed corpus replay in the full and -scoped fuzz workspaces, then writes `coverage/total.lcov`, `coverage/SUMMARY.txt`, -and browsable `coverage/html/index.html`. Use it instead of a separate `just test` -step in a coverage job; reporting does not rerun tests. Ordinary uninstrumented -test results cannot retroactively produce coverage. The merged profile and -executable list remain in `coverage/` for report diagnosis. - -Corpus replay defaults to the paths in `fuzz/committed-seeds.txt`, using their -working-tree contents. Unlisted files, including local fuzz discoveries, are -excluded. To include all local corpus files, run -`RSCRYPTO_FUZZ_CORPUS=local just test-coverage` or -`RSCRYPTO_FUZZ_CORPUS=local just test-fuzz-asan --all`. The same variable applies -to direct Cargo replay tests; `committed` explicitly selects the default. -Replay never deletes discoveries. Promote a minimized regression by adding its -seed file and repository-relative path to `fuzz/committed-seeds.txt` (sorted, -one path per line). `just test-scripts` checks that this inventory matches the -tracked corpus files; stage new seed files before running that check. - -Coverage uses the development toolchain, cargo-nextest, cargo-llvm-cov, and the -`llvm-tools-preview` rustup component. It measures Rust source under `src/` on -the host, including inline tests, with the existing test profile. Doctest -coverage is deferred until supported without nightly. Live fuzzing, sanitizers, -Miri, timing checks, release-only paths, and other target architectures remain -separate evidence; corpus replay reuses the fuzz implementations without -launching nightly libFuzzer. Reporting validates LLVM function mappings before -publishing; a failed run does not publish a report. +The wrapper consumes the first `--`; +a second one reaches Nextest for its libtest-compatible arguments such as `--skip` and `--exact`. +`--test` selects an integration binary, `--lib` selects library tests, and a name filters tests. +Runner arguments select explicit work regardless of affected scope and skip doctests. +Runs without runner arguments retain the separate Cargo doctest step. +`RSCRYPTO_TEST_THREADS` sets `NEXTEST_TEST_THREADS`; Nextest's explicit `--test-threads` option takes precedence. + +Run `just test-coverage` when you need source coverage. +It runs the complete native and portable test suites plus committed corpus replay in the full +and scoped fuzz workspaces, then writes `coverage/total.lcov`, `coverage/SUMMARY.txt`, and browsable `coverage/html/index.html`. +Use it instead of a separate `just test` step in a coverage job; reporting does not rerun tests. +Ordinary uninstrumented test results cannot retroactively produce coverage. +The merged profile and executable list remain in `coverage/` for report diagnosis. + +Corpus replay defaults to the paths in `fuzz/committed-seeds.txt`, using their working-tree contents. +Unlisted files, including local fuzz discoveries, are excluded. +To include all local corpus files, run `RSCRYPTO_FUZZ_CORPUS=local just test-coverage` or `RSCRYPTO_FUZZ_CORPUS=local just test-fuzz-asan --all`. +The same variable applies to direct Cargo replay tests; `committed` explicitly selects the default. +Replay never deletes discoveries. +Promote a minimized regression by adding its seed file and repository-relative path to `fuzz/committed-seeds.txt` +(sorted, one path per line). +`just test-scripts` checks that this inventory matches the tracked corpus files; +stage new seed files before running that check. + +Coverage uses the development toolchain, cargo-nextest, cargo-llvm-cov, and the `llvm-tools-preview` rustup component. +It measures Rust source under `src/` on the host, including inline tests, with the existing test profile. +Doctest coverage is deferred until supported without nightly. +Live fuzzing, sanitizers, Miri, timing checks, release-only paths, +and other target architectures remain separate evidence; +corpus replay reuses the fuzz implementations without launching nightly libFuzzer. +Reporting validates LLVM function mappings before publishing; +a failed run does not publish a report. Run `just test-scripts` after changing command selection or script orchestration. It uses substitute executors without running cryptographic workloads. -Run `just test-harnesses` for DudeCT balancing and raw-exporter self-tests without -timing cases. `just ct-test` includes those tests plus CT tooling regressions. +Run `just test-harnesses` for DudeCT balancing and raw-exporter self-tests without timing cases. +`just ct-test` includes those tests plus CT tooling regressions. For broad or compatibility-sensitive changes, run: @@ -166,25 +176,26 @@ just test --all --portable Add the risk-specific evidence reached by the change: -| Change | Required evidence | -| --- | --- | +| Change | Required evidence | +| ----------------------------------------------- | ----------------- | | Parser, import, DER, PHC, hex, or hostile input | `just test-fuzz ` or `just test-fuzz --all` | -| Unsafe Rust, SIMD, assembly, or dispatch | Backend differential tests; `just test-fuzz-asan --all` where native | -| Portable unsafe path | `just test-miri` | -| Constant-time claim boundary | `just ct-full --target `; update `ct.toml` only with matching evidence | -| Apple Silicon RSA assembly | `just test-rsa-macos-asm` on physical Apple Silicon | -| Public API, examples, or compatibility | Run `just test-examples`; review callers, tests, docs, migration guidance, and release intent | -| Dependency | `just check`; inspect the selected graph | - -Cross-compilation proves compilation, not runtime behavior, constant-time -execution, or performance. Record target lanes that cannot run. - -RISC-V, POWER, and IBM Z CI separate cross-compilation from native execution to avoid long builds -on the physical runner. Both native-dispatch and portable release suites, -doctests, and the full CT campaign remain required. The transfer commands and -integrity requirements are documented in [scripts/README.md](scripts/README.md). -A successful preparation job does not qualify the target; its execution job -must also pass for the same source and artifacts. +| Unsafe Rust, SIMD, assembly, or dispatch | Backend differential tests; `just test-fuzz-asan --all` where native | +| Portable unsafe path | `just test-miri` | +| Constant-time claim boundary | `just ct-full --target `; update `ct.toml` only with matching evidence | +| Apple Silicon RSA assembly | `just test-rsa-macos-asm` on physical Apple Silicon | +| Public API, examples, or compatibility | Run `just test-examples`; review callers, tests, docs, explicit API removals, and release intent | +| Dependency | `just check`; inspect the selected graph | + +Cross-compilation proves compilation, not runtime behavior, constant-time execution, or performance. +Record target lanes that cannot run. + +RISC-V, POWER, and IBM Z CI separate cross-compilation from native execution to avoid long builds on +the physical runner. +Both native-dispatch and portable release suites, doctests, +and the full CT campaign remain required. +The transfer commands and integrity requirements are documented in [scripts/README.md](scripts/README.md). +A successful preparation job does not qualify the target; +its execution job must also pass for the same source and artifacts. ## Review and submit @@ -210,56 +221,59 @@ Open a draft pull request: gh pr create --base main --fill --draft ``` -Before merging, resolve every review thread, inspect the final diff, and confirm -the required local and target-specific evidence. +Before merging, resolve every review thread, inspect the final diff, +and confirm the required local and target-specific evidence. ## Release -Prepare the version and changelog on a clean release branch, using the reviewed -change files: +Prepare the version and changelog on a clean release branch, using the reviewed change files: ```bash cargo rail release run rscrypto --bump auto --skip-tag --allow-non-default-branch ``` -Review the generated diff, including manifests and lockfiles in independent -workspaces, validate it, and merge through a PR. Complete physical Apple Silicon -RSA assembly and timing qualification locally before submission. macOS does -not run in hosted CI. +Review the generated diff, including manifests and lockfiles in independent workspaces, validate it, +and merge through a PR. +The `release.auxiliary_cargo_manifests` list in [`.config/rail.toml`](.config/rail.toml) owns the +standalone workspaces whose lockfiles must follow the package version. +Complete physical Apple Silicon RSA assembly and timing qualification locally +before submission. macOS does not run in hosted CI. -For the one-time publishing setup, create a GitHub environment named `release` -restricted to `main`. Configure rscrypto's crates.io Trusted Publisher for -`loadingalias/rscrypto`, workflow `release.yml`, and environment `release`. +For the one-time publishing setup, create a GitHub environment named `release` restricted to `main`. +Configure rscrypto's crates.io Trusted Publisher for `loadingalias/rscrypto`, workflow `release.yml`, and environment `release`. The workflow obtains a short-lived token; no crates.io secret is required. See the [crates.io setup instructions](https://crates.io/docs/trusted-publishing). -To deploy, select **Actions → Release → Run workflow → main**. No version input -is needed. The workflow rejects unconsumed change files, a version/changelog -mismatch, or a tag pointing elsewhere. CI (with macOS ARM64 qualified locally before committing), full -CT on all configured CI architectures, and both fuzz architectures plus Miri -run concurrently against the triggering commit. Publication requires all three -workflows to succeed. Benchmarks are separate. - -The final job packages the same commit, publishes to crates.io, then creates -`v` and a GitHub Release using the reviewed changelog entry. Only this -job receives registry authentication and repository write permission. - -After a transient failure, use **Re-run failed jobs** on the same run. A retry -accepts an existing crates.io version only when its checksum matches the local -package and it is not yanked. It never moves an existing tag or overwrites a -GitHub Release. If qualification artifacts have expired, rerun all jobs. Resolve -checksum, tag, or release-note conflicts before retrying; do not bypass them. +To deploy, select **Actions → Release → Run workflow → main**. +No version input is needed. +The workflow rejects unconsumed change files, a version/changelog mismatch, +or a tag pointing elsewhere. +CI (with macOS ARM64 qualified locally before committing), +full CT on all configured CI architectures, +and both fuzz architectures plus Miri run concurrently against the triggering commit. +Publication requires all three workflows to succeed. +Benchmarks are separate. + +The final job packages the same commit, publishes to crates.io, +then creates `v` and a GitHub Release using the reviewed changelog entry. +Only this job receives registry authentication and repository write permission. + +After a transient failure, use **Re-run failed jobs** on the same run. +A retry accepts an existing crates.io version only when its checksum matches the local package +and it is not yanked. +It never moves an existing tag or overwrites a GitHub Release. +If qualification artifacts have expired, rerun all jobs. +Resolve checksum, tag, or release-note conflicts before retrying; do not bypass them. ## Security and test evidence -Do not broaden constant-time, audit, FIPS, compliance, secret-lifecycle, or -platform claims without matching evidence. Security boundaries are defined by -[`THREAT_MODEL.md`](THREAT_MODEL.md), [`ct.toml`](ct.toml), and the linked -evidence documents. Report vulnerabilities privately through -[`SECURITY.md`](SECURITY.md). - -Use official vectors or an independent implementation as the oracle for -cryptographic correctness. Keep vector provenance, licensing, transforms, and -coverage reviewable. Fuzz targets live in [`fuzz/`](fuzz/) and -[`fuzz-packages/`](fuzz-packages/); commit only small, minimized seeds that -exercise production paths. +Do not broaden constant-time, audit, FIPS, compliance, secret-lifecycle, +or platform claims without matching evidence. +Security boundaries are defined by [`THREAT_MODEL.md`](THREAT_MODEL.md), [`ct.toml`](ct.toml), +and the linked evidence documents. +Report vulnerabilities privately through [`SECURITY.md`](SECURITY.md). + +Use official vectors or an independent implementation as the oracle for cryptographic correctness. +Keep vector provenance, licensing, transforms, and coverage reviewable. +Fuzz targets live in [`fuzz/`](fuzz/) and [`fuzz-packages/`](fuzz-packages/); commit only small, +minimized seeds that exercise production paths. diff --git a/Cargo.toml b/Cargo.toml index 414bfcb0..2abe201a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ unexpected_cfgs = { level = "deny", check-cfg = [ 'cfg(miri)', 'cfg(fuzzing)', 'cfg(rscrypto_internal_fuzzing)', + 'cfg(rscrypto_internal)', 'cfg(target_feature, values("movdiri", "movdir64b", "serialize"))', ] } diff --git a/README.md b/README.md index cadd59c9..508e7a2d 100644 --- a/README.md +++ b/README.md @@ -5,63 +5,58 @@ [![MSRV 1.91.0](https://img.shields.io/badge/MSRV-1.91.0-blue)](Cargo.toml) [![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/rscrypto)](#license) -`rscrypto` puts cryptographic primitives, cryptographic and fast hashes, -password hashing, and checksums behind one feature model. Target-gated SIMD and -assembly accelerate portable Rust backends without a production C/FFI, +`rscrypto` puts cryptographic primitives, cryptographic and fast hashes, password hashing, +and checksums behind one feature model. +Target-gated SIMD and assembly accelerate portable Rust backends without a production C/FFI, OpenSSL, or system-library dependency. -`rscrypto` is a primitives crate, not a TLS stack, PKI toolkit, key store, or -protocol implementation. +`rscrypto` is a primitives crate, not a TLS stack, PKI toolkit, key store, or protocol implementation. ## Measured performance -The historical benchmark snapshots contain comparisons that measured different -work: ML-KEM rows mixed entropy sources and key preparation, and Argon2 rows -passed different salt lengths to dryoc. The affected ratios and aggregates that -include them are withdrawn as performance claims pending corrected measurements. +The historical benchmark snapshots contain comparisons that measured different work: +ML-KEM rows mixed entropy sources and key preparation, +and Argon2 rows passed different salt lengths to dryoc. +The affected ratios and aggregates that include them are withdrawn +as performance claims pending corrected measurements. Their numerical impact has not been measured. -The [`benchmark overview`](benchmark_results/OVERVIEW.md) retains the historical -record. See the [`comparison contracts`](docs/benchmarking.md#ml-kem-and-argon2-comparison-contracts) -for the corrected workloads and validation requirements. +The [`benchmark overview`](benchmark_results/OVERVIEW.md) retains the historical record. +See the [`comparison contracts`](docs/benchmarking.md#ml-kem-and-argon2-comparison-contracts) for the corrected workloads and validation requirements. ## Assurance -Security claims fail closed: missing or stale evidence removes the claim rather -than weakening the gate. +Security claims fail closed: missing +or stale evidence removes the claim rather than weakening the gate. - Correctness evidence combines NIST, RFC, upstream, and Wycheproof vectors with separate implementations, properties, negative tests, and Miri. -- Fuzz targets exercise production implementations across primitive, parser, - state-machine, and trait boundaries. Minimized seeds replay as tests, with a - separate sanitizer lane. +- Fuzz targets exercise production implementations across primitive, parser, state-machine, + and trait boundaries. + Minimized seeds replay as tests, with a separate sanitizer lane. - Portable-versus-accelerated differential tests cover lengths, alignments, tails, state transitions, dispatch, and fallback behavior on native targets. - The constant-time harness inventories exact operations in [`ct.toml`](ct.toml) and combines optimized linked-binary inspection, BINSEC proofs for declared fixed-shape kernels, and DudeCT timing tests for declared end-to-end cases. -- Secret owners redact `Debug` and clear initialized storage on drop. Duplication - rules vary by type: keyed BLAKE2/BLAKE3 state supports `Clone`. The - [ownership inventory](docs/secret-ownership.md) lists these boundaries. +- Secret owners redact `Debug` and clear initialized storage on drop. + Duplication rules vary by type: keyed BLAKE2/BLAKE3 state supports `Clone`. + The [ownership inventory](docs/secret-ownership.md) lists these boundaries. Verification failures are opaque; failed AEAD opens clear unauthenticated plaintext. -A constant-time claim exists only when evidence for the required target, -feature, compiler, profile, and operation passes. Source that looks branchless -is not treated as proof. +A constant-time claim exists only when evidence for the required target, feature, compiler, profile, +and operation passes. +Source that looks branchless is not treated as proof. -Inspect the [`test evidence`](docs/test-vector-coverage.md), -[`constant-time model`](docs/constant-time.md), -[`secret lifecycle`](docs/secret-lifecycle.md), and -[`threat model`](THREAT_MODEL.md). +Inspect the [`test evidence`](docs/test-vector-coverage.md), [`constant-time model`](docs/constant-time.md), [`secret lifecycle`](docs/secret-lifecycle.md), and [`threat model`](THREAT_MODEL.md). -The remaining independent-review gap is a third-party security audit. The -project cannot currently fund one. Automated evidence does not replace that -review, so `rscrypto` does not claim to be audited, FIPS 140-3 validated, -formally verified, or constant time as a whole crate. +The remaining independent-review gap is a third-party security audit. +The project cannot currently fund one. +Automated evidence does not replace that review, so `rscrypto` does not claim to be audited, +FIPS 140-3 validated, formally verified, or constant time as a whole crate. -Report suspected vulnerabilities through -[GitHub Private Vulnerability Reporting](https://github.com/loadingalias/rscrypto/security/advisories/new) -under the [`SECURITY.md`](SECURITY.md) process, not a public issue. +Report suspected vulnerabilities through [GitHub Private Vulnerability Reporting](https://github.com/loadingalias/rscrypto/security/advisories/new) under the +[`SECURITY.md`](SECURITY.md) process, not a public issue. ## Install only what you use @@ -69,20 +64,20 @@ Minimal `no_std` SHA-2 build: ```toml [dependencies] -rscrypto = { version = "0.9", default-features = false, features = ["sha2"] } +rscrypto = { version = "0.10", default-features = false, features = ["sha2"] } ``` Full primitive stack with OS randomness enabled: ```toml [dependencies] -rscrypto = { version = "0.9", features = ["full", "getrandom"] } +rscrypto = { version = "0.10", features = ["full", "getrandom"] } ``` -The default feature is `std`; `default-features = false` removes it. Enable -`getrandom` only for APIs that obtain salts, keys, nonces, or RSA key-generation -entropy from the operating system. The [`feature guide`](docs/features.md) -explains build selection; [`Cargo.toml`](Cargo.toml) owns the exact feature graph. +The default feature is `std`; `default-features = false` removes it. +Enable `getrandom` only for APIs that obtain salts, keys, nonces, +or RSA key-generation entropy from the operating system. +The [`feature guide`](docs/features.md) explains build selection; [`Cargo.toml`](Cargo.toml) owns the exact feature graph. ## Quick start @@ -98,9 +93,9 @@ hasher.update(b"world"); assert_eq!(hasher.finalize(), one_shot); ``` -Hash APIs support one-shot and streaming use. Runnable workflows for AEAD, -signatures, RSA, P-256 ECDH, X25519, ML-KEM, password hashing, and backend introspection are -in [`examples/README.md`](examples/README.md). +Hash APIs support one-shot and streaming use. +Runnable workflows for AEAD, signatures, RSA, P-256 ECDH, X25519, ML-KEM, password hashing, +and backend introspection are in [`examples/README.md`](examples/README.md). ## Primitive and feature map @@ -118,25 +113,26 @@ in [`examples/README.md`](examples/README.md). The compatibility-only WebSocket accept digest requires `websocket-sha1`, which is excluded from `full` and every other umbrella feature. -Use [docs.rs](https://docs.rs/rscrypto) for exact types and methods. Use the -[`migration guide`](docs/migration.md) when replacing another library. +Use [docs.rs](https://docs.rs/rscrypto) for exact types and methods. ## Platforms and dispatch -The portable Rust implementation is the byte-for-byte authority. Compile-time -target support and, with `std`, detected runtime CPU capabilities select -eligible SIMD or assembly kernels. Unsupported acceleration falls back to -portable Rust. +The portable Rust implementation is the byte-for-byte authority. +Compile-time target support and, with `std`, +detected runtime CPU capabilities select eligible SIMD or assembly kernels. +Unsupported acceleration falls back to portable Rust. -The [`platform guide`](docs/platforms.md) explains the supported target catalog, -dispatch, `no_std` coverage, and the limits of `portable-only`. +The [`platform guide`](docs/platforms.md) explains the supported target catalog, dispatch, `no_std` coverage, +and the limits of `portable-only`. ## Project -Read [`CONTRIBUTING.md`](CONTRIBUTING.md) before changing code. Published changes -live in [`CHANGELOG.md`](CHANGELOG.md). +The guides and examples describe the accompanying source. +Use the matching version of the [API documentation](https://docs.rs/rscrypto) for a published dependency. + +Read [`CONTRIBUTING.md`](CONTRIBUTING.md) before changing code. +Published changes live in [`CHANGELOG.md`](CHANGELOG.md). ## License -Dual-licensed under [Apache-2.0](LICENSE-APACHE) or [MIT](LICENSE-MIT), at your -option. +Dual-licensed under [Apache-2.0](LICENSE-APACHE) or [MIT](LICENSE-MIT), at your option. diff --git a/benches/aead.rs b/benches/aead.rs index dff37be3..7fab971d 100644 --- a/benches/aead.rs +++ b/benches/aead.rs @@ -192,9 +192,9 @@ fn chacha20_poly1305_encrypt(c: &mut Criterion) { for (len, data) in &inputs { common::set_throughput(&mut g, *len); let mut buf = data.clone(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let mut buf_owned = data.clone(); - #[cfg(all(feature = "diag", target_arch = "x86_64", target_os = "linux"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64", target_os = "linux"))] let mut buf_x86_asm = data.clone(); let mut buf_combined: Vec = Vec::with_capacity(data.len().strict_add(16)); @@ -205,7 +205,7 @@ fn chacha20_poly1305_encrypt(c: &mut Criterion) { }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] g.bench_with_input(BenchmarkId::new("rscrypto-owned", len), data, |b, d| { b.iter(|| { buf_owned.copy_from_slice(d); @@ -218,7 +218,7 @@ fn chacha20_poly1305_encrypt(c: &mut Criterion) { }) }); - #[cfg(all(feature = "diag", target_arch = "x86_64", target_os = "linux"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64", target_os = "linux"))] if *len != 0 { g.bench_with_input(BenchmarkId::new("rscrypto-x86-asm", len), data, |b, d| { b.iter(|| { @@ -347,9 +347,9 @@ fn chacha20_poly1305_decrypt(c: &mut Criterion) { .expect("valid AEAD benchmark operation must succeed"); let mut buf = ciphertext.clone(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let mut buf_owned = ciphertext.clone(); - #[cfg(all(feature = "diag", target_arch = "x86_64", target_os = "linux"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64", target_os = "linux"))] let mut buf_x86_asm = ciphertext.clone(); g.bench_with_input(BenchmarkId::new("rscrypto", len), &ciphertext, |b, ct| { @@ -367,7 +367,7 @@ fn chacha20_poly1305_decrypt(c: &mut Criterion) { }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] g.bench_with_input(BenchmarkId::new("rscrypto-owned", len), &ciphertext, |b, ct| { b.iter(|| { buf_owned.copy_from_slice(ct); @@ -383,7 +383,7 @@ fn chacha20_poly1305_decrypt(c: &mut Criterion) { }) }); - #[cfg(all(feature = "diag", target_arch = "x86_64", target_os = "linux"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64", target_os = "linux"))] if *len != 0 { g.bench_with_input(BenchmarkId::new("rscrypto-x86-asm", len), &ciphertext, |b, ct| { b.iter(|| { diff --git a/benches/aead_kernels.rs b/benches/aead_kernels.rs index f912510b..1109be3c 100644 --- a/benches/aead_kernels.rs +++ b/benches/aead_kernels.rs @@ -3,26 +3,36 @@ //! These benches are intentionally outside the production comparison bench so //! global result tables do not treat kernel-only timings as user-facing AEADs. +#[cfg(all(rscrypto_internal, feature = "diag"))] #[path = "common/criterion.rs"] +#[cfg(all(rscrypto_internal, feature = "diag"))] mod bench_config; +#[cfg(all(rscrypto_internal, feature = "diag"))] mod common; +#[cfg(all(rscrypto_internal, feature = "diag"))] use core::hint::black_box; +#[cfg(all(rscrypto_internal, feature = "diag"))] use criterion::{BenchmarkId, Criterion}; #[cfg(target_arch = "aarch64")] +#[cfg(all(rscrypto_internal, feature = "diag"))] const KEY_32: [u8; 32] = [0x42u8; 32]; +#[cfg(all(rscrypto_internal, feature = "diag"))] const POLY_KEY: [u8; 32] = [ 0x7b, 0xac, 0x2b, 0x25, 0x2d, 0xb4, 0x47, 0xaf, 0x09, 0xb6, 0x7a, 0x55, 0xa4, 0xe9, 0x55, 0x84, 0x0a, 0xe1, 0xd6, 0x73, 0x10, 0x75, 0xd9, 0xeb, 0x2a, 0x93, 0x75, 0x78, 0x3e, 0xd5, 0x53, 0xff, ]; #[cfg(target_arch = "aarch64")] +#[cfg(all(rscrypto_internal, feature = "diag"))] const NONCE_12: [u8; 12] = [0x07u8; 12]; +#[cfg(all(rscrypto_internal, feature = "diag"))] const AAD: &[u8] = b"rscrypto-bench"; #[cfg(target_arch = "aarch64")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn chacha20_xor_kernel(c: &mut Criterion) { if !bench_config::selected("aead-kernel/chacha20-copy-and-xor") { return; @@ -57,6 +67,7 @@ fn chacha20_xor_kernel(c: &mut Criterion) { g.finish(); } +#[cfg(all(rscrypto_internal, feature = "diag"))] fn poly1305_auth_kernel(c: &mut Criterion) { if !bench_config::selected("aead-kernel/poly1305-auth") { return; @@ -71,10 +82,11 @@ fn poly1305_auth_kernel(c: &mut Criterion) { g.bench_with_input(BenchmarkId::new("dispatched", len), data, |b, d| { b.iter(|| { - black_box( - rscrypto::aead::diag_chacha20poly1305_authenticate_aead(black_box(AAD), black_box(d), black_box(&POLY_KEY)) - .expect("selected AEAD benchmark kernel must be available"), - ) + black_box(rscrypto::aead::diag_chacha20poly1305_authenticate_aead( + black_box(AAD), + black_box(d), + black_box(&POLY_KEY), + )) }) }); @@ -86,8 +98,7 @@ fn poly1305_auth_kernel(c: &mut Criterion) { black_box(AAD), black_box(d), black_box(&POLY_KEY), - ) - .expect("selected AEAD benchmark kernel must be available"), + ), ) }) }); @@ -96,6 +107,7 @@ fn poly1305_auth_kernel(c: &mut Criterion) { g.finish(); } +#[cfg(all(rscrypto_internal, feature = "diag"))] fn main() { bench_config::run(&[ #[cfg(target_arch = "aarch64")] @@ -103,3 +115,8 @@ fn main() { poly1305_auth_kernel, ]); } + +#[cfg(not(all(rscrypto_internal, feature = "diag")))] +fn main() -> Result<(), &'static str> { + Err("use just bench --bench aead_kernels to enable internal kernel benchmarks") +} diff --git a/benches/auth.rs b/benches/auth.rs index 75c88b8a..d89aec02 100644 --- a/benches/auth.rs +++ b/benches/auth.rs @@ -28,8 +28,8 @@ use rscrypto::{ P256EphemeralSecret, P256PublicKey, Pbkdf2Sha256, Pbkdf2Sha512, X25519SecretKey, }; use rustcrypto_ml_kem::{ - DecapsulationKey as RustCryptoMlKemDecapsulationKey, KeyExport as _, MlKem512 as RustCryptoMlKem512, - MlKem768 as RustCryptoMlKem768, MlKem1024 as RustCryptoMlKem1024, Seed as RustCryptoMlKemSeed, kem::Decapsulate as _, + DecapsulationKey as RustCryptoMlKemDecapsulationKey, MlKem512 as RustCryptoMlKem512, MlKem768 as RustCryptoMlKem768, + MlKem1024 as RustCryptoMlKem1024, Seed as RustCryptoMlKemSeed, kem::Decapsulate as _, }; use x25519_dalek::{PublicKey as DalekX25519PublicKey, StaticSecret as DalekX25519Secret}; @@ -110,7 +110,7 @@ impl ring::hkdf::KeyType for RingHkdfLen { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn print_auth_diag_once() { use std::sync::Once; @@ -136,7 +136,7 @@ fn print_auth_diag_once() { }); } -#[cfg(not(feature = "diag"))] +#[cfg(not(all(rscrypto_internal, feature = "diag")))] #[inline] fn print_auth_diag_once() {} @@ -1201,7 +1201,7 @@ fn ecdsa_p384_public_key(c: &mut Criterion) { g.finish(); } -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] fn ecdsa_p256_internal(c: &mut Criterion) { if !bench_config::selected("ecdsa-p256/internal") { return; @@ -1303,7 +1303,7 @@ fn ecdsa_p256_internal(c: &mut Criterion) { g.finish(); } -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] fn ecdsa_p384_internal(c: &mut Criterion) { if !bench_config::selected("ecdsa-p384/internal") { return; @@ -1573,7 +1573,7 @@ fn ed25519_verify(c: &mut Criterion) { g.finish(); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn ed25519_verify_phase(c: &mut Criterion) { if !bench_config::selected("ed25519/verify-phase") { return; @@ -2155,12 +2155,8 @@ macro_rules! mlkem_profile_benches { $rustcrypto:ty, $aws_algorithm:ident ) => { - // Expanded encoding is intentional: all rows must return the same key bytes, - // rather than comparing RustCrypto's preferred seed export with expanded keys. - #[expect(deprecated, reason = "expanded key encoding must match the other benchmark implementations")] fn $keygen_fn(c: &mut Criterion) { if !bench_config::selected(concat!($group, "/keygen/")) { return; } - use rustcrypto_ml_kem::ExpandedKeyEncoding as _; let key_random = deterministic_bytes::<64>(0x10); let rs_keygen = || { let (ek, dk) = <$profile>::generate_keypair(|out| { @@ -2181,10 +2177,6 @@ macro_rules! mlkem_profile_benches { let (ek, dk) = $fips::KG::keygen_from_seed(array_from_slice(&seed[..32]), array_from_slice(&seed[32..])); (ek.into_bytes(), dk.into_bytes()) }); - checked_mlkem_bench(&mut g, "rustcrypto", &expected, || { - let dk = RustCryptoMlKemDecapsulationKey::<$rustcrypto>::from_seed(RustCryptoMlKemSeed::from(*black_box(&key_random))); - (array_from_slice(dk.encapsulation_key().to_bytes().as_slice()), array_from_slice(dk.to_expanded_bytes().as_slice())) - }); g.finish(); aws_lc_bench! { @@ -2310,8 +2302,6 @@ macro_rules! mlkem_profile_benches { } } - // The expanded-key import is required to hold the serialized input fixed. - #[expect(deprecated, reason = "expanded key encoding must match the other benchmark implementations")] fn $decapsulate_fn(c: &mut Criterion) { if !bench_config::selected(concat!($group, "/decapsulate/")) { return; } let key_random = deterministic_bytes::<64>(0x30); @@ -2326,7 +2316,7 @@ macro_rules! mlkem_profile_benches { let dk_bytes = *dk.as_bytes(); let ct_bytes = ct.to_bytes(); let prepared_dk = dk.prepare().expect("ML-KEM key preparation"); - let rustcrypto_dk = RustCryptoMlKemDecapsulationKey::<$rustcrypto>::from_expanded(&dk_bytes.into()).expect("RustCrypto key import"); + let rustcrypto_dk = RustCryptoMlKemDecapsulationKey::<$rustcrypto>::from_seed(RustCryptoMlKemSeed::from(key_random)); let rustcrypto_ct = ct_bytes.into(); let fips_dk = $fips::DecapsKey::try_from_bytes(dk_bytes).expect("fips203 key import"); let fips_ct = $fips::CipherText::try_from_bytes(ct_bytes).expect("fips203 ciphertext import"); @@ -2367,10 +2357,6 @@ macro_rules! mlkem_profile_benches { let ct = <<$profile as rscrypto::Kem>::Ciphertext>::try_from_slice(black_box(&ct_bytes)).expect("ML-KEM ciphertext import"); *<$profile>::decapsulate(&dk, &ct).expect("ML-KEM decapsulation").as_bytes() }); - checked_mlkem_bench(&mut g, "rustcrypto", &expected, || { - let dk = RustCryptoMlKemDecapsulationKey::<$rustcrypto>::from_expanded(&(*black_box(&dk_bytes)).into()).expect("RustCrypto key import"); - array_from_slice(dk.decapsulate(&(*black_box(&ct_bytes)).into()).as_slice()) - }); checked_mlkem_bench(&mut g, "libcrux", &expected, || { let dk = libcrux_ml_kem::MlKemPrivateKey::from(*black_box(&dk_bytes)); let ct = libcrux_ml_kem::MlKemCiphertext::from(*black_box(&ct_bytes)); @@ -2441,18 +2427,18 @@ fn main() { ecdsa_p256_public_key, ecdsa_p256_sign, ecdsa_p256_verify, - #[cfg(all(feature = "diag", feature = "ecdsa-p256"))] + #[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] ecdsa_p256_internal, ecdsa_p384_public_key, ecdsa_p384_sign, ecdsa_p384_verify, - #[cfg(all(feature = "diag", feature = "ecdsa-p384"))] + #[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] ecdsa_p384_internal, ed25519_public_key, ed25519_keypair_from_secret, ed25519_sign, ed25519_verify, - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] ed25519_verify_phase, x25519_public_key, x25519_diffie_hellman, diff --git a/benches/blake3.rs b/benches/blake3.rs index f40e5646..30eafa27 100644 --- a/benches/blake3.rs +++ b/benches/blake3.rs @@ -8,19 +8,19 @@ mod common; use core::hint::black_box; use criterion::{BenchmarkId, Criterion}; -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] use rscrypto::hashes::crypto::blake3::{ Blake3DiagKernel, diag_blake3_chunk_cvs_with_kernel, diag_blake3_digest_with_kernel, diag_blake3_kernel_available, diag_blake3_keyed_digest_with_kernel, diag_blake3_parent_cvs_with_kernel, diag_blake3_streaming_digest_with_kernel, diag_blake3_xof_with_kernel, }; -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] const BLAKE3_CHUNK_LEN: usize = 1024; -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] const BLAKE3_OUT_LEN: usize = 32; -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn diag_kernels() -> &'static [Blake3DiagKernel] { &[ Blake3DiagKernel::Portable, @@ -43,7 +43,7 @@ fn diag_kernels() -> &'static [Blake3DiagKernel] { ] } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn chunk_tail_diag_kernels() -> &'static [Blake3DiagKernel] { &[ #[cfg(target_arch = "x86_64")] @@ -63,7 +63,7 @@ fn chunk_tail_diag_kernels() -> &'static [Blake3DiagKernel] { ] } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn parent_tail_diag_kernels() -> &'static [Blake3DiagKernel] { &[ #[cfg(target_arch = "x86_64")] @@ -83,7 +83,7 @@ fn parent_tail_diag_kernels() -> &'static [Blake3DiagKernel] { ] } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn print_blake3_diag_once() { use std::sync::Once; @@ -110,7 +110,7 @@ fn print_blake3_diag_once() { }); } -#[cfg(not(feature = "diag"))] +#[cfg(not(all(rscrypto_internal, feature = "diag")))] #[inline] fn print_blake3_diag_once() {} @@ -130,7 +130,7 @@ fn oneshot(c: &mut Criterion) { b.iter(|| black_box(rscrypto::Blake3::digest(black_box(d)))) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] for &kernel in diag_kernels() { if !diag_blake3_kernel_available(kernel) { continue; @@ -174,7 +174,7 @@ fn keyed(c: &mut Criterion) { b.iter(|| black_box(rscrypto::Blake3::keyed_digest(black_box(&key), black_box(d)))) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] for &kernel in diag_kernels() { if !diag_blake3_kernel_available(kernel) { continue; @@ -249,7 +249,7 @@ fn streaming(c: &mut Criterion) { }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] for &kernel in diag_kernels() { if !diag_blake3_kernel_available(kernel) || !kernel.supports_streaming() { continue; @@ -303,7 +303,7 @@ fn xof(c: &mut Criterion) { }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] for &kernel in diag_kernels() { if !diag_blake3_kernel_available(kernel) { continue; @@ -337,7 +337,7 @@ fn xof(c: &mut Criterion) { g.finish(); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn xof_output(c: &mut Criterion) { if !bench_config::selected("blake3/xof-output") { return; @@ -397,7 +397,7 @@ fn xof_output(c: &mut Criterion) { g.finish(); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn tail_diagnostics(c: &mut Criterion) { if !bench_config::selected("blake3/") { return; @@ -497,9 +497,9 @@ fn main() { derive_key, streaming, xof, - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] xof_output, - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] tail_diagnostics, ]); } diff --git a/benches/rsa.rs b/benches/rsa.rs index beeddea0..f43e3527 100644 --- a/benches/rsa.rs +++ b/benches/rsa.rs @@ -28,7 +28,7 @@ use rsa::{ pss::{Signature as RustCryptoPssSignature, VerifyingKey as RustCryptoPssVerifyingKey}, signature::Verifier as _, }; -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] use rscrypto::auth::rsa::{ diag_rsa_blinding_factor_inverse_with_scratch, diag_rsa_public_operation_bitserial, diag_rsa_public_operation_cios, diag_rsa_public_operation_cios_portable, diag_rsa_public_operation_generic_exponent, @@ -185,7 +185,7 @@ fn pkcs1_der_from_modulus_exponent(modulus: &[u8], exponent: &[u8]) -> Vec { sequence(&body) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn synthetic_pkcs1_der(modulus_len: usize) -> Vec { let mut modulus = vec![0xff; modulus_len]; modulus[0] = 0x80; @@ -365,7 +365,7 @@ fn rsa_private_signing(c: &mut Criterion) { }; let mut group = c.benchmark_group("rsa-2048-private-signing"); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] { let mut inverse_scratch = key.private_scratch(); let mut computed_inverse = vec![0u8; key.signature_len()]; @@ -523,7 +523,7 @@ fn rsa_components_for_size( let representative = modulus_minus_one(&pss_key); let mut out = vec![0u8; pss_key.modulus().len()]; - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let (pss_encoded, pss_em_bits, mut pss_db, mut pss_db_mask, pkcs1_encoded) = { let mut pss_encoded = vec![0u8; pss_key.modulus().len()]; pss_key @@ -601,7 +601,7 @@ fn rsa_components_for_size( .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] { let mut cios_scratch = pss_key.public_scratch(); let mut product_scratch = pss_key.public_scratch(); @@ -628,7 +628,7 @@ fn rsa_components_for_size( }) }); } - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("padding-pss-sha256-rscrypto", |b| { b.iter(|| { diag_rsa_verify_pss_encoded_with_scratch( @@ -642,7 +642,7 @@ fn rsa_components_for_size( .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("padding-pkcs1v15-sha256-rscrypto", |b| { b.iter(|| { diag_rsa_verify_pkcs1v15_encoded( @@ -790,27 +790,27 @@ fn rsa_public_exponents(c: &mut Criterion) { let mut scratch_e17 = key_e17.public_scratch(); let mut scratch_e65537 = key_e65537.public_scratch(); let mut scratch_generic = key_generic.public_scratch(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let mut cios_scratch_e3 = key_e3.public_scratch(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let mut product_scratch_e3 = key_e3.public_scratch(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let mut generic_scratch_e3 = key_e3.public_scratch(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let mut cios_scratch_e17 = key_e17.public_scratch(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let mut product_scratch_e17 = key_e17.public_scratch(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let mut generic_scratch_e17 = key_e17.public_scratch(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let mut cios_scratch_e65537 = key_e65537.public_scratch(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let mut product_scratch_e65537 = key_e65537.public_scratch(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let mut generic_scratch_e65537 = key_e65537.public_scratch(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let mut cios_scratch_generic = key_generic.public_scratch(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let mut product_scratch_generic = key_generic.public_scratch(); let mut out = vec![0u8; modulus.len()]; @@ -822,14 +822,14 @@ fn rsa_public_exponents(c: &mut Criterion) { .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e3-bitserial-baseline", |b| { b.iter(|| { diag_rsa_public_operation_bitserial(black_box(&key_e3), black_box(&input), black_box(&mut out)) .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e3-product-montgomery", |b| { b.iter(|| { diag_rsa_public_operation_product( @@ -841,7 +841,7 @@ fn rsa_public_exponents(c: &mut Criterion) { .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e3-generic-exponent", |b| { b.iter(|| { diag_rsa_public_operation_generic_exponent( @@ -853,7 +853,7 @@ fn rsa_public_exponents(c: &mut Criterion) { .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e3-cios-candidate", |b| { b.iter(|| { diag_rsa_public_operation_cios( @@ -872,14 +872,14 @@ fn rsa_public_exponents(c: &mut Criterion) { .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e17-bitserial-baseline", |b| { b.iter(|| { diag_rsa_public_operation_bitserial(black_box(&key_e17), black_box(&input), black_box(&mut out)) .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e17-product-montgomery", |b| { b.iter(|| { diag_rsa_public_operation_product( @@ -891,7 +891,7 @@ fn rsa_public_exponents(c: &mut Criterion) { .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e17-generic-exponent", |b| { b.iter(|| { diag_rsa_public_operation_generic_exponent( @@ -903,7 +903,7 @@ fn rsa_public_exponents(c: &mut Criterion) { .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e17-cios-candidate", |b| { b.iter(|| { diag_rsa_public_operation_cios( @@ -922,14 +922,14 @@ fn rsa_public_exponents(c: &mut Criterion) { .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e65537-bitserial-baseline", |b| { b.iter(|| { diag_rsa_public_operation_bitserial(black_box(&key_e65537), black_box(&input), black_box(&mut out)) .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e65537-product-montgomery", |b| { b.iter(|| { diag_rsa_public_operation_product( @@ -941,7 +941,7 @@ fn rsa_public_exponents(c: &mut Criterion) { .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e65537-generic-exponent", |b| { b.iter(|| { diag_rsa_public_operation_generic_exponent( @@ -953,7 +953,7 @@ fn rsa_public_exponents(c: &mut Criterion) { .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e65537-cios-candidate", |b| { b.iter(|| { diag_rsa_public_operation_cios( @@ -972,14 +972,14 @@ fn rsa_public_exponents(c: &mut Criterion) { .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e0x49d2a1-bitserial-baseline", |b| { b.iter(|| { diag_rsa_public_operation_bitserial(black_box(&key_generic), black_box(&input), black_box(&mut out)) .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e0x49d2a1-product-montgomery", |b| { b.iter(|| { diag_rsa_public_operation_product( @@ -991,7 +991,7 @@ fn rsa_public_exponents(c: &mut Criterion) { .expect("valid RSA benchmark fixture must succeed") }) }); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] group.bench_function("public-op-e0x49d2a1-cios-candidate", |b| { b.iter(|| { diag_rsa_public_operation_cios( @@ -1034,7 +1034,7 @@ fn rsa_hash_components(c: &mut Criterion) { group.finish(); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn rsa_montgomery_thresholds(c: &mut Criterion) { if !bench_config::selected("rsa-montgomery-thresholds") { return; @@ -1166,7 +1166,7 @@ fn main() { rsa_private_signing, rsa_public_exponents, rsa_hash_components, - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] rsa_montgomery_thresholds, ]); } diff --git a/benches/sha2.rs b/benches/sha2.rs index 05f81113..18fa7ecb 100644 --- a/benches/sha2.rs +++ b/benches/sha2.rs @@ -29,7 +29,7 @@ macro_rules! aws_lc_bench { ($($tokens:tt)*) => {}; } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn print_sha2_diag_once() { use std::sync::Once; @@ -55,7 +55,7 @@ fn print_sha2_diag_once() { }); } -#[cfg(not(feature = "diag"))] +#[cfg(not(all(rscrypto_internal, feature = "diag")))] #[inline] fn print_sha2_diag_once() {} @@ -209,7 +209,7 @@ fn sha256_internal(_c: &mut Criterion) { } print_sha2_diag_once(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] { let blocks = common::random_bytes(64 * 16); let mut g = _c.benchmark_group("sha256/internal/compress"); diff --git a/ct.toml b/ct.toml index 2f626df6..fbe1b57f 100644 --- a/ct.toml +++ b/ct.toml @@ -555,8 +555,8 @@ reason = "High-level AEAD open varies the public ciphertext/tag transcript with name = "aes128_gcm_siv_diag_derive_fixed_vs_random_key" primitive = "aead.open_authentication" filter = "aes128_gcm_siv_diag_derive_fixed_vs_random_key" -left_class = "s390x_gcmsiv_key_diag: derive message keys from fixed AES-128-GCM-SIV master key" -right_class = "s390x_gcmsiv_key_diag: derive message keys from random AES-128-GCM-SIV master key" +left_class = "gcmsiv_key_diag: derive message keys from fixed AES-128-GCM-SIV master key" +right_class = "gcmsiv_key_diag: derive message keys from random AES-128-GCM-SIV master key" samples = 20000 smoke_samples = 2000 @@ -564,8 +564,8 @@ smoke_samples = 2000 name = "aes256_gcm_siv_diag_derive_fixed_vs_random_key" primitive = "aead.open_authentication" filter = "aes256_gcm_siv_diag_derive_fixed_vs_random_key" -left_class = "s390x_gcmsiv_key_diag: derive message keys from fixed AES-256-GCM-SIV master key" -right_class = "s390x_gcmsiv_key_diag: derive message keys from random AES-256-GCM-SIV master key" +left_class = "gcmsiv_key_diag: derive message keys from fixed AES-256-GCM-SIV master key" +right_class = "gcmsiv_key_diag: derive message keys from random AES-256-GCM-SIV master key" samples = 20000 smoke_samples = 2000 @@ -3078,8 +3078,8 @@ name = "x86_64-unknown-linux-gnu" group = "linux" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2378 -compiler_api_sha256 = "39d02c3eed97a71d3eb9e4cc50fdd1335cdb925ac0e6463582dce2946f8b70e2" +compiler_api_item_count = 2279 +compiler_api_sha256 = "622f2db6dc8ee2cb61e3edb7a853c7574d4015cda366079125a2ca6cd6398cce" claim = "ct-intended" physical_timing = "required" binsec = "required" @@ -3089,8 +3089,8 @@ name = "aarch64-unknown-linux-gnu" group = "linux" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2372 -compiler_api_sha256 = "3c750de8b6cd2b5848396cfd25c1fa53d1b8977339f926aabb19601bbd93f654" +compiler_api_item_count = 2279 +compiler_api_sha256 = "622f2db6dc8ee2cb61e3edb7a853c7574d4015cda366079125a2ca6cd6398cce" claim = "ct-intended" physical_timing = "required" binsec = "required" @@ -3123,8 +3123,8 @@ group = "win" backend = "llvm" linker = "msvc-link-unpinned" claim = "ct-intended" -compiler_api_item_count = 2376 -compiler_api_sha256 = "de2f6c1b82316e086b9f0504515ebde73e0f5b712cdc012c6af47a1dcfccf066" +compiler_api_item_count = 2279 +compiler_api_sha256 = "622f2db6dc8ee2cb61e3edb7a853c7574d4015cda366079125a2ca6cd6398cce" physical_timing = "required" binsec = "unsupported" binsec_reason = "BINSEC PE/Windows evidence is not part of the current release evidence gate." @@ -3145,8 +3145,8 @@ name = "aarch64-apple-darwin" group = "macos" backend = "llvm" linker = "apple-ld-unpinned" -compiler_api_item_count = 2372 -compiler_api_sha256 = "3c750de8b6cd2b5848396cfd25c1fa53d1b8977339f926aabb19601bbd93f654" +compiler_api_item_count = 2279 +compiler_api_sha256 = "622f2db6dc8ee2cb61e3edb7a853c7574d4015cda366079125a2ca6cd6398cce" claim = "ct-intended" physical_timing = "required" binsec = "unsupported" @@ -3157,8 +3157,8 @@ name = "s390x-unknown-linux-gnu" group = "ibm" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2371 -compiler_api_sha256 = "fa877ed86fd0e69103cae38c62b56623dd568a24a2e10d595510335e553f909c" +compiler_api_item_count = 2279 +compiler_api_sha256 = "622f2db6dc8ee2cb61e3edb7a853c7574d4015cda366079125a2ca6cd6398cce" claim = "ct-intended" physical_timing = "required" binsec = "unsupported" @@ -3169,8 +3169,8 @@ name = "powerpc64le-unknown-linux-gnu" group = "ibm" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2371 -compiler_api_sha256 = "0bc167ff8ba62d64268ba208db2d430055b52aaec7559e0b1c1f1ff7fff9a3f3" +compiler_api_item_count = 2279 +compiler_api_sha256 = "622f2db6dc8ee2cb61e3edb7a853c7574d4015cda366079125a2ca6cd6398cce" claim = "ct-intended" physical_timing = "required" binsec = "unsupported" @@ -3181,8 +3181,8 @@ name = "riscv64gc-unknown-linux-gnu" group = "linux" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2371 -compiler_api_sha256 = "ab570a0df15e466383aa090a1cee061533721f0a32e6360fdebdbc03c760ca52" +compiler_api_item_count = 2279 +compiler_api_sha256 = "622f2db6dc8ee2cb61e3edb7a853c7574d4015cda366079125a2ca6cd6398cce" claim = "ct-intended" physical_timing = "required" binsec = "unsupported" diff --git a/docs/benchmarking.md b/docs/benchmarking.md index 6ef554dd..732f838d 100644 --- a/docs/benchmarking.md +++ b/docs/benchmarking.md @@ -1,63 +1,66 @@ # Benchmarking -Benchmark the exact primitive, operation, input size, feature set, and target -you plan to deploy. A crate-wide aggregate is not a deployment result. +Benchmark the exact primitive, operation, input size, feature set, and target you plan to deploy. +A crate-wide aggregate is not a deployment result. ## Read published results -[`benchmark_results/OVERVIEW.md`](../benchmark_results/OVERVIEW.md) owns the -published summary. New local and development-machine runs each write a unique directory: +[`benchmark_results/OVERVIEW.md`](../benchmark_results/OVERVIEW.md) owns the published summary. +New local and development-machine runs each write a unique directory: ```text benchmark_results/criterion// ``` -Each run records its literal requests, log, source-file hashes, compiler -and Cargo identity, build and runtime environment, CPU/OS information, raw `criterion/` -data, and `status.txt`. Successful discovery adds the resolved case plan. -Status starts as `running` and becomes `complete` or -`failed` with the execution exit code. `complete` requires every planned case's -identity, statistical samples, and estimates, plus comparison estimates when a -baseline was supplied for that case. A successful process with missing results -fails the run. An abruptly interrupted run may remain -`running`. Packaging failures retain the run and do not publish a checksum. +Each run records its literal requests, log, source-file hashes, compiler and Cargo identity, +build and runtime environment, CPU/OS information, raw `criterion/` data, and `status.txt`. +Successful discovery adds the resolved case plan. +Status starts as `running` and becomes `complete` or `failed` with the execution exit code. +`complete` requires every planned case's identity, statistical samples, and estimates, +plus comparison estimates when a baseline was supplied for that case. +A successful process with missing results fails the run. +An abruptly interrupted run may remain `running`. +Packaging failures retain the run and do not publish a checksum. Source hashes identify dirty worktrees; they do not replace retaining the source. Export only when you need a portable artifact: -```sh +```bash just bench-export benchmark_results/criterion/ ``` -This archives that run, including failed-run evidence, under -`benchmark_results/.transfers/` with a SHA-256 checksum. Export refuses an -existing archive. `output_dir=` changes the results root, preserving the -`criterion/` and `.transfers/` layout. On development machines, use the default -root; collection exports the selected run before downloading it: +This archives that run, including failed-run evidence, under `benchmark_results/.transfers/` with a SHA-256 checksum. +Export refuses an existing archive. +`output_dir=` changes the results root, preserving the `criterion/` and `.transfers/` layout. +On development machines, use the default root; +collection exports the selected run before downloading it: -```sh +```bash just ssh-collect-bench ``` -Collect before destroying the machine. The destination must not already exist. +Collect before destroying the machine. +The destination must not already exist. Historical date/OS/architecture result directories remain unchanged. -Every run starts with fresh Criterion output. To compare against a completed -run, select it explicitly: +Every run starts with fresh Criterion output. +To compare against a completed run, select it explicitly: -```sh +```bash just bench sha256 baseline=benchmark_results/criterion/ ``` -Only validated Criterion `base` data for matching cases and configurations is -copied into the new run; the previous run is unchanged. Compatibility includes -the Cargo command, compiler, manifest profiles, Cargo configuration file hashes, -CPU identity, build/runtime controls, and effective Criterion settings. Source -revision and the watchdog budget are recorded separately from compatibility. -Baselines require a resolved plan and verified measurements. The command fails -if there are no matching configurations/cases; unmatched cases otherwise run -without comparison. Matching metadata does not prove identical thermal, power, -or system-load conditions: control those before interpreting a comparison. +Only validated Criterion `base` data for matching cases and configurations is copied into the new run; +the previous run is unchanged. +Compatibility includes the Cargo command, compiler, manifest profiles, +Cargo configuration file hashes, CPU identity, build/runtime controls, +and effective Criterion settings. +Source revision and the watchdog budget are recorded separately from compatibility. +Baselines require a resolved plan and verified measurements. +The command fails if there are no matching configurations/cases; +unmatched cases otherwise run without comparison. +Matching metadata does not prove identical thermal, power, or system-load conditions: +control those before interpreting a comparison. Speedup is: @@ -65,81 +68,86 @@ Speedup is: comparison_time / rscrypto_time ``` -Above `1.00x` favors `rscrypto`; below `1.00x` favors the comparison. Summary -tables treat `0.95x` through `1.05x` as a tie. +Above `1.00x` favors `rscrypto`; below `1.00x` favors the comparison. +Summary tables treat `0.95x` through `1.05x` as a tie. -[`.config/benchmark-matrix.json`](../.config/benchmark-matrix.json) owns -benchmark binaries, required features, aliases, and filters. The benchmark -source owns each timed operation. Inspect both before claiming equivalent work. +[`.config/benchmark-matrix.json`](../.config/benchmark-matrix.json) owns benchmark binaries, required features, aliases, and filters. +The benchmark source owns each timed operation. +Inspect both before claiming equivalent work. ## Run a manual workflow -The [Bench workflow](../.github/workflows/bench.yml) runs only on manual -request. Select the revision with GitHub's branch selector, then choose: +The [Bench workflow](../.github/workflows/bench.yml) runs only on manual request. +Select the revision with GitHub's branch selector, then choose: -| Input | Examples | Meaning | -| --- | --- | --- | -| `architectures` | `x86_64-linux` | One native platform; x86-64 runs both Intel and AMD. | +| Input | Examples | Meaning | +| --------------- | --------------------------------------------- | ------- | +| `architectures` | `x86_64-linux` | One native platform; x86-64 runs both Intel and AMD. | | `architectures` | `s390x-linux,powerpc64le-linux,riscv64-linux` | Any subset, separated by commas or spaces. | -| `architectures` | `all` | Linux x86-64, Linux ARM64, Windows x86-64, IBM Z, IBM POWER, and RISC-V. | -| `selection` | `sha256` | One catalog algorithm. | -| `selection` | `sha256,blake3` | Multiple algorithms. | -| `selection` | `hashes`, `checksums`, `auth`, `aead` | A catalog group. Groups can also be combined. | -| `selection` | `all` | All algorithms in the catalog's `all` selector. | -| `selection` | `bench=sha2,auth` | Entire benchmark targets, including cases beyond an individual algorithm. | -| `filter` | `^sha256/rscrypto/64$` | Narrow the selected scope to matching Criterion cases. | +| `architectures` | `all` | Linux x86-64, Linux ARM64, Windows x86-64, IBM Z, IBM POWER, and RISC-V. | +| `selection` | `sha256` | One catalog algorithm. | +| `selection` | `sha256,blake3` | Multiple algorithms. | +| `selection` | `hashes`, `checksums`, `auth`, `aead` | A catalog group. Groups can also be combined. | +| `selection` | `all` | All algorithms in the catalog's `all` selector. | +| `selection` | `bench=sha2,auth` | Entire benchmark targets, including cases beyond an individual algorithm. | +| `filter` | `^sha256/rscrypto/64$` | Narrow the selected scope to matching Criterion cases. | The remaining platform names are `aarch64-linux` and `x86_64-win`. -Algorithm/group selectors and explicit `bench=` targets are alternative forms -of `selection`; the workflow rejects invalid architecture and catalog selections -before starting measurement runners. A case filter that matches nothing fails during -discovery. The catalog remains the authority for available selectors and targets. +Algorithm/group selectors and explicit `bench=` targets are alternative forms of `selection`; +the workflow rejects invalid architecture and catalog selections +before starting measurement runners. +A case filter that matches nothing fails during discovery. +The catalog remains the authority for available selectors and targets. -Optional sampling fields override the shared Criterion settings; blank fields -preserve the repository defaults. The diagnostic checkbox enables diagnostic -features for the selected targets; it does not select separate targets. +Optional sampling fields override the shared Criterion settings; +blank fields preserve the repository defaults. +The diagnostic checkbox enables diagnostic features for the selected targets; +it does not select separate targets. A small planning job validates the request and creates the exact runner matrix. -AWS provides fixed on-demand instance types for Linux x86-64/ARM64 and Windows -x86-64. Both x86-64 operating systems run separate Intel and AMD jobs with -distinct artifact names. Machine shapes live in [runs-on.yml](../.github/runs-on.yml); -these use current-generation processors without an AVX2-only baseline. macOS +AWS provides fixed on-demand instance types for Linux x86-64/ARM64 and Windows x86-64. +Both x86-64 operating systems run separate Intel and AMD jobs with distinct artifact names. +Machine shapes live in [runs-on.yml](../.github/runs-on.yml); benchmark preparation uses `bench-cross-build`, +and measurement uses the `measure-*` profiles. +They are sized independently of CI and CT profiles. +These use the fixed processor families named in the catalog without an AVX2-only baseline. macOS benchmarks run locally on the Apple Silicon Mac. -IBM and RISE provide their existing native runners. The selected -architectures run concurrently; benchmark configurations run sequentially on -each machine. RISC-V, POWER, and IBM Z compile the selected configurations on -x86-64 using `--ci-cross-build TARGET`. Their native jobs use `--ci-cross-run` -with the verified tools archive, then discover and measure the transferred binaries. +IBM and RISE provide their existing native runners. +The selected architectures run concurrently; +benchmark configurations run sequentially on each machine. +RISC-V, POWER, and IBM Z compile the selected configurations on x86-64 using `--ci-cross-build TARGET`. +Their native jobs use `--ci-cross-run` with the verified tools archive, +then discover and measure the transferred binaries. Other Linux jobs use `--ci-bench`; Windows uses `-CiBench`. -No caches or speed-regression gates are enabled. Donated hosts may be shared, -and fixed AWS instance types do not eliminate host noise. Equal vCPU counts -do not imply equal physical core counts; interpret parallel results with the -recorded CPU topology. Inspect uncertainty -and repeat matched measurements before making performance claims. - -Each job retains `target/bench/` as a GitHub artifact, including failed-run -evidence, source and machine identity, the resolved case plan, logs, and raw -Criterion results. Preparation and native measurement each have a separate -90-minute invocation budget; +No caches or speed-regression gates are enabled. +Donated hosts may be shared, and fixed AWS instance types do not eliminate host noise. +Equal vCPU counts do not imply equal physical core counts; +interpret parallel results with the recorded CPU topology. +Inspect uncertainty and repeat matched measurements before making performance claims. + +Each job retains `target/bench/` as a GitHub artifact, including failed-run evidence, source and machine identity, +the resolved case plan, logs, and raw Criterion results. +Preparation and native measurement each have a separate 90-minute invocation budget; `all` is a selection, not a guarantee that every case will fit that budget. -Narrow large runs by algorithm, group, target, or case filter. The workflow -allows additional provisioning time, especially on RISC-V. +Narrow large runs by algorithm, group, target, or case filter. +The workflow allows additional provisioning time, especially on RISC-V. Manual dispatch becomes available after the workflow reaches the default branch. -Cross-build preparation never executes target code. It seals each unique catalog -build configuration, source identity, compiler/linker evidence, exact binary hash, -and requested sampling settings. Native consumption rejects changed sources, -settings, configurations, or ELF architectures before discovery. It uses the same -case filtering, measurement, and result verification as an ordinary run, without -compiling again. The retained input manifest identifies the build host; result -compatibility records the measurement host and its runtime settings. Cross-built -and native-built results have distinct build identities for baseline comparisons. -The two invocations use `just bench ... target=TARGET prepare_archive=ARCHIVE` and -`just bench ... target=TARGET run_archive=ARCHIVE` with matching selections and settings. +Cross-build preparation never executes target code. +It seals each unique catalog build configuration, source identity, compiler/linker evidence, +exact binary hash, and requested sampling settings. +Native consumption rejects changed sources, settings, configurations, +or ELF architectures before discovery. +It uses the same case filtering, measurement, and result verification as an ordinary run, +without compiling again. +The retained input manifest identifies the build host; +result compatibility records the measurement host and its runtime settings. +Cross-built and native-built results have distinct build identities for baseline comparisons. +The two invocations use `just bench ... target=TARGET prepare_archive=ARCHIVE` and `just bench ... target=TARGET run_archive=ARCHIVE` with matching selections and settings. The same granular selections work locally: -```sh +```bash just bench sha256 blake3 just bench hashes just bench all @@ -148,70 +156,73 @@ just bench bench=sha2 'filter=^sha256/rscrypto/64$' ## Timed workload boundaries -Choose the timed boundary from the question the workload answers. State it next -to the benchmark group in source, including input restoration, allocation, key -or state construction, output handling, and destruction. Put material included -or excluded work in the case identity; implementation names must identify the -library or backend actually called. A renamed boundary starts a new baseline. - -- **Reusable-buffer operation:** allocate storage and prepare reusable state - outside timing. Time the operation on that state. If fresh state is required, - describe any batched setup explicitly; do not call its allocation part of the - measured operation. -- **Copy plus operation:** restore the input into preallocated storage inside - timing, then operate on it. Use `copy-and-…` in the operation name. Retain this - boundary when measuring the cost of preserving an immutable source message. -- **Complete application operation:** include the actual lifecycle being - studied, and name its stages, such as `copy-and-construct-and-seal`. State which - application costs remain excluded; constructing a cipher does not imply that - packet allocation, entropy, or transport is included. - -Do not move setup out of timing just to obtain a smaller number. `iter` includes -work and destruction inside its closure and destruction of its return value. +Choose the timed boundary from the question the workload answers. +State it next to the benchmark group in source, including input restoration, allocation, +key or state construction, output handling, and destruction. +Put material included or excluded work in the case identity; +implementation names must identify the library or backend actually called. +A renamed boundary starts a new baseline. + +- **Reusable-buffer operation:** allocate storage and prepare reusable state outside timing. + Time the operation on that state. + If fresh state is required, describe any batched setup explicitly; + do not call its allocation part of the measured operation. +- **Copy plus operation:** restore the input into preallocated storage inside timing, + then operate on it. + Use `copy-and-…` in the operation name. + Retain this boundary when measuring the cost of preserving an immutable source message. +- **Complete application operation:** include the actual lifecycle being studied, + and name its stages, such as `copy-and-construct-and-seal`. + State which application costs remain excluded; + constructing a cipher does not imply that packet allocation, entropy, or transport is included. + +Do not move setup out of timing just to obtain a smaller number. +`iter` includes work and destruction inside its closure and destruction of its return value. `iter_batched` excludes the setup closure and defers returned-output destruction; consumed inputs can still be destroyed inside the timed closure. -`iter_batched_ref` also defers destruction of the setup object. The existing -P-256 ECDH batches prepare fresh consumed keys outside timing; RapidHash map -insertion batches allocate empty maps outside timing. These are different -boundaries from AEAD's timed buffer restoration. - -The AEAD `copy-and-encrypt`, `copy-and-decrypt`, `copy-and-seal`, and -`copy-and-open` groups reuse preallocated buffers and cipher contexts. They time -input restoration, cryptography, and per-call output handling and cleanup. -Fixture generation, initial buffer allocation, and reusable-context construction -and destruction are excluded. Rows labeled `appended-tag` copy or produce the -combined ciphertext/tag representation; other rows use detached tags. Throughput -counts message bytes, not restoration traffic or tag bytes. These are not -cryptography-only measurements or complete packet-processing measurements. -AES-SIV `copy-and-construct-and-seal` additionally constructs and destroys a -cipher inside each iteration. Construction-only and header-mask groups state -their own boundaries next to their registrations. - -The ChaCha diagnostic `chacha20-copy-and-xor` group restores the message and -applies the keystream in the timed closure, reusing an allocated buffer. Poly1305 -instead reads immutable fixture bytes and returns a tag without restoring a -message buffer. Neither is a complete AEAD operation. - -BLAKE2 `short-oneshot` and `short-keyed` retain only the 16- and 128-byte inputs -absent from the main size matrix. `single-update` measures construction, one -update, and finalization at the small sizes; it is distinct from the multi-chunk -streaming workload. Plain parameter-group duplicates are removed; the main -one-shot rows remain the baselines for salt/personalization hashing. All are -complete hash operations, not isolated host overhead. -Ascon's `rscrypto/scalar-loop` rows compare repeated rscrypto scalar API calls -with its batch API, not with an external library. +`iter_batched_ref` also defers destruction of the setup object. +The existing P-256 ECDH batches prepare fresh consumed keys outside timing; +RapidHash map insertion batches allocate empty maps outside timing. +These are different boundaries from AEAD's timed buffer restoration. + +The AEAD `copy-and-encrypt`, `copy-and-decrypt`, `copy-and-seal`, and `copy-and-open` groups reuse preallocated buffers and cipher contexts. +They time input restoration, cryptography, and per-call output handling and cleanup. +Fixture generation, initial buffer allocation, +and reusable-context construction and destruction are excluded. +Rows labeled `appended-tag` copy or produce the combined ciphertext/tag representation; +other rows use detached tags. +Throughput counts message bytes, not restoration traffic or tag bytes. +These are not cryptography-only measurements or complete packet-processing measurements. +AES-SIV `copy-and-construct-and-seal` additionally constructs and destroys a cipher inside each iteration. +Construction-only and header-mask groups state their own boundaries next to their registrations. + +The ChaCha diagnostic `chacha20-copy-and-xor` group restores the message and applies the keystream in the timed closure, +reusing an allocated buffer. +Poly1305 instead reads immutable fixture bytes and returns a tag without restoring a message buffer. +Neither is a complete AEAD operation. + +BLAKE2 `short-oneshot` and `short-keyed` retain only the 16- and 128-byte inputs absent from the main size matrix. +`single-update` measures construction, one update, and finalization at the small sizes; +it is distinct from the multi-chunk streaming workload. +Plain parameter-group duplicates are removed; +the main one-shot rows remain the baselines for salt/personalization hashing. +All are complete hash operations, not isolated host overhead. +Ascon's `rscrypto/scalar-loop` rows compare repeated rscrypto scalar API calls with its batch API, +not with an external library. ## ML-KEM and Argon2 comparison contracts -Compare only rows in the same operation group, with matching build and host -identities. These contracts supersede the old ML-KEM IDs and the Argon2 IDs -without `salt16-raw32`. Do not reuse their measurements as baselines. The effect -of the old mismatches on reported ratios has not been measured. +Compare only rows in the same operation group, with matching build and host identities. +These contracts supersede the old ML-KEM IDs and the Argon2 IDs without `salt16-raw32`. +Do not reuse their measurements as baselines. +The effect of the old mismatches on reported ratios has not been measured. -ML-KEM uses fixed 64-byte key-generation seeds and fixed 32-byte encapsulation -randomness in `derand` groups. Fixture construction is untimed. Decapsulation -uses the same expanded secret-key bytes and ciphertext in every implementation; -it consumes no entropy. The three parameter sets have separate groups. +ML-KEM uses fixed 64-byte key-generation seeds +and fixed 32-byte encapsulation randomness in `derand` groups. +Fixture construction is untimed. +Decapsulation uses the same key material and ciphertext in every implementation; +it consumes no entropy. +The three parameter sets have separate groups. | Operation suffix | Timed input and preparation | Timed output | | --- | --- | --- | @@ -230,109 +241,119 @@ it consumes no entropy. The three parameter sets have separate groups. | `decapsulate/import-encoded` | Identical expanded secret-key and ciphertext bytes; each API's import, validation, and decapsulation | 32-byte shared-secret array | All ML-KEM rows include output conversion and destruction of per-call objects. -`Criterion::iter` includes destruction of returned arrays. Reused keys are -constructed and destroyed outside timing; imported keys are constructed and -destroyed inside timing. Internal allocations and their cleanup remain timed, +`Criterion::iter` includes destruction of returned arrays. +Reused keys are constructed and destroyed outside timing; +imported keys are constructed and destroyed inside timing. +Internal allocations and their cleanup remain timed, including AWS-LC's allocated ciphertext/shared-secret buffers and key objects. -The harness does not supply reusable scratch storage or equalize library-specific -cleanup policies. These measure the selected APIs on valid inputs, not identical -validation or zeroization guarantees. RustCrypto expanded-key import/export uses -its deprecated compatibility API intentionally to keep key encodings identical. +The harness does not supply reusable scratch storage or equalize library-specific cleanup policies. +These measure the selected APIs on valid inputs, not identical validation or zeroization guarantees. +RustCrypto participates in encapsulation and reused decoded-key decapsulation. Its decoded secret key +is constructed from the fixture seed outside timing. Expanded-key generation and import rows use +implementations with supported expanded-key APIs. Each deterministic ML-KEM row runs its actual timed closure once outside timing -and checks the complete output against the shared fixture. Key-generation checks -compare both encoded keys; encapsulation checks compare ciphertext and secret; -decapsulation checks compare the secret. AWS-LC's randomized generation and -encapsulation closures are checked through cross-implementation decapsulation. -A mismatch aborts execution before that row is timed. Discovery lists identities; -it does not substitute for executing the selected rows' correctness checks. - -Argon2 competitor groups include `salt16-raw32` in their IDs. They use the same -password, full 16-byte salt, Argon2 version 0x13, memory/time/lane parameters, -and 32-byte raw output. They consume no entropy and do no PHC encoding. Parameter -objects and caller-owned output buffers are prepared outside timing; each call -includes the selected API's scratch allocation, computation, and scratch -cleanup. The output buffer is reused and destroyed outside timing. Cleanup -policies remain those of each library. Untimed checks compare all 32 output -bytes with RustCrypto and, where its parameter limits allow a row, dryoc. -Argon2 parallel-scaling rows use the same salt and output size while varying -lane count. Scrypt and PHC fixtures are separate workloads. +and checks the complete output against the shared fixture. +Key-generation checks compare both encoded keys; encapsulation checks compare ciphertext and secret; +decapsulation checks compare the secret. +AWS-LC's randomized generation +and encapsulation closures are checked through cross-implementation decapsulation. +A mismatch aborts execution before that row is timed. +Discovery lists identities; it does not substitute +for executing the selected rows' correctness checks. + +Argon2 competitor groups include `salt16-raw32` in their IDs. +They use the same password, full 16-byte salt, Argon2 version 0x13, memory/time/lane parameters, +and 32-byte raw output. +They consume no entropy and do no PHC encoding. +Parameter objects and caller-owned output buffers are prepared outside timing; +each call includes the selected API's scratch allocation, computation, and scratch cleanup. +The output buffer is reused and destroyed outside timing. +Cleanup policies remain those of each library. +Untimed checks compare all 32 output bytes with RustCrypto and, +where its parameter limits allow a row, dryoc. +Argon2 parallel-scaling rows use the same salt and output size while varying lane count. +Scrypt and PHC fixtures are separate workloads. ## Measure locally -[`.config/criterion.json`](../.config/criterion.json) supplies one configuration -for every Criterion harness, including direct Cargo invocations: 20 samples, -100 ms warmup, 400 ms requested measurement time, 10,000 bootstrap resamples, -95% confidence, 5% significance, and a 1% noise threshold. Benchmark groups may -not override these settings. `warmup_ms=`, `measure_ms=`, and `sample_size=` -override the shared defaults for every selected case in that invocation; their -`BENCH_` environment equivalents have lower precedence than explicit arguments. +[`.config/criterion.json`](../.config/criterion.json) supplies one configuration for every Criterion harness, +including direct Cargo invocations: 20 samples, 100 ms warmup, 400 ms requested measurement time, +10,000 bootstrap resamples, 95% confidence, 5% significance, and a 1% noise threshold. +Benchmark groups may not override these settings. +`warmup_ms=`, `measure_ms=`, and `sample_size=` override the shared defaults for every selected case in that invocation; +their `BENCH_` environment equivalents have lower precedence than explicit arguments. Boolean controls reject unrecognized values and empty strings. These are bounded development defaults, not a promise of statistical precision. -Inspect confidence intervals and repeat a focused selection when the uncertainty -cannot support the intended claim. Criterion can extend the requested measurement -window to collect the requested samples for slow operations. `argon2id` includes -small, OWASP, and parallel workloads; no expensive-workload opt-in is required. +Inspect confidence intervals and repeat a focused selection +when the uncertainty cannot support the intended claim. +Criterion can extend the requested measurement window to collect the requested samples +for slow operations. +`argon2id` includes small, OWASP, and parallel workloads; no expensive-workload opt-in is required. `just bench` bounds the whole pipeline—build, discovery, measurement, analysis, -and result verification—to at most 90 minutes. `just profile` uses the same limit for build, -discovery, and capture. The configured limit may be lowered but cannot exceed -5,400 seconds. Shutdown starts before the deadline, reserving up to five seconds -to retain failed-run evidence before stopping surviving child processes. A timed -out run exits with status 124; partial results do not constitute a complete run. -Plans whose requested sampling windows alone exhaust the budget are rejected -before measurement. Build costs, analysis, and slow operations can still make a -smaller plan hit the deadline. Direct Cargo invocation bounds each harness; +and result verification—to at most 90 minutes. +`just profile` uses the same limit for build, discovery, and capture. +The configured limit may be lowered but cannot exceed 5,400 seconds. +Shutdown starts before the deadline, +reserving up to five seconds to retain failed-run evidence +before stopping surviving child processes. +A timed out run exits with status 124; partial results do not constitute a complete run. +Plans whose requested sampling windows alone exhaust the budget are rejected before measurement. +Build costs, analysis, and slow operations can still make a smaller plan hit the deadline. +Direct Cargo invocation bounds each harness; use `just bench` to bound a selection spanning multiple harnesses and its builds. -Use an algorithm or family selector, or choose explicit benchmark targets with -`bench=` (`bench=` for several). `filter=` narrows the -selected algorithms or targets. For example, `sha256 filter=rscrypto` stays -within SHA-256; `bench=sha2 filter=rscrypto` searches the entire SHA-2 target. -Repeat `filter=` for multiple patterns. Each build configuration is -listed once. A lightweight invocation of the same executable matches all -patterns using Criterion's regex engine, without constructing benchmark fixtures. -The matched cases run as one measurement process per build configuration. The harness reads the -resolved case set from a file and applies an anchored, escaped union filter. A pattern matching no -cases fails before measurement. Patterns are passed verbatim: commas are regex characters, not -separators. Quote each argument for your shell. `BENCH_FILTER` supplies one -literal pattern in addition to any `filter=` arguments. Empty `filter=` values -are rejected; omit the argument for an unfiltered run. Positional selectors accept catalog names; use `filter=` for raw regexes. -Exact algorithm names select only that algorithm; use family names such as -`crc64` to select several. `blake2` includes all implementations and operations, -including dryoc one-shot and keyed cases. +Use an algorithm or family selector, or choose explicit benchmark targets with `bench=` (`bench=` for several). +`filter=` narrows the selected algorithms or targets. +For example, `sha256 filter=rscrypto` stays within SHA-256; `bench=sha2 filter=rscrypto` searches the entire SHA-2 target. +Repeat `filter=` for multiple patterns. +Each build configuration is listed once. +A lightweight invocation of the same executable matches all patterns using Criterion's regex engine, +without constructing benchmark fixtures. +The matched cases run as one measurement process per build configuration. +The harness reads the resolved case set from a file and applies an anchored, escaped union filter. +A pattern matching no cases fails before measurement. +Patterns are passed verbatim: commas are regex characters, not separators. +Quote each argument for your shell. +`BENCH_FILTER` supplies one literal pattern in addition to any `filter=` arguments. +Empty `filter=` values are rejected; omit the argument for an unfiltered run. +Positional selectors accept catalog names; use `filter=` for raw regexes. +Exact algorithm names select only that algorithm; use family names such as `crc64` to select several. +`blake2` includes all implementations and operations, including dryoc one-shot and keyed cases. Discover the actual cases before choosing a measurement scope: -```sh +```bash just bench crc64-nvme --list just bench bench=sha2 --list just bench blake3 --diag --list just bench bench=aead_kernels --list ``` -`--list` builds the selected configuration and lists its cases without measuring, -creating a run, or copying baseline data. It uses the same filters and case -deduplication as measurement. Each row shows its benchmark binary, exact case -name and work class: +`--list` builds the selected configuration and lists its cases without measuring, creating a run, +or copying baseline data. +It uses the same filters and case deduplication as measurement. +Each row shows its benchmark binary, exact case name and work class: - `ordinary`: public operation and comparison workloads. - `expensive`: catalog-declared high-cost workloads, including password hashing, PBKDF2, and RSA private signing. - `diagnostic`: internal components, backend experiments, and overhead probes. -Classes come from `.config/benchmark-matrix.json`; they describe workload intent, -not measured duration or a timing guarantee. A diagnostic case can also be costly. -Classes do not block execution. `--diag` (or `diag=true`) enables the `diag` -feature for the selected benchmark builds; some target configurations already -require it. Cases depend on the compiled features and host capabilities. +Classes come from `.config/benchmark-matrix.json`; they describe workload intent, not measured duration or a timing guarantee. +A diagnostic case can also be costly. +Classes do not block execution. +`--diag` (or `diag=true`) enables `diag` and the internal compiler cfg for the selected benchmark builds; +some target configurations already require it. +Cases depend on the compiled features and host capabilities. Dedicated diagnostic targets such as `aead_kernels` require explicit selection; generic runs include the catalog's required Criterion targets. Run the narrowest useful case: -```sh +```bash just bench bench=sha2 just bench bench=auth filter='^ecdsa-p256/' just bench sha256 'filter=^sha256/rscrypto/\d+$' 'filter=^sha256/rscrypto/[0-9]{1,3}$' @@ -342,27 +363,25 @@ just bench mlkem Explicit targets, including unfiltered `bench=sha2`, run without a scope override. -`requests.json` preserves the resolved target/filter requests and run budget. `plan.json` records -each configuration's selected cases, Cargo command/artifact, executable hash, -compatibility evidence, effective settings, baseline cases, execution command, -and output location. Raw results live under -`criterion/-/` using Criterion's directory layout. -`output.txt` is the build, discovery, and measurement log; `source.json` and -`source-state.json` identify the source files and worktree. The runner verifies -all planned measurements once before marking the run complete. +`requests.json` preserves the resolved target/filter requests and run budget. +`plan.json` records each configuration's selected cases, Cargo command/artifact, executable hash, +compatibility evidence, effective settings, baseline cases, execution command, and output location. +Raw results live under `criterion/-/` using Criterion's directory layout. +`output.txt` is the build, discovery, and measurement log; `source.json` and `source-state.json` identify the source files and worktree. +The runner verifies all planned measurements once before marking the run complete. The shared environment collector separates build inputs from runtime controls, -including Rayon thread controls, CRC backend overrides, and -`RSCRYPTO_FORCE_AVX512`. Unset known runtime controls are explicit JSON nulls. +including Rayon thread controls, CRC backend overrides, and `RSCRYPTO_FORCE_AVX512`. +Unset known runtime controls are explicit JSON nulls. Benchmark plans and profile metadata carry the same compatibility evidence. -Criterion measures elapsed time. `just bench-structural` uses Gungraun and -Valgrind to count instructions and cache events on supported Linux hosts; those -counts do not prove wall-clock speed. +Criterion measures elapsed time. +`just bench-structural` uses Gungraun and Valgrind to count instructions and cache events on supported Linux hosts; +those counts do not prove wall-clock speed. After a benchmark exposes a concrete cost, inspect it with: -```sh +```bash just profile sha2 --list just profile sha2 'sha256/rscrypto/64' 10 just profile blake3 --diag --list @@ -370,39 +389,38 @@ just perf-codegen sha2 -- --asm just perf-llvm-lines sha2 -- --filter ``` -Profiling requires one exact case name. `--list` builds the selected target and -lists its cases without recording. Capture checks that the name occurs exactly -once, then runs that executable with only the resolved case selected. Unrelated -workload groups skip fixture construction. The -requested duration applies to that case; process startup and profiler overhead -add to the total elapsed time. +Profiling requires one exact case name. +`--list` builds the selected target and lists its cases without recording. +Capture checks that the name occurs exactly once, +then runs that executable with only the resolved case selected. +Unrelated workload groups skip fixture construction. +The requested duration applies to that case; +process startup and profiler overhead add to the total elapsed time. -Benchmarking and profiling share the Cargo command and CPU-flag policy. Both use -`bench`, which inherits release optimization settings and retains debug symbols -without stripping. Explicit `RUSTFLAGS` or `CARGO_ENCODED_RUSTFLAGS` take -precedence; local macOS runs otherwise use `-C target-cpu=native`. Match those -flags, target, and Cargo features when comparing with a deployment build. +Benchmarking and profiling share the Cargo command and CPU-flag policy. +Both use `bench`, which inherits release optimization settings and retains debug symbols without stripping. +Explicit `RUSTFLAGS` or `CARGO_ENCODED_RUSTFLAGS` take precedence; local macOS runs otherwise use `-C target-cpu=native`. +Match those flags, target, and Cargo features when comparing with a deployment build. Each target uses its explicit catalog features with Cargo defaults disabled, independent of algorithm selectors, raw filters, or multi-target selection. Each distinct target configuration is built once before its selected cases run. Profiling and code inspection use the same catalog target configuration; -`--diag` enables the same additional feature in each command. Only BLAKE3 and password-hashing -targets enable `parallel`, where their workloads exercise it. Cargo ignores the -panic setting for benchmarks, -so release's `panic = "abort"` remains a difference +`--diag` enables the same feature and internal compiler cfg in each command. +Only BLAKE3 and password-hashing targets enable `parallel`, where their workloads exercise it. +Cargo ignores the panic setting for benchmarks, so release's `panic = "abort"` remains a difference ([Cargo profiles](https://doc.rust-lang.org/cargo/reference/profiles.html)). -Each capture gets a unique directory under `target/profiles/` containing -`profile.json.gz`, `cases.json`, `metadata.json`, a log, and source evidence. Metadata records the exact -case, executable path and SHA-256, Cargo artifact description, build and capture -commands, compiler and tool versions, build/runtime environment, and capture outcome. Source evidence records input -hashes, revision, and worktree status. Keep the matching executable and -its symbols available when investigating a saved profile. - -Keep raw results and run metadata for any published claim. Local measurements -without that evidence are useful only for the machine that produced them. -P-256 ECDH uses the `p256-ecdh` benchmark alias. Its operation rows compare -caller-filled generation, public derivation, canonical SEC1 parsing, agreement, -and a TLS-shaped two-party roundtrip; raw target results and the overview remain -the only performance record. +Each capture gets a unique directory under `target/profiles/` containing `profile.json.gz`, `cases.json`, `metadata.json`, a log, and source evidence. +Metadata records the exact case, executable path and SHA-256, Cargo artifact description, +build and capture commands, compiler and tool versions, build/runtime environment, +and capture outcome. +Source evidence records input hashes, revision, and worktree status. +Keep the matching executable and its symbols available when investigating a saved profile. + +Keep raw results and run metadata for any published claim. +Local measurements without that evidence are useful only for the machine that produced them. +P-256 ECDH uses the `p256-ecdh` benchmark alias. +Its operation rows compare caller-filled generation, public derivation, canonical SEC1 parsing, +agreement, and a TLS-shaped two-party roundtrip; +raw target results and the overview remain the only performance record. diff --git a/docs/constant-time.md b/docs/constant-time.md index 77832e73..1b745e73 100644 --- a/docs/constant-time.md +++ b/docs/constant-time.md @@ -1,13 +1,14 @@ # Constant-time claims Constant time means secret values do not change control flow, memory addresses, -or variable-latency operands within a defined operation. Public input lengths, -algorithm parameters, target features, allocation, scheduling, and external -entropy sources may still affect time. +or variable-latency operands within a defined operation. +Public input lengths, algorithm parameters, target features, allocation, scheduling, +and external entropy sources may still affect time. -`ct.toml` is the authoritative operation inventory. An operation is claimed -only for the targets, features, compiler, linked binary, and evidence named -there. Unlisted code is not claimed constant time. +`ct.toml` is the authoritative operation inventory. +An operation is claimed only for the targets, features, compiler, linked binary, +and evidence named there. +Unlisted code is not claimed constant time. ## Evidence model @@ -20,32 +21,38 @@ Release evidence combines: - BINSEC proofs for declared fixed-shape kernels. - DudeCT timing tests for declared end-to-end cases. -Source that looks branchless is not proof. Compiler lowering, inlining, target -features, and linking can change machine behavior. Evidence for the release -harness does not automatically cover a downstream binary compiled differently. +Source that looks branchless is not proof. +Compiler lowering, inlining, target features, and linking can change machine behavior. +Evidence for the release harness does not automatically cover a downstream binary compiled +differently. Build and validate the local evidence artifacts with: -```sh +```bash just ct-artifacts just ct-validate ``` -`ct-validate` rejects missing or stale generated artifacts. `just ct-full` -builds them, runs available timing checks, and emits reports. A target-specific -claim requires the evidence required by `ct.toml`; a local host cannot -stand in for another target. +`ct-validate` rejects missing or stale generated artifacts. +`just ct-full` builds them, runs available timing checks, and emits reports. +A target-specific claim requires the evidence required by `ct.toml`; +a local host cannot stand in for another target. -For strict manifest and artifact coverage, run `just ct-validate --strict-coverage` -after building the artifacts. This checks compiler output and coverage; timing -and formal evidence require their respective target runs. +For strict manifest and artifact coverage, run `just ct-validate --strict-coverage` after building the artifacts. +This checks compiler output and coverage; +timing and formal evidence require their respective target runs. + +The bounded PBKDF2 proof hooks use `verify_primitive` with one iteration and a fixed salt. +They must reach key derivation and comparison; the application password-policy verifier rejects these +deliberately weak parameters before comparison. Earlier proofs of the policy-rejecting hooks do not establish +PBKDF2 verification coverage. Internal-hook regression tests check both successful and failed verification. +Application password policies remain separate and unchanged. ## Public decisions and exclusions -Ordinary equality is permitted for public values such as nonces, encoded public -keys, ciphertext lengths, and signature inputs. Secret owners expose -`CtDecision` where comparison must remain opaque until explicit -declassification. +Ordinary equality is permitted for public values such as nonces, encoded public keys, +ciphertext lengths, and signature inputs. +Secret owners expose `CtDecision` where comparison must remain opaque until explicit declassification. These operations are intentionally outside blanket constant-time claims: @@ -57,36 +64,49 @@ These operations are intentionally outside blanket constant-time claims: - External implementations of public traits. - Diagnostic APIs, which deliberately expose evidence values. -P-256 ECDH scalar sampling and canonical SEC1 validation are public prelude -operations outside the private-arithmetic claim. Once a valid scalar and peer -point exist, public derivation and agreement use fixed loop bounds, full-table -secret-digit scans, masked exceptional-point selection, and no -secret-dependent addresses. `ct.toml` scopes the required linked-binary and -target evidence; a source-level fixed-work design is not itself a release -claim. Its operation entry distinguishes the portable implementation from the -selected Apple/Linux AArch64, Linux x86-64, and Windows x86-64 assembly. -Physical Graviton3, Graviton4, and Intel Granite Rapids development runs cover -both Linux operation-level DudeCT cases and preserve the measured binary, -disassembly, symbols, linker command, and raw samples. Those bundles measure -intermediate Phase 4 candidates and do not replace exact-candidate evidence. -The retained G3 maxima are 1.12000 for public -derivation and 2.59291 for agreement; the Intel Granite Rapids maxima are -1.76752 and 1.33030, respectively, against the threshold of 10. - -Windows x86-64 now requires the same native timing campaign, compiler API -inventory, artifact validation, and cleanup sentinel as the other selected -native lanes. Its BINSEC proof policy remains unsupported. Required means the -evidence must be collected, not that a candidate has passed: each release still -needs successful exact-source native results across all selected architectures. +P-256 ECDH scalar sampling and canonical SEC1 validation are public prelude operations outside the +private-arithmetic claim. +Once a valid scalar and peer point exist, public derivation and agreement use fixed loop bounds, +full-table secret-digit scans, masked exceptional-point selection, +and no secret-dependent addresses. +`ct.toml` scopes the required linked-binary and target evidence; +a source-level fixed-work design is not itself a release claim. +Its operation entry distinguishes the portable implementation from the selected Apple/Linux AArch64, +Linux x86-64, and Windows x86-64 assembly. +The [September 2026 P-256 ECDH snapshot](../benchmark_results/OVERVIEW.md#p-256-ecdh-development-snapshot) retains the historical Graviton3, Graviton4, +and Intel Granite Rapids results. +Those development bundles preserve binaries and raw timing samples, +but later source changes require new exact-candidate evidence. + +Recent ECDSA hardening preserves masked point selection on AArch64 and Windows, +masked selection in portable P-256, and fixed-bound table traversal. +RISC-V generator-table loads remain unconditional under LLVM optimization. +These implementation changes preserve signature semantics; +they do not by themselves establish a timing claim for a release or a downstream build. + +Windows x86-64 now requires the same native timing campaign, compiler API inventory, +artifact validation, and cleanup sentinel as the other selected native lanes. +Its BINSEC proof policy remains unsupported. +Required means the evidence must be collected, not that a candidate has passed: +each release still needs successful exact-source native results across all selected architectures. Neither cross-compilation nor a different microarchitecture is timing proof. -RISC-V CI prepares CT artifacts on x86-64 and measures the transferred executable -on physical RISC-V. The preparation bundle binds the exact source, compiler, -binary, disassembly, and validation evidence. The timing reports retain both -host identities. Preparation alone supplies no timing result, and transferred -execution retains the same required cases and acceptance thresholds. See -[the transfer workflow](../scripts/README.md#constant-time-evidence). - -Authentication failures remain opaque even when their inputs are public. See -[`secret-ownership.md`](secret-ownership.md) for comparison capabilities and -[`secret-lifecycle.md`](secret-lifecycle.md) for cleanup evidence. +RISC-V, POWER, and IBM Z CI prepare CT artifacts on x86-64 +and measure the transferred executables on the matching native hardware. +The preparation bundle binds the exact source, compiler, binary, disassembly, +and validation evidence. +The timing reports retain both host identities. +Preparation alone supplies no timing result, +and transferred execution retains the same required cases and acceptance thresholds. +See [the transfer workflow](../scripts/README.md#constant-time-evidence). + +The [Constant-Time workflow](../.github/workflows/ct.yml) runs through manual dispatch or release qualification, +not on each pull request or push. +Its AWS measurement profiles use fixed On-Demand instances +and are sized separately from benchmark profiles in [`.github/runs-on.yml`](../.github/runs-on.yml). +Preparation uses Spot instances; its completion supplies no timing evidence. +Diagnostic replay remains separate from the full release campaign. + +Authentication failures remain opaque even when their inputs are public. +See [`secret-ownership.md`](secret-ownership.md) for comparison capabilities and [`secret-lifecycle.md`](secret-lifecycle.md) +for cleanup evidence. diff --git a/docs/features.md b/docs/features.md index 650db7bd..9c73c72d 100644 --- a/docs/features.md +++ b/docs/features.md @@ -5,69 +5,76 @@ Select the smallest feature set that exposes the primitives you use. ## Start here -The default feature is `std`; `std` enables `alloc`. Disable defaults for -`no_std`, then name every required primitive: +The default feature is `std`; `std` enables `alloc`. +Disable defaults for `no_std`, then name every required primitive: ```toml # no_std SHA-2 -rscrypto = { version = "0.9", default-features = false, features = ["sha2"] } +rscrypto = { version = "0.10", default-features = false, features = ["sha2"] } # Full API with OS randomness -rscrypto = { version = "0.9", features = ["full", "getrandom"] } +rscrypto = { version = "0.10", features = ["full", "getrandom"] } ``` Umbrella features trade build size for convenience: -| Feature | Includes | -| --- | --- | -| `checksums` | CRC-16, CRC-24, CRC-32, and CRC-64 | +| Feature | Includes | +| --------------- | -------- | +| `checksums` | CRC-16, CRC-24, CRC-32, and CRC-64 | | `crypto-hashes` | SHA-2, SHA-3, BLAKE2, BLAKE3, and Ascon hash | -| `fast-hashes` | XXH3 and RapidHash | -| `hashes` | Cryptographic and fast hashes | -| `auth` | MACs, KDFs, password hashing, signatures, and key exchange | -| `aead` | Every AEAD implementation | -| `full` | Checksums, hashes, authentication, and AEADs | +| `fast-hashes` | XXH3 and RapidHash | +| `hashes` | Cryptographic and fast hashes | +| `auth` | MACs, KDFs, password hashing, signatures, and key exchange | +| `aead` | Every AEAD implementation | +| `full` | Checksums, hashes, authentication, and AEADs | -Prefer leaf features such as `sha2`, `blake3`, `aes-gcm`, `ed25519`, `p256-ecdh`, or -`ml-kem` in libraries and constrained builds. +Prefer leaf features such as `sha2`, `blake3`, `aes-gcm`, `ed25519`, `p256-ecdh`, or `ml-kem` in libraries and constrained builds. `websocket-sha1` exposes only the compatibility digest for WebSocket handshakes. It is excluded from every umbrella feature, including `full`; enable it explicitly. ## Capability features -| Feature | Effect | -| --- | --- | -| `alloc` | Enables APIs that own dynamic memory, including `SecretVec` and `SecretString`. | -| `std` | Enables runtime CPU detection and standard-library integrations; implies `alloc`. | -| `getrandom` | Enables fallible helpers that obtain keys, nonces, salts, or seeds from the OS. | -| `parallel` | Enables Rayon-backed BLAKE3 and Argon2 work; implies `std`, `blake3`, and `argon2`. | -| `serde` | Serializes public types. | +| Feature | Effect | +| --------------- | ------ | +| `alloc` | Enables APIs that own dynamic memory, including `SecretVec` and `SecretString`. | +| `std` | Enables runtime CPU detection and standard-library integrations; implies `alloc`. | +| `getrandom` | Enables fallible helpers that obtain keys, nonces, salts, or seeds from the OS. | +| `parallel` | Enables Rayon-backed BLAKE3 and Argon2 work; implies `std`, `blake3`, and `argon2`. | +| `serde` | Serializes public types. | | `serde-secrets` | Also serializes secret keys and shared secrets; use only at an explicit key-storage boundary. | | `portable-only` | Makes runtime capability detection report no SIMD or ASM capabilities. | -| `diag` | Exposes unstable diagnostic and evidence hooks; implies `std`. Do not use it as application API. | +| `diag` | Exposes capability and backend-selection introspection; implies `std`. | -`getrandom` changes entropy acquisition, not algorithm availability. APIs that -accept caller-provided entropy remain available without it. +Benchmark, constant-time, zeroization, forced-kernel, and component hooks require both `diag` and the +repository-only `rscrypto_internal` compiler cfg. Ordinary Cargo feature combinations, including +`--all-features`, do not expose those operations. The internal cfg is unsupported for application dependencies +and carries no compatibility guarantee. -`p256-ecdh` is a standalone leaf: it does not enable ECDSA, HMAC, `alloc`, or -`std`. See [`platforms.md`](platforms.md) for backend selection, -[`constant-time.md`](constant-time.md) for timing claims, and -[`test-vector-coverage.md`](test-vector-coverage.md) for independent vectors. +`getrandom` changes entropy acquisition, not algorithm availability. +APIs that accept caller-provided entropy remain available without it. -`portable-only` affects dispatchers that consult `platform::caps()`. It does -not remove accelerated code from the binary or override backends selected by -compile-time `target_feature` settings. See [`platforms.md`](platforms.md). +`p256-ecdh` is a standalone leaf: it does not enable ECDSA, HMAC, `alloc`, or `std`. +See [`platforms.md`](platforms.md) for backend selection, [`constant-time.md`](constant-time.md) for timing claims, +and [`test-vector-coverage.md`](test-vector-coverage.md) for independent vectors. + +`portable-only` affects dispatchers that consult `platform::caps()`. +It does not remove accelerated code from the binary +or override backends selected by compile-time `target_feature` settings. +See [`platforms.md`](platforms.md). ## Verify a selection -```sh +```bash cargo check --no-default-features --features sha2 just plan just check ``` -`just check` and `just ci-check` lint the combined native and portable feature -sets. Use the Cargo command above to check an isolated feature selection. +`just check` and `just ci-check` lint the combined native and portable feature sets. +`just ci-compat` additionally checks each standalone feature on the development compiler +and the minimum supported Rust version. +It also builds bare-metal targets and executes scalar and SIMD WebAssembly vectors in Wasmtime. +Use the Cargo command above to check an isolated feature selection. Use [docs.rs](https://docs.rs/rscrypto) to see which items each feature exposes. diff --git a/docs/migration.md b/docs/migration.md deleted file mode 100644 index 97a947a6..00000000 --- a/docs/migration.md +++ /dev/null @@ -1,113 +0,0 @@ -# Migrating to rscrypto - -Migrate one primitive at a time. `rscrypto` is not API-compatible with other -cryptography crates, and replacing a toolkit does not replace its protocol, -certificate, key-storage, or compliance behavior. - -## Choose features - -Disable default features for `no_std`, then enable only the primitives you use: - -```toml -rscrypto = { version = "0.9", default-features = false, features = ["sha2"] } -``` - -Common source crates map as follows: - -| Source | `rscrypto` feature | -| --- | --- | -| `aes-gcm`, `aes-gcm-siv`, `chacha20poly1305`, `ascon-aead`, `aegis` | `aes-gcm`, `aes-gcm-siv`, `chacha20poly1305`, `xchacha20poly1305`, `ascon-aead`, or `aegis256` | -| `sha2`, `sha3`, `blake2`, `blake3`, `tiny-keccak`, `sha3-kmac` | `sha2`, `sha3`, `blake2b`, `blake2s`, `blake3`, or `kmac` | -| `hmac`, `hkdf`, `pbkdf2`, `argon2`, `scrypt` | `hmac`, `hkdf`, `pbkdf2`, `argon2`, `scrypt`, and `phc-strings` as needed | -| `p256`, `p384`, `ed25519-dalek`, `x25519-dalek`, `rsa` | `p256-ecdh`, `ecdsa-p256`, `ecdsa-p384`, `ed25519`, `x25519`, or `rsa` | -| `crc`, `crc-fast`, `crc32fast`, `crc32c`, `crc64fast` | `crc16`, `crc24`, `crc32`, or `crc64` | -| `xxhash-rust`, `twox-hash`, `rapidhash` | `xxh3` or `rapidhash` | - -`aws-lc-rs`, `ring`, `dryoc`, and `openssl` are broader toolkits. Map each -primitive separately and keep their protocol or certificate work outside -`rscrypto`. `aws-lc-sys` has no direct replacement because `rscrypto` exposes -Rust APIs, not AWS-LC symbols. - -## Review these API boundaries - -- Digests return fixed Rust arrays and `finalize` borrows the hasher. Reset or - create a new hasher before processing another message. -- Keys, tags, signatures, ciphertexts, and shared secrets use distinct types. - Convert at the input boundary instead of carrying generic byte buffers. -- Combined AEAD output includes the authentication tag; detached APIs return it - separately. Opening requires the same nonce and associated data. Failed - in-place opens clear unauthenticated plaintext. -- Random key and nonce helpers require `getrandom`. Without it, supply entropy - explicitly and preserve uniqueness requirements. -- X25519 rejects an all-zero shared secret. Feed successful output into a KDF - that binds the protocol transcript; do not use the raw secret as a key. -- P-256 ECDH accepts only canonical uncompressed SEC1 peer keys. Its ephemeral - scalar has no public import/export path and is consumed by agreement. Feed - the fixed-width raw x-coordinate into a protocol-bound KDF; it is not a - uniformly distributed application key. ECDH does not authenticate the peer; - authenticate both public keys or the transcript that binds them. -- Password helpers validate and emit bounded PHC strings. Set an application - policy for parameters, accepted algorithms, and rehashing. -- Caller-controlled nonce operations and other sharp tools live under - `expert`; ordinary callers should use the root API. - -## Move secret ownership into rscrypto - -Use fixed-size owners when size is part of the protocol contract. Fallible -fillers write directly into zero-initialized owner storage. The examples below -call `getrandom::fill` directly, so add `getrandom = "0.4"` as an application -dependency; enabling rscrypto's `getrandom` feature does not expose that crate -to your code. - -Construct a fixed-size owner: - -```rust -use rscrypto::SecretBytes; - -let key = SecretBytes::<32>::try_fill_with(|bytes| { - getrandom::fill(bytes) -})?; -# Ok::<(), getrandom::Error>(()) -``` - -With `alloc`, transfer existing allocations without copying: - -```rust -use rscrypto::{SecretString, SecretVec}; - -let bytes = SecretVec::from_vec(vec![1, 2, 3]); -let text = SecretString::from_string(String::from("credential")); -assert_eq!(bytes.as_bytes(), &[1, 2, 3]); -assert_eq!(text.as_str(), "credential"); -``` - -The old infallible ECDSA blinding callbacks are deprecated. Use the fallible -entry points so entropy failure returns before private arithmetic. This example -requires rscrypto's `ecdsa-p256` feature: - -```rust -use rscrypto::{EcdsaBlindedSigningError, EcdsaP256SecretKey}; - -let secret = EcdsaP256SecretKey::from_bytes([0x42; 32])?; -let signature = secret.try_sign_blinded_with(b"message", |bytes| getrandom::fill(bytes)); -match signature { - Ok(signature) => assert_eq!(signature.as_bytes().len(), 64), - Err(EcdsaBlindedSigningError::Random(error)) => return Err(error.into()), - Err(EcdsaBlindedSigningError::Signing(error)) => return Err(error.into()), -} -# Ok::<(), Box>(()) -``` - -## Verify the migration - -Run the example closest to the operation, then test old and new implementations -against the same vectors before removing the old dependency: - -```sh -just test-examples -just test --all -``` - -See [`../examples/README.md`](../examples/README.md) for runnable workflows, -[`features.md`](features.md) for build selection, and -[docs.rs](https://docs.rs/rscrypto) for exact types and methods. diff --git a/docs/platforms.md b/docs/platforms.md index 8c8c4e86..ed21fc9c 100644 --- a/docs/platforms.md +++ b/docs/platforms.md @@ -1,95 +1,97 @@ # Platforms -Portable Rust defines every supported primitive. SIMD and assembly are -accelerators, never separate specifications. +Portable Rust defines every supported primitive. +SIMD and assembly are accelerators, never separate specifications. ## Backend selection Dispatch has three tiers: 1. Compile-time target features may select an eligible backend. -2. With `std`, cached runtime detection selects from CPU- and OS-authorized +1. With `std`, cached runtime detection selects from CPU- and OS-authorized capabilities. -3. Otherwise, the portable implementation runs. - -`no_std` builds use compile-time selection only. `portable-only` makes runtime -detection return no accelerated capabilities, but it does not override -compile-time target features or remove code from the binary. - -Capability overrides and process authorization such as Linux AMX permission -must occur before the first `platform::caps()` call because detection is cached. - -Every accelerated path must match portable Rust for representative lengths, -alignments, tails, and state transitions. Cross-compilation proves only that a -target builds; runtime behavior requires target execution. - -P-256 ECDH remains a standalone leaf with a safe Rust authority on every -supported target. Apple and Linux AArch64 builds select embedded s2n-bignum -fixed-base and arbitrary-point assembly at compile time unless `portable-only` -or Miri is active. Linux x86-64 selects the corresponding baseline or ADX/BMI2 -ELF kernels after cached runtime capability detection. Windows x86-64 uses the -same baseline or ADX/BMI2 arithmetic behind Microsoft x64 wrappers; public SEC1 -validation crosses one target-shaped batch boundary instead of five field-call -wrappers. The deterministic provenance transform keeps those backends -independent of the ECDSA feature and clears their secret-derived frames, -saved-register spill slots, and volatile integer registers. - -Physical Graviton3, Graviton4, Intel Granite Rapids Linux, and Intel Granite Rapids -Windows development evidence covers the applicable native ABI, direct portable -differentials, independent vectors and implementations, and equivalent-work -performance for the measured Phase 4 candidates. - -The sealed Linux bundles retain complete operation-level timing artifacts and -optimized cleanup evidence, but later shared-source edits mean they are not exact-final-source -release evidence. Exact-final-source Windows timing and cleanup artifacts are -not available, and dedicated physical timing is unavailable; the native runtime -and benchmark do not stand in for those gates. Other targets and -microarchitectures retain their portable fallback or remain without native -evidence. Evidence from one CPU is never substituted for another. +1. Otherwise, the portable implementation runs. + +`no_std` builds use compile-time selection only. +`portable-only` makes runtime detection return no accelerated capabilities, +but it does not override compile-time target features or remove code from the binary. + +Capability overrides and process authorization such as Linux AMX permission must occur +before the first `platform::caps()` call because detection is cached. + +Every accelerated path must match portable Rust for representative lengths, alignments, tails, +and state transitions. +Cross-compilation proves only that a target builds; runtime behavior requires target execution. + +SHA-224 and SHA-256 share the SHA-256 compression capability policy. +Their x86-64 SHA-NI backend requires both `sha` and `sse4.1`; unsupported CPUs retain portable fallback. +Scalar WebAssembly builds exclude SIMD backends from hash, AEAD, +and Argon2 dispatch when `simd128` is disabled. +This compile-time boundary is separate from `portable-only` runtime dispatch. + +P-256 ECDH remains a standalone leaf with a safe Rust authority on every supported target. +Apple and Linux AArch64 builds select embedded s2n-bignum fixed-base +and arbitrary-point assembly at compile time unless `portable-only` or Miri is active. +Linux x86-64 selects the corresponding baseline or ADX/BMI2 ELF kernels +after cached runtime capability detection. +Windows x86-64 uses the same baseline or ADX/BMI2 arithmetic behind Microsoft x64 wrappers; +public SEC1 validation crosses one target-shaped batch boundary instead of five field-call wrappers. +The deterministic provenance transform keeps those backends independent of the ECDSA feature +and clears their secret-derived frames, saved-register spill slots, and volatile integer registers. + +The [P-256 ECDH development snapshot](../benchmark_results/OVERVIEW.md#p-256-ecdh-development-snapshot) records the September 2026 Graviton3, Graviton4, +and Intel Granite Rapids Linux/Windows results and their source identities. +Its Linux timing and cleanup bundles predate later shared-source changes. +The snapshot does not supply exact-candidate Windows timing or cleanup evidence. +Native runtime tests and benchmarks do not replace those gates, +and evidence from one CPU does not qualify another. ## Supported targets -[`.config/target-matrix.json`](../.config/target-matrix.json) is the target -support catalog. Targets outside it may compile, but are not part of the tested -support contract. Target-specific evidence must be collected independently. - -Current validation is defined by the [CI workflow](../.github/workflows/ci.yml) -and [repository recipes](../scripts/README.md): - -| Check | Scope | -| --- | --- | -| `just check` | Host and catalogued cross-target compilation and lint checks. | -| Native CI | Native and portable suites plus doctests on Linux x86-64, AArch64, POWER, IBM Z, and RISC-V, and Windows x86-64. RISC-V, POWER, and IBM Z build on x86-64 and execute transferred artifacts on native hardware. | -| `just check-macos` | Local Apple Silicon checks, native and portable release suites plus doctests, and physical RSA assembly qualification before every commit. | -| `just test-musl` | Native and portable suites plus doctests on matching x86-64 or AArch64 Linux hosts. | -| `just ci-compat` | Feature/MSRV and bare-metal compilation; scalar and SIMD vector execution for `wasm32-unknown-unknown` and `wasm32-wasip1` in Wasmtime. | - -A configured check is not a passing result for the current revision. Inspect -matching run artifacts before qualifying a release. Bare-metal checks do not -execute on devices, and Wasmtime results do not establish browser-engine -behavior. Windows AArch64 runtime CI remains deferred. macOS checks and tests run -locally before commits; physical Apple Silicon timing qualification remains -a separate local requirement. - -Performance and constant-time claims require retained evidence for the exact -operation and configuration. Neither a target's presence in the catalog nor a -passing compile check supplies that evidence. See the -[benchmark record](../benchmark_results/OVERVIEW.md) and -[constant-time evidence model](constant-time.md). +[`.config/target-matrix.json`](../.config/target-matrix.json) is the target support catalog. +Targets outside it may compile, but are not part of the tested support contract. +Target-specific evidence must be collected independently. + +Current validation is defined by the [CI workflow](../.github/workflows/ci.yml) and [repository recipes](../scripts/README.md): + +| Check | Scope | +| ------------------ | ----- | +| `just check` | Host and catalogued cross-target compilation and lint checks. | +| Native CI | Native and portable suites plus doctests on Linux x86-64, AArch64, POWER, IBM Z, and RISC-V, and Windows x86-64. RISC-V, POWER, and IBM Z build on x86-64 and execute transferred artifacts on native hardware. | +| `just check-macos` | Local Apple Silicon checks, native and portable release suites plus doctests, internal evidence regressions, and physical RSA assembly qualification before every commit. | +| `just test-musl` | Native and portable suites plus doctests on matching x86-64 or AArch64 Linux hosts. | +| `just ci-compat` | Feature/MSRV and bare-metal compilation; scalar and SIMD vector execution for `wasm32-unknown-unknown` and `wasm32-wasip1` in Wasmtime. | + +A configured check is not a passing result for the current revision. +Inspect matching run artifacts before qualifying a release. +Bare-metal checks do not execute on devices, +and Wasmtime results do not establish browser-engine behavior. +Windows AArch64 runtime CI remains deferred. macOS checks and tests run locally before commits; +physical Apple Silicon timing qualification remains a separate local requirement. + +AWS runner shapes and Spot policy live in [`.github/runs-on.yml`](../.github/runs-on.yml). +Native CI, cross-build preparation, fuzzing, and CT measurement use separate profiles. +The [runner guidance](../scripts/README.md#native-tooling) explains profile selection and when catalog changes take effect. +A smaller runner does not reduce the required test or evidence surface. + +Performance and constant-time claims require retained evidence for the exact operation +and configuration. +Neither a target's presence in the catalog nor a passing compile check supplies that evidence. +See the [benchmark record](../benchmark_results/OVERVIEW.md) and [constant-time evidence model](constant-time.md). Retained POWER, IBM Z, and RISC-V evidence covers native unit/backend behavior -and focused portable-versus-accelerated tests. Windows AArch64 has compile-only -evidence; Windows x86-64 has native runtime evidence. Apple Silicon is the only -supported macOS architecture. -`x86_64-apple-darwin` is not catalogued, tested, or maintained; it may compile -incidentally, but that does not make it a supported target. +and focused portable-versus-accelerated tests. +Windows AArch64 has compile-only evidence; Windows x86-64 has native runtime evidence. +Apple Silicon is the only supported macOS architecture. +`x86_64-apple-darwin` is not catalogued, tested, or maintained; it may compile incidentally, +but that does not make it a supported target. -Backend availability varies by primitive, target, compiler, and CPU. Use -`rscrypto::platform` and the `introspect` example to inspect one build: +Backend availability varies by primitive, target, compiler, and CPU. +Use `rscrypto::platform` and the `introspect` example to inspect one build: -```sh +```bash cargo run --example introspect --features 'crc32,sha2,chacha20poly1305,diag' ``` -Use [`constant-time.md`](constant-time.md) for target-specific timing claims and -[`benchmarking.md`](benchmarking.md) for performance evidence. +Use [`constant-time.md`](constant-time.md) for target-specific timing claims and [`benchmarking.md`](benchmarking.md) +for performance evidence. diff --git a/docs/test-vector-coverage.md b/docs/test-vector-coverage.md index fcc64569..4572fc77 100644 --- a/docs/test-vector-coverage.md +++ b/docs/test-vector-coverage.md @@ -1,9 +1,9 @@ # Test evidence -This map identifies the independent evidence behind each primitive family and -the important boundaries that remain outside it. Test filenames are the stable -entry points; individual corpus files remain owned by `testdata/` and the test -readers. +This map identifies the independent evidence behind each primitive family +and the important boundaries that remain outside it. +Test filenames are the stable entry points; +individual corpus files remain owned by `testdata/` and the test readers. ## Coverage map @@ -19,23 +19,28 @@ readers. | ECDSA, Ed25519, X25519 | RFC or official vectors, Wycheproof, RustCrypto/dalek oracles, properties, and fuzzing | ASN.1, JWK, or variable-length profiles are excluded where the public API accepts fixed arrays only. | | P-256 ECDH | All 25 NIST CAVP P-256 ECC CDH component records, all 355 pinned Wycheproof `ecpoint` cases, RustCrypto differentials, a ring cross-agreement, Miri, and fuzzing | The first public surface accepts canonical uncompressed SEC1 points only. Wycheproof supplies the full-width leading-zero and all-zero x-coordinate cases; the NIST slice does not contain a full leading-zero byte. | | ML-KEM-512/768/1024 | NIST ACVP key-generation, encapsulation, decapsulation, and key-check vectors plus `fips203` differentials | No vendored Wycheproof ML-KEM suite is mapped. | -| RSA signatures, encryption, and parsing | NIST CAVP, Wycheproof, RustCrypto oracles, profile-confusion, allocation, and leakage tests | Public APIs expose fixed SHA-2 profiles rather than every Wycheproof parameter combination. | +| RSA signatures, encryption, and parsing | NIST CAVP, Wycheproof, RustCrypto and system OpenSSL/LibreSSL oracles, profile-confusion, allocation, and leakage tests | Public APIs expose fixed SHA-2 profiles rather than every Wycheproof parameter combination; system-library oracle availability depends on the test host. | | Dispatch and fallback | Portable-versus-accelerated differential tests across lengths, tails, and vectored input | Cross-compilation alone is not runtime evidence. | -The WebSocket accept digest has the RFC 6455 example, private SHA-1 known-answer -tests, RustCrypto differential tests, and fuzzing. It is compatibility-only and -makes no collision-resistance or authentication claim. +The WebSocket accept digest has the RFC 6455 example, private SHA-1 known-answer tests, +RustCrypto differential tests, and fuzzing. +It is compatibility-only and makes no collision-resistance or authentication claim. ## Run the evidence -```sh +```bash just test --all -just test-fuzz +just test-fuzz --all ``` -Specialized Miri, target, constant-time, and leakage recipes are listed by -`just --list`. +Specialized Miri, target, constant-time, and leakage recipes are listed by `just --list`. -A passing vector proves behavior for that vector. Stronger assurance comes from -combining published vectors, a separate implementation, properties, hostile -inputs, fuzzing, portable-versus-accelerated equivalence, and target execution. +The [Fuzz workflow](../.github/workflows/fuzz.yml) runs x86-64 fuzzing and focused Miri checks for pull requests. +Release qualification selects both x86-64 and ARM64 fuzzing. +Each fuzz job replays the committed corpus under AddressSanitizer before its bounded live campaign. +Runner profiles can change independently of the target selection, concurrency, and sampling budgets; +elapsed time alone does not establish equal fuzzing throughput. + +A passing vector proves behavior for that vector. +Stronger assurance comes from combining published vectors, a separate implementation, properties, +hostile inputs, fuzzing, portable-versus-accelerated equivalence, and target execution. diff --git a/examples/README.md b/examples/README.md index c54ed8d9..e49f405b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,7 +1,9 @@ # Examples -These binaries cover complete workflows. One-call hashing, MAC, and checksum -operations stay in the API documentation. +These binaries cover complete workflows. +One-call hashing, MAC, and checksum operations stay in the API documentation. + +Run them with the accompanying source revision. Run every example with its minimum feature set: @@ -28,9 +30,7 @@ Run one example: cargo run --example aead_seal_open --features alloc,chacha20poly1305,getrandom ``` -Replace the example name and feature list with the matching row. P-256 ECDH and -X25519 return raw shared secrets that a protocol must bind to its transcript -with a KDF; neither operation authenticates the peer. ML-KEM encapsulation -alone does not define a hybrid key-establishment protocol. - -See [`docs/migration.md`](../docs/migration.md) when replacing another library. +Replace the example name and feature list with the matching row. +P-256 ECDH and X25519 return raw shared secrets +that a protocol must bind to its transcript with a KDF; neither operation authenticates the peer. +ML-KEM encapsulation alone does not define a hybrid key-establishment protocol. diff --git a/justfile b/justfile index c0b7499c..c7e2dd5d 100644 --- a/justfile +++ b/justfile @@ -174,7 +174,7 @@ test-transfer: # Run CT harness and exporter self-tests without timing cases. [group('constant-time')] test-harnesses: - scripts/lib/toolchain.sh --exec cargo test --locked --manifest-path tools/ct-dudect/Cargo.toml -p rscrypto-ct-dudect -p dudect-bencher --lib --bins + scripts/lib/python.sh scripts/ct/internal.py --target "$(scripts/lib/toolchain.sh --print-host)" -- scripts/lib/toolchain.sh --exec cargo test --locked --manifest-path tools/ct-dudect/Cargo.toml -p rscrypto-ct-dudect -p dudect-bencher --lib --bins # Execute every runnable example with its minimum feature set. [group('tests')] @@ -287,3 +287,8 @@ bench-export run: [group('tooling')] update *args: @scripts/update-all.sh "$@" + +# Run internal-hook regressions with native and portable dispatch. +[group('tests')] +test-evidence: + @scripts/lib/python.sh scripts/ct/internal.py --target "${CARGO_BUILD_TARGET:-$(scripts/lib/toolchain.sh --print-host)}" -- scripts/lib/python.sh scripts/test/evidence_suite.py diff --git a/scripts/README.md b/scripts/README.md index a503ec55..7a55580a 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,345 +1,380 @@ # Scripts -Repository scripts implement local development, testing, evidence, and -benchmark commands. User-facing entry points are the recipes reported by -`just --list`. Supporting modules are invoked by those entry points. +Repository scripts implement local development, testing, evidence, and benchmark commands. +User-facing entry points are the recipes reported by `just --list`. +Supporting modules are invoked by those entry points. ## Check entry points -| Script | Caller | -| --- | --- | -| `check/check.sh` | `just check`, `just ci-check`, `just ci-check-target` | -| `check/compat.py` | `just ci-compat` | -| `test/test-musl.sh` | `just test-musl` | -| `check/macos.sh` | `just check-macos`, local commit hooks | -| `check/dependencies.sh` | `just ci-policy`, dependency checks within `just check` | +| Script | Caller | +| -------------------------------------- | ------ | +| `check/check.sh` | `just check`, `just ci-check`, `just ci-check-target` | +| `check/compat.py` | `just ci-compat` | +| `test/test-musl.sh` | `just test-musl` | +| `check/macos.sh` | `just check-macos`, local commit hooks | +| `check/dependencies.sh` | `just ci-policy`, dependency checks within `just check` | | `check/lint-independent-workspaces.sh` | `check/check.sh` | -`check/check_runner_test.py` tests command selection, repair behavior, and -failure propagation with substitute executors. Run it with -`scripts/lib/python.sh scripts/check/check_runner_test.py`. +`check/check_runner_test.py` tests command selection, repair behavior, and failure propagation with substitute executors. +Run it with `scripts/lib/python.sh scripts/check/check_runner_test.py`. ## Test entry points -| Script | Caller | -| --- | --- | -| `test/test.sh` | `just test` | -| `test/cross.py` | `just test-cross prepare TARGET ARCHIVE`, `just test-cross run TARGET ARCHIVE` | +| Script | Caller | +| ------------------------ | ------ | +| `test/test.sh` | `just test` | +| `test/cross.py` | `just test-cross prepare TARGET ARCHIVE`, `just test-cross run TARGET ARCHIVE` | | `test/doctest_bundle.py` | RISC-V doctest compilation and target execution | -| `test/test-examples.sh` | `just test-examples` | -| `test/test-miri.sh` | `just test-miri` | -| `test/test-fuzz.sh` | `just test-fuzz` | +| `test/test-examples.sh` | `just test-examples` | +| `test/test-miri.sh` | `just test-miri` | +| `test/test-fuzz.sh` | `just test-fuzz` | | `test/test-fuzz-asan.sh` | `just test-fuzz-asan` | -| `test/test-coverage.py` | `just test-coverage` | -| `test/test-rsa-asm.sh` | `just test-rsa-linux-asm`, `just test-rsa-macos-asm` | +| `test/test-coverage.py` | `just test-coverage` | +| `test/test-rsa-asm.sh` | `just test-rsa-linux-asm`, `just test-rsa-macos-asm` | -`just test-scripts` runs argument forwarding, toolchain, test, check, and fuzz regressions with -substitute executors. `just test-harnesses` directly runs the CT harness and -exporter self-tests; `just ct-test` includes them. +`just test-scripts` runs argument forwarding, toolchain, test, check, and fuzz regressions with substitute executors. +`just test-harnesses` directly runs the CT harness and exporter self-tests; `just ct-test` includes them. `just test-transfer` checks source binding, artifact integrity, safe extraction, and the pinned rustdoc compile/run contract, including deliberate failures. It requires the repository-pinned nightly and runs a small Rust fixture. -RISC-V, POWER, and IBM Z CI build on Ubuntu x86-64 using `--ci-cross-build TARGET` tooling. It runs the -same target-specific checks and builds all release tests in both dispatch modes, -including all doctest compilation checks. Nextest archives and persisted doctest programs -are transferred to the matching native runner, whose `--ci-cross-run TOOLS_ARCHIVE` tooling -only executes them. Preparation is not a runtime pass. The Rust release profile, -target compiler, feature sets, and test assertions remain unchanged. - -Each preparation job also cross-builds the pinned `just` and Nextest tools into a -separate source-bound archive. The native bootstrap verifies that archive and each -tool's ELF architecture before adding its directory to PATH. No Cargo tools compile -on the execution runner. Both Nextest builds use the same locked crate release; +RISC-V, POWER, and IBM Z CI build on Ubuntu x86-64 using `--ci-cross-build TARGET` tooling. +It runs the same target-specific checks and builds all release tests in both dispatch modes, +including all doctest compilation checks. +Nextest archives and persisted doctest programs are transferred to the matching native runner, +whose `--ci-cross-run TOOLS_ARCHIVE` tooling only executes them. +Preparation is not a runtime pass. +The Rust release profile, target compiler, feature sets, and test assertions remain unchanged. + +Each preparation job also cross-builds the pinned `just` +and Nextest tools into a separate source-bound archive. +The native bootstrap verifies that archive and each tool's ELF architecture +before adding its directory to PATH. +No Cargo tools compile on the execution runner. +Both Nextest builds use the same locked crate release; producer and consumer identities must match except for their host architecture. -GNU cross-compilers and target libc development packages use the same Ubuntu CI -snapshot as native provisioning. No compiler cache is used. +GNU cross-compilers and target libc development packages use the same Ubuntu CI snapshot +as native provisioning. +No compiler cache is used. Cross-builds exercise dependency build scripts and procedural macros on x86-64. -They preserve target runtime evidence but do not qualify those tools running as -native POWER, IBM Z, or RISC-V host programs. - -The archive records the Git revision, effective source digest, compiler, Nextest, -release settings, and every file's digest and executable bit. Execution rejects -different sources, missing or changed files, inherited selection overrides, and -the wrong host architecture. The source checkout supplies fixtures and must -match the build checkout, including untracked source files. CI downloads only -the named artifact from the current workflow run; artifacts are not shared caches. -The build runner and artifact service remain trusted. Digests detect corruption -and mismatches, not a compromised producer that forges its own metadata. - -Doctests use rustdoc's extraction inventory and compilation checks, preserving -`compile_fail`, error-code checks, `no_run`, and `should_panic`. Transfer preparation -disables merging because the pinned rustdoc's merged runner executes despite -global `--no-run`. Each runnable standalone program must subsequently execute on -the target hardware. Ordinary `just test` doctests retain rustdoc's default merging behavior. - -Example names and feature requirements come from Cargo metadata. Use -`just test-miri --rsa` for the focused RSA scope and `just test-fuzz --targets A,B` -for an explicit fuzz target group. Package scope is selected before targets: -`--all` includes matching targets from full and scoped packages; `--full` and -`--scoped` restrict it. The default is full, including named targets. +They preserve target runtime evidence but do not qualify those tools running as native POWER, IBM Z, +or RISC-V host programs. + +The archive records the Git revision, effective source digest, compiler, Nextest, release settings, +and every file's digest and executable bit. +Execution rejects different sources, missing or changed files, inherited selection overrides, +and the wrong host architecture. +The source checkout supplies fixtures and must match the build checkout, +including untracked source files. +CI downloads only the named artifact from the current workflow run; artifacts are not shared caches. +The build runner and artifact service remain trusted. +Digests detect corruption and mismatches, not a compromised producer that forges its own metadata. + +Doctests use rustdoc's extraction inventory and compilation checks, preserving `compile_fail`, error-code checks, +`no_run`, and `should_panic`. +Transfer preparation disables merging +because the pinned rustdoc's merged runner executes despite global `--no-run`. +Each runnable standalone program must subsequently execute on the target hardware. +Ordinary `just test` doctests retain rustdoc's default merging behavior. + +Example names and feature requirements come from Cargo metadata. +Use `just test-miri --rsa` for the focused RSA scope and `just test-fuzz --targets A,B` for an explicit fuzz target group. +Package scope is selected before targets: `--all` includes matching targets from full and scoped packages; +`--full` and `--scoped` restrict it. +The default is full, including named targets. `just test-fuzz --build --all` builds every fuzz package without starting fuzzing. ## Constant-time evidence -`just ct-dudect --smoke` uses each selected case's `smoke_samples` from -`ct.toml`. `--samples` overrides the environment sample setting, which overrides -manifest smoke budgets. Each smoke case retains its own measurements; the -latest report summarizes the selected cases and their requested budgets. - -| Script | Caller | -| --- | --- | -| `ct/zig-cc.sh` | `ct/artifacts.sh`, `ct/binsec.py` | -| `ct/test.sh` | `just ct-test` | -| `ct/artifacts.sh` | `just ct-artifacts`, `ct/full.py` | -| `ct/dudect.sh` | `just ct-dudect`, `ct/full.py` (preparation only) | +`just ct-dudect --smoke` uses each selected case's `smoke_samples` from `ct.toml`. +`--samples` overrides the environment sample setting, which overrides manifest smoke budgets. +Each smoke case retains its own measurements; +the latest report summarizes the selected cases and their requested budgets. + +| Script | Caller | +| ---------------------- | ------ | +| `ct/zig-cc.sh` | `ct/artifacts.sh`, `ct/binsec.py` | +| `ct/test.sh` | `just ct-test` | +| `ct/artifacts.sh` | `just ct-artifacts`, `ct/full.py` | +| `ct/dudect.sh` | `just ct-dudect`, `ct/full.py` (preparation only) | | `ct/dudect_execute.py` | `ct/dudect.sh`, `ct/full.py` | `ct/manifest.py` owns shared target and measurement selection. `ct/provenance.py` owns shared file hashing and build identity. -These targets use `just ct-full --target TARGET --prepare-archive ARCHIVE` -on the x86-64 build host and the corresponding `--run-archive ARCHIVE` on the matching -native hardware. Preparation retains strict API/artifact validation, generated-code checks, -and the cleanup sentinel. It also compiles and disassembles the exact DudeCT -executable that will be timed. The consumer verifies the source and artifacts, +These targets use `just ct-full --target TARGET --prepare-archive ARCHIVE` on the x86-64 build host and the corresponding `--run-archive ARCHIVE` on the matching native hardware. +Preparation retains strict API/artifact validation, generated-code checks, and the cleanup sentinel. +It also compiles and disassembles the exact DudeCT executable that will be timed. +The consumer verifies the source and artifacts, then runs the existing full manifest campaign with unchanged sampling, threshold, -and per-case timeouts. No target code is rebuilt during measurement. Reports -distinguish build and measurement hosts and retain the original preparation bundle. -This transfer mode is restricted to the three targets in `lib/cross_build.py`; it cannot bypass native BINSEC on -targets that require it. - -`ct/full.py`, `ct/binsec.py`, and `ct/validate.py` back `just ct-full`, -`just ct-binsec`, and `just ct-validate`. The remaining Python files under -`ct/` implement local artifact provenance, disassembly analysis, report -parsing, and their focused regression tests. - -DudeCT prepares one binary, disassembly, symbol map, linker log, and provenance -snapshot per invocation under `target/ct///dudect/runs//shared/`. -The bundle is read-only after preparation. Each `ct-full` case executes that binary -and writes its own CSV, stdout, and report under `cases/-/`. +and per-case timeouts. +No target code is rebuilt during measurement. +Reports distinguish build and measurement hosts and retain the original preparation bundle. +This transfer mode is restricted to the three targets in `lib/cross_build.py`; +it cannot bypass native BINSEC on targets that require it. + +`ct/full.py`, `ct/binsec.py`, and `ct/validate.py` back `just ct-full`, `just ct-binsec`, and `just ct-validate`. +The remaining Python files under `ct/` implement local artifact provenance, disassembly analysis, +report parsing, and their focused regression tests. + +DudeCT prepares one binary, disassembly, symbol map, linker log, +and provenance snapshot per invocation under `target/ct///dudect/runs//shared/`. +The bundle is read-only after preparation. +Each `ct-full` case executes that binary and writes its own CSV, stdout, and report under `cases/-/`. Reports reference the shared files and their hashes; they do not copy them. -Standalone selection evidence lives under the same run's `selection/` directory, -with a small latest report at `dudect/dudect-report.json`. Failed preparation or -execution cannot reuse a previous run's measurements. Historical runs remain on -disk until explicitly removed; full reports inventory only their current run. - -`just ct-replay --source-root SOURCE --archive ARCHIVE --out OUTPUT --case CASE` -repeats one prepared RISC-V case three times on one allowed CPU. It validates the -original source and transferred binary, preserves the manifest sample count and -timeout, and retains all results at threshold 10. Timing failures do not shorten -the planned campaign; execution failures do. Host snapshots record affinity, -frequency settings where exposed, load, and processes. They do not guarantee an -otherwise idle machine. Replay is diagnostic evidence, not full qualification. +Standalone selection evidence lives under the same run's `selection/` directory, with a small latest report at `dudect/dudect-report.json`. +Failed preparation or execution cannot reuse a previous run's measurements. +Historical runs remain on disk until explicitly removed; +full reports inventory only their current run. + +`just ct-replay --source-root SOURCE --archive ARCHIVE --out OUTPUT --case CASE` repeats one prepared RISC-V case three times on one allowed CPU. +It validates the original source and transferred binary, +preserves the manifest sample count and timeout, and retains all results at threshold 10. +Timing failures do not shorten the planned campaign; execution failures do. +Host snapshots record affinity, frequency settings where exposed, load, and processes. +They do not guarantee an otherwise idle machine. +Replay is diagnostic evidence, not full qualification. Use `--repetitions 1` for one candidate measurement at the same sample count. -The CT workflow's `replay_p384` input selects the original run 34672864167 and -commit 32734d2d. It requires that run's prepared artifact to remain available. +The CT workflow's `replay_p384` input selects the original run 34672864167 and commit 32734d2d. +It requires that run's prepared artifact to remain available. With `replay_p384` disabled, `diagnose_p384` instead prepares the current commit -and measures its P-384 public-key derivation case once on RISC-V. It overrides -the architecture selection. Neither diagnostic mode qualifies a release. +and measures its P-384 public-key derivation case once on RISC-V. +It overrides the architecture selection. +Neither diagnostic mode qualifies a release. ## Benchmarks and updates -| Script | Caller | -| --- | --- | -| `bench/runner.py` | `just bench`, `just profile`, `just bench-export`, code inspection recipes | +| Script | Caller | +| -------------------- | ------ | +| `bench/runner.py` | `just bench`, `just profile`, `just bench-export`, code inspection recipes | | `bench/execution.py` | shared Cargo build, discovery, and provenance for measurement/profiling | -| `bench/measure.py` | runner: measurement and completion verification | -| `bench/profile.py` | runner: exact-case Samply capture | -| `bench/evidence.py` | shared build/runtime environment collector | -| `bench/settings.py` | measurement, profiling, and watchdog | -| `bench/transfer.py` | Compile-only preparation and verified native consumption for RISC-V, POWER, and IBM Z | -| `bench/bounded.py` | `just bench`, `just profile`: process-tree deadline | -| `update-all.sh` | `just update` | +| `bench/measure.py` | runner: measurement and completion verification | +| `bench/profile.py` | runner: exact-case Samply capture | +| `bench/evidence.py` | shared build/runtime environment collector | +| `bench/settings.py` | measurement, profiling, and watchdog | +| `bench/transfer.py` | Compile-only preparation and verified native consumption for RISC-V, POWER, and IBM Z | +| `bench/bounded.py` | `just bench`, `just profile`: process-tree deadline | +| `update-all.sh` | `just update` | `bench/benchmark_catalog.py` owns algorithm and target selection. -`.config/criterion.json` owns shared Criterion defaults and the maximum run -budget. `benches/common/criterion.rs` applies them to every Criterion harness; -`bench/settings.py` resolves invocation-wide overrides. `bench/bounded.py` stops -the whole benchmark or profile process tree within the budget. -`bench/runner.py` resolves filters to unique cases. `bench/measure.py` executes -one process per configuration and verifies statistical artifacts before the -runner marks a run complete. Export is a separate runner command. -`just bench --list` uses that same resolver to list actual cases with catalog work -classes, without starting a measurement run. `--diag` enables diagnostic cases. -`bench/benchmark_catalog_test.py` includes benchmark runner and profiling -regression tests. Run that focused suite with -`scripts/lib/python.sh scripts/bench/benchmark_catalog_test.py`. It exercises the -Python runner, Just argument forwarding, and export in temporary directories, with substitute -Cargo, benchmark, and profiler executors; it does not run cryptographic benchmarks. - -Local and development-machine benchmarks share unique run directories under -`benchmark_results/criterion//`, with logs, plan, provenance, raw data, -and completion status. Explicit exports and checksums live in -`benchmark_results/.transfers/`. See [benchmarking](../docs/benchmarking.md) -for explicit baseline comparisons and remote collection. - -Cargo Rail planning supplies affected scope for `just test`. Check, Miri, and -fuzz commands run independently of that plan. +`.config/criterion.json` owns shared Criterion defaults and the maximum run budget. +`benches/common/criterion.rs` applies them to every Criterion harness; `bench/settings.py` resolves invocation-wide overrides. +`bench/bounded.py` stops the whole benchmark or profile process tree within the budget. +`bench/runner.py` resolves filters to unique cases. +`bench/measure.py` executes one process per configuration and verifies statistical artifacts +before the runner marks a run complete. +Export is a separate runner command. +`just bench --list` uses that same resolver to list actual cases with catalog work classes, +without starting a measurement run. +`--diag` enables diagnostic cases. +`bench/benchmark_catalog_test.py` includes benchmark runner and profiling regression tests. +Run that focused suite with `scripts/lib/python.sh scripts/bench/benchmark_catalog_test.py`. +It exercises the Python runner, Just argument forwarding, and export in temporary directories, +with substitute Cargo, benchmark, and profiler executors; it does not run cryptographic benchmarks. + +Local and development-machine benchmarks share unique run directories under `benchmark_results/criterion//`, with logs, plan, +provenance, raw data, and completion status. +Explicit exports and checksums live in `benchmark_results/.transfers/`. +See [benchmarking](../docs/benchmarking.md) for explicit baseline comparisons and remote collection. + +Cargo Rail planning supplies affected scope for `just test`. +Check, Miri, and fuzz commands run independently of that plan. ## Shared libraries -| Script | Sourced or invoked by | -| --- | --- | -| `lib/rail-plan.sh` | `test/test.sh` | -| `lib/fuzz-packages.sh` | Fuzz scripts | -| `lib/python.sh` | Python-backed check, test, CT, and benchmark scripts | +| Script | Sourced or invoked by | +| -------------------------------------- | --------------------- | +| `lib/rail-plan.sh` | `test/test.sh` | +| `lib/fuzz-packages.sh` | Fuzz scripts | +| `lib/python.sh` | Python-backed check, test, CT, and benchmark scripts | | `lib/toolchain.py`, `lib/toolchain.sh` | Shared toolchain selection for installers, builds, checks, tests, and benchmarks | -| `tooling/transfer.py` | Cross-build and verify the pinned native runner tools | -| `lib/evidence_bundle.py` | Source binding, sealing, and transfer integrity for cross-compiled tests, tools, and CT | -| `lib/cross_build.py` | Explicit target identities and cross-compiler environment for test, tool, and CT preparation | +| `tooling/transfer.py` | Cross-build and verify the pinned native runner tools | +| `lib/evidence_bundle.py` | Source binding, sealing, and transfer integrity for cross-compiled tests, tools, and CT | +| `lib/cross_build.py` | Explicit target identities and cross-compiler environment for test, tool, and CT preparation | -Python tooling requires Python 3.11 or newer. The updater installs its catalog-pinned -Python libraries into a temporary virtual environment; checks and benchmarks use -the standard library. +Python tooling requires Python 3.11 or newer. +The updater installs its catalog-pinned Python libraries into a temporary virtual environment; +checks and benchmarks use the standard library. ## Native tooling +AWS runner profiles live in [`.github/runs-on.yml`](../.github/runs-on.yml). +Adjust CPU, instance families, images, storage, and Spot policy there; +workflows reference profile names without overriding their shapes. +CI, CT, and benchmark cross-builds have separate profiles, as do native CI, fuzzing, +and CT measurement. +Ordinary jobs use price-capacity-optimized Spot; +CT and benchmark measurement use separate fixed On-Demand profiles. +Compiler caching remains disabled in CI. +For this public repository, RunsOn reads the catalog from the default branch: +new profiles must land there before workflow jobs can resolve their names. + `just update` refreshes the tooling catalog, stable Rust, every Cargo manifest -(including standalone and fuzz support workspaces), lockfiles, and existing -GitHub Action pins. It runs on local macOS and has no dependency publish-age -filter. Inspect its changes before committing. - -Run `scripts/tooling/.sh` on the native Ubuntu version pinned in -[the catalog](../.config/tooling.toml). Platforms are `aarch64-linux`, -`x86_64-linux`, `riscv64-linux`, `s390x-linux`, and `powerpc64le-linux`. -The installers use sudo when needed. Windows uses the corresponding -`aarch64-win.ps1` or `x86_64-win.ps1` in an elevated PowerShell session. -Local macOS tools remain locally managed; `scripts/tooling/aarch64-macos.sh` -can provision the pinned prerequisites for `just check-macos`. +(including standalone and fuzz support workspaces), lockfiles, and existing GitHub Action pins. +It runs on local macOS and has no dependency publish-age filter. +Inspect its changes before committing. + +Run `scripts/tooling/.sh` on the native Ubuntu version pinned in [the catalog](../.config/tooling.toml). +Platforms are `aarch64-linux`, `x86_64-linux`, `riscv64-linux`, `s390x-linux`, and `powerpc64le-linux`. +The installers use sudo when needed. +Windows uses the corresponding `aarch64-win.ps1` or `x86_64-win.ps1` in an elevated PowerShell session. +Local macOS tools remain locally managed; `scripts/tooling/aarch64-macos.sh` can provision the pinned prerequisites for `just check-macos`. CI calls these same installers with `--ci` on Linux or `-Ci` on Windows. -The catalog's `ci` section selects the Cargo tools needed by `just ci-check`, -`just test --all --release`, and `just test --all --release --portable`. Both -test commands include doctests. Only Linux x86-64 adds the `ci-policy` tools and runs `just ci-policy`: -Cargo Deny checks the full target graph in `deny.toml`, and Cargo Audit checks -the lockfile. Every host retains native and portable Clippy, independent-workspace -linting, documentation, and runtime tests; RISC-V performs its compilation checks -on the cross-build host and executes the resulting tests on native hardware. Linux CI omits OpenSSL development -packages, pkgconf, and recommended APT packages; CMake, Clang/libclang, Perl, -and the C/C++ build tools remain prerequisites for native test dependencies. -This mode omits Cargo Rail because `--all` bypasses affected-work -planning; use the full installer for ordinary `just test` and benchmark work. -Linux CI uses the catalog's `linux-ci` Ubuntu release and packages from the -same archive snapshot as development provisioning. It uses Cargo Binstall on -x86-64, ARM64, and RISC-V to select compatible binaries, falling back to source -when unavailable. IBM Z and POWER build Cargo tools from source. -CI does not install optional profiling, mutation, or live-fuzzing tools or alter -shell startup files. These jobs validate CI provisioning, not the full optional -development toolset. - -After Linux installation, source -`$HOME/.local/share/rscrypto-tooling/environment.sh` in each new CI step. -Windows CI runs installation and validation in one PowerShell step to retain -the MSVC/SDK environment. Windows x86-64 installs catalog-pinned NASM for native -dependency assembly in both modes. - -All full profiles install the prerequisites for `just ci-check`, `just test`, and -Criterion `just bench`. RISC-V, Z, and POWER use snapshot-pinned native -CMake/Clang. They do not install cross targets, Miri, browsers, or profiling -tools. The shared selector in -`lib/toolchain.py` uses `.config/toolchains.toml` to choose the pinned nightly -for POWER, IBM Z, and RISC-V; other hosts use `rust-toolchain.toml`. +The catalog's `ci` section selects the Cargo tools needed by `just ci-check`, `just test --all --release`, and `just test --all --release --portable`. +Both test commands include doctests. +Only Linux x86-64 adds the `ci-policy` tools and runs `just ci-policy`: Cargo Deny checks the full target graph in `deny.toml`, +and Cargo Audit checks the lockfile. +Every host retains native and portable Clippy, independent-workspace linting, documentation, +and runtime tests; RISC-V performs its compilation checks on the cross-build host +and executes the resulting tests on native hardware. +Linux CI omits OpenSSL development packages, pkgconf, and recommended APT packages; CMake, +Clang/libclang, Perl, and the C/C++ build tools remain prerequisites for native test dependencies. +This mode omits Cargo Rail because `--all` bypasses affected-work planning; +use the full installer for ordinary `just test` and benchmark work. +Linux CI uses the catalog's `linux-ci` Ubuntu release and packages from the same archive snapshot +as development provisioning. +It uses Cargo Binstall on x86-64, ARM64, and RISC-V to select compatible binaries, +falling back to source when unavailable. +IBM Z and POWER build Cargo tools from source. +CI does not install optional profiling, mutation, +or live-fuzzing tools or alter shell startup files. +These jobs validate CI provisioning, not the full optional development toolset. + +After Linux installation, source `$HOME/.local/share/rscrypto-tooling/environment.sh` in each new CI step. +Windows CI runs installation and validation in one PowerShell step to retain the MSVC/SDK +environment. +Windows x86-64 installs catalog-pinned NASM for native dependency assembly in both modes. + +All full profiles install the prerequisites for `just ci-check`, `just test`, and Criterion `just bench`. +RISC-V, Z, and POWER use snapshot-pinned native CMake/Clang. +They do not install cross targets, Miri, browsers, or profiling tools. +The shared selector in `lib/toolchain.py` uses `.config/toolchains.toml` to choose the pinned nightly for POWER, IBM Z, and RISC-V; +other hosts use `rust-toolchain.toml`. Installers provision stable tooling plus the selected native toolchain. -Build, native check, test, and benchmark entry points use that selection rather -than an ambient `RUSTUP_TOOLCHAIN`; formatting uses the stable development pin. +Build, native check, test, and benchmark entry points use that selection rather than an ambient `RUSTUP_TOOLCHAIN`; +formatting uses the stable development pin. Specialized Miri and fuzz checks retain their opt-in nightly recipes. -`ci.yml` also runs `--ci-package` provisioning and `just ci-package` on an -independent runner. This executes examples, verifies the publishable Cargo -archive, and runs external std/core/alloc consumers against the unpacked crate -on stable and MSRV. Core and alloc also compile on the existing Thumb sentinel. +`ci.yml` also runs `--ci-package` provisioning and `just ci-package` on an independent runner. +This executes examples, verifies the publishable Cargo archive, +and runs external std/core/alloc consumers against the unpacked crate on stable and MSRV. +Core and alloc also compile on the existing Thumb sentinel. No package is published. -`fuzz.yml` uses `--ci-fuzz` for committed ASan corpus replay and bounded live -fuzzing. Manual runs select x86-64, ARM64, or both, exact target names, and a -per-target duration. PR campaigns use 60 seconds per target; manual campaigns -default to 120 seconds. Both have a 30-minute planned live-fuzzing budget per -architecture, accounting for eight concurrent targets. Selection must fit its -budget before replay starts. Manual fuzz jobs have a 90-minute limit including -installation, builds, corpus replay, and live fuzzing; PR and Miri jobs retain -their 60-minute limit. +`fuzz.yml` uses `--ci-fuzz` for committed ASan corpus replay and bounded live fuzzing. +Manual runs select x86-64, ARM64, or both, exact target names, and a per-target duration. +PR campaigns use 60 seconds per target; manual campaigns default to 120 seconds. +Both have a 30-minute planned live-fuzzing budget per architecture, +accounting for eight concurrent targets. +Selection must fit its budget before replay starts. +Manual fuzz jobs have a 90-minute limit including installation, builds, corpus replay, +and live fuzzing; PR and Miri jobs retain their 60-minute limit. `--ci-miri` installs the pinned interpreter for an independent focused Miri row, -including RSA's unsafe-boundary tests. All rows share fail-fast cancellation. - -`ct.yml` always runs full CT evidence, only through manual dispatch or a reusable -workflow call. It does not run on pull requests or pushes. Manual runs select -one, many, or all six native platforms, defaulting to all. The release -workflow calls it for all platforms and requires success on the same candidate -before publishing. Linux uses `--ci-ct-full` and -Windows uses `-CiCt`. The Linux installer additionally installs the pinned BINSEC, -Bitwuzla and decoder on GNU Linux x86-64/ARM64. Proof dependencies use a fixed -opam repository revision from `.config/tooling.toml`. Unsupported proof targets -retain their explicit `ct.toml` policies. No solver is installed there. - -CT architectures run concurrently on fixed AWS instances or donated native -runners. Each host completes builds and proofs before serial timing cases. -`just ct-full` uses manifest-required cases and budgets without filtering. RSA -timing lives in this single harness, including entropy-backed signing; its -consolidated operation cases retain 2000 observations per class and a threshold -of 8. Proof failures stop timing; required timing failures stop later cases. +including RSA's unsafe-boundary tests. +All rows share fail-fast cancellation. + +`ct.yml` always runs full CT evidence, only through manual dispatch or a reusable workflow call. +It does not run on pull requests or pushes. +Manual runs select one, many, or all six native platforms, defaulting to all. +The release workflow calls it for all platforms and requires success on the same candidate +before publishing. +Linux uses `--ci-ct-full` and Windows uses `-CiCt`. +The Linux installer additionally installs the pinned BINSEC, +Bitwuzla and decoder on GNU Linux x86-64/ARM64. +Proof dependencies use a fixed opam repository revision from `.config/tooling.toml`. +Unsupported proof targets retain their explicit `ct.toml` policies. +No solver is installed there. + +CT architectures run concurrently on fixed AWS instances or donated native runners. +Each host completes builds and proofs before serial timing cases. +`just ct-full` uses manifest-required cases and budgets without filtering. +RSA timing lives in this single harness, including entropy-backed signing; +its consolidated operation cases retain 2000 observations per class and a threshold of 8. +Proof failures stop timing; required timing failures stop later cases. Local `just ct-dudect --smoke` remains a diagnostic shortcut outside this workflow. Full CT evidence does not establish the complete secret-lifecycle claim by itself. -The fuzz and CT workflows retain final evidence for seven days and run without -caches. CT preparation archives are retained for two days. CT and benchmark -selection jobs validate requests and emit only the requested runner rows; they -do not install Rust, build code, or invoke Cargo Rail. +The fuzz and CT workflows retain final evidence for seven days and run without caches. +CT preparation archives are retained for two days. +CT and benchmark selection jobs validate requests and emit only the requested runner rows; +they do not install Rust, build code, or invoke Cargo Rail. -`bench.yml` is manual-only. It selects one, many, or all six native CI platforms -and catalog algorithms, groups, or benchmark targets, with optional case filters. -A small planner starts only the selected runners. The existing benchmark runner -owns measurement and evidence. `--ci-bench` (Linux) and `-CiBench` (Windows) -install native benchmark build prerequisites and Just without test, profiling, -or cross-target tools. See [Benchmarking](../docs/benchmarking.md#run-a-manual-workflow). +`bench.yml` is manual-only. +It selects one, many, or all six native CI platforms and catalog algorithms, groups, +or benchmark targets, with optional case filters. +A small planner starts only the selected runners. +The existing benchmark runner owns measurement and evidence. +`--ci-bench` (Linux) and `-CiBench` (Windows) install native benchmark build prerequisites and Just without test, +profiling, or cross-target tools. +See [Benchmarking](../docs/benchmarking.md#run-a-manual-workflow). Only x86-64 and ARM64 Linux install perf, Valgrind, Gungraun, and samply. Their installer enables perf events and requires perf for the running kernel. -Use `just bench-structural` for Gungraun and `just profile` for samply; -Criterion benchmarks remain available on every native platform. Provisioning -checks tools, but native test, benchmark, and profiling execution must still -be verified on each machine. +Use `just bench-structural` for Gungraun and `just profile` for samply; Criterion benchmarks remain available on every native platform. +Provisioning checks tools, but native test, benchmark, +and profiling execution must still be verified on each machine. ### CI compatibility -The compatibility matrix row starts alongside every native row and participates -in the same fail-fast policy. `x86_64-linux.sh --ci-compat` installs only the -catalog-selected compatibility tools, Rust versions, and cross-target libraries. -`just ci-compat` uses bounded workers with separate build directories and a -shared CPU budget. A failed command terminates running siblings and prevents -queued work from starting. Logs remain under `target/compat/`. - -Compatibility checks cover each standalone Cargo feature on the development -compiler and the declared minimum Rust version, broad native/portable feature -sets, and allocation-free and allocation-enabled Thumb sentinels. Every supported -bare-metal target also receives a release library build. Bare-metal evidence is -compile-only; it is not device execution. - -Bare WASM and WASI both compile and execute the existing runtime vector harness -in Wasmtime, with scalar and SIMD artifacts tested separately. The scalar module -must load with SIMD disabled. Bare WASM calls an explicit argument-free export; -WASI uses its command entry point. These are Wasmtime results, not browser-engine -results. The library also receives broad feature builds for both WASM targets. - -The x86-64 and ARM64 Linux rows install native musl build prerequisites and run -`just test-musl`: the complete native and portable test suites plus doctests, -compiled and executed for the matching musl target. Apple ARM64 checks and tests execute locally through -`just check-macos` before commits; Windows ARM64 execution remains deferred. No -compatibility lane enables persistent caches. +The compatibility matrix row starts alongside every native row +and participates in the same fail-fast policy. +`x86_64-linux.sh --ci-compat` installs only the catalog-selected compatibility tools, Rust versions, and cross-target libraries. +`just ci-compat` uses bounded workers with separate build directories and a shared CPU budget. +A failed command terminates running siblings and prevents queued work from starting. +Logs remain under `target/compat/`. + +Compatibility checks cover each standalone Cargo feature on the development compiler +and the declared minimum Rust version, broad native/portable feature sets, +and allocation-free and allocation-enabled Thumb sentinels. +Every supported bare-metal target also receives a release library build. +Bare-metal evidence is compile-only; it is not device execution. + +Bare WASM and WASI both compile and execute the existing runtime vector harness in Wasmtime, +with scalar and SIMD artifacts tested separately. +The scalar module must load with SIMD disabled. +Bare WASM calls an explicit argument-free export; WASI uses its command entry point. +These are Wasmtime results, not browser-engine results. +The library also receives broad feature builds for both WASM targets. + +The x86-64 and ARM64 Linux rows install native musl build prerequisites and run `just test-musl`: +the complete native and portable test suites plus doctests and separate internal evidence suites, +compiled and executed for the matching musl target. +Apple ARM64 checks and tests execute locally through `just check-macos` before commits; +Windows ARM64 execution remains deferred. +No compatibility lane enables persistent caches. + +## Internal evidence builds + +`scripts/ct/internal.py` enables `--cfg rscrypto_internal` for repository evidence builds. +CT artifact generation, BINSEC, DudeCT, their self-tests, and their independent-workspace lint checks +use it automatically. Diagnostic benchmarks, profiles, and code inspection use the same flag resolver. +The RSA assembly gates use it for their public-operation candidate tests. +The resolver preserves target compiler flags and passes encoded arguments to Cargo. Build provenance +records the effective flags; normal builds, docs, tests, and published Cargo feature combinations do not opt in. + +`just test-evidence` runs the production library and evidence integration tests with native and portable +dispatch. It retains forced-kernel, component, and PBKDF2 verification regressions after their hooks leave +the public API. `just ct-test` and native qualification include this recipe. Cross-test archives carry separate +native and portable internal suites and require all four suites at execution. Run it alongside ordinary tests +when changing evidence hooks; ordinary tests continue to check the application build without internal access. + +Use `just bench --diag` or `just profile --diag` for diagnostic workloads. +The `aead-diag` selector and `--bench aead_kernels` enable their required internal hooks automatically. +Cross-prepared diagnostic benchmarks resolve flags for the destination target before recording build provenance. +Keep internal builds separate from public-surface checks. Use `just ct-binsec` for binary proofs and +`just ct-dudect` for timing evidence. ## Release orchestration -`.github/workflows/release.yml` calls CI, CT, and fuzz qualification before its -publication job. `scripts/release/release.py` validates the candidate, reconciles -registry checksums on retries, and creates the source tag and GitHub Release. -Its failure/recovery tests run through `just test-scripts`. Maintainer setup, -preparation, deployment, and retry instructions live in -[CONTRIBUTING.md](../CONTRIBUTING.md#release). - -`scripts/check/macos.sh` owns `just check-macos`, which replaces hosted macOS -checks and tests with local Apple Silicon validation. Install `.githooks` with -`just install-hooks` in each maintainer checkout. macOS remains a supported -release target; physical Apple Silicon timing qualification remains a separate -local pre-submit requirement. +`.github/workflows/release.yml` calls CI, CT, and fuzz qualification before its publication job. +`scripts/release/release.py` validates the candidate, reconciles registry checksums on retries, +and creates the source tag and GitHub Release. +Its failure/recovery tests run through `just test-scripts`. +Maintainer setup, preparation, deployment, and retry instructions live in [CONTRIBUTING.md](../CONTRIBUTING.md#release). + +`scripts/check/macos.sh` owns `just check-macos`, which replaces hosted macOS checks and tests with local Apple Silicon validation. +Install `.githooks` with `just install-hooks` in each maintainer checkout. macOS remains a supported release target; +physical Apple Silicon timing qualification remains a separate local pre-submit requirement. diff --git a/scripts/bench/execution.py b/scripts/bench/execution.py index 22c22bfa..71716153 100644 --- a/scripts/bench/execution.py +++ b/scripts/bench/execution.py @@ -15,7 +15,7 @@ import tomllib sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "lib")) -from toolchain import select_host +from toolchain import select_host, host from evidence import collect @@ -78,6 +78,15 @@ def build_environment() -> None: os.environ["RUSTFLAGS"] = "-C target-cpu=native" +def enable_internal(rows: list[dict], target: str | None = None) -> None: + """Resolve internal flags before both compilation and provenance collection.""" + if any("diag" in row["features"] for row in rows): + sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "ct")) + from internal import build_environment as internal_environment + environment, _ = internal_environment(target or host()) + os.environ["CARGO_ENCODED_RUSTFLAGS"] = environment["CARGO_ENCODED_RUSTFLAGS"] + + def build_command(binary: str, features: list[str]) -> list[str]: return ["cargo", "bench", "--locked", "--profile", "bench", "--features", ",".join(sorted(set(features))), "--no-default-features", "--bench", binary] diff --git a/scripts/bench/run_test.py b/scripts/bench/run_test.py index fa198b44..8e439ace 100644 --- a/scripts/bench/run_test.py +++ b/scripts/bench/run_test.py @@ -33,6 +33,9 @@ if os.environ.get('STALL_BUILD'): import time; time.sleep(30) features = sys.argv[sys.argv.index('--features') + 1] +flags = os.environ.get('CARGO_ENCODED_RUSTFLAGS', '').split('\x1f') +if 'diag' in features.split(','): assert 'rscrypto_internal' in flags +with open('build-flags.jsonl', 'a') as log: log.write(json.dumps(flags) + '\n') for index, value in enumerate(sys.argv): if value != '--bench': continue binary = sys.argv[index + 1] @@ -115,6 +118,9 @@ def setUp(self): self.addCleanup(temporary.cleanup) self.root = Path(temporary.name) shutil.copytree(ROOT / "scripts/bench", self.root / "scripts/bench", ignore=shutil.ignore_patterns("__pycache__")) + (self.root / "scripts/ct").mkdir() + for name in ("internal.py", "provenance.py"): + shutil.copy2(ROOT / "scripts/ct" / name, self.root / "scripts/ct" / name) (self.root / "scripts/lib").mkdir() for name in ("python.sh", "toolchain.py"): shutil.copy2(ROOT / "scripts/lib" / name, self.root / "scripts/lib" / name) diff --git a/scripts/bench/runner.py b/scripts/bench/runner.py index 67bdfc36..892f6861 100644 --- a/scripts/bench/runner.py +++ b/scripts/bench/runner.py @@ -18,7 +18,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent)) from benchmark_catalog import case_class, load_catalog, resolve_selector -from execution import build, build_command, build_environment, build_identity, digest, discover, match_cases, exit_code, identity, write_json, source_evidence +from execution import enable_internal, build, build_command, build_environment, build_identity, digest, discover, match_cases, exit_code, identity, write_json, source_evidence from measure import measure, verify import settings @@ -202,6 +202,8 @@ def bench(args, catalog) -> None: from transfer import prepare prepare(ROOT, args.target, args.prepare_archive.resolve(), Path(args.output_dir).resolve(), rows, effective) return + if not args.run_archive: + enable_internal(rows) if args.list: with tempfile.TemporaryDirectory(prefix="rscrypto-list-") as directory: root = Path(directory) @@ -268,9 +270,12 @@ def main() -> int: bench(args, catalog) elif args.mode == "profile": from profile import profile - profile(args, target(catalog, args.target, args.diag)) + entry = target(catalog, args.target, args.diag) + enable_internal([entry]) + profile(args, entry) else: entry = target(catalog, args.target, args.diag) + enable_internal([entry]) command = ["cargo", "asm" if args.mode == "codegen" else "llvm-lines", "--locked", "--lib", "--profile", "bench", "--no-default-features", "--features", ",".join(entry["features"])] extra = args.args[1:] if args.args[:1] == ["--"] else args.args diff --git a/scripts/bench/transfer.py b/scripts/bench/transfer.py index 44098c8b..f16d0c33 100644 --- a/scripts/bench/transfer.py +++ b/scripts/bench/transfer.py @@ -8,7 +8,7 @@ import subprocess import tempfile -from execution import build, build_command, build_identity, digest, exit_code, hardware +from execution import enable_internal, build, build_command, build_identity, digest, exit_code, hardware from evidence import collect from cross_build import TARGETS, environment, require_host, verify_elf import evidence_bundle as bundle @@ -22,6 +22,7 @@ def prepare(root, target, archive, output, rows, settings): if archive.exists(): raise ValueError(f'refusing to overwrite benchmark evidence: {archive}') os.environ.update(environment(target)) + enable_internal(rows, target) source = bundle.source_identity(root) parent = output / 'preparation' / target parent.mkdir(parents=True, exist_ok=True) diff --git a/scripts/check/check_runner_test.py b/scripts/check/check_runner_test.py index be4e1e42..6b069554 100755 --- a/scripts/check/check_runner_test.py +++ b/scripts/check/check_runner_test.py @@ -45,6 +45,9 @@ def check_vendored_packages(source): ]})) else: Path(os.environ['CHECK_LOG']).write_text(json.dumps(args)) + if os.environ.get('CHECK_FLAG_LOG'): + with Path(os.environ['CHECK_FLAG_LOG']).open('a') as log: + log.write(json.dumps([manifest.parent.name, os.environ.get('CARGO_ENCODED_RUSTFLAGS')]) + '\\n') if manifest.parent.name == os.environ.get('CHECK_FAIL_WORKSPACE'): sys.exit(7) ''') @@ -78,6 +81,38 @@ def check_vendored_packages(source): assert Path(command[command.index('--manifest-path') + 1]).resolve() == (workspace / 'Cargo.toml').resolve(), command assert 'Linting independent workspace: tools/later/' not in result.stdout + # Only the CT workspaces expose internal hooks. Ambient flags + # must survive that opt-in without leaking it to the next workspace. + workspace.rename(root / 'tools/ct-binsec-harness') + (root / 'tools/ct-dudect').mkdir() + (root / 'tools/ct-dudect/Cargo.toml').write_text('[workspace]\n') + (root / 'tools/ct-harness').mkdir() + (root / 'tools/ct-harness/Cargo.toml').write_text('[workspace]\n') + for name in ('scripts/ct/internal.py', 'scripts/ct/provenance.py', 'scripts/lib/python.sh'): + path = root / name + path.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(source / name, path) + flag_log = root / 'flags.jsonl' + for failure, expected_status in (('', 0), ('ct-binsec-harness', 7), ('ct-dudect', 7), ('ct-harness', 7)): + flag_log.write_text('') + result = subprocess.run(['bash', str(script)], cwd=root, capture_output=True, text=True, timeout=30, + env={**environment, 'PATH': f'{binary}:{os.environ["PATH"]}', + 'CHECK_LOG': str(log), 'METADATA_PREFIX': '/repo/tools/harness/', + 'REAL_JQ': shutil.which('jq'), 'METADATA_SEPARATOR': '/', + 'CARGO_BUILD_TARGET': 'x86_64-unknown-linux-gnu', + 'CARGO_ENCODED_RUSTFLAGS': '-C\x1ftarget-cpu=generic', + 'CHECK_FLAG_LOG': str(flag_log), 'CHECK_FAIL_WORKSPACE': failure}) + assert result.returncode == expected_status, result.stderr + flags = [json.loads(line) for line in flag_log.read_text().splitlines()] + expected = [['ct-binsec-harness', '-C\x1ftarget-cpu=generic\x1f--cfg\x1frscrypto_internal']] + if failure != 'ct-binsec-harness': + expected.append(['ct-dudect', '-C\x1ftarget-cpu=generic\x1f--cfg\x1frscrypto_internal']) + if failure not in ('ct-binsec-harness', 'ct-dudect'): + expected.append(['ct-harness', '-C\x1ftarget-cpu=generic\x1f--cfg\x1frscrypto_internal']) + if not failure: + expected.append(['later', '-C\x1ftarget-cpu=generic']) + assert flags == expected, flags + def main(): source = Path(__file__).resolve().parents[2] diff --git a/scripts/check/compat_test.py b/scripts/check/compat_test.py index 3e097f93..64f10ce1 100644 --- a/scripts/check/compat_test.py +++ b/scripts/check/compat_test.py @@ -28,23 +28,31 @@ def test_musl_runs_both_profiles_and_preserves_failures(self): selector.chmod(0o755) runner = root / 'scripts/test/test.sh' runner.write_text('#!' + sys.executable + '\n' + """import json,os,sys +from pathlib import Path with open(os.environ['LOG'], 'a') as log: log.write(json.dumps([sys.argv[1:], os.environ['CARGO_BUILD_TARGET'], os.environ['CC_' + os.environ['CARGO_BUILD_TARGET'].replace('-', '_')], os.environ['CARGO_TARGET_' + os.environ['CARGO_BUILD_TARGET'].replace('-', '_').upper() + '_LINKER']]) + '\\n') -sys.exit(int(os.environ['FAIL'])) +count = len(Path(os.environ['LOG']).read_text().splitlines()) +sys.exit(7 if count == int(os.environ['FAIL_AT']) else 0) """) runner.chmod(0o755) + binary = root / 'bin' + binary.mkdir() + (binary / 'just').symlink_to(runner) + environment = {key: value for key, value in os.environ.items() if key not in ('BASH_ENV', 'ENV')} + environment['PATH'] = str(binary) + os.pathsep + os.environ['PATH'] for arch in ('x86_64', 'aarch64'): - for fail in (0, 7): + for fail_at in (0, 1, 2, 3): log = root / 'log' log.write_text('') result = subprocess.run([shutil.which('bash'), 'scripts/test/test-musl.sh'], cwd=root, - env={**os.environ, 'FIXTURE_HOST': arch + '-unknown-linux-gnu', - 'LOG': str(log), 'FAIL': str(fail)}, capture_output=True, text=True) - self.assertEqual(result.returncode, fail, result.stderr) + env={**environment, 'FIXTURE_HOST': arch + '-unknown-linux-gnu', + 'LOG': str(log), 'FAIL_AT': str(fail_at)}, capture_output=True, text=True) + self.assertEqual(result.returncode, 7 if fail_at else 0, result.stderr) rows = [json.loads(line) for line in log.read_text().splitlines()] - profiles = [['--all', '--release']] if fail else [['--all', '--release'], ['--all', '--release', '--portable']] + profiles = [['--all', '--release'], ['--all', '--release', '--portable'], ['test-evidence']] + profiles = profiles[:fail_at or len(profiles)] self.assertEqual(rows, [[profile, arch + '-unknown-linux-musl', 'musl-gcc', 'musl-gcc'] for profile in profiles]) diff --git a/scripts/check/lint-independent-workspaces.sh b/scripts/check/lint-independent-workspaces.sh index 5301c406..89110004 100755 --- a/scripts/check/lint-independent-workspaces.sh +++ b/scripts/check/lint-independent-workspaces.sh @@ -91,6 +91,7 @@ check_cfg_flags=( '--check-cfg=cfg(miri)' '--check-cfg=cfg(fuzzing)' '--check-cfg=cfg(rscrypto_internal_fuzzing)' + '--check-cfg=cfg(rscrypto_internal)' '--check-cfg=cfg(target_feature,values("movdiri","movdir64b","serialize"))' ) @@ -124,5 +125,11 @@ for manifest in "${manifests[@]}"; do compiler_flags+=(--cap-lints "$LINT_CAP") fi - CARGO_TARGET_DIR="$TARGET_DIR" cargo "${cargo_args[@]}" -- "${compiler_flags[@]}" + if [[ "$relative_manifest" == tools/ct-binsec-harness/Cargo.toml || "$relative_manifest" == tools/ct-dudect/Cargo.toml || "$relative_manifest" == tools/ct-harness/Cargo.toml ]]; then + target="${CARGO_BUILD_TARGET:-$("$REPO_ROOT/scripts/lib/toolchain.sh" --print-host)}" + CARGO_TARGET_DIR="$TARGET_DIR" "$REPO_ROOT/scripts/lib/python.sh" "$REPO_ROOT/scripts/ct/internal.py" \ + --target "$target" -- cargo "${cargo_args[@]}" -- "${compiler_flags[@]}" + else + CARGO_TARGET_DIR="$TARGET_DIR" cargo "${cargo_args[@]}" -- "${compiler_flags[@]}" + fi done diff --git a/scripts/check/macos.sh b/scripts/check/macos.sh index cf69b978..3f3e5b6d 100755 --- a/scripts/check/macos.sh +++ b/scripts/check/macos.sh @@ -12,4 +12,5 @@ cd "$(dirname "${BASH_SOURCE[0]}")/../.." just ci-check just test --all --release just test --all --release --portable +just test-evidence just test-rsa-macos-asm diff --git a/scripts/check/macos_test.py b/scripts/check/macos_test.py index 06ef4ede..6dc002f5 100644 --- a/scripts/check/macos_test.py +++ b/scripts/check/macos_test.py @@ -31,7 +31,7 @@ def test_gate_preserves_ci_modes_and_stops_on_failure(self): self.assertEqual(subprocess.run([str(script)], env=env).returncode, 0) self.assertEqual((root / 'calls').read_text().splitlines(), [ 'ci-check', 'test --all --release', 'test --all --release --portable', - 'test-rsa-macos-asm']) + 'test-evidence', 'test-rsa-macos-asm']) (root / 'calls').unlink() result = subprocess.run([str(script)], env={**env, 'FAIL_COMMAND': 'test --all --release'}) self.assertNotEqual(result.returncode, 0) diff --git a/scripts/ct/artifacts.sh b/scripts/ct/artifacts.sh index aaf25e26..9b566656 100755 --- a/scripts/ct/artifacts.sh +++ b/scripts/ct/artifacts.sh @@ -111,6 +111,9 @@ done PYTHON="$("$ROOT/scripts/lib/python.sh" --print)" +CARGO_ENCODED_RUSTFLAGS="$("$PYTHON" "$ROOT/scripts/ct/internal.py" --target "$TARGET" --print-encoded-rustflags)" +export CARGO_ENCODED_RUSTFLAGS + OUT_DIR="$ROOT/target/ct/$TARGET/$PROFILE" ARTIFACT_DIR="$OUT_DIR/artifacts" BUILD_TARGET_DIR="$ROOT/target/ct-build/$TARGET/$PROFILE" diff --git a/scripts/ct/binsec.py b/scripts/ct/binsec.py index 3a1aadda..1e0e446a 100755 --- a/scripts/ct/binsec.py +++ b/scripts/ct/binsec.py @@ -17,6 +17,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent)) from provenance import sha256_file +from internal import build_environment from manifest import binsec_kernel_targets, target_record ROOT = Path(__file__).resolve().parents[2] @@ -225,15 +226,10 @@ def build_harness(target: str, profile: str, rustflags: list[str]) -> tuple[Path target, "--release", ] - env = os.environ.copy() + proof_flags = list(rustflags) if rustflags else default_target_rustflags(target) + proof_flags.extend(binsec_proof_rustflags(target)) + env, effective_rustflags = build_environment(target, proof_flags) configure_cross_linker(env, target) - effective_rustflags = list(rustflags) - if not rustflags: - effective_rustflags.extend(default_target_rustflags(target)) - effective_rustflags.extend(binsec_proof_rustflags(target)) - if effective_rustflags: - existing = env.get("RUSTFLAGS", "") - env["RUSTFLAGS"] = " ".join([existing, *effective_rustflags]).strip() result = subprocess.run(cmd, cwd=ROOT, env=env, text=True, check=False) if result.returncode != 0: raise SystemExit(result.returncode) diff --git a/scripts/ct/ci.py b/scripts/ct/ci.py index 96dfaf3a..ca13147a 100644 --- a/scripts/ct/ci.py +++ b/scripts/ct/ci.py @@ -12,7 +12,7 @@ def main(): if sys.argv[1:] == ['plan']: - matrix = platforms(os.environ['INPUT_ARCHITECTURES'], os.environ['GITHUB_RUN_ID']) + matrix = platforms(os.environ['INPUT_ARCHITECTURES'], os.environ['GITHUB_RUN_ID'], runner_prefix='ct') for row in matrix['include']: row['timeout'] = 360 builds = [] diff --git a/scripts/ct/ci_test.py b/scripts/ct/ci_test.py index e4cddb35..f4e6636f 100644 --- a/scripts/ct/ci_test.py +++ b/scripts/ct/ci_test.py @@ -42,6 +42,16 @@ def test_one_many_all_selection(self): rows = json.loads(values['matrix'])['include'] self.assertEqual(len(rows), count) self.assertTrue(all(row['timeout'] == 360 for row in rows)) + expected_runners = { + 'x86_64-linux': 'runs-on=123/runner=ct-x86_64-linux-intel/env=production', + 'aarch64-linux': 'runs-on=123/runner=ct-aarch64-linux/env=production', + 'x86_64-win': 'runs-on=123/runner=ct-x86_64-win-intel/env=production', + 'riscv64-linux': 'ubuntu-24.04-riscv', + 'powerpc64le-linux': 'ubuntu-24.04-ppc64le-p10', + 's390x-linux': 'ubuntu-24.04-s390x', + } + self.assertEqual([row['runner'] for row in rows], + [expected_runners[row['platform']] for row in rows]) expected = { 'riscv64-linux': 'riscv64gc-unknown-linux-gnu', 'powerpc64le-linux': 'powerpc64le-unknown-linux-gnu', diff --git a/scripts/ct/dudect.sh b/scripts/ct/dudect.sh index 15571942..726edb55 100755 --- a/scripts/ct/dudect.sh +++ b/scripts/ct/dudect.sh @@ -88,6 +88,11 @@ fi export RUSTUP_TOOLCHAIN RUSTUP_TOOLCHAIN="$("$TOOLCHAIN" --target "$TARGET")" +# Build and provenance reporting must observe the same internal compiler flags. +PYTHON="$("$ROOT/scripts/lib/python.sh" --print)" +export CARGO_ENCODED_RUSTFLAGS +CARGO_ENCODED_RUSTFLAGS="$("$PYTHON" "$ROOT/scripts/ct/internal.py" --target "$TARGET" --print-encoded-rustflags)" + HOST_TARGET="$(rustc -vV | awk -F': ' '/^host:/ {print $2}')" target_runs_on_host() { local target="$1" @@ -226,7 +231,6 @@ else "$LLVM_NM" --defined-only --demangle "$BINARY_PATH" > "$BINARY_SYMBOLS_PATH" fi -PYTHON="$("$ROOT/scripts/lib/python.sh" --print)" "$PYTHON" -X utf8 "$ROOT/scripts/ct/dudect_report.py" --prepare \ --out "$OUT_DIR/prepared.json" --target "$TARGET" --profile "$PROFILE" \ --binary "$BINARY_PATH" "${BINARY_OBJECT_ARGS[@]}" \ diff --git a/scripts/ct/internal.py b/scripts/ct/internal.py new file mode 100644 index 00000000..d4c997df --- /dev/null +++ b/scripts/ct/internal.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Enable repository-only evidence hooks for a Cargo invocation.""" + +from __future__ import annotations + +import argparse +import os +from pathlib import Path +import subprocess +import sys + +# Embedded Windows Python omits the script directory. +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +from provenance import resolved_rustflags + +ROOT = Path(__file__).resolve().parents[2] +INTERNAL_CFG = ["--cfg", "rscrypto_internal"] + + +def host_command(command: list[str], platform: str = os.name) -> list[str]: + """Make repository shell wrappers executable by Windows subprocesses.""" + if platform == "nt" and Path(command[0]).suffix == ".sh": + return ["bash", *command] + return command + + +def build_environment(target: str, extra_flags: list[str] | None = None) -> tuple[dict[str, str], list[str]]: + """Preserve resolved target flags and make the actual evidence flags recordable.""" + flags = [*resolved_rustflags(ROOT, target)[2], *(extra_flags or []), *INTERNAL_CFG] + environment = os.environ.copy() + # Encoded arguments preserve spaces and take precedence over ambient flag sources. + environment["CARGO_ENCODED_RUSTFLAGS"] = "\x1f".join(flags) + return environment, flags + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--target", required=True) + parser.add_argument("--print-encoded-rustflags", action="store_true") + parser.add_argument("command", nargs=argparse.REMAINDER) + args = parser.parse_args() + command = args.command[1:] if args.command[:1] == ["--"] else args.command + if args.print_encoded_rustflags: + if command: + parser.error("--print-encoded-rustflags cannot be combined with a command") + environment, _ = build_environment(args.target) + print(environment["CARGO_ENCODED_RUSTFLAGS"], end="") + return 0 + if not command: + parser.error("a Cargo command is required after --") + environment, _ = build_environment(args.target) + return subprocess.run(host_command(command), cwd=ROOT, env=environment, check=False).returncode + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/ct/internal_test.py b/scripts/ct/internal_test.py new file mode 100644 index 00000000..455ed136 --- /dev/null +++ b/scripts/ct/internal_test.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""Verify internal compiler opt-in and BINSEC flag provenance.""" + +import contextlib +import io +import os +from pathlib import Path +import subprocess +import tempfile +import unittest +from unittest.mock import patch + +import binsec +import internal + +TARGET = 'x86_64-unknown-linux-gnu' + + +class InternalBuildTests(unittest.TestCase): + def test_windows_shell_wrappers_run_through_bash(self): + command = ['scripts/lib/python.sh', 'scripts/test/evidence_suite.py', '--case', 'argument with spaces'] + self.assertEqual(internal.host_command(command, 'nt'), ['bash', *command]) + + def test_native_commands_and_posix_shell_wrappers_are_unchanged(self): + native = ['cargo', 'test', '--locked'] + shell_wrapper = ['scripts/lib/toolchain.sh', '--exec', *native] + self.assertIs(internal.host_command(native, 'nt'), native) + self.assertIs(internal.host_command(shell_wrapper, 'posix'), shell_wrapper) + + def test_shell_export_preserves_encoded_arguments(self): + original = '-C\x1flink-arg=path with spaces\x1f--cfg\x1fevidence="gcm"' + output = io.StringIO() + with patch.dict(os.environ, {'CARGO_ENCODED_RUSTFLAGS': original}, clear=True), \ + patch('sys.argv', ['internal.py', '--target', TARGET, '--print-encoded-rustflags']), \ + contextlib.redirect_stdout(output), patch.object(internal.subprocess, 'run') as execute: + self.assertEqual(internal.main(), 0) + self.assertEqual(output.getvalue(), original + '\x1f--cfg\x1frscrypto_internal') + execute.assert_not_called() + + def test_flag_precedence_and_argument_boundaries(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + (root / '.cargo').mkdir() + (root / '.cargo/config.toml').write_text( + '[target.x86_64-unknown-linux-gnu]\nrustflags = ["-C", "target-cpu=x86-64"]\n') + cases = [ + ({}, ['-C', 'target-cpu=x86-64']), + ({'CARGO_ENCODED_RUSTFLAGS': '', 'RUSTFLAGS': '-C target-cpu=native'}, []), + ({'RUSTFLAGS': ''}, []), + ({'CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS': '-C target-feature=+aes'}, + ['-C', 'target-cpu=x86-64', '-C', 'target-feature=+aes']), + ({'CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS': '--cfg evidence="hmac"'}, + ['-C', 'target-cpu=x86-64', '--cfg', 'evidence="hmac"']), + ({'RUSTFLAGS': '--cfg evidence="hmac"'}, ['--cfg', 'evidence="hmac"']), + ({'CARGO_ENCODED_RUSTFLAGS': '-C\x1flink-arg=path with spaces'}, ['-C', 'link-arg=path with spaces']), + ({'CARGO_ENCODED_RUSTFLAGS': '-C\x1f\x1fdebuginfo=1'}, ['-C', '', 'debuginfo=1']), + ({'RUSTFLAGS': '-C target-cpu=native', 'CARGO_ENCODED_RUSTFLAGS': '-C\x1ftarget-cpu=generic'}, + ['-C', 'target-cpu=generic']), + ] + for environment, expected in cases: + with self.subTest(environment=environment), patch.dict(os.environ, environment, clear=True), \ + patch.object(internal, 'ROOT', root): + result, flags = internal.build_environment(TARGET, ['-C', 'relocation-model=static']) + self.assertEqual(flags, [*expected, '-C', 'relocation-model=static', '--cfg', 'rscrypto_internal']) + self.assertEqual(result['CARGO_ENCODED_RUSTFLAGS'].split('\x1f'), flags) + self.assertEqual(dict(os.environ), environment) + (root / '.cargo/config.toml').unlink() + with patch.dict(os.environ, {'CARGO_BUILD_RUSTFLAGS': '--cfg evidence="hmac"'}, clear=True), \ + patch.object(internal, 'ROOT', root): + _, flags = internal.build_environment(TARGET) + self.assertEqual(flags, ['--cfg', 'evidence="hmac"', '--cfg', 'rscrypto_internal']) + + def test_binsec_build_records_the_flags_it_uses(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + binary = root / 'target/ct-binsec-build' / TARGET / 'release' / binsec.HARNESS_BIN + binary.parent.mkdir(parents=True) + binary.touch() + for status in (0, 7): + with self.subTest(status=status), patch.dict(os.environ, {'CARGO_ENCODED_RUSTFLAGS': '-C\x1fdebuginfo=1'}, clear=True), \ + patch.object(binsec, 'ROOT', root), patch.object(internal, 'ROOT', root), \ + patch.object(binsec, 'configure_cross_linker'), \ + patch.object(binsec.subprocess, 'run', return_value=subprocess.CompletedProcess([], status)) as build: + if status: + with self.assertRaises(SystemExit) as failure: + binsec.build_harness(TARGET, 'release', []) + self.assertEqual(failure.exception.code, status) + else: + actual, flags = binsec.build_harness(TARGET, 'release', []) + self.assertEqual(actual, binary) + self.assertEqual(flags, ['-C', 'debuginfo=1', '-C', 'target-cpu=x86-64', '-C', + 'relocation-model=static', '-C', 'link-arg=-no-pie', + '--cfg', 'rscrypto_internal']) + self.assertEqual(build.call_args.kwargs['env']['CARGO_ENCODED_RUSTFLAGS'].split('\x1f'), flags) + + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/ct/provenance.py b/scripts/ct/provenance.py index 3709a72d..15810b28 100755 --- a/scripts/ct/provenance.py +++ b/scripts/ct/provenance.py @@ -80,20 +80,22 @@ def target_rustflags(root: Path, target: str) -> list[str]: def resolved_rustflags(root: Path, target: str) -> tuple[list[str], list[str], list[str], str]: configured = target_rustflags(root, target) - if value := os.environ.get("CARGO_ENCODED_RUSTFLAGS"): - environment = [part for part in value.split("\x1f") if part] + if "CARGO_ENCODED_RUSTFLAGS" in os.environ: + value = os.environ["CARGO_ENCODED_RUSTFLAGS"] + environment = value.split("\x1f") if value else [] return configured, environment, environment, "CARGO_ENCODED_RUSTFLAGS" - if value := os.environ.get("RUSTFLAGS"): - environment = shlex.split(value) + if "RUSTFLAGS" in os.environ: + # Cargo splits on spaces and trims each word; shell quotes remain literal. + environment = [part.strip() for part in os.environ["RUSTFLAGS"].split(" ") if part.strip()] return configured, environment, environment, "RUSTFLAGS" target_key = target_env_key(target, "RUSTFLAGS") if value := os.environ.get(target_key): - environment = shlex.split(value) + environment = value.split() return configured, environment, configured + environment, target_key if configured: return configured, [], configured, ".cargo/config.toml" if value := os.environ.get("CARGO_BUILD_RUSTFLAGS"): - environment = shlex.split(value) + environment = value.split() return configured, environment, environment, "CARGO_BUILD_RUSTFLAGS" return configured, [], [], "none" diff --git a/scripts/ct/smoke_test.py b/scripts/ct/smoke_test.py index 094192fa..3ab689a4 100644 --- a/scripts/ct/smoke_test.py +++ b/scripts/ct/smoke_test.py @@ -17,7 +17,7 @@ def main(): with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) for name in ('scripts/ct/dudect.sh', 'scripts/ct/dudect_execute.py', 'scripts/ct/manifest.py', - 'scripts/ct/provenance.py', 'scripts/lib/python.sh'): + 'scripts/ct/provenance.py', 'scripts/ct/internal.py', 'scripts/lib/python.sh'): path = root / name path.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(ROOT / name, path) @@ -31,6 +31,7 @@ def tool(path, body): tool(binary / 'llvm', "print('fixture symbols')") tool(binary / 'cargo', ''' args = sys.argv +Path('build-flags.json').write_text(json.dumps(os.environ['CARGO_ENCODED_RUSTFLAGS'].split('\\x1f'))) build = Path(args[args.index('--target-dir') + 1]) / args[args.index('--target') + 1] / 'release' build.mkdir(parents=True, exist_ok=True) path = build / 'rscrypto-ct-dudect' @@ -40,7 +41,7 @@ def tool(path, body): # Reporting and the timing binary are substitutes; selection, budgets, shell # precedence, isolation, and summary publication execute production code. (root / 'scripts/ct/dudect_report.py').write_text(''' -import json, sys +import json, os, sys from pathlib import Path def write_report(path, report): @@ -53,6 +54,7 @@ def case_report(prepared, args): 'case_count': 1, 'failure_count': 0, 'diagnostic_failure_count': 0} if __name__ == '__main__': + Path('report-flags.json').write_text(json.dumps(os.environ['CARGO_ENCODED_RUSTFLAGS'].split('\\x1f'))) path = Path(sys.argv[sys.argv.index('--out') + 1]) path.write_text(json.dumps({'metadata': {'binary': {'path': sys.executable}}, 'manifest_cases': { 'cheap': {'smoke_samples': 2000}, 'expensive': {'smoke_samples': 16}}})) @@ -61,7 +63,9 @@ def case_report(prepared, args): env = {key: value for key, value in os.environ.items() if key not in ('BASH_ENV', 'ENV') and not key.startswith('RSCRYPTO_CT_DUDECT_')} env.update(PATH=str(binary) + os.pathsep + os.environ['PATH'], PYTHON=sys.executable, - LLVM_OBJDUMP=str(binary / 'llvm'), LLVM_NM=str(binary / 'llvm')) + LLVM_OBJDUMP=str(binary / 'llvm'), LLVM_NM=str(binary / 'llvm'), + CARGO_ENCODED_RUSTFLAGS='-C\x1flink-arg=path with spaces') + expected_flags = ['-C', 'link-arg=path with spaces', '--cfg', 'rscrypto_internal'] for args, override, expected in ( (['--smoke'], {}, [['cheap', 2000], ['expensive', 16]]), (['--smoke', '--filter', 'expensive'], {}, [['expensive', 16]]), @@ -74,6 +78,8 @@ def case_report(prepared, args): result = subprocess.run(['bash', 'scripts/ct/dudect.sh', '--target', 'fixture-host', *args], cwd=root, env={**env, **override}, capture_output=True, text=True, timeout=20) assert result.returncode == 0, result.stdout + result.stderr + assert json.loads((root / 'build-flags.json').read_text()) == expected_flags + assert json.loads((root / 'report-flags.json').read_text()) == expected_flags actual = [json.loads(line) for line in (root / 'budgets.jsonl').read_text().splitlines()] assert actual == expected, (args, actual) if '--smoke' in args: @@ -86,6 +92,8 @@ def case_report(prepared, args): assert result.returncode == 2 and 'physical host' in result.stderr, result.stderr result = subprocess.run([*cross, '--prepare-only'], cwd=root, env=env, capture_output=True, text=True, timeout=20) assert result.returncode == 0, result.stdout + result.stderr + assert json.loads((root / 'build-flags.json').read_text()) == expected_flags + assert json.loads((root / 'report-flags.json').read_text()) == expected_flags assert not (root / 'budgets.jsonl').read_text() manifest = tomllib.loads((ROOT / 'ct.toml').read_text()) assert all(isinstance(case['smoke_samples'], int) and case['smoke_samples'] >= 2 for case in manifest['dudect_case']) diff --git a/scripts/ct/test.sh b/scripts/ct/test.sh index fb8cb946..35370230 100755 --- a/scripts/ct/test.sh +++ b/scripts/ct/test.sh @@ -7,8 +7,10 @@ PYTHON="$("$ROOT/scripts/lib/python.sh" --print)" "$PYTHON" "$ROOT/scripts/ct/evidence_validation_test.py" "$PYTHON" "$ROOT/scripts/ct/dudect_pipeline_test.py" "$PYTHON" "$ROOT/scripts/ct/preparation_test.py" +"$PYTHON" "$ROOT/scripts/ct/internal_test.py" "$PYTHON" "$ROOT/scripts/ct/smoke_test.py" "$PYTHON" "$ROOT/scripts/ct/zeroization_test.py" "$PYTHON" "$ROOT/scripts/ct/ci_test.py" "$PYTHON" "$ROOT/scripts/ct/replay_test.py" just --justfile "$ROOT/justfile" test-harnesses +just --justfile "$ROOT/justfile" test-evidence diff --git a/scripts/lib/ci_platforms.py b/scripts/lib/ci_platforms.py index b0fe0324..3020267c 100644 --- a/scripts/lib/ci_platforms.py +++ b/scripts/lib/ci_platforms.py @@ -11,7 +11,7 @@ } -def platforms(value: str, run_id: str) -> dict: +def platforms(value: str, run_id: str, *, runner_prefix: str = 'measure') -> dict: names = list(PLATFORMS) if value.strip() == 'all' else list(dict.fromkeys(value.replace(',', ' ').split())) if not names or any(name not in PLATFORMS for name in names): raise ValueError('architectures must be all or a list of: ' + ', '.join(PLATFORMS)) @@ -20,7 +20,7 @@ def platforms(value: str, run_id: str) -> dict: rows = [] for name in names: runner, timeout = PLATFORMS[name] - label = (f'runs-on={run_id}/runner={runner}/env=production' + label = (f'runs-on={run_id}/runner={runner_prefix}-{runner.removeprefix("measure-")}/env=production' if runner.startswith('measure-') else runner) rows.append({'platform': name, 'runner': label, 'timeout': timeout}) return {'include': rows} diff --git a/scripts/test/cross.py b/scripts/test/cross.py index a34147f3..9e7a0f5a 100755 --- a/scripts/test/cross.py +++ b/scripts/test/cross.py @@ -16,6 +16,9 @@ import evidence_bundle as bundle from cross_build import TARGETS, environment, require_host import doctest_bundle +from evidence_suite import TARGET_ARGS + +MODES = ("native", "portable", "internal-native", "internal-portable") def features(mode): @@ -23,7 +26,7 @@ def features(mode): selected = sorted(set(graph) - {'portable-only'}) if any('portable-only' in graph[name] for name in selected): raise ValueError('native features indirectly enable portable-only') - return ['--all-features'] if mode == 'portable' else ['--no-default-features', '--features', ','.join(selected)] + return ['--all-features'] if mode.endswith('portable') else ['--no-default-features', '--features', ','.join(selected)] def nextest_identity(report): @@ -65,13 +68,22 @@ def prepare(target, archive): 'checks': 'ci-check-target', 'modes': {}} metadata['linker'] = {'path': str(compiler), 'sha256': bundle.digest(compiler), 'version': subprocess.check_output([str(compiler), '--version'], text=True)} - for mode in ('native', 'portable'): + for mode in MODES: + internal = mode.startswith('internal-') + mode_env = env.copy() + if internal: + flags = subprocess.check_output([sys.executable, '-B', str(ROOT / 'scripts/ct/internal.py'), + '--target', target, '--print-encoded-rustflags'], cwd=ROOT, env=env, text=True) + mode_env['CARGO_ENCODED_RUSTFLAGS'] = flags args = ['--target', target, *features(mode)] command = ['cargo', 'nextest', 'archive', '--locked', '--workspace', '--release', *args, '--archive-file', str(out / f'{mode}.tar.zst')] - subprocess.run(command, cwd=ROOT, env=env, check=True) - plan = doctest_bundle.prepare(ROOT, out / f'{mode}-docs', args, env) - metadata['modes'][mode] = {'command': command, 'doctests': plan['total']} + if internal: + command += TARGET_ARGS + subprocess.run(command, cwd=ROOT, env=mode_env, check=True) + plan = {'total': 0} if internal else doctest_bundle.prepare(ROOT, out / f'{mode}-docs', args, env) + metadata['modes'][mode] = {'command': command, 'doctests': plan['total'], + 'internal': internal, 'encoded_rustflags': mode_env.get('CARGO_ENCODED_RUSTFLAGS')} bundle.seal(ROOT, out, 'rscrypto.cross.tests', target, identity, metadata) bundle.pack(out, archive) @@ -88,21 +100,27 @@ def execute(target, archive): manifest = bundle.verify(ROOT, incoming, 'rscrypto.cross.tests', target) if nextest_identity(manifest['metadata']['nextest']) != nextest_identity(version): raise ValueError('producer and consumer Nextest versions differ') - if set(manifest['metadata']['modes']) != {'native', 'portable'}: - raise ValueError('both dispatch modes are required') + if set(manifest['metadata']['modes']) != set(MODES): + raise ValueError('ordinary and internal suites require both dispatch modes') + for mode, record in manifest['metadata']['modes'].items(): + internal = mode.startswith('internal-') + flags = (record.get('encoded_rustflags') or '').split('\x1f') + if record.get('internal') != internal or ('rscrypto_internal' in flags) != internal: + raise ValueError('test archive internal-build provenance differs from its suite') results = {} - for mode in ('native', 'portable'): + for mode in MODES: with (out / f'{mode}-nextest.log').open('w') as log: subprocess.run(['cargo', 'nextest', 'run', '--archive-file', str(incoming / f'{mode}.tar.zst'), '--workspace-remap', str(ROOT), '--config-file', str(ROOT / '.config/nextest.toml'), '--no-tests', 'fail'], cwd=ROOT, stdout=log, stderr=subprocess.STDOUT, check=True) - results[mode] = doctest_bundle.execute(ROOT, incoming / f'{mode}-docs', out / f'{mode}-docs') + results[mode] = ({'status': 'pass'} if mode.startswith('internal-') else + doctest_bundle.execute(ROOT, incoming / f'{mode}-docs', out / f'{mode}-docs')) # Detect accidental changes to inputs throughout execution as well as before it. bundle.verify(ROOT, incoming, 'rscrypto.cross.tests', target) (out / 'summary.json').write_text(json.dumps({'status': 'pass', 'source': manifest['source'], 'archive_sha256': bundle.digest(archive), 'host': platform.uname()._asdict(), 'nextest': version, 'modes': results}, indent=2) + '\n') - print(f'{target} native/portable suites and doctests passed: {out}', flush=True) + print(f'{target} ordinary/internal suites and doctests passed: {out}', flush=True) def main(): diff --git a/scripts/test/evidence_suite.py b/scripts/test/evidence_suite.py new file mode 100644 index 00000000..f4055604 --- /dev/null +++ b/scripts/test/evidence_suite.py @@ -0,0 +1,20 @@ +"""Shared scope for internal production regressions on native and transferred test lanes.""" + +from pathlib import Path +import subprocess + +TARGET_ARGS = [ + "--lib", "--test", "aead_kernel_equivalence", "--test", "argon2_kernels", + "--test", "chacha20poly1305", "--test", "rsa_public_key", "--test", "pbkdf2_evidence", +] + + +def main(): + root = Path(__file__).resolve().parents[2] + for dispatch in ("--native", "--portable"): + subprocess.run(["just", "--justfile", str(root / "justfile"), "test", "--release", dispatch, + "--", *TARGET_ARGS], cwd=root, check=True) + + +if __name__ == "__main__": + main() diff --git a/scripts/test/test-musl.sh b/scripts/test/test-musl.sh index 5d72f1ce..ee83a6d4 100755 --- a/scripts/test/test-musl.sh +++ b/scripts/test/test-musl.sh @@ -13,3 +13,4 @@ export "CARGO_TARGET_${target_key^^}_LINKER"=musl-gcc export "CC_$target_key"=musl-gcc scripts/test/test.sh --all --release scripts/test/test.sh --all --release --portable +just test-evidence diff --git a/scripts/test/test-rsa-asm.sh b/scripts/test/test-rsa-asm.sh index 8b8c7076..10138462 100755 --- a/scripts/test/test-rsa-asm.sh +++ b/scripts/test/test-rsa-asm.sh @@ -50,7 +50,9 @@ run_gate() { run_named_test "$test_name" cargo "${args[@]}" -- done - build_output=$(cargo test --locked --release --features rsa,diag --test rsa_public_key --no-run --message-format=json) + build_output=$(scripts/lib/python.sh scripts/ct/internal.py \ + --target "$(scripts/lib/toolchain.sh --print-host)" -- \ + cargo test --locked --release --features rsa,diag --test rsa_public_key --no-run --message-format=json) binary=$(jq -ers '[.[] | select(.reason == "compiler-artifact" and .target.name == "rsa_public_key" and .executable != null) | .executable] | if length == 1 then .[0] else error("expected one RSA test executable") end' <<<"$build_output") [[ -n "$binary" && -x "$binary" ]] || fail "could not resolve optimized RSA test binary" diff --git a/scripts/test/test_runner_test.py b/scripts/test/test_runner_test.py index f6a4bcd2..46238814 100644 --- a/scripts/test/test_runner_test.py +++ b/scripts/test/test_runner_test.py @@ -12,6 +12,43 @@ ROOT = Path(__file__).resolve().parents[2] +def check_evidence_recipe(): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + for name in ('justfile', 'scripts/lib/python.sh', 'scripts/ct/internal.py', + 'scripts/ct/provenance.py', 'scripts/test/evidence_suite.py'): + destination = root / name + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(ROOT / name, destination) + binary = root / 'bin' + binary.mkdir() + fake = binary / 'just' + fake.write_text(f'#!{sys.executable}\n' + ''' +import json, os, sys +with open(os.environ['TEST_LOG'], 'a') as log: + print(json.dumps({'args': sys.argv[1:], 'flags': os.environ['CARGO_ENCODED_RUSTFLAGS']}), file=log) +sys.exit(int(os.environ.get('RUN_EXIT', '0'))) +''') + fake.chmod(0o755) + log = root / 'commands.jsonl' + flags = '-C\x1flink-arg=path with spaces' + env = {**{key: value for key, value in os.environ.items() if key not in ('BASH_ENV', 'ENV')}, + 'PATH': str(binary) + os.pathsep + os.environ['PATH'], + 'PYTHON': sys.executable, 'TEST_LOG': str(log), 'CARGO_BUILD_TARGET': 's390x-unknown-linux-gnu', + 'CARGO_ENCODED_RUSTFLAGS': flags} + for status, count in ((0, 2), (17, 1)): + log.write_text('') + result = subprocess.run([shutil.which('just'), '--justfile', str(root / 'justfile'), 'test-evidence'], + cwd=root, env={**env, 'RUN_EXIT': str(status)}, capture_output=True, text=True) + assert (result.returncode == 0) == (status == 0), result.stderr + rows = [json.loads(line) for line in log.read_text().splitlines()] + assert len(rows) == count, rows + assert '--native' in rows[0]['args'] + if status == 0: + assert '--portable' in rows[1]['args'] + assert all(row['flags'] == flags + '\x1f--cfg\x1frscrypto_internal' for row in rows), rows + + def main(): with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) @@ -100,6 +137,7 @@ def run(args, **extra): result, rows = run(['--all']) assert result.returncode == 127 and not rows assert 'cargo-nextest is required' in result.stderr + check_evidence_recipe() print('Test runner regressions passed') diff --git a/scripts/test/transfer_test.py b/scripts/test/transfer_test.py index e95989ab..8a4bb724 100755 --- a/scripts/test/transfer_test.py +++ b/scripts/test/transfer_test.py @@ -240,7 +240,7 @@ def test_prepare_keeps_both_full_release_modes_and_fails_closed(self): case.doCleanups() def check_test_prepare_keeps_both_full_release_modes_and_fails_closed(self, target, machine): - for name in ('Cargo.toml', '.config/tooling.toml'): + for name in ('Cargo.toml', '.config/tooling.toml', 'scripts/ct/internal.py', 'scripts/ct/provenance.py'): destination = self.root / name destination.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(ROOT / name, destination) @@ -257,7 +257,10 @@ def check_test_prepare_keeps_both_full_release_modes_and_fails_closed(self, targ if name=='cargo' and args==['nextest','--version']: print('cargo-nextest '+os.environ['TRANSFER_NEXTEST']) elif name=='cargo': - if os.environ.get('TRANSFER_FAIL'): sys.exit(23) + internal = 'internal-' in args[args.index('--archive-file')+1] + flags = os.environ.get('CARGO_ENCODED_RUSTFLAGS', '').split('\\x1f') + assert ('rscrypto_internal' in flags) == internal + if os.environ.get('TRANSFER_FAIL') in ('1', 'internal' if internal else 'public'): sys.exit(23) Path(args[args.index('--archive-file')+1]).write_bytes(b'archive fixture') elif name=='rustc': print('rustc pinned fixture') elif name.endswith('gcc'): print('gcc pinned fixture') @@ -281,12 +284,23 @@ def docs(root, directory, args, env): calls = [json.loads(row) for row in log.read_text().splitlines()] self.assertIn(['just', 'ci-check-target', target], calls) builds = [row for row in calls if row[:3] == ['cargo', 'nextest', 'archive']] - self.assertEqual(len(builds), 2) + self.assertEqual(len(builds), 4) self.assertTrue(all('--workspace' in row and '--locked' in row and '--release' in row for row in builds)) self.assertTrue(all(row[row.index('--target') + 1] == target for row in builds)) self.assertNotIn('portable-only', builds[0][builds[0].index('--features') + 1].split(',')) self.assertIn('--all-features', builds[1]) self.assertEqual(prepare_docs.call_count, 2) + for row in builds[:2]: + self.assertNotIn('--lib', row) + for row in builds[2:]: + self.assertIn('--lib', row) + self.assertEqual([row[i + 1] for i, value in enumerate(row) if value == '--test'], + ['aead_kernel_equivalence', 'argon2_kernels', 'chacha20poly1305', 'rsa_public_key', 'pbkdf2_evidence']) + self.assertIn('--all-features', builds[3]) + with patch.dict(os.environ, TRANSFER_FAIL='internal'): + failed = self.root / 'target/failed-internal.tar.gz' + with self.assertRaises(subprocess.CalledProcessError): cross.prepare(target, failed) + self.assertFalse(failed.exists()) with patch.dict(os.environ, TRANSFER_FAIL='1'): failed = self.root / 'target/failed.tar.gz' with self.assertRaises(subprocess.CalledProcessError): cross.prepare(target, failed) @@ -364,13 +378,16 @@ def test_real_nextest_archive_executes_after_transfer(self): ('host: riscv64gc-unknown-linux-gnu' if line == 'host: x86_64-unknown-linux-gnu' else 'host: x86_64-unknown-linux-gnu') if line.startswith('host: ') else line for line in metadata['nextest'].splitlines()) - for mode in ('native', 'portable'): + for mode in cross.MODES: + internal = mode.startswith('internal-') + mode_env = {**env, **({'CARGO_ENCODED_RUSTFLAGS': '--cfg\x1frscrypto_internal'} if internal else {})} built = subprocess.run(['cargo', 'nextest', 'archive', '--locked', '--workspace', '--release', - '--archive-file', str(directory / (mode + '.tar.zst'))], cwd=root, env=env, + '--archive-file', str(directory / (mode + '.tar.zst'))], cwd=root, env=mode_env, capture_output=True, text=True) self.assertEqual(built.returncode, 0, built.stderr) - plan = doctest_bundle.prepare(root, directory / (mode + '-docs'), [], env) - metadata['modes'][mode] = {'doctests': plan['total']} + plan = {'total': 0} if internal else doctest_bundle.prepare(root, directory / (mode + '-docs'), [], env) + metadata['modes'][mode] = {'doctests': plan['total'], 'internal': internal, + 'encoded_rustflags': mode_env.get('CARGO_ENCODED_RUSTFLAGS')} bundle.seal(root, directory, 'rscrypto.cross.tests', TARGET, bundle.source_identity(root), metadata) archive = root / 'target/transfer.tar.gz'; bundle.pack(directory, archive) # Removing the original build tree catches hidden dependencies on it. @@ -382,9 +399,26 @@ def test_real_nextest_archive_executes_after_transfer(self): self.assertEqual(len(summaries), 1) summary = json.loads(summaries[0].read_text()) self.assertEqual(summary['status'], 'pass') + self.assertEqual(set(summary['modes']), {'native', 'portable', 'internal-native', 'internal-portable'}) self.assertEqual(summary['nextest'], consumer_version) self.assertNotEqual(summary['nextest'], metadata['nextest']) + for mutation in ('missing-suite', 'missing-flag', 'wrong-boundary'): + broken = json.loads(json.dumps(metadata)) + if mutation == 'missing-suite': + del broken['modes']['internal-portable'] + elif mutation == 'missing-flag': + broken['modes']['internal-native']['encoded_rustflags'] = '' + else: + broken['modes']['native']['internal'] = True + bundle.seal(root, directory, 'rscrypto.cross.tests', TARGET, bundle.source_identity(root), broken) + archive.unlink() + bundle.pack(directory, archive) + with self.subTest(mutation=mutation), patch.object(cross, 'ROOT', root), \ + patch('platform.system', return_value='Linux'), patch('platform.machine', return_value='riscv64'): + with self.assertRaisesRegex(ValueError, 'suites|suite'): + cross.execute(TARGET, archive) + if __name__ == '__main__': unittest.main() diff --git a/scripts/tooling/aarch64-macos.sh b/scripts/tooling/aarch64-macos.sh index 78bda521..8933fe35 100755 --- a/scripts/tooling/aarch64-macos.sh +++ b/scripts/tooling/aarch64-macos.sh @@ -11,3 +11,10 @@ while IFS= read -r tool; do version=$(python3 scripts/tooling/catalog.py get cargo "$tool") cargo "+$channel" install --locked --version "$version" "$tool" done < <(python3 scripts/tooling/catalog.py get ci cargo) +while IFS= read -r tool; do + case "$tool" in + cargo-*) cargo "+$channel" "${tool#cargo-}" --version ;; + ripgrep) rg --version ;; + *) "$tool" --version ;; + esac +done < <(python3 scripts/tooling/catalog.py get ci cargo) diff --git a/scripts/tooling/catalog.py b/scripts/tooling/catalog.py index 40506a3e..c73e9b4c 100644 --- a/scripts/tooling/catalog.py +++ b/scripts/tooling/catalog.py @@ -100,7 +100,7 @@ def validate(data): ('ci-policy', {'cargo-deny', 'cargo-audit'}), ('ci-compat', {'just'}), ('ci-fuzz', {'just', 'cargo-fuzz'}), - ('ci-ct', {'just'}), + ('ci-ct', {'just', 'cargo-nextest'}), ('ci-miri', {'just'}), ('ci-package', {'just'}), ('ci-bench', {'just'})): diff --git a/scripts/tooling/install_test.py b/scripts/tooling/install_test.py index 62be615e..7e67ae00 100755 --- a/scripts/tooling/install_test.py +++ b/scripts/tooling/install_test.py @@ -70,7 +70,8 @@ def provision(self, platform, fail=False, real_apt=False, without_preference=Fal root = Path(temporary.name) binaries = root / 'bin' binaries.mkdir() - for name in ('uname', 'id', 'apt-get', 'apt-cache', 'cargo', 'clang', 'cmake', 'python3', 'rustup', 'wasmtime', 'opam'): + for name in ('uname', 'id', 'apt-get', 'apt-cache', 'cargo', 'clang', 'cmake', 'python3', 'rustup', + 'wasmtime', 'opam', 'just', 'rg', 'lychee', 'rumdl', 'samply', 'gungraun-runner'): script = binaries / name script.write_text('#!' + sys.executable + '\n' + STUB) script.chmod(0o755) @@ -192,6 +193,13 @@ def test_focused_profiles_install_only_their_execution_dependencies(self): self.assertFalse(any('musl-tools=1.0' in c or 'target' in c and c[0] == 'rustup' for c in calls)) archives = [c[-2] for c in calls if c[0] == 'python3' and 'install-archive' in c] self.assertEqual(archives, ['cargo-binstall'] if 'cargo-binstall' in CATALOG[platform]['assets'] else []) + for tool in CATALOG[profile]['cargo']: + if tool.startswith('cargo-'): + self.assertTrue(any(c[0] == 'cargo' and c[-2:] == [tool.removeprefix('cargo-'), '--version'] + for c in calls)) + else: + probe = ['rg' if tool == 'ripgrep' else tool, '--version'] + self.assertIn(probe, calls) def test_package_profile_installs_consumer_prerequisites_once(self): result, calls, _ = self.provision('x86_64-linux', profile='ci-package') diff --git a/scripts/tooling/linux.sh b/scripts/tooling/linux.sh index 974abb50..1d3a5108 100755 --- a/scripts/tooling/linux.sh +++ b/scripts/tooling/linux.sh @@ -221,6 +221,15 @@ for tool in "${cargo_tools[@]}"; do cargo +"$channel" install --locked --target "$(catalog_get "$platform" rust-host)" --version "$version" "$tool" fi done + +verify_cargo_tool() { + case "$1" in + cargo-*) cargo +"$channel" "${1#cargo-}" --version ;; + ripgrep) rg --version ;; + *) "$1" --version ;; + esac +} +for tool in "${cargo_tools[@]}"; do verify_cargo_tool "$tool"; done if [[ "$ci" == false && "$platform" != aarch64-linux && "$platform" != x86_64-linux ]]; then env -u RUSTC_WRAPPER -u CARGO_ENCODED_RUSTFLAGS \ cargo +"$channel" install --locked --target "$(catalog_get "$platform" rust-host)" --version "$(catalog_get versions cargo-rail)" cargo-rail @@ -273,8 +282,6 @@ fi if [[ "$ci" == false ]]; then cargo rail --version; fi case "$profile" in ci-compat) wasmtime --version ;; - ci-fuzz) cargo fuzz --version ;; - ci-ct|ci-miri|ci-package|ci-bench) just --version ;; - ci|ci-cross-build|ci-cross-run) cargo nextest --version ;; + ci-cross-run) just --version; cargo nextest --version ;; esac printf 'Installed %s tooling. Load with: source "%s"\n' "$platform" "$environment" diff --git a/scripts/tooling/windows.ps1 b/scripts/tooling/windows.ps1 index eaf51e33..83c79722 100644 --- a/scripts/tooling/windows.ps1 +++ b/scripts/tooling/windows.ps1 @@ -160,6 +160,15 @@ try { foreach ($tool in $cargoTools) { Invoke-Native 'cargo' @("+$channel", 'binstall', '--locked', '--no-confirm', '--targets', $native.'rust-host', "$tool@$($catalog.cargo.$tool)") } + foreach ($tool in $cargoTools) { + if ($tool.StartsWith('cargo-')) { + Invoke-Native 'cargo' @("+$channel", $tool.Substring(6), '--version') + } elseif ($tool -eq 'ripgrep') { + Invoke-Native 'rg' @('--version') + } else { + Invoke-Native $tool @('--version') + } + } $probeDirectory = Join-Path $temporary 'compiler-probe' New-Item -ItemType Directory -Force (Join-Path $probeDirectory 'src') | Out-Null Set-Content -Path (Join-Path $probeDirectory 'Cargo.toml') -Encoding ASCII -Value @( @@ -181,7 +190,6 @@ try { if ($Platform -eq 'x86_64-win') { Invoke-Native 'nasm' @('-v') } } if (-not $Ci) { Invoke-Native 'cargo' @("+$channel", 'rail', '--version') } - if (-not $CiBench -and -not $CiCt) { Invoke-Native 'cargo' @("+$channel", 'nextest', '--version') } # Persist the complete MSVC/SDK environment, not only the paths to installed executables. if (-not $Ci) { diff --git a/src/aead/aegis256.rs b/src/aead/aegis256.rs index aa8d1afa..c09bebeb 100644 --- a/src/aead/aegis256.rs +++ b/src/aead/aegis256.rs @@ -410,7 +410,7 @@ fn encrypt_portable(key: &[u8; KEY_SIZE], nonce: &[u8; NONCE_SIZE], aad: &[u8], tag } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Run one portable AEGIS-256 state update and return its diagnostic final tag. #[unsafe(no_mangle)] #[inline(never)] diff --git a/src/aead/aes.rs b/src/aead/aes.rs index faec8fe1..46498d3b 100644 --- a/src/aead/aes.rs +++ b/src/aead/aes.rs @@ -635,7 +635,7 @@ pub(crate) fn aes128_expand_key(key: &[u8; KEY_SIZE_128]) -> Aes128EncKey { /// /// This bypasses runtime hardware selection without creating a second AES implementation. On /// RV64 and s390x, the table-free fixslice fallback is the portable authority. -#[cfg(all(feature = "diag", feature = "aes-siv"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-siv"))] #[inline] pub(crate) fn aes128_expand_key_forced_portable(key: &[u8; KEY_SIZE_128]) -> Aes128EncKey { #[cfg(any(target_arch = "riscv64", target_arch = "s390x"))] diff --git a/src/aead/aes128gcm.rs b/src/aead/aes128gcm.rs index f1d2069f..9b61e19f 100644 --- a/src/aead/aes128gcm.rs +++ b/src/aead/aes128gcm.rs @@ -299,7 +299,7 @@ fn encrypt_j0_tag(ek: &aes::Aes128EncKey, j0: &[u8; 16], acc: u128) -> [u8; TAG_ tag } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Exercise AES-128 counter-mode encryption and fold the fixed diagnostic output to one block. #[must_use] pub fn diag_aes128gcm_ctr32_be(cipher: &Aes128Gcm, nonce: &Nonce96, plaintext: &[u8; 44]) -> [u8; 16] { @@ -309,7 +309,7 @@ pub fn diag_aes128gcm_ctr32_be(cipher: &Aes128Gcm, nonce: &Nonce96, plaintext: & diag_fold16(&buffer) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Return the AES-128-GCM GHASH accumulator for diagnostic backend comparison. #[unsafe(no_mangle)] #[inline(never)] @@ -333,7 +333,7 @@ pub fn diag_aes128gcm_ghash(cipher: &Aes128Gcm, aad: &[u8], ciphertext: &[u8]) - acc.0.to_be_bytes() } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Encrypt a diagnostic GHASH accumulator into the final AES-128-GCM tag. #[must_use] pub fn diag_aes128gcm_tag_aes(cipher: &Aes128Gcm, nonce: &Nonce96, acc: &[u8; 16]) -> [u8; 16] { @@ -341,7 +341,7 @@ pub fn diag_aes128gcm_tag_aes(cipher: &Aes128Gcm, nonce: &Nonce96, acc: &[u8; 16 encrypt_j0_tag(&cipher.ek, &j0, u128::from_be_bytes(*acc)) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn diag_fold16(data: &[u8]) -> [u8; 16] { let (blocks, tail) = data.as_chunks::<16>(); let mut acc = 0u128; diff --git a/src/aead/aes128gcmsiv.rs b/src/aead/aes128gcmsiv.rs index 9796f20f..8a46fda8 100644 --- a/src/aead/aes128gcmsiv.rs +++ b/src/aead/aes128gcmsiv.rs @@ -227,14 +227,25 @@ fn compute_tag( s } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Derive the per-nonce authentication and encryption keys for diagnostic comparison. #[must_use] pub fn diag_aes128gcmsiv_derive_keys(cipher: &Aes128GcmSiv, nonce: &Nonce96) -> ([u8; 16], [u8; 16]) { + #[cfg(target_arch = "aarch64")] + if matches!( + cipher.backend, + AeadBackend::Aarch64AesPmull | AeadBackend::Aarch64Sve2AesPmull + ) { + // SAFETY: mirror the production AArch64 GCM-SIV key-derivation dispatch because: + // 1. Backend resolution selected an AArch64 AES+PMULL backend. + // 2. The selected backend constructs `cipher.master_ek` with AES-CE round keys. + // 3. `nonce.as_bytes()` is exactly the 96-bit GCM-SIV nonce. + return unsafe { aes::aarch64_gcmsiv_derive_keys_128_inline(&cipher.master_ek, nonce.as_bytes()) }; + } derive_keys(&cipher.master_ek, nonce) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Return the AES-128-GCM-SIV POLYVAL digest before nonce and AES tag finalization. #[must_use] pub fn diag_aes128gcmsiv_polyval_digest(auth_key: &[u8; 16], aad: &[u8], plaintext: &[u8]) -> [u8; 16] { @@ -246,7 +257,7 @@ pub fn diag_aes128gcmsiv_polyval_digest(auth_key: &[u8; 16], aad: &[u8], plainte pv.finalize() } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Encrypt one diagnostic tag block with a raw AES-128 key. #[must_use] pub fn diag_aes128gcmsiv_raw_tag_aes(enc_key: &[u8; 16], block: &[u8; 16]) -> [u8; 16] { @@ -264,7 +275,7 @@ pub fn diag_aes128gcmsiv_raw_tag_aes(enc_key: &[u8; 16], block: &[u8; 16]) -> [u out } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Exercise AES-128 counter-mode encryption and fold the fixed diagnostic output to one block. #[must_use] pub fn diag_aes128gcmsiv_ctr32(enc_key: &[u8; 16], tag: &[u8; 16], plaintext: &[u8; 44]) -> [u8; 16] { @@ -307,7 +318,7 @@ pub fn diag_aes128gcmsiv_ctr32(enc_key: &[u8; 16], tag: &[u8; 16], plaintext: &[ diag_fold16(&buffer) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn diag_fold16(data: &[u8]) -> [u8; 16] { let (blocks, tail) = data.as_chunks::<16>(); let mut acc = 0u128; diff --git a/src/aead/aes256gcm.rs b/src/aead/aes256gcm.rs index 81a9e005..21928272 100644 --- a/src/aead/aes256gcm.rs +++ b/src/aead/aes256gcm.rs @@ -297,7 +297,7 @@ fn encrypt_j0_tag(ek: &aes::Aes256EncKey, j0: &[u8; 16], acc: u128) -> [u8; TAG_ tag } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Exercise AES-256 counter-mode encryption and fold the fixed diagnostic output to one block. #[must_use] pub fn diag_aes256gcm_ctr32_be(cipher: &Aes256Gcm, nonce: &Nonce96, plaintext: &[u8; 44]) -> [u8; 16] { @@ -307,7 +307,7 @@ pub fn diag_aes256gcm_ctr32_be(cipher: &Aes256Gcm, nonce: &Nonce96, plaintext: & diag_fold16(&buffer) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Return the AES-256-GCM GHASH accumulator for diagnostic backend comparison. #[unsafe(no_mangle)] #[inline(never)] @@ -331,7 +331,7 @@ pub fn diag_aes256gcm_ghash(cipher: &Aes256Gcm, aad: &[u8], ciphertext: &[u8]) - acc.0.to_be_bytes() } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Encrypt a diagnostic GHASH accumulator into the final AES-256-GCM tag. #[must_use] pub fn diag_aes256gcm_tag_aes(cipher: &Aes256Gcm, nonce: &Nonce96, acc: &[u8; 16]) -> [u8; 16] { @@ -339,7 +339,7 @@ pub fn diag_aes256gcm_tag_aes(cipher: &Aes256Gcm, nonce: &Nonce96, acc: &[u8; 16 encrypt_j0_tag(&cipher.ek, &j0, u128::from_be_bytes(*acc)) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn diag_fold16(data: &[u8]) -> [u8; 16] { let (blocks, tail) = data.as_chunks::<16>(); let mut acc = 0u128; diff --git a/src/aead/aes256gcmsiv.rs b/src/aead/aes256gcmsiv.rs index 14376a2e..fc2f96ad 100644 --- a/src/aead/aes256gcmsiv.rs +++ b/src/aead/aes256gcmsiv.rs @@ -235,14 +235,25 @@ fn compute_tag( s } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Derive the per-nonce authentication and encryption keys for diagnostic comparison. #[must_use] pub fn diag_aes256gcmsiv_derive_keys(cipher: &Aes256GcmSiv, nonce: &Nonce96) -> ([u8; 16], [u8; 32]) { + #[cfg(target_arch = "aarch64")] + if matches!( + cipher.backend, + AeadBackend::Aarch64AesPmull | AeadBackend::Aarch64Sve2AesPmull + ) { + // SAFETY: mirror the production AArch64 GCM-SIV key-derivation dispatch because: + // 1. Backend resolution selected an AArch64 AES+PMULL backend. + // 2. The selected backend constructs `cipher.master_ek` with AES-CE round keys. + // 3. `nonce.as_bytes()` is exactly the 96-bit GCM-SIV nonce. + return unsafe { aes::aarch64_gcmsiv_derive_keys_inline(&cipher.master_ek, nonce.as_bytes()) }; + } derive_keys(&cipher.master_ek, nonce) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Encrypt one diagnostic tag block with a raw AES-256 key. #[must_use] pub fn diag_aes256gcmsiv_raw_tag_aes(enc_key: &[u8; 32], block: &[u8; 16]) -> [u8; 16] { @@ -260,7 +271,7 @@ pub fn diag_aes256gcmsiv_raw_tag_aes(enc_key: &[u8; 32], block: &[u8; 16]) -> [u out } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Exercise AES-256 counter-mode encryption and fold the fixed diagnostic output to one block. #[must_use] pub fn diag_aes256gcmsiv_ctr32(enc_key: &[u8; 32], tag: &[u8; 16], plaintext: &[u8; 44]) -> [u8; 16] { @@ -303,7 +314,7 @@ pub fn diag_aes256gcmsiv_ctr32(enc_key: &[u8; 32], tag: &[u8; 16], plaintext: &[ diag_fold16(&buffer) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn diag_fold16(data: &[u8]) -> [u8; 16] { let (blocks, tail) = data.as_chunks::<16>(); let mut acc = 0u128; diff --git a/src/aead/aes_round.rs b/src/aead/aes_round.rs index 73538fd8..706542c0 100644 --- a/src/aead/aes_round.rs +++ b/src/aead/aes_round.rs @@ -1,7 +1,7 @@ //! Shared portable AES round helpers for AEGIS and Hamburg vperm constants. #[cfg(any( - all(feature = "diag", feature = "aegis256"), + all(rscrypto_internal, feature = "diag", feature = "aegis256"), all(test, not(target_arch = "s390x")), all(feature = "aegis256", not(target_arch = "riscv64")), ))] @@ -113,7 +113,7 @@ pub(crate) const MC_ROT2: [u8; 16] = [ // shared RISC-V/s390x vperm constants; diagnostic proof harnesses need this // bounded portable leaf even on targets with architecture-specific AES paths. #[cfg(any( - all(feature = "diag", feature = "aegis256"), + all(rscrypto_internal, feature = "diag", feature = "aegis256"), all(test, not(target_arch = "s390x")), all(feature = "aegis256", not(target_arch = "riscv64")), ))] @@ -146,7 +146,7 @@ const fn gf256_mul(a: u8, b: u8) -> u8 { } #[cfg(any( - all(feature = "diag", feature = "aegis256"), + all(rscrypto_internal, feature = "diag", feature = "aegis256"), all(test, not(target_arch = "s390x")), all(feature = "aegis256", not(target_arch = "riscv64")), ))] @@ -156,7 +156,7 @@ const fn gf256_sq(x: u8) -> u8 { } #[cfg(any( - all(feature = "diag", feature = "aegis256"), + all(rscrypto_internal, feature = "diag", feature = "aegis256"), all(test, not(target_arch = "s390x")), all(feature = "aegis256", not(target_arch = "riscv64")), ))] @@ -178,7 +178,7 @@ const fn gf256_inv(x: u8) -> u8 { } #[cfg(any( - all(feature = "diag", feature = "aegis256"), + all(rscrypto_internal, feature = "diag", feature = "aegis256"), all(test, not(target_arch = "s390x")), all(feature = "aegis256", not(target_arch = "riscv64")), ))] @@ -190,7 +190,7 @@ const fn sbox(x: u8) -> u8 { } #[cfg(any( - all(feature = "diag", feature = "aegis256"), + all(rscrypto_internal, feature = "diag", feature = "aegis256"), all(test, not(target_arch = "s390x")), all(feature = "aegis256", not(target_arch = "riscv64")), ))] @@ -200,7 +200,7 @@ const fn col_byte(col: u32, row: usize) -> u8 { } #[cfg(any( - all(feature = "diag", feature = "aegis256"), + all(rscrypto_internal, feature = "diag", feature = "aegis256"), all(test, not(target_arch = "s390x")), all(feature = "aegis256", not(target_arch = "riscv64")), ))] @@ -211,7 +211,7 @@ const fn xtime(x: u8) -> u8 { } #[cfg(any( - all(feature = "diag", feature = "aegis256"), + all(rscrypto_internal, feature = "diag", feature = "aegis256"), all(test, not(target_arch = "s390x")), all(feature = "aegis256", not(target_arch = "riscv64")), ))] @@ -228,7 +228,7 @@ const fn mix_column(col: [u8; 4]) -> u32 { } #[cfg(any( - all(feature = "diag", feature = "aegis256"), + all(rscrypto_internal, feature = "diag", feature = "aegis256"), all(test, not(target_arch = "s390x")), all(feature = "aegis256", not(target_arch = "riscv64")), ))] @@ -263,7 +263,7 @@ const fn aes_round(s0: u32, s1: u32, s2: u32, s3: u32) -> (u32, u32, u32, u32) { } #[cfg(any( - all(feature = "diag", feature = "aegis256"), + all(rscrypto_internal, feature = "diag", feature = "aegis256"), all(test, not(target_arch = "s390x")), all(feature = "aegis256", not(target_arch = "riscv64")), ))] @@ -289,7 +289,7 @@ pub(crate) fn aes_enc_round_portable(block: &[u8; BLOCK_SIZE], round_key: &[u8; out } -#[cfg(all(feature = "diag", feature = "aegis256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aegis256"))] #[must_use] /// Apply one portable AES encryption round for backend diagnostics. pub fn diag_aes_enc_round_portable(block: &[u8; BLOCK_SIZE], round_key: &[u8; BLOCK_SIZE]) -> [u8; BLOCK_SIZE] { diff --git a/src/aead/aes_siv_cmac256.rs b/src/aead/aes_siv_cmac256.rs index 3a9fa748..14df068c 100644 --- a/src/aead/aes_siv_cmac256.rs +++ b/src/aead/aes_siv_cmac256.rs @@ -148,7 +148,7 @@ impl AesSivCmac256 { } } - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] fn new_forced_portable(key: &AesSivCmac256Key) -> Self { let (cmac_key_bytes, ctr_key_bytes) = key .as_bytes() @@ -422,7 +422,7 @@ impl AesSivCmac256 { /// /// This diagnostic exists only for constant-time and backend-equivalence evidence. It is not a /// supported CMAC, S2V, or deterministic-SIV product API. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[inline(never)] #[must_use] @@ -441,7 +441,7 @@ pub fn diag_aes_siv_cmac256_s2v_portable( /// /// This diagnostic exists only for generated-code and timing evidence. The returned byte is an /// opaque success indicator; authentication failure still clears the complete plaintext buffer. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[inline(never)] #[must_use] @@ -462,7 +462,7 @@ pub fn diag_aes_siv_cmac256_open_portable( digest ^ u8::from(accepted) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] /// Exercise AES-SIV construction, seal, open, local cleanup, and retained-owner drop. #[unsafe(no_mangle)] @@ -572,7 +572,7 @@ mod tests { assert_eq!(actual, expected); } - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn forced_portable_cmac_s2v_and_ctr_match_selected_backend() { const LENGTHS: &[usize] = &[0, 1, 15, 16, 17, 31, 32, 33, 47, 48, 49, 63, 64, 65, 127, 128, 129]; diff --git a/src/aead/ascon128.rs b/src/aead/ascon128.rs index a6a4cdab..9486cfe7 100644 --- a/src/aead/ascon128.rs +++ b/src/aead/ascon128.rs @@ -365,7 +365,7 @@ impl Aead for AsconAead128 { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Compare a portable Ascon-AEAD128 tag computation with an expected diagnostic tag. #[unsafe(no_mangle)] #[inline(never)] diff --git a/src/aead/chacha20.rs b/src/aead/chacha20.rs index 627fba6e..504ff8e2 100644 --- a/src/aead/chacha20.rs +++ b/src/aead/chacha20.rs @@ -509,7 +509,7 @@ pub(super) unsafe fn xor_keystream_aarch64_neon( // Forced entry points let backend-equivalence tests bypass runtime dispatch. /// Run the **portable** ChaCha20 XOR-keystream regardless of host caps. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub fn diag_chacha20_xor_keystream_portable( key: &[u8; KEY_SIZE], initial_counter: u32, @@ -525,7 +525,7 @@ pub fn diag_chacha20_xor_keystream_portable( /// /// Caller must verify the host has `aarch64::NEON` and that `buffer`'s 64-byte block count fits the counter range /// starting at `initial_counter`. Compile-time gated to `target_arch = "aarch64"`. -#[cfg(all(feature = "diag", target_arch = "aarch64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "aarch64"))] pub unsafe fn diag_chacha20_xor_keystream_aarch64_neon( key: &[u8; KEY_SIZE], initial_counter: u32, @@ -542,7 +542,7 @@ pub unsafe fn diag_chacha20_xor_keystream_aarch64_neon( /// /// Caller must verify the host has `x86::AVX2` and that `buffer`'s 64-byte block count fits the counter range starting /// at `initial_counter`. -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] pub unsafe fn diag_chacha20_xor_keystream_x86_avx2( key: &[u8; KEY_SIZE], initial_counter: u32, @@ -559,7 +559,7 @@ pub unsafe fn diag_chacha20_xor_keystream_x86_avx2( /// /// Caller must verify the host has `x86::AVX512F + AVX512VL + AVX512BW + AVX512DQ` and that `buffer`'s 64-byte block /// count fits the counter range starting at `initial_counter`. -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] pub unsafe fn diag_chacha20_xor_keystream_x86_avx512( key: &[u8; KEY_SIZE], initial_counter: u32, @@ -577,7 +577,12 @@ pub unsafe fn diag_chacha20_xor_keystream_x86_avx512( /// Caller must verify the host has `power::POWER8_VECTOR` and that `buffer`'s 64-byte block count fits the counter /// range starting at `initial_counter`. The portable kernel — which has been the correctness oracle since commit /// `2631aefa` fixed the rotation-amount bug here — must produce identical bytes. -#[cfg(all(feature = "diag", target_arch = "powerpc64", target_endian = "little"))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "powerpc64", + target_endian = "little" +))] pub unsafe fn diag_chacha20_xor_keystream_power_vsx( key: &[u8; KEY_SIZE], initial_counter: u32, @@ -594,7 +599,7 @@ pub unsafe fn diag_chacha20_xor_keystream_power_vsx( /// /// Caller must verify the host has `s390x::VECTOR` and that `buffer`'s 64-byte block count fits the counter range /// starting at `initial_counter`. Same correctness-oracle invariant as POWER VSX above. -#[cfg(all(feature = "diag", target_arch = "s390x"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "s390x"))] pub unsafe fn diag_chacha20_xor_keystream_s390x_vector( key: &[u8; KEY_SIZE], initial_counter: u32, @@ -611,7 +616,7 @@ pub unsafe fn diag_chacha20_xor_keystream_s390x_vector( /// /// Caller must verify the host has `riscv::V` and that `buffer`'s 64-byte block count fits the counter range starting /// at `initial_counter`. -#[cfg(all(feature = "diag", target_arch = "riscv64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "riscv64"))] pub unsafe fn diag_chacha20_xor_keystream_riscv64_vector( key: &[u8; KEY_SIZE], initial_counter: u32, @@ -628,7 +633,7 @@ pub unsafe fn diag_chacha20_xor_keystream_riscv64_vector( /// /// Caller must verify the host has `wasm::SIMD128` and that `buffer`'s 64-byte block count fits the counter range /// starting at `initial_counter`. -#[cfg(all(feature = "diag", target_arch = "wasm32"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "wasm32"))] pub unsafe fn diag_chacha20_xor_keystream_wasm_simd128( key: &[u8; KEY_SIZE], initial_counter: u32, diff --git a/src/aead/chacha20poly1305.rs b/src/aead/chacha20poly1305.rs index 9a2ffc1b..313fd334 100644 --- a/src/aead/chacha20poly1305.rs +++ b/src/aead/chacha20poly1305.rs @@ -49,7 +49,10 @@ mod aarch64_asm; #[cfg(all( target_arch = "x86_64", target_os = "linux", - any(feature = "diag", all(not(debug_assertions), not(feature = "portable-only"))) + any( + all(rscrypto_internal, feature = "diag"), + all(not(debug_assertions), not(feature = "portable-only")) + ) ))] #[path = "chacha20poly1305/x86_64_asm.rs"] mod x86_64_asm; @@ -245,7 +248,11 @@ impl ChaCha20Poly1305 { #[cfg(all( target_arch = "x86_64", target_os = "linux", - any(test, feature = "diag", all(not(debug_assertions), not(feature = "portable-only"))) + any( + test, + all(rscrypto_internal, feature = "diag"), + all(not(debug_assertions), not(feature = "portable-only")) + ) ))] #[inline] fn x86_64_asm_caps_available(caps: crate::platform::Caps) -> bool { @@ -301,7 +308,7 @@ impl ChaCha20Poly1305 { } } - #[cfg(all(feature = "diag", target_arch = "x86_64", target_os = "linux"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64", target_os = "linux"))] fn encrypt_in_place_asm_x86_64_forced( &self, nonce: &Nonce96, @@ -327,7 +334,7 @@ impl ChaCha20Poly1305 { Some(Ok(ChaCha20Poly1305Tag::from_bytes(tag))) } - #[cfg(all(feature = "diag", target_arch = "x86_64", target_os = "linux"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64", target_os = "linux"))] fn decrypt_in_place_asm_x86_64_forced( &self, nonce: &Nonce96, @@ -636,7 +643,7 @@ impl ChaCha20Poly1305 { /// /// Lower-level ChaCha20 and Poly1305 dispatch remains enabled. Returns an error when the input lengths exceed the /// supported limits. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub fn diag_chacha20poly1305_encrypt_in_place_owned( cipher: &ChaCha20Poly1305, nonce: &Nonce96, @@ -647,7 +654,7 @@ pub fn diag_chacha20poly1305_encrypt_in_place_owned( cipher.encrypt_in_place_owned_unchecked(nonce, aad, buffer) } -#[cfg(all(feature = "diag", target_arch = "x86_64", target_os = "linux"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64", target_os = "linux"))] /// Encrypts in place through the Linux x86-64 assembly entrypoint when that entrypoint is available. /// /// Returns `None` when the current CPU cannot execute the assembly backend. @@ -664,7 +671,7 @@ pub fn diag_chacha20poly1305_encrypt_in_place_x86_64_asm( cipher.encrypt_in_place_asm_x86_64_forced(nonce, aad, buffer) } -#[cfg(all(feature = "diag", target_arch = "x86_64", target_os = "linux"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64", target_os = "linux"))] /// Authenticates and decrypts in place through the Linux x86-64 assembly entrypoint when it is available. /// /// Returns `None` when the current CPU cannot execute the assembly backend. @@ -687,7 +694,7 @@ pub fn diag_chacha20poly1305_decrypt_in_place_x86_64_asm( /// /// Lower-level ChaCha20 and Poly1305 dispatch remains enabled. Authentication failure zeroes `buffer` and returns an /// opaque verification error; unsupported input lengths also return an error. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub fn diag_chacha20poly1305_decrypt_in_place_owned( cipher: &ChaCha20Poly1305, nonce: &Nonce96, @@ -968,7 +975,7 @@ mod tests { } } - #[cfg(all(feature = "diag", target_arch = "x86_64", target_os = "linux"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64", target_os = "linux"))] #[test] fn x86_64_open_asm_matches_owned_path() { if !ChaCha20Poly1305::x86_64_asm_caps_available(crate::platform::caps()) { diff --git a/src/aead/ghash.rs b/src/aead/ghash.rs index da0fba79..103a0bb4 100644 --- a/src/aead/ghash.rs +++ b/src/aead/ghash.rs @@ -45,7 +45,7 @@ pub(crate) fn h_to_polyval(h_bytes: &[u8; KEY_SIZE]) -> u128 { } /// Computes one GHASH block with the portable POLYVAL-domain reduction. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[must_use] pub fn diag_ghash_block_portable(h_bytes: &[u8; KEY_SIZE], block: &[u8; KEY_SIZE]) -> [u8; KEY_SIZE] { let h = h_to_polyval(h_bytes); diff --git a/src/aead/header_protection.rs b/src/aead/header_protection.rs index ebf1a644..0fcd619b 100644 --- a/src/aead/header_protection.rs +++ b/src/aead/header_protection.rs @@ -208,7 +208,7 @@ impl Drop for ChaCha20HeaderProtection { } } -#[cfg(all(feature = "diag", feature = "aes-gcm"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-gcm"))] /// Exercise AES-128 header protection while retaining key, schedule, and temporary-block cleanup. #[unsafe(no_mangle)] #[inline(never)] @@ -218,7 +218,7 @@ pub fn diag_zeroize_aes128_header_protection(key: [u8; 16], sample: [u8; SAMPLE_ Aes128HeaderProtection::new(&key).mask(&sample) } -#[cfg(all(feature = "diag", feature = "aes-gcm"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-gcm"))] /// Exercise AES-256 header protection while retaining key, schedule, and temporary-block cleanup. #[unsafe(no_mangle)] #[inline(never)] @@ -228,7 +228,7 @@ pub fn diag_zeroize_aes256_header_protection(key: [u8; 32], sample: [u8; SAMPLE_ Aes256HeaderProtection::new(&key).mask(&sample) } -#[cfg(all(feature = "diag", feature = "chacha20poly1305"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "chacha20poly1305"))] /// Exercise ChaCha20 header protection while retaining key, context, and temporary-block cleanup. #[unsafe(no_mangle)] #[inline(never)] diff --git a/src/aead/mod.rs b/src/aead/mod.rs index e14bd0d3..4f01870e 100644 --- a/src/aead/mod.rs +++ b/src/aead/mod.rs @@ -213,53 +213,59 @@ mod polyval; mod targets; #[cfg(feature = "xchacha20poly1305")] mod xchacha20poly1305; -#[cfg(all(feature = "diag", feature = "aegis256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aegis256"))] pub use aegis256::diag_aegis256_update_portable; #[cfg(feature = "aegis256")] pub use aegis256::{Aegis256, Aegis256Key, Aegis256Tag}; -#[cfg(all(feature = "diag", feature = "aegis256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aegis256"))] pub use aes_round::diag_aes_enc_round_portable; #[cfg(feature = "aes-siv")] pub use aes_siv_cmac256::{ AesSivCmac256, AesSivCmac256Key, AesSivCmac256Nonce, AesSivCmac256NonceError, AesSivCmac256Tag, }; -#[cfg(all(feature = "diag", feature = "aes-siv"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-siv"))] pub use aes_siv_cmac256::{ diag_aes_siv_cmac256_open_portable, diag_aes_siv_cmac256_s2v_portable, diag_zeroize_aes_siv_cmac256, }; #[cfg(feature = "aes-gcm")] pub use aes128gcm::{Aes128Gcm, Aes128GcmKey, Aes128GcmTag}; -#[cfg(all(feature = "diag", feature = "aes-gcm"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-gcm"))] pub use aes128gcm::{diag_aes128gcm_ctr32_be, diag_aes128gcm_ghash, diag_aes128gcm_tag_aes}; #[cfg(feature = "aes-gcm-siv")] pub use aes128gcmsiv::{Aes128GcmSiv, Aes128GcmSivKey, Aes128GcmSivTag}; -#[cfg(all(feature = "diag", feature = "aes-gcm-siv"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-gcm-siv"))] pub use aes128gcmsiv::{ diag_aes128gcmsiv_ctr32, diag_aes128gcmsiv_derive_keys, diag_aes128gcmsiv_polyval_digest, diag_aes128gcmsiv_raw_tag_aes, }; #[cfg(feature = "aes-gcm")] pub use aes256gcm::{Aes256Gcm, Aes256GcmKey, Aes256GcmTag}; -#[cfg(all(feature = "diag", feature = "aes-gcm"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-gcm"))] pub use aes256gcm::{diag_aes256gcm_ctr32_be, diag_aes256gcm_ghash, diag_aes256gcm_tag_aes}; #[cfg(feature = "aes-gcm-siv")] pub use aes256gcmsiv::{Aes256GcmSiv, Aes256GcmSivKey, Aes256GcmSivTag}; -#[cfg(all(feature = "diag", feature = "aes-gcm-siv"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-gcm-siv"))] pub use aes256gcmsiv::{diag_aes256gcmsiv_ctr32, diag_aes256gcmsiv_derive_keys, diag_aes256gcmsiv_raw_tag_aes}; -#[cfg(all(feature = "diag", feature = "ascon-aead"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ascon-aead"))] pub use ascon128::diag_ascon_aead128_tag_portable; #[cfg(feature = "ascon-aead")] pub use ascon128::{AsconAead128, AsconAead128Key, AsconAead128Tag}; #[cfg(all( + rscrypto_internal, feature = "diag", target_arch = "aarch64", any(feature = "chacha20poly1305", feature = "xchacha20poly1305") ))] pub use chacha20::diag_chacha20_xor_keystream_aarch64_neon; // Backend-equivalence tests use these entry points to bypass runtime dispatch. -#[cfg(all(feature = "diag", any(feature = "chacha20poly1305", feature = "xchacha20poly1305")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + any(feature = "chacha20poly1305", feature = "xchacha20poly1305") +))] pub use chacha20::diag_chacha20_xor_keystream_portable; #[cfg(all( + rscrypto_internal, feature = "diag", target_arch = "powerpc64", target_endian = "little", @@ -267,24 +273,28 @@ pub use chacha20::diag_chacha20_xor_keystream_portable; ))] pub use chacha20::diag_chacha20_xor_keystream_power_vsx; #[cfg(all( + rscrypto_internal, feature = "diag", target_arch = "riscv64", any(feature = "chacha20poly1305", feature = "xchacha20poly1305") ))] pub use chacha20::diag_chacha20_xor_keystream_riscv64_vector; #[cfg(all( + rscrypto_internal, feature = "diag", target_arch = "s390x", any(feature = "chacha20poly1305", feature = "xchacha20poly1305") ))] pub use chacha20::diag_chacha20_xor_keystream_s390x_vector; #[cfg(all( + rscrypto_internal, feature = "diag", target_arch = "wasm32", any(feature = "chacha20poly1305", feature = "xchacha20poly1305") ))] pub use chacha20::diag_chacha20_xor_keystream_wasm_simd128; #[cfg(all( + rscrypto_internal, feature = "diag", target_arch = "x86_64", any(feature = "chacha20poly1305", feature = "xchacha20poly1305") @@ -292,11 +302,12 @@ pub use chacha20::diag_chacha20_xor_keystream_wasm_simd128; pub use chacha20::{diag_chacha20_xor_keystream_x86_avx2, diag_chacha20_xor_keystream_x86_avx512}; #[cfg(feature = "chacha20poly1305")] pub use chacha20poly1305::{ChaCha20Poly1305, ChaCha20Poly1305Key, ChaCha20Poly1305Tag}; -#[cfg(all(feature = "diag", feature = "chacha20poly1305"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "chacha20poly1305"))] pub use chacha20poly1305::{ diag_chacha20poly1305_decrypt_in_place_owned, diag_chacha20poly1305_encrypt_in_place_owned, }; #[cfg(all( + rscrypto_internal, feature = "diag", feature = "chacha20poly1305", target_arch = "x86_64", @@ -305,24 +316,29 @@ pub use chacha20poly1305::{ pub use chacha20poly1305::{ diag_chacha20poly1305_decrypt_in_place_x86_64_asm, diag_chacha20poly1305_encrypt_in_place_x86_64_asm, }; -#[cfg(all(feature = "diag", feature = "aes-gcm"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-gcm"))] pub use ghash::diag_ghash_block_portable; -#[cfg(all(feature = "diag", feature = "chacha20poly1305"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "chacha20poly1305"))] pub use header_protection::diag_zeroize_chacha20_header_protection; -#[cfg(all(feature = "diag", feature = "aes-gcm"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-gcm"))] pub use header_protection::{diag_zeroize_aes128_header_protection, diag_zeroize_aes256_header_protection}; #[cfg(feature = "aes-gcm")] pub use nonce_counter::{NonceCounter, NonceCounterExhausted, NonceCounterSealError}; #[cfg(all( + rscrypto_internal, feature = "diag", target_arch = "aarch64", any(target_os = "linux", target_os = "macos"), any(feature = "chacha20poly1305", feature = "xchacha20poly1305") ))] pub use poly1305::diag_chacha20poly1305_authenticate_aead_aarch64_neon_par4; -#[cfg(all(feature = "diag", any(feature = "chacha20poly1305", feature = "xchacha20poly1305")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + any(feature = "chacha20poly1305", feature = "xchacha20poly1305") +))] pub use poly1305::{diag_chacha20poly1305_authenticate_aead, diag_poly1305_block_portable_digest}; -#[cfg(all(feature = "diag", feature = "aes-gcm-siv"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-gcm-siv"))] pub use polyval::diag_polyval_reduce_portable; #[cfg(feature = "xchacha20poly1305")] pub use xchacha20poly1305::{XChaCha20Poly1305, XChaCha20Poly1305Key, XChaCha20Poly1305Tag}; diff --git a/src/aead/poly1305.rs b/src/aead/poly1305.rs index 2d466f14..9a2975bc 100644 --- a/src/aead/poly1305.rs +++ b/src/aead/poly1305.rs @@ -1029,20 +1029,17 @@ pub(crate) fn authenticate_aead_short_text_portable(aad: &[u8], ciphertext: &[u8 ) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Computes a ChaCha20-Poly1305 authenticator through the selected Poly1305 backend. -/// -/// Always returns `Some(tag)` on supported targets, where slice lengths fit the AEAD length fields. -/// The `Option` return type is retained for diagnostic API compatibility. -pub fn diag_chacha20poly1305_authenticate_aead(aad: &[u8], ciphertext: &[u8], key: &[u8; 32]) -> Option<[u8; 16]> { +pub fn diag_chacha20poly1305_authenticate_aead(aad: &[u8], ciphertext: &[u8], key: &[u8; 32]) -> [u8; 16] { #[cfg(feature = "chacha20poly1305")] let primitive = AeadPrimitive::ChaCha20Poly1305; #[cfg(all(not(feature = "chacha20poly1305"), feature = "xchacha20poly1305"))] let primitive = AeadPrimitive::XChaCha20Poly1305; - Some(authenticate_aead(primitive, aad, ciphertext, key)) + authenticate_aead(primitive, aad, ciphertext, key) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[unsafe(no_mangle)] #[inline(never)] /// Computes a diagnostic Poly1305 tag after one block using the portable backend. @@ -1055,21 +1052,19 @@ pub fn diag_poly1305_block_portable_digest(key: &[u8; 32], block: &[u8; 16], par } #[cfg(all( + rscrypto_internal, feature = "diag", target_arch = "aarch64", any(target_os = "linux", target_os = "macos") ))] /// Computes a ChaCha20-Poly1305 authenticator with the four-lane AArch64 NEON backend. -/// -/// Always returns `Some(tag)` on supported targets, where slice lengths fit the AEAD length fields. -/// The `Option` return type is retained for diagnostic API compatibility. pub fn diag_chacha20poly1305_authenticate_aead_aarch64_neon_par4( aad: &[u8], ciphertext: &[u8], key: &[u8; 32], -) -> Option<[u8; 16]> { +) -> [u8; 16] { let lengths = super::AeadByteLengths::from_usize(aad.len(), ciphertext.len()); - Some(aarch64_neon::authenticate_aead_par4(aad, ciphertext, key, lengths)) + aarch64_neon::authenticate_aead_par4(aad, ciphertext, key, lengths) } fn authenticate_aead_with( @@ -1187,19 +1182,20 @@ mod tests { let actual = super::authenticate_aead(primitive(), &aad, &ciphertext, &poly_key); assert_eq!(actual, expected); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] assert_eq!( super::diag_chacha20poly1305_authenticate_aead(&aad, &ciphertext, &poly_key), - Some(expected) + expected ); #[cfg(all( + rscrypto_internal, feature = "diag", target_arch = "aarch64", any(target_os = "linux", target_os = "macos") ))] assert_eq!( super::diag_chacha20poly1305_authenticate_aead_aarch64_neon_par4(&aad, &ciphertext, &poly_key), - Some(expected) + expected ); } diff --git a/src/aead/polyval.rs b/src/aead/polyval.rs index 50c82615..9b9cd482 100644 --- a/src/aead/polyval.rs +++ b/src/aead/polyval.rs @@ -1768,7 +1768,7 @@ pub(super) fn clmul128_reduce(a: u128, b: u128) -> u128 { /// Multiplies two little-endian POLYVAL field elements with the portable /// carryless-multiply and reduction implementation. -#[cfg(all(feature = "diag", feature = "aes-gcm-siv"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-gcm-siv"))] #[must_use] pub fn diag_polyval_reduce_portable(a: &[u8; 16], b: &[u8; 16]) -> [u8; 16] { clmul128_reduce_portable(u128::from_le_bytes(*a), u128::from_le_bytes(*b)).to_le_bytes() diff --git a/src/auth/argon2/mod.rs b/src/auth/argon2/mod.rs index 51bead73..6f5c4e2b 100644 --- a/src/auth/argon2/mod.rs +++ b/src/auth/argon2/mod.rs @@ -219,7 +219,7 @@ pub enum Argon2Error { /// The allocator refused to provide the memory matrix. AllocationFailed, /// A forced diagnostic backend is unavailable on the current host. - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] BackendUnavailable, /// Password generation parameters exceed the verifier's resource limits. #[cfg(feature = "phc-strings")] @@ -240,7 +240,7 @@ impl fmt::Display for Argon2Error { Self::AssociatedDataTooLong => "Argon2 associated data exceeds 2^32-1 bytes", Self::ResourceOverflow => "Argon2 memory matrix exceeds the target's address space", Self::AllocationFailed => "Argon2 memory-matrix allocation failed", - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] Self::BackendUnavailable => "requested Argon2 diagnostic backend is unavailable", #[cfg(all(feature = "phc-strings", feature = "getrandom"))] Self::EntropyUnavailable => "Argon2 entropy source unavailable", @@ -458,7 +458,7 @@ pub fn diag_active_kernel() -> KernelId { /// # Errors /// /// Returns [`Argon2Error`] for invalid operation inputs or output length. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub fn diag_hash_active( params: &Argon2Params, password: &[u8], @@ -474,7 +474,7 @@ pub fn diag_hash_active( /// # Errors /// /// Returns [`Argon2Error`] for invalid operation inputs or output length. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub fn diag_hash_portable( params: &Argon2Params, password: &[u8], @@ -493,6 +493,7 @@ pub fn diag_hash_portable( } #[cfg(all( + rscrypto_internal, feature = "diag", any( target_arch = "x86_64", @@ -515,7 +516,7 @@ fn diag_compress_for(kernel: KernelId) -> Result { /// # Errors /// /// Returns [`Argon2Error`] for invalid parameters. -#[cfg(all(feature = "diag", target_arch = "aarch64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "aarch64"))] pub fn diag_hash_aarch64_neon( params: &Argon2Params, password: &[u8], @@ -538,7 +539,7 @@ pub fn diag_hash_aarch64_neon( /// # Errors /// /// Returns [`Argon2Error`] for invalid parameters or when AVX2 is unavailable. -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] pub fn diag_hash_x86_avx2( params: &Argon2Params, password: &[u8], @@ -561,7 +562,7 @@ pub fn diag_hash_x86_avx2( /// # Errors /// /// Returns [`Argon2Error`] for invalid parameters or when AVX-512F plus AVX-512VL is unavailable. -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] pub fn diag_hash_x86_avx512( params: &Argon2Params, password: &[u8], @@ -584,7 +585,7 @@ pub fn diag_hash_x86_avx512( /// # Errors /// /// Returns [`Argon2Error`] for invalid parameters or when VSX is unavailable. -#[cfg(all(feature = "diag", target_arch = "powerpc64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "powerpc64"))] pub fn diag_hash_power_vsx( params: &Argon2Params, password: &[u8], @@ -607,7 +608,7 @@ pub fn diag_hash_power_vsx( /// # Errors /// /// Returns [`Argon2Error`] for invalid parameters or when the z13+ vector facility is unavailable. -#[cfg(all(feature = "diag", target_arch = "s390x"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "s390x"))] pub fn diag_hash_s390x_vector( params: &Argon2Params, password: &[u8], @@ -630,7 +631,7 @@ pub fn diag_hash_s390x_vector( /// # Errors /// /// Returns [`Argon2Error`] for invalid parameters or when the RISC-V V extension is unavailable. -#[cfg(all(feature = "diag", target_arch = "riscv64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "riscv64"))] pub fn diag_hash_riscv64_v( params: &Argon2Params, password: &[u8], @@ -653,7 +654,7 @@ pub fn diag_hash_riscv64_v( /// # Errors /// /// Returns [`Argon2Error`] for invalid parameters or when WASM SIMD128 is unavailable. -#[cfg(all(feature = "diag", target_arch = "wasm32"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "wasm32"))] pub fn diag_hash_wasm_simd128( params: &Argon2Params, password: &[u8], @@ -683,7 +684,7 @@ pub fn diag_hash_wasm_simd128( /// /// Runs one 1 KiB BlaMka compression, bypassing the full hash pipeline. /// Used by kernel microbenches and cross-kernel differential tests. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub fn diag_compress_portable( dst: &mut [u64; BLOCK_WORDS], x: &[u64; BLOCK_WORDS], @@ -695,7 +696,7 @@ pub fn diag_compress_portable( } /// Single-block compress via the aarch64 NEON kernel (diagnostic). -#[cfg(all(feature = "diag", target_arch = "aarch64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "aarch64"))] pub fn diag_compress_aarch64_neon( dst: &mut [u64; BLOCK_WORDS], x: &[u64; BLOCK_WORDS], @@ -712,7 +713,7 @@ pub fn diag_compress_aarch64_neon( /// # Panics /// /// Panics if the host does not support AVX2. -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] pub fn diag_compress_x86_avx2( dst: &mut [u64; BLOCK_WORDS], x: &[u64; BLOCK_WORDS], @@ -733,7 +734,7 @@ pub fn diag_compress_x86_avx2( /// # Panics /// /// Panics if the host does not support AVX-512F + AVX-512VL. -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] pub fn diag_compress_x86_avx512( dst: &mut [u64; BLOCK_WORDS], x: &[u64; BLOCK_WORDS], @@ -754,7 +755,7 @@ pub fn diag_compress_x86_avx512( /// # Panics /// /// Panics if the host does not support VSX. -#[cfg(all(feature = "diag", target_arch = "powerpc64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "powerpc64"))] pub fn diag_compress_power_vsx( dst: &mut [u64; BLOCK_WORDS], x: &[u64; BLOCK_WORDS], @@ -774,7 +775,7 @@ pub fn diag_compress_power_vsx( /// # Panics /// /// Panics if the host does not support the z13+ vector facility. -#[cfg(all(feature = "diag", target_arch = "s390x"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "s390x"))] pub fn diag_compress_s390x_vector( dst: &mut [u64; BLOCK_WORDS], x: &[u64; BLOCK_WORDS], @@ -794,7 +795,7 @@ pub fn diag_compress_s390x_vector( /// # Panics /// /// Panics if the host does not support the RISC-V V extension. -#[cfg(all(feature = "diag", target_arch = "riscv64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "riscv64"))] pub fn diag_compress_riscv64_v( dst: &mut [u64; BLOCK_WORDS], x: &[u64; BLOCK_WORDS], @@ -814,7 +815,7 @@ pub fn diag_compress_riscv64_v( /// # Panics /// /// Panics if the host does not support wasm SIMD128. -#[cfg(all(feature = "diag", target_arch = "wasm32"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "wasm32"))] pub fn diag_compress_wasm_simd128( dst: &mut [u64; BLOCK_WORDS], x: &[u64; BLOCK_WORDS], @@ -835,7 +836,7 @@ pub fn diag_compress_wasm_simd128( } /// Block-word count (128) — exposed for diagnostic kernel tests. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub const DIAG_BLOCK_WORDS: usize = BLOCK_WORDS; // ─── H' variable-length Blake2b helper (RFC 9106 §3.3) ────────────────────── @@ -896,7 +897,7 @@ fn h_prime(input_parts: &[&[u8]], out: &mut [u8]) { ct::zeroize(&mut v_prev); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn h_prime_diag_blake2b_portable(input_parts: &[&[u8]], out: &mut [u8]) { let out_len = out.len(); assert!(out_len > 0, "H' output length must be positive"); @@ -1000,7 +1001,7 @@ fn compute_h0( hasher.finalize() } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn compute_h0_diag_blake2b_portable( params: &Argon2Params, context: Argon2Context<'_>, @@ -1400,10 +1401,8 @@ fn fill_segment( /// Fill a single segment via a [`MatrixView`]. /// -/// Common kernel for the sequential and parallel fill paths. The body is -/// identical to the legacy `fill_segment(&mut Matrix, ...)`; the only -/// change is that block reads/writes go through [`MatrixView::block`] / -/// [`MatrixView::block_mut`] rather than through `&mut Matrix`. +/// Common kernel for the sequential and parallel fill paths. Block reads and +/// writes use [`MatrixView::block`] and [`MatrixView::block_mut`]. /// /// # Safety /// @@ -1638,7 +1637,7 @@ fn fill_slice( #[derive(Clone, Copy)] struct HashBackend { compress: CompressFn, - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] diag_blake2b: bool, } @@ -1669,13 +1668,17 @@ fn argon2_hash_with_context( out, HashBackend { compress: active_compress(), - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] diag_blake2b: false, }, ) } -#[cfg(all(feature = "diag", not(all(target_arch = "wasm32", not(target_feature = "simd128")))))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + not(all(target_arch = "wasm32", not(target_feature = "simd128"))) +))] fn argon2_hash_with_kernel( params: &Argon2Params, password: &[u8], @@ -1698,7 +1701,7 @@ fn argon2_hash_with_kernel( ) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn argon2_hash_with_kernel_diag_blake2b( params: &Argon2Params, password: &[u8], @@ -1743,7 +1746,7 @@ fn argon2_hash_with_kernel_inner( // Compute H0 only after all fallible resource acquisition is complete. let mut h0 = { - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] { if backend.diag_blake2b { compute_h0_diag_blake2b_portable(params, context, password, salt, variant, out.len()) @@ -1751,7 +1754,7 @@ fn argon2_hash_with_kernel_inner( compute_h0(params, context, password, salt, variant, out.len()) } } - #[cfg(not(feature = "diag"))] + #[cfg(not(all(rscrypto_internal, feature = "diag")))] { compute_h0(params, context, password, salt, variant, out.len()) } @@ -1762,24 +1765,24 @@ fn argon2_hash_with_kernel_inner( let mut buf = [0u8; BLOCK_SIZE]; // B[lane][0] = H'(H0 || LE32(0) || LE32(lane), BLOCK_SIZE) let lane_le = lane.to_le_bytes(); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] if backend.diag_blake2b { h_prime_diag_blake2b_portable(&[&h0, &0u32.to_le_bytes(), &lane_le], &mut buf); } else { h_prime(&[&h0, &0u32.to_le_bytes(), &lane_le], &mut buf); } - #[cfg(not(feature = "diag"))] + #[cfg(not(all(rscrypto_internal, feature = "diag")))] h_prime(&[&h0, &0u32.to_le_bytes(), &lane_le], &mut buf); matrix.set(lane, 0, block_from_bytes(&buf)); // B[lane][1] = H'(H0 || LE32(1) || LE32(lane), BLOCK_SIZE) - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] if backend.diag_blake2b { h_prime_diag_blake2b_portable(&[&h0, &1u32.to_le_bytes(), &lane_le], &mut buf); } else { h_prime(&[&h0, &1u32.to_le_bytes(), &lane_le], &mut buf); } - #[cfg(not(feature = "diag"))] + #[cfg(not(all(rscrypto_internal, feature = "diag")))] h_prime(&[&h0, &1u32.to_le_bytes(), &lane_le], &mut buf); matrix.set(lane, 1, block_from_bytes(&buf)); ct::zeroize(&mut buf); @@ -1804,13 +1807,13 @@ fn argon2_hash_with_kernel_inner( } } let mut acc_bytes = block_to_bytes(&acc); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] if backend.diag_blake2b { h_prime_diag_blake2b_portable(&[&acc_bytes], out); } else { h_prime(&[&acc_bytes], out); } - #[cfg(not(feature = "diag"))] + #[cfg(not(all(rscrypto_internal, feature = "diag")))] h_prime(&[&acc_bytes], out); // Wipe scratch diff --git a/src/auth/curve25519_edwards.rs b/src/auth/curve25519_edwards.rs index 3fe25528..aebd3430 100644 --- a/src/auth/curve25519_edwards.rs +++ b/src/auth/curve25519_edwards.rs @@ -30,26 +30,20 @@ const _: unsafe fn(&[u8; 32]) -> point::ExtendedPoint = point_avx2::scalar_mul_b #[cfg(target_arch = "x86_64")] const _: unsafe fn(&[u8; 32]) -> point::ExtendedPoint = point_avx2::scalar_mul_basepoint_ifma; -#[cfg(all(feature = "diag", feature = "ed25519"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ed25519"))] pub use point::diag_select_basepoint_cached_limb_digest as diag_ed25519_select_basepoint_cached_limb_digest; -#[cfg(all(feature = "diag", feature = "ed25519", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ed25519", target_arch = "x86_64"))] pub use point_avx2::{ diag_select_basepoint_cached_avx2_limb_digest as diag_ed25519_select_basepoint_cached_avx2_limb_digest, diag_select_basepoint_cached_ifma_limb_digest as diag_ed25519_select_basepoint_cached_ifma_limb_digest, }; /// Dispatch `[s]B` (fixed-base scalar mul) to the fastest validated CT path. -#[cfg_attr( - all( - target_arch = "x86_64", - target_os = "linux", - not(any(test, miri, feature = "portable-only")) - ), - expect( - dead_code, - reason = "x86_64 Linux library builds use the assembly fixed-base entry points" - ) -)] +#[cfg(not(all( + target_arch = "x86_64", + target_os = "linux", + not(any(test, miri, feature = "portable-only")) +)))] #[must_use] pub(crate) fn basepoint_mul_dispatch(scalar_bytes: &[u8; 32]) -> point::ExtendedPoint { #[cfg(target_arch = "x86_64")] diff --git a/src/auth/ecdsa.rs b/src/auth/ecdsa.rs index 85335979..0207fc74 100644 --- a/src/auth/ecdsa.rs +++ b/src/auth/ecdsa.rs @@ -948,23 +948,6 @@ impl EcdsaP256SecretKey { EcdsaP256PublicKey::from_secret_affine_ct(public_key_from_secret_p256(&self.0)) } - /// Derive the matching P-256 public key with caller-supplied blinding. - /// - /// The closure should fill the buffer from a CSPRNG. Blinding does not - /// change the public key; it randomizes the portable fixed-base scalar and - /// the internal projective representation used during derivation. - #[deprecated(note = "use try_public_key_blinded_with; this compatibility wrapper will be removed after one release")] - #[must_use] - pub fn public_key_blinded(&self, fill: impl FnOnce(&mut [u8; 64])) -> EcdsaP256PublicKey { - match self.try_public_key_blinded_with(|blind| { - fill(blind); - Ok::<(), core::convert::Infallible>(()) - }) { - Ok(public) => public, - Err(never) => match never {}, - } - } - /// Try to derive the matching P-256 public key with caller-supplied blinding. /// /// The filler runs against zero-initialized rscrypto-owned storage. If it @@ -999,35 +982,6 @@ impl EcdsaP256SecretKey { sign_digest_p256(&self.0, &digest) } - /// Sign a message with P-256/SHA-256 and caller-supplied blinding. - /// - /// The closure should fill the buffer from a CSPRNG. The ECDSA nonce remains - /// deterministic; the random bytes blind the internal projective `kG` point - /// and the private-scalar product. The portable backend also adds a random - /// multiple of the group order before fixed-base multiplication. On s390x, - /// independent scalar masks protect the projective and order arithmetic, - /// while a fixed-work algebraic fold reduces the wide nonce. - /// - /// # Errors - /// - /// Returns [`EcdsaError::SigningFailure`] if deterministic nonce derivation - /// reaches an invalid ECDSA scalar. - #[deprecated(note = "use try_sign_blinded_with; this compatibility wrapper will be removed after one release")] - pub fn try_sign_blinded( - &self, - message: &[u8], - fill: impl FnOnce(&mut [u8; 64]), - ) -> Result { - match self.try_sign_blinded_with(message, |blind| { - fill(blind); - Ok::<(), core::convert::Infallible>(()) - }) { - Ok(signature) => Ok(signature), - Err(EcdsaBlindedSigningError::Random(never)) => match never {}, - Err(EcdsaBlindedSigningError::Signing(err)) => Err(err), - } - } - /// Try to sign a message with P-256/SHA-256 and caller-supplied blinding. /// /// The filler runs against zero-initialized rscrypto-owned storage. If it @@ -1169,23 +1123,6 @@ impl EcdsaP384SecretKey { EcdsaP384PublicKey::from_secret_affine_ct(public_key_from_secret_p384(&self.0)) } - /// Derive the matching P-384 public key with caller-supplied blinding. - /// - /// The closure should fill the buffer from a CSPRNG. Blinding does not - /// change the public key; it randomizes the internal projective - /// representation used during derivation. - #[deprecated(note = "use try_public_key_blinded_with; this compatibility wrapper will be removed after one release")] - #[must_use] - pub fn public_key_blinded(&self, fill: impl FnOnce(&mut [u8; 96])) -> EcdsaP384PublicKey { - match self.try_public_key_blinded_with(|blind| { - fill(blind); - Ok::<(), core::convert::Infallible>(()) - }) { - Ok(public) => public, - Err(never) => match never {}, - } - } - /// Try to derive the matching P-384 public key with caller-supplied blinding. /// /// The filler runs against zero-initialized rscrypto-owned storage. If it @@ -1220,34 +1157,6 @@ impl EcdsaP384SecretKey { sign_digest_p384(&self.0, &digest) } - /// Sign a message with P-384/SHA-384 and caller-supplied blinding. - /// - /// The closure should fill the buffer from a CSPRNG. The ECDSA nonce remains - /// deterministic; the random bytes blind the internal projective `kG` point - /// and mask the private-scalar product. On s390x, independent scalar masks - /// protect the projective and order arithmetic, while a fixed-work algebraic - /// fold reduces the wide nonce. - /// - /// # Errors - /// - /// Returns [`EcdsaError::SigningFailure`] if deterministic nonce derivation - /// reaches an invalid ECDSA scalar. - #[deprecated(note = "use try_sign_blinded_with; this compatibility wrapper will be removed after one release")] - pub fn try_sign_blinded( - &self, - message: &[u8], - fill: impl FnOnce(&mut [u8; 96]), - ) -> Result { - match self.try_sign_blinded_with(message, |blind| { - fill(blind); - Ok::<(), core::convert::Infallible>(()) - }) { - Ok(signature) => Ok(signature), - Err(EcdsaBlindedSigningError::Random(never)) => match never {}, - Err(EcdsaBlindedSigningError::Signing(err)) => Err(err), - } - } - /// Try to sign a message with P-384/SHA-384 and caller-supplied blinding. /// /// The filler runs against zero-initialized rscrypto-owned storage. If it @@ -1385,28 +1294,6 @@ impl EcdsaP256Keypair { self.secret.try_sign(message) } - /// Sign a message with P-256/SHA-256 and caller-supplied blinding. - /// - /// # Errors - /// - /// Returns [`EcdsaError::SigningFailure`] if deterministic nonce derivation - /// reaches an invalid ECDSA scalar. - #[deprecated(note = "use try_sign_blinded_with; this compatibility wrapper will be removed after one release")] - pub fn try_sign_blinded( - &self, - message: &[u8], - fill: impl FnOnce(&mut [u8; 64]), - ) -> Result { - match self.try_sign_blinded_with(message, |blind| { - fill(blind); - Ok::<(), core::convert::Infallible>(()) - }) { - Ok(signature) => Ok(signature), - Err(EcdsaBlindedSigningError::Random(never)) => match never {}, - Err(EcdsaBlindedSigningError::Signing(err)) => Err(err), - } - } - /// Try to sign with P-256/SHA-256 and fallible caller-supplied blinding. #[inline] pub fn try_sign_blinded_with( @@ -1510,28 +1397,6 @@ impl EcdsaP384Keypair { self.secret.try_sign(message) } - /// Sign a message with P-384/SHA-384 and caller-supplied blinding. - /// - /// # Errors - /// - /// Returns [`EcdsaError::SigningFailure`] if deterministic nonce derivation - /// reaches an invalid ECDSA scalar. - #[deprecated(note = "use try_sign_blinded_with; this compatibility wrapper will be removed after one release")] - pub fn try_sign_blinded( - &self, - message: &[u8], - fill: impl FnOnce(&mut [u8; 96]), - ) -> Result { - match self.try_sign_blinded_with(message, |blind| { - fill(blind); - Ok::<(), core::convert::Infallible>(()) - }) { - Ok(signature) => Ok(signature), - Err(EcdsaBlindedSigningError::Random(never)) => match never {}, - Err(EcdsaBlindedSigningError::Signing(err)) => Err(err), - } - } - /// Try to sign with P-384/SHA-384 and fallible caller-supplied blinding. #[inline] pub fn try_sign_blinded_with( @@ -2673,7 +2538,7 @@ impl Jacobian { } #[cfg(any( - feature = "diag", + all(rscrypto_internal, feature = "diag"), all(target_arch = "aarch64", any(target_os = "macos", target_os = "linux")), all(target_arch = "x86_64", target_os = "linux") ))] @@ -3041,7 +2906,7 @@ fn sign_digest_with_r_product_blinded( )) } -#[cfg(any(test, feature = "diag", target_arch = "s390x"))] +#[cfg(any(test, all(rscrypto_internal, feature = "diag"), target_arch = "s390x"))] fn blinded_nonce_inverse_montgomery( curve: &Curve, nonce: &SecretScalar, @@ -3889,7 +3754,7 @@ fn select_signing_generator_affine_ct(curve: &Curve, digit: u } /// Return the P-256 signing-comb coordinates selected by `digit` as Montgomery limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] pub fn diag_ecdsa_p256_select_signing_generator_affine_limb_digest(digit: u8) -> [u64; 8] { let selected = select_signing_generator_affine_ct(&P256, usize::from(digit)); let mut out = [0u64; 8]; @@ -3899,6 +3764,7 @@ pub fn diag_ecdsa_p256_select_signing_generator_affine_limb_digest(digit: u8) -> } #[cfg(all( + rscrypto_internal, feature = "diag", feature = "ecdsa-p256", any( @@ -3921,7 +3787,7 @@ pub(crate) fn diag_zeroize_ecdsa_p256_platform_scratch(wide: [u8; 64]) -> u64 { } /// Exercise P-256 safegcd inversion so release tooling can inspect its scratch cleanup. -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -3935,7 +3801,7 @@ pub(crate) fn diag_zeroize_ecdsa_p256_safegcd_scratch(secret: [u8; 32]) -> u64 { } /// Exercise P-256 public-derivation blinding cleanup on success and partial-fill failure. -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -3955,7 +3821,7 @@ pub(crate) fn diag_zeroize_ecdsa_p256_public_blinding(value: u8, fail: bool) -> } /// Exercise P-256 signing blinding cleanup on success and partial-fill failure. -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -3975,7 +3841,7 @@ pub(crate) fn diag_zeroize_ecdsa_p256_signing_blinding(value: u8, fail: bool) -> } /// Derive the deterministic P-256 nonce for `message` and return its scalar limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] pub fn diag_ecdsa_p256_nonce_reduce_limb_digest(secret: [u8; 32], message: &[u8]) -> [u64; 4] { let secret = ZeroizingBytes::new(secret); let digest = Sha256::digest(message); @@ -3986,7 +3852,7 @@ pub fn diag_ecdsa_p256_nonce_reduce_limb_digest(secret: [u8; 32], message: &[u8] } /// Reduce a wide P-256 nonce candidate to a nonzero scalar and return its limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] pub fn diag_ecdsa_p256_reduce_wide_order_limb_digest(wide: [u8; 64]) -> [u64; 4] { let wide = ZeroizingBytes::new(wide); let nonce = SecretScalar::new(reduce_wide_order_nonzero(wide.as_array(), &P256_ORDER_MODULUS)); @@ -3994,7 +3860,7 @@ pub fn diag_ecdsa_p256_reduce_wide_order_limb_digest(wide: [u8; 64]) -> [u64; 4] } /// Return the affine limbs produced by blinded P-256 basepoint multiplication. -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] pub fn diag_ecdsa_p256_basepoint_blinded_limb_digest(secret: [u8; 32], blind: [u8; 64], message: &[u8]) -> [u64; 8] { let secret = ZeroizingBytes::new(secret); let blind = ZeroizingBytes::new(blind); @@ -4011,7 +3877,7 @@ pub fn diag_ecdsa_p256_basepoint_blinded_limb_digest(secret: [u8; 32], blind: [u } /// Run P-256 scalar signing finalization with supplied nonce material and return `r || s` limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] pub fn diag_ecdsa_p256_scalar_finish_limb_digest(secret: [u8; 32], nonce_wide: [u8; 64], message: &[u8]) -> [u64; 8] { let secret = ZeroizingBytes::new(secret); let nonce_wide = ZeroizingBytes::new(nonce_wide); @@ -4027,7 +3893,7 @@ pub fn diag_ecdsa_p256_scalar_finish_limb_digest(secret: [u8; 32], nonce_wide: [ } /// Multiply the P-256 secret scalar by a fixed public `r` and return the order-field limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] pub fn diag_ecdsa_p256_order_mul_fixed_r_limb_digest(secret: [u8; 32]) -> [u64; 4] { let secret = ZeroizingBytes::new(secret); let secret_scalar = SecretScalar::from_be_bytes(secret.as_array()); @@ -4037,7 +3903,7 @@ pub fn diag_ecdsa_p256_order_mul_fixed_r_limb_digest(secret: [u8; 32]) -> [u64; } /// Run the blinded P-256 order multiplication stage for a fixed public `r` and return its limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] pub fn diag_ecdsa_p256_order_mul_blinded_fixed_r_limb_digest(secret: [u8; 32], blind: [u8; 64]) -> [u64; 4] { let secret = ZeroizingBytes::new(secret); let blind = ZeroizingBytes::new(blind); @@ -4054,7 +3920,7 @@ pub fn diag_ecdsa_p256_order_mul_blinded_fixed_r_limb_digest(secret: [u8; 32], b } /// Derive and invert the deterministic P-256 nonce and return its Montgomery limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] pub fn diag_ecdsa_p256_nonce_inverse_limb_digest(secret: [u8; 32], message: &[u8]) -> [u64; 4] { let secret = ZeroizingBytes::new(secret); let digest = Sha256::digest(message); @@ -4070,7 +3936,7 @@ pub fn diag_ecdsa_p256_nonce_inverse_limb_digest(secret: [u8; 32], message: &[u8 } /// Derive and invert the deterministic P-256 nonce through the caller-blinded s390x path. -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] pub fn diag_ecdsa_p256_nonce_inverse_blinded_limb_digest( secret: [u8; 32], blind: [u8; 64], @@ -4087,7 +3953,7 @@ pub fn diag_ecdsa_p256_nonce_inverse_blinded_limb_digest( } /// Run the final P-256 signing multiplication with supplied nonce material and return its limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] pub fn diag_ecdsa_p256_final_multiply_limb_digest(secret: [u8; 32], nonce_wide: [u8; 64], message: &[u8]) -> [u64; 4] { let secret = ZeroizingBytes::new(secret); let nonce_wide = ZeroizingBytes::new(nonce_wide); @@ -4112,7 +3978,7 @@ pub fn diag_ecdsa_p256_final_multiply_limb_digest(secret: [u8; 32], nonce_wide: } /// Return the P-384 signing-comb coordinates selected by `digit` as Montgomery limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] pub fn diag_ecdsa_p384_select_signing_generator_affine_limb_digest(digit: u8) -> [u64; 12] { let selected = select_signing_generator_affine_ct(&P384, usize::from(digit)); let mut out = [0u64; 12]; @@ -4122,6 +3988,7 @@ pub fn diag_ecdsa_p384_select_signing_generator_affine_limb_digest(digit: u8) -> } #[cfg(all( + rscrypto_internal, feature = "diag", feature = "ecdsa-p384", target_arch = "aarch64", @@ -4142,7 +4009,7 @@ pub(crate) fn diag_zeroize_ecdsa_p384_platform_scratch(wide: [u8; 96]) -> u64 { } /// Exercise P-384 safegcd inversion so release tooling can inspect its scratch cleanup. -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -4156,7 +4023,7 @@ pub(crate) fn diag_zeroize_ecdsa_p384_safegcd_scratch(secret: [u8; 48]) -> u64 { } /// Exercise P-384 public-derivation blinding cleanup on success and partial-fill failure. -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -4176,7 +4043,7 @@ pub(crate) fn diag_zeroize_ecdsa_p384_public_blinding(value: u8, fail: bool) -> } /// Exercise P-384 signing blinding cleanup on success and partial-fill failure. -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -4196,7 +4063,7 @@ pub(crate) fn diag_zeroize_ecdsa_p384_signing_blinding(value: u8, fail: bool) -> } /// Derive the deterministic P-384 nonce for `message` and return its scalar limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] pub fn diag_ecdsa_p384_nonce_reduce_limb_digest(secret: [u8; 48], message: &[u8]) -> [u64; 6] { let secret = ZeroizingBytes::new(secret); let digest = Sha384::digest(message); @@ -4207,7 +4074,7 @@ pub fn diag_ecdsa_p384_nonce_reduce_limb_digest(secret: [u8; 48], message: &[u8] } /// Reduce a wide P-384 nonce candidate to a nonzero scalar and return its limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] pub fn diag_ecdsa_p384_reduce_wide_order_limb_digest(wide: [u8; 96]) -> [u64; 6] { let wide = ZeroizingBytes::new(wide); let nonce = SecretScalar::new(reduce_wide_order_nonzero(wide.as_array(), &P384_ORDER_MODULUS)); @@ -4215,7 +4082,7 @@ pub fn diag_ecdsa_p384_reduce_wide_order_limb_digest(wide: [u8; 96]) -> [u64; 6] } /// Return the affine limbs produced by blinded P-384 basepoint multiplication. -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] pub fn diag_ecdsa_p384_basepoint_blinded_limb_digest(secret: [u8; 48], blind: [u8; 96], message: &[u8]) -> [u64; 12] { let secret = ZeroizingBytes::new(secret); let blind = ZeroizingBytes::new(blind); @@ -4232,7 +4099,7 @@ pub fn diag_ecdsa_p384_basepoint_blinded_limb_digest(secret: [u8; 48], blind: [u } /// Derive the P-384 nonce point and return its reduced affine x-coordinate limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] pub fn diag_ecdsa_p384_basepoint_r_limb_digest(secret: [u8; 48], message: &[u8]) -> [u64; 6] { let secret = ZeroizingBytes::new(secret); let digest = Sha384::digest(message); @@ -4249,7 +4116,7 @@ pub fn diag_ecdsa_p384_basepoint_r_limb_digest(secret: [u8; 48], message: &[u8]) } /// Run P-384 scalar signing finalization with supplied nonce material and return `r || s` limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] pub fn diag_ecdsa_p384_scalar_finish_limb_digest(secret: [u8; 48], nonce_wide: [u8; 96], message: &[u8]) -> [u64; 12] { let secret = ZeroizingBytes::new(secret); let nonce_wide = ZeroizingBytes::new(nonce_wide); @@ -4265,7 +4132,7 @@ pub fn diag_ecdsa_p384_scalar_finish_limb_digest(secret: [u8; 48], nonce_wide: [ } /// Multiply the P-384 secret scalar by a fixed public `r` and return the order-field limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] pub fn diag_ecdsa_p384_order_mul_fixed_r_limb_digest(secret: [u8; 48]) -> [u64; 6] { let secret = ZeroizingBytes::new(secret); let secret_scalar = SecretScalar::from_be_bytes(secret.as_array()); @@ -4275,7 +4142,7 @@ pub fn diag_ecdsa_p384_order_mul_fixed_r_limb_digest(secret: [u8; 48]) -> [u64; } /// Derive and invert the deterministic P-384 nonce and return its Montgomery limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] pub fn diag_ecdsa_p384_nonce_inverse_limb_digest(secret: [u8; 48], message: &[u8]) -> [u64; 6] { let secret = ZeroizingBytes::new(secret); let digest = Sha384::digest(message); @@ -4291,7 +4158,7 @@ pub fn diag_ecdsa_p384_nonce_inverse_limb_digest(secret: [u8; 48], message: &[u8 } /// Derive and invert the deterministic P-384 nonce through the caller-blinded s390x path. -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] pub fn diag_ecdsa_p384_nonce_inverse_blinded_limb_digest( secret: [u8; 48], blind: [u8; 96], @@ -4308,7 +4175,7 @@ pub fn diag_ecdsa_p384_nonce_inverse_blinded_limb_digest( } /// Run the final P-384 signing multiplication with supplied nonce material and return its limbs. -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] pub fn diag_ecdsa_p384_final_multiply_limb_digest(secret: [u8; 48], nonce_wide: [u8; 96], message: &[u8]) -> [u64; 6] { let secret = ZeroizingBytes::new(secret); let nonce_wide = ZeroizingBytes::new(nonce_wide); diff --git a/src/auth/ed25519.rs b/src/auth/ed25519.rs index 980cc4a7..fbf35c43 100644 --- a/src/auth/ed25519.rs +++ b/src/auth/ed25519.rs @@ -427,7 +427,7 @@ pub struct Ed25519Keypair { expanded: hash::ExpandedSecret, } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[derive(Clone, Copy, Debug)] pub struct DiagEd25519VerifyScalars { @@ -686,7 +686,7 @@ fn hash_challenge(r_bytes: &[u8; PUBLIC_KEY_LENGTH], public_key: &[u8; PUBLIC_KE Sha512::digest_64_byte_prefix(&prefix, message) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[must_use] pub fn diag_ed25519_verify_scalars( @@ -708,7 +708,7 @@ pub fn diag_ed25519_verify_scalars( }) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[must_use] pub fn diag_ed25519_verify_challenge_reduce_digest( @@ -722,7 +722,7 @@ pub fn diag_ed25519_verify_challenge_reduce_digest( scalar::to_bytes(&challenge) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[must_use] pub fn diag_ed25519_verify_public_decode_digest(public_key: &[u8; PUBLIC_KEY_LENGTH]) -> [u8; PUBLIC_KEY_LENGTH] { @@ -731,7 +731,7 @@ pub fn diag_ed25519_verify_public_decode_digest(public_key: &[u8; PUBLIC_KEY_LEN .unwrap_or_default() } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[must_use] pub fn diag_ed25519_verify_r_decode_digest(r_bytes: &[u8; PUBLIC_KEY_LENGTH]) -> [u8; PUBLIC_KEY_LENGTH] { @@ -741,7 +741,7 @@ pub fn diag_ed25519_verify_r_decode_digest(r_bytes: &[u8; PUBLIC_KEY_LENGTH]) -> .unwrap_or_default() } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[must_use] pub fn diag_ed25519_verify_portable_double_scalar_digest( @@ -756,6 +756,7 @@ pub fn diag_ed25519_verify_portable_double_scalar_digest( } #[cfg(all( + rscrypto_internal, feature = "diag", target_arch = "aarch64", any(target_os = "macos", target_os = "linux"), diff --git a/src/auth/ed25519/field_avx2.rs b/src/auth/ed25519/field_avx2.rs index 7c64d58a..c57c7e36 100644 --- a/src/auth/ed25519/field_avx2.rs +++ b/src/auth/ed25519/field_avx2.rs @@ -19,8 +19,8 @@ //! //! # Arithmetic convention //! -//! Field arithmetic is modular math (mod 2²⁵⁵ − 19). Per CLAUDE.md rules, -//! `wrapping_*` is the correct choice for intentional modular arithmetic. +//! Field arithmetic is modular math (mod 2²⁵⁵ − 19). `wrapping_*` expresses +//! intentional modular arithmetic. #[cfg(target_arch = "x86_64")] use core::arch::x86_64::*; diff --git a/src/auth/ed25519/point.rs b/src/auth/ed25519/point.rs index f9836402..2d72752d 100644 --- a/src/auth/ed25519/point.rs +++ b/src/auth/ed25519/point.rs @@ -522,7 +522,7 @@ fn select_signed_cached(table: &[CachedPoint; 8], digit: i8) -> CachedPoint { } /// Select one signed digit from the first portable basepoint table and return its field limbs. -#[cfg(all(feature = "diag", feature = "ed25519"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ed25519"))] #[inline(always)] pub fn diag_select_basepoint_cached_limb_digest(digit: i8) -> [u64; 15] { let selected = select_signed_cached(&BASEPOINT_RADIX16_TABLE[0], digit); diff --git a/src/auth/ed25519/point_avx2.rs b/src/auth/ed25519/point_avx2.rs index 571cb43d..3aa75180 100644 --- a/src/auth/ed25519/point_avx2.rs +++ b/src/auth/ed25519/point_avx2.rs @@ -472,7 +472,7 @@ pub(crate) unsafe fn scalar_mul_basepoint_avx2(scalar_bytes: &[u8; 32]) -> Exten /// # Safety /// /// Caller must ensure AVX2 is available. -#[cfg(all(feature = "diag", feature = "ed25519"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ed25519"))] #[inline] #[target_feature(enable = "avx2")] pub unsafe fn diag_select_basepoint_cached_avx2_limb_digest(digit: i8) -> [u64; 20] { @@ -889,7 +889,7 @@ pub(crate) unsafe fn scalar_mul_basepoint_ifma(scalar_bytes: &[u8; 32]) -> Exten /// # Safety /// /// Caller must ensure AVX2, AVX-512 IFMA, and AVX-512 VL are available. -#[cfg(all(feature = "diag", feature = "ed25519"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ed25519"))] #[inline] #[target_feature(enable = "avx2,avx512ifma,avx512vl")] pub unsafe fn diag_select_basepoint_cached_ifma_limb_digest(digit: i8) -> [u64; 20] { diff --git a/src/auth/hkdf.rs b/src/auth/hkdf.rs index b129d0b8..e21a8bb7 100644 --- a/src/auth/hkdf.rs +++ b/src/auth/hkdf.rs @@ -287,7 +287,7 @@ impl HkdfSha256 { &self.prk } - #[cfg(any(test, feature = "diag"))] + #[cfg(any(test, all(rscrypto_internal, feature = "diag")))] pub(crate) fn extract_with_compress_for_test( salt: &[u8], input_key_material: &[u8], @@ -319,7 +319,7 @@ impl HkdfSha256 { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[unsafe(no_mangle)] #[inline(never)] /// Derive a portable HKDF-SHA256 diagnostic output using fixed `b"salt"` and `b"info"` inputs. @@ -526,7 +526,7 @@ impl HkdfSha384 { &self.prk } - #[cfg(any(test, feature = "diag"))] + #[cfg(any(test, all(rscrypto_internal, feature = "diag")))] pub(crate) fn extract_with_compress_for_test( salt: &[u8], input_key_material: &[u8], @@ -558,7 +558,7 @@ impl HkdfSha384 { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[unsafe(no_mangle)] #[inline(never)] /// Derive a portable HKDF-SHA384 diagnostic output using fixed `b"salt"` and `b"info"` inputs. @@ -746,7 +746,7 @@ impl HkdfSha512 { &self.prk } - #[cfg(any(test, feature = "diag"))] + #[cfg(any(test, all(rscrypto_internal, feature = "diag")))] pub(crate) fn extract_with_compress_for_test( salt: &[u8], input_key_material: &[u8], @@ -778,7 +778,7 @@ impl HkdfSha512 { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[unsafe(no_mangle)] #[inline(never)] /// Derive a portable HKDF-SHA512 diagnostic output using fixed `b"salt"` and `b"info"` inputs. diff --git a/src/auth/hmac.rs b/src/auth/hmac.rs index 8fcf8605..27d2f0fa 100644 --- a/src/auth/hmac.rs +++ b/src/auth/hmac.rs @@ -236,7 +236,7 @@ impl HmacSha256 { } } - #[cfg(any(feature = "diag", all(test, feature = "hkdf")))] + #[cfg(any(all(rscrypto_internal, feature = "diag"), all(test, feature = "hkdf")))] pub(crate) fn new_with_compress_for_test( key: &[u8], compress: crate::hashes::crypto::sha256::kernels::CompressBlocksFn, @@ -270,7 +270,7 @@ impl HmacSha256 { } } - #[cfg(any(feature = "diag", all(test, feature = "hkdf")))] + #[cfg(any(all(rscrypto_internal, feature = "diag"), all(test, feature = "hkdf")))] pub(crate) fn mac_with_compress_for_test( key: &[u8], data: &[u8], @@ -282,7 +282,7 @@ impl HmacSha256 { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Compare the portable HMAC-SHA256 tag for the diagnostic message `b"binsec"` with `expected`. pub fn diag_hmac_sha256_verify_portable( key: &[u8; SHA256_TAG_SIZE], @@ -295,7 +295,7 @@ pub fn diag_hmac_sha256_verify_portable( ct::fixed_eq(&tag, expected) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Compare the leading 64 bits of the portable HMAC-SHA256 diagnostic tag with `expected`. pub fn diag_hmac_sha256_verify_truncated_64_portable( key: &[u8; SHA256_TAG_SIZE], @@ -309,7 +309,7 @@ pub fn diag_hmac_sha256_verify_truncated_64_portable( ct::fixed_eq(&tag, expected) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -569,7 +569,7 @@ impl HmacSha384 { ::verify_tag(key, data, expected) } - #[cfg(any(test, feature = "diag"))] + #[cfg(any(test, all(rscrypto_internal, feature = "diag")))] pub(crate) fn new_with_compress_for_test( key: &[u8], compress: crate::hashes::crypto::sha384::kernels::CompressBlocksFn, @@ -603,7 +603,7 @@ impl HmacSha384 { } } - #[cfg(any(test, feature = "diag"))] + #[cfg(any(test, all(rscrypto_internal, feature = "diag")))] pub(crate) fn mac_with_compress_for_test( key: &[u8], data: &[u8], @@ -615,7 +615,7 @@ impl HmacSha384 { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Compare the portable HMAC-SHA384 tag for the diagnostic message `b"binsec"` with `expected`. pub fn diag_hmac_sha384_verify_portable( key: &[u8; SHA384_TAG_SIZE], @@ -860,7 +860,7 @@ impl HmacSha512 { ::verify_tag(key, data, expected) } - #[cfg(any(test, feature = "diag"))] + #[cfg(any(test, all(rscrypto_internal, feature = "diag")))] pub(crate) fn new_with_compress_for_test( key: &[u8], compress: crate::hashes::crypto::sha512::kernels::CompressBlocksFn, @@ -894,7 +894,7 @@ impl HmacSha512 { } } - #[cfg(any(test, feature = "diag"))] + #[cfg(any(test, all(rscrypto_internal, feature = "diag")))] pub(crate) fn mac_with_compress_for_test( key: &[u8], data: &[u8], @@ -906,7 +906,7 @@ impl HmacSha512 { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Compare the portable HMAC-SHA512 tag for the diagnostic message `b"binsec"` with `expected`. pub fn diag_hmac_sha512_verify_portable( key: &[u8; SHA512_TAG_SIZE], diff --git a/src/auth/hmac_sha3.rs b/src/auth/hmac_sha3.rs index d60e10a3..eb97fa24 100644 --- a/src/auth/hmac_sha3.rs +++ b/src/auth/hmac_sha3.rs @@ -185,7 +185,7 @@ define_hmac_sha3!( "SHA3-512" ); -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] diff --git a/src/auth/mlkem.rs b/src/auth/mlkem.rs index 763bbecf..ce3a9d2a 100644 --- a/src/auth/mlkem.rs +++ b/src/auth/mlkem.rs @@ -901,7 +901,7 @@ macro_rules! mlkem_diag_keygen_secret_noise { #[doc = concat!("Diagnostic digest for ", $doc_name, " PKE key generation with fixed public matrix seed.")] /// This is only available under `diag`; production key generation continues to derive /// both seeds through the FIPS 203 `G(d || k)` expansion. - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] #[inline] #[must_use] pub fn $name(rho: [u8; ML_KEM_SEED_SIZE], sigma: [u8; ML_KEM_SEED_SIZE]) -> [u8; ML_KEM_SHARED_SECRET_SIZE] { @@ -928,7 +928,7 @@ mlkem_diag_keygen_secret_noise!( "ML-KEM-1024" ); -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[inline] #[must_use] @@ -942,7 +942,13 @@ pub fn diag_mlkem_ntt_input_digest(poly: [u16; 256]) -> u16 { /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] #[doc(hidden)] #[inline] #[must_use] @@ -951,7 +957,7 @@ pub unsafe fn diag_mlkem_s390x_ntt_input_digest(poly: [u16; 256]) -> u16 { unsafe { portable::diag_s390x_ntt_input_digest(poly) } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[inline] #[must_use] @@ -965,7 +971,13 @@ pub fn diag_mlkem_inverse_ntt_montgomery_product_input_digest(poly: [u16; 256]) /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] #[doc(hidden)] #[inline] #[must_use] @@ -974,7 +986,7 @@ pub unsafe fn diag_mlkem_s390x_inverse_ntt_montgomery_product_input_digest(poly: unsafe { portable::diag_s390x_inverse_ntt_montgomery_product_input_digest(poly) } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[inline] #[must_use] @@ -982,7 +994,7 @@ pub fn diag_mlkem_multiply_ntts_add_assign_input_digest(a: [u16; 256], b: [u16; portable::diag_multiply_ntts_add_assign_input_digest(a, b, acc) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[inline] #[must_use] @@ -994,7 +1006,7 @@ pub fn diag_mlkem768_multiply_ntts_accumulate_input_digest( portable::diag_multiply_ntts_accumulate_k3_input_digest(a, b, acc) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[inline] #[must_use] @@ -1006,7 +1018,7 @@ pub fn diag_mlkem1024_multiply_ntts_accumulate_input_digest( portable::diag_multiply_ntts_accumulate_k4_input_digest(a, b, acc) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[inline] #[must_use] @@ -1014,7 +1026,7 @@ pub fn diag_mlkem_to_montgomery_product_domain_input_digest(poly: [u16; 256]) -> portable::diag_to_montgomery_product_domain_input_digest(poly) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[inline] #[must_use] @@ -1028,7 +1040,13 @@ pub fn diag_mlkem_from_montgomery_product_domain_input_digest(poly: [u16; 256]) /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] #[doc(hidden)] #[inline] #[must_use] @@ -1043,7 +1061,13 @@ pub unsafe fn diag_mlkem_s390x_to_montgomery_product_domain_input_digest(poly: [ /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] #[doc(hidden)] #[inline] #[must_use] @@ -1058,7 +1082,13 @@ pub unsafe fn diag_mlkem_s390x_from_montgomery_product_domain_input_digest(poly: /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] #[doc(hidden)] #[inline] #[must_use] @@ -1077,7 +1107,13 @@ pub unsafe fn diag_mlkem_s390x_multiply_ntts_add_assign_input_digest( /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] #[doc(hidden)] #[inline] #[must_use] @@ -1096,7 +1132,13 @@ pub unsafe fn diag_mlkem_s390x_multiply_ntts_accumulate_k3_input_digest( /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] #[doc(hidden)] #[inline] #[must_use] @@ -1109,7 +1151,7 @@ pub unsafe fn diag_mlkem_s390x_multiply_ntts_accumulate_k4_input_digest( unsafe { portable::diag_s390x_multiply_ntts_accumulate_k4_input_digest(a, b, acc) } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[inline] #[must_use] @@ -1123,7 +1165,13 @@ pub fn diag_mlkem_compress_decompress_values_digest(values: [u16; 4]) -> u16 { /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] #[doc(hidden)] #[inline] #[must_use] diff --git a/src/auth/mlkem/portable.rs b/src/auth/mlkem/portable.rs index a7f64fbe..fcb3ff9c 100644 --- a/src/auth/mlkem/portable.rs +++ b/src/auth/mlkem/portable.rs @@ -828,7 +828,7 @@ pub(super) fn keygen_1024(random: &[u8; 64]) -> ([u8; 1568], [u8; 3168]) { keygen::<4, 4, 128, 1536, 1568, 3168>(random) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub(super) fn diag_keygen_secret_noise_digest< const K: usize, const ETA1_RANDOM_BYTES: usize, @@ -850,7 +850,7 @@ pub(super) fn diag_keygen_secret_noise_digest< digest } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub(super) fn diag_ntt_input_digest(mut poly: Poly) -> u16 { ntt(&mut poly); let digest = diag_fold_poly(&poly); @@ -864,7 +864,13 @@ pub(super) fn diag_ntt_input_digest(mut poly: Poly) -> u16 { /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] pub(super) unsafe fn diag_s390x_ntt_input_digest(mut poly: Poly) -> u16 { // SAFETY: Direct z/Vector diagnostic call because: // 1. The caller guarantees the s390x z/Vector facility is available. @@ -879,7 +885,7 @@ pub(super) unsafe fn diag_s390x_ntt_input_digest(mut poly: Poly) -> u16 { digest } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub(super) fn diag_inverse_ntt_montgomery_product_input_digest(mut poly: Poly) -> u16 { inverse_ntt_montgomery_product(&mut poly); let digest = diag_fold_poly(&poly); @@ -893,7 +899,13 @@ pub(super) fn diag_inverse_ntt_montgomery_product_input_digest(mut poly: Poly) - /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] pub(super) unsafe fn diag_s390x_inverse_ntt_montgomery_product_input_digest(mut poly: Poly) -> u16 { // SAFETY: Direct z/Vector diagnostic call because: // 1. The caller guarantees the s390x z/Vector facility is available. @@ -909,7 +921,7 @@ pub(super) unsafe fn diag_s390x_inverse_ntt_montgomery_product_input_digest(mut digest } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub(super) fn diag_multiply_ntts_add_assign_input_digest(a: Poly, b: Poly, mut acc: Poly) -> u16 { multiply_ntts_add_assign(&mut acc, &a, &b); let digest = diag_fold_poly(&acc); @@ -917,7 +929,7 @@ pub(super) fn diag_multiply_ntts_add_assign_input_digest(a: Poly, b: Poly, mut a digest } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub(super) fn diag_multiply_ntts_accumulate_k3_input_digest( mut a: PolyVec<3>, mut b: PolyVec<3>, @@ -931,7 +943,7 @@ pub(super) fn diag_multiply_ntts_accumulate_k3_input_digest( digest } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub(super) fn diag_multiply_ntts_accumulate_k4_input_digest( mut a: PolyVec<4>, mut b: PolyVec<4>, @@ -945,7 +957,7 @@ pub(super) fn diag_multiply_ntts_accumulate_k4_input_digest( digest } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub(super) fn diag_to_montgomery_product_domain_input_digest(mut poly: Poly) -> u16 { poly_to_montgomery_product_domain(&mut poly); let digest = diag_fold_poly(&poly); @@ -953,7 +965,7 @@ pub(super) fn diag_to_montgomery_product_domain_input_digest(mut poly: Poly) -> digest } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub(super) fn diag_from_montgomery_product_domain_input_digest(mut poly: Poly) -> u16 { poly_from_montgomery_product_domain(&mut poly); let digest = diag_fold_poly(&poly); @@ -967,7 +979,13 @@ pub(super) fn diag_from_montgomery_product_domain_input_digest(mut poly: Poly) - /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] pub(super) unsafe fn diag_s390x_to_montgomery_product_domain_input_digest(mut poly: Poly) -> u16 { // SAFETY: Direct z/Vector diagnostic call because: // 1. The caller guarantees the s390x z/Vector facility is available. @@ -988,7 +1006,13 @@ pub(super) unsafe fn diag_s390x_to_montgomery_product_domain_input_digest(mut po /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] pub(super) unsafe fn diag_s390x_from_montgomery_product_domain_input_digest(mut poly: Poly) -> u16 { // SAFETY: Direct z/Vector diagnostic call because: // 1. The caller guarantees the s390x z/Vector facility is available. @@ -1010,7 +1034,13 @@ pub(super) unsafe fn diag_s390x_from_montgomery_product_domain_input_digest(mut /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] pub(super) unsafe fn diag_s390x_multiply_ntts_add_assign_input_digest(a: Poly, b: Poly, mut acc: Poly) -> u16 { // SAFETY: Direct z/Vector diagnostic call because: // 1. The caller guarantees the s390x z/Vector facility is available. @@ -1032,7 +1062,13 @@ pub(super) unsafe fn diag_s390x_multiply_ntts_add_assign_input_digest(a: Poly, b /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] pub(super) unsafe fn diag_s390x_multiply_ntts_accumulate_k3_input_digest( mut a: PolyVec<3>, mut b: PolyVec<3>, @@ -1060,7 +1096,13 @@ pub(super) unsafe fn diag_s390x_multiply_ntts_accumulate_k3_input_digest( /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] pub(super) unsafe fn diag_s390x_multiply_ntts_accumulate_k4_input_digest( mut a: PolyVec<4>, mut b: PolyVec<4>, @@ -1082,7 +1124,7 @@ pub(super) unsafe fn diag_s390x_multiply_ntts_accumulate_k4_input_digest( digest } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] pub(super) fn diag_compress_decompress_values_digest(values: [u16; 4]) -> u16 { let compressed = compress_values_4::<10>(values); let decompressed = decompress_values_4::<10>(compressed); @@ -1119,7 +1161,13 @@ pub(super) fn diag_compress_decompress_values_digest(values: [u16; 4]) -> u16 { /// /// The caller must ensure the CPU supports the s390x z/Vector facility before /// executing this function. -#[cfg(all(feature = "diag", target_arch = "s390x", not(miri), not(feature = "portable-only")))] +#[cfg(all( + rscrypto_internal, + feature = "diag", + target_arch = "s390x", + not(miri), + not(feature = "portable-only") +))] pub(super) unsafe fn diag_s390x_compress_decompress_values_digest(values: [u16; 4]) -> u16 { // SAFETY: Direct z/Vector diagnostic calls because: // 1. The caller guarantees the s390x z/Vector facility is available. @@ -1168,13 +1216,12 @@ fn fill_diag_seed(out: &mut [u8; SEED_BYTES], seed: u8) { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[inline(never)] fn diag_fold_poly(poly: &Poly) -> u16 { let mut acc = 0u16; - for (i, &coeff) in poly.iter().enumerate() { - let i = u16::try_from(i).expect("ML-KEM polynomial index fits in u16"); - acc ^= coeff.wrapping_mul(i.wrapping_add(1)); + for &coeff in poly { + acc ^= coeff; } acc } @@ -5816,7 +5863,7 @@ fn signed_to_mod_q_s16x4(value: int16x4_t) -> uint16x4_t { #[cfg(all( target_arch = "aarch64", - any(test, feature = "diag"), + any(test, all(rscrypto_internal, feature = "diag")), not(miri), not(feature = "portable-only") ))] @@ -6373,7 +6420,7 @@ fn base_case_multiply_normal_reference(a0: u16, a1: u16, b0: u16, b1: u16, gamma #[cfg(any( test, - feature = "diag", + all(rscrypto_internal, feature = "diag"), not(all(target_arch = "aarch64", not(miri), not(feature = "portable-only"))) ))] fn poly_to_montgomery_product_domain(poly: &mut Poly) { diff --git a/src/auth/mod.rs b/src/auth/mod.rs index 4ad56d02..8c8d6652 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -275,9 +275,9 @@ where pub use argon2::{Argon2Context, Argon2Error, Argon2Params, Argon2d, Argon2i, Argon2id}; #[cfg(all(feature = "argon2", feature = "phc-strings"))] pub use argon2::{Argon2VerificationLimits, Argon2idPassword}; -#[cfg(all(feature = "diag", feature = "ed25519"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ed25519"))] pub use curve25519_edwards::diag_ed25519_select_basepoint_cached_limb_digest; -#[cfg(all(feature = "diag", feature = "ed25519", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ed25519", target_arch = "x86_64"))] pub use curve25519_edwards::{ diag_ed25519_select_basepoint_cached_avx2_limb_digest, diag_ed25519_select_basepoint_cached_ifma_limb_digest, }; @@ -287,7 +287,7 @@ pub use ecdsa::{EcdsaBlindedSigningError, EcdsaError, EcdsaKeyGenerationError}; pub use ecdsa::{EcdsaP256Keypair, EcdsaP256PublicKey, EcdsaP256SecretKey, EcdsaP256Signature}; #[cfg(feature = "ecdsa-p384")] pub use ecdsa::{EcdsaP384Keypair, EcdsaP384PublicKey, EcdsaP384SecretKey, EcdsaP384Signature}; -#[cfg(all(feature = "diag", feature = "ecdsa-p256"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p256"))] pub use ecdsa::{ diag_ecdsa_p256_basepoint_blinded_limb_digest, diag_ecdsa_p256_final_multiply_limb_digest, diag_ecdsa_p256_nonce_inverse_blinded_limb_digest, diag_ecdsa_p256_nonce_inverse_limb_digest, @@ -295,7 +295,7 @@ pub use ecdsa::{ diag_ecdsa_p256_order_mul_fixed_r_limb_digest, diag_ecdsa_p256_reduce_wide_order_limb_digest, diag_ecdsa_p256_scalar_finish_limb_digest, diag_ecdsa_p256_select_signing_generator_affine_limb_digest, }; -#[cfg(all(feature = "diag", feature = "ecdsa-p384"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ecdsa-p384"))] pub use ecdsa::{ diag_ecdsa_p384_basepoint_blinded_limb_digest, diag_ecdsa_p384_basepoint_r_limb_digest, diag_ecdsa_p384_final_multiply_limb_digest, diag_ecdsa_p384_nonce_inverse_blinded_limb_digest, @@ -304,6 +304,7 @@ pub use ecdsa::{ diag_ecdsa_p384_scalar_finish_limb_digest, diag_ecdsa_p384_select_signing_generator_affine_limb_digest, }; #[cfg(all( + rscrypto_internal, feature = "diag", feature = "ed25519", target_arch = "aarch64", @@ -312,7 +313,7 @@ pub use ecdsa::{ not(miri) ))] pub use ed25519::diag_ed25519_verify_aarch64_asm_double_scalar_digest; -#[cfg(all(feature = "diag", feature = "ed25519"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ed25519"))] pub use ed25519::{ DiagEd25519VerifyScalars, diag_ed25519_verify_challenge_reduce_digest, diag_ed25519_verify_portable_double_scalar_digest, diag_ed25519_verify_public_decode_digest, @@ -322,11 +323,11 @@ pub use ed25519::{ pub use ed25519::{Ed25519Keypair, Ed25519PublicKey, Ed25519SecretKey, Ed25519Signature}; #[cfg(feature = "hkdf")] pub use hkdf::{HkdfOutputLengthError, HkdfSha256, HkdfSha384, HkdfSha512}; -#[cfg(all(feature = "diag", feature = "hkdf"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "hkdf"))] pub use hkdf::{diag_hkdf_sha256_derive_portable, diag_hkdf_sha384_derive_portable, diag_hkdf_sha512_derive_portable}; #[cfg(feature = "hmac")] pub use hmac::{HmacSha256, HmacSha256Tag, HmacSha384, HmacSha384Tag, HmacSha512, HmacSha512Tag}; -#[cfg(all(feature = "diag", feature = "hmac"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "hmac"))] pub use hmac::{ diag_hmac_sha256_verify_portable, diag_hmac_sha256_verify_truncated_64_portable, diag_hmac_sha384_verify_portable, diag_hmac_sha512_verify_portable, @@ -346,7 +347,7 @@ pub use mlkem::{ MlKem1024, MlKem1024Ciphertext, MlKem1024DecapsulationKey, MlKem1024EncapsulationKey, MlKem1024PreparedDecapsulationKey, MlKem1024PreparedEncapsulationKey, MlKem1024SharedSecret, MlKemError, }; -#[cfg(all(feature = "diag", feature = "ml-kem"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ml-kem"))] pub use mlkem::{ diag_mlkem_compress_decompress_values_digest, diag_mlkem_from_montgomery_product_domain_input_digest, diag_mlkem_inverse_ntt_montgomery_product_input_digest, diag_mlkem_multiply_ntts_add_assign_input_digest, @@ -356,6 +357,7 @@ pub use mlkem::{ diag_mlkem1024_multiply_ntts_accumulate_input_digest, }; #[cfg(all( + rscrypto_internal, feature = "diag", feature = "p256-ecdh", any( @@ -372,7 +374,7 @@ pub use p256_ecdh::diag_p256_ecdh_select_window_limb_digest; pub use p256_ecdh::{P256EphemeralSecret, P256KeyGenerationError, P256PublicKey, P256PublicKeyError, P256SharedSecret}; #[cfg(feature = "pbkdf2")] pub use pbkdf2::{Pbkdf2Error, Pbkdf2Params, Pbkdf2Sha256, Pbkdf2Sha512, Pbkdf2VerifyPolicy}; -#[cfg(all(feature = "diag", feature = "pbkdf2"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "pbkdf2"))] pub use pbkdf2::{diag_pbkdf2_sha256_verify_portable, diag_pbkdf2_sha512_verify_portable}; #[cfg(feature = "poly1305")] pub use poly1305::{Poly1305, Poly1305OneTimeKey, Poly1305Tag}; @@ -384,7 +386,7 @@ pub use rsa::{ RsaPublicKey, RsaPublicKeyPolicy, RsaPublicOpError, RsaPublicScratch, RsaSignatureProfile, RsaSignatureSigner, RsaSignatureVerifier, RsaTlsSignatureSchemes, RsaX509PublicKey, RsaX509PublicKeyAlgorithm, }; -#[cfg(all(feature = "rsa", feature = "diag"))] +#[cfg(all(feature = "rsa", all(rscrypto_internal, feature = "diag")))] pub use rsa::{ diag_rsa_blinding_factor_inverse_with_scratch, diag_rsa_import_pkcs8_private_key_der_stage, diag_rsa_private_component_validation_32, diag_rsa_private_exponentiate_fixed_width, @@ -398,6 +400,6 @@ pub use scrypt::{ScryptPassword, ScryptVerificationLimits}; #[cfg(feature = "x25519")] pub use x25519::{X25519Error, X25519PublicKey, X25519SecretKey, X25519SharedSecret}; -#[cfg(all(feature = "diag", feature = "x25519"))] -pub use crate::backend::curve25519::diag_curve25519_conditional_swap; +#[cfg(all(rscrypto_internal, feature = "diag", feature = "x25519"))] +pub use crate::backend::curve25519_swap::diag_curve25519_conditional_swap; pub use crate::traits::Mac; diff --git a/src/auth/p256_ecdh.rs b/src/auth/p256_ecdh.rs index 2344164d..7c30594d 100644 --- a/src/auth/p256_ecdh.rs +++ b/src/auth/p256_ecdh.rs @@ -377,6 +377,7 @@ impl Drop for P256SharedSecret { /// Return the production P-256 secret-window selection as Montgomery limbs. #[cfg(all( + rscrypto_internal, feature = "diag", any( feature = "portable-only", @@ -393,7 +394,7 @@ pub fn diag_p256_ecdh_select_window_limb_digest(digit: u8) -> [u64; 8] { } /// Exercise P-256 ECDH candidate cleanup on success and partial-fill failure. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -410,7 +411,7 @@ pub(crate) fn diag_zeroize_p256_ecdh_generation(value: u8, fail: bool) -> u8 { } /// Exercise P-256 ECDH scalar, projective-state, and shared-secret cleanup. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] diff --git a/src/auth/p256_portable.rs b/src/auth/p256_portable.rs index ee46a67d..e548446f 100644 --- a/src/auth/p256_portable.rs +++ b/src/auth/p256_portable.rs @@ -1088,7 +1088,7 @@ pub(super) fn agree(bytes: &[u8; FIELD_BYTES], public: PublicPoint) -> [u8; FIEL /// Return the production P-256 window-table selection as Montgomery limbs. #[cfg(all( feature = "p256-ecdh", - feature = "diag", + all(rscrypto_internal, feature = "diag"), any( feature = "portable-only", miri, diff --git a/src/auth/pbkdf2.rs b/src/auth/pbkdf2.rs index 79eb9d92..cc2d044c 100644 --- a/src/auth/pbkdf2.rs +++ b/src/auth/pbkdf2.rs @@ -467,27 +467,6 @@ macro_rules! define_pbkdf2_sha2 { ) } - /// Verify `expected` after a full public-length comparison using an - /// explicit lower-bound password policy. - /// - /// This compatibility method does not impose an upper work limit. Use - /// [`verify_with_policy_bounded`](Self::verify_with_policy_bounded) when - /// the iteration count comes from an untrusted password record. - /// - /// Generated-code timing claims are configuration- and release-evidence-bound; - /// see `ct.toml`. - #[must_use = "password verification must be checked; a dropped Result silently accepts the wrong password"] - pub fn verify_with_policy( - &self, - salt: &[u8], - iterations: u32, - expected: &[u8], - policy: &Pbkdf2VerifyPolicy, - ) -> Result<(), VerificationError> { - let params = Self::params_with_policy(salt, iterations, policy).map_err(|_| VerificationError::new())?; - self.verify_primitive(params.salt(), params.iterations(), expected) - } - /// Verify `expected` under an explicit lower-bound policy and /// caller-selected verification work limit. #[must_use = "password verification must be checked; a dropped Result silently accepts the wrong password"] @@ -517,7 +496,7 @@ macro_rules! define_pbkdf2_sha2 { /// /// This is the primitive/test-vector verification path. Stored password /// verification should use [`verify`](Self::verify), - /// [`verify_with_policy`](Self::verify_with_policy), or + /// [`verify_with_policy_bounded`](Self::verify_with_policy_bounded), or /// [`verify_password`](Self::verify_password). #[must_use = "password verification must be checked; a dropped Result silently accepts the wrong password"] pub fn verify_primitive(&self, salt: &[u8], iterations: u32, expected: &[u8]) -> Result<(), VerificationError> { @@ -642,25 +621,6 @@ macro_rules! define_pbkdf2_sha2 { ) } - /// Verify a password in one shot using an explicit lower-bound password - /// policy. - /// - /// This compatibility method does not impose an upper work limit. Use - /// [`verify_password_with_policy_bounded`](Self::verify_password_with_policy_bounded) - /// when the iteration count comes from an untrusted password record. - #[inline] - #[must_use = "password verification must be checked; a dropped Result silently accepts the wrong password"] - pub fn verify_password_with_policy( - password: &[u8], - salt: &[u8], - iterations: u32, - expected: &[u8], - policy: &Pbkdf2VerifyPolicy, - ) -> Result<(), VerificationError> { - let params = Self::params_with_policy(salt, iterations, policy).map_err(|_| VerificationError::new())?; - Self::new(password).verify_primitive(params.salt(), params.iterations(), expected) - } - /// Verify a password in one shot under an explicit lower-bound policy /// and caller-selected verification work limit. #[inline] @@ -681,7 +641,7 @@ macro_rules! define_pbkdf2_sha2 { /// /// This is the primitive/test-vector verification path. Stored password /// verification should use [`verify_password`](Self::verify_password) or - /// [`verify_password_with_policy`](Self::verify_password_with_policy). + /// [`verify_password_with_policy_bounded`](Self::verify_password_with_policy_bounded). #[inline] #[must_use = "password verification must be checked; a dropped Result silently accepts the wrong password"] pub fn verify_password_primitive( @@ -693,8 +653,8 @@ macro_rules! define_pbkdf2_sha2 { Self::new(password).verify_primitive(salt, iterations, expected) } - /// Test-only: build with a specific digest compress function. - #[cfg(any(test, feature = "diag"))] + /// Build with a specific digest compress function for tests and internal proofs. + #[cfg(any(test, all(rscrypto_internal, feature = "diag")))] pub(crate) fn new_with_compress_for_test(password: &[u8], compress: $compress_ty) -> Self { let mut key_block = [0u8; $block_size_const]; if password.len() > $block_size_const { @@ -779,8 +739,8 @@ define_pbkdf2_sha2! { } } -/// Test-only: one-shot SHA-256 digest using a specific compress function. -#[cfg(any(test, feature = "diag"))] +/// One-shot SHA-256 digest with a specific compress function for tests and internal proofs. +#[cfg(any(test, all(rscrypto_internal, feature = "diag")))] fn sha256_oneshot_with_compress(data: &[u8], compress: Sha256CompressBlocksFn) -> [u8; SHA256_OUTPUT_SIZE] { let mut state = SHA256_H0; let mut pos = 0usize; @@ -809,10 +769,10 @@ fn sha256_oneshot_with_compress(data: &[u8], compress: Sha256CompressBlocksFn) - out } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[must_use] /// Return whether portable PBKDF2-HMAC-SHA256 derives `expected` from `password` -/// using the salt `salt` and one iteration. +/// using the salt `salt` and one iteration, without application password-policy checks. pub fn diag_pbkdf2_sha256_verify_portable( password: &[u8; SHA256_OUTPUT_SIZE], expected: &[u8; SHA256_OUTPUT_SIZE], @@ -821,14 +781,14 @@ pub fn diag_pbkdf2_sha256_verify_portable( crate::hashes::crypto::sha256::kernels::Sha256KernelId::Portable, ); Pbkdf2Sha256::new_with_compress_for_test(password, compress) - .verify(b"salt", 1, expected) + .verify_primitive(b"salt", 1, expected) .is_ok() } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[must_use] /// Return whether portable PBKDF2-HMAC-SHA512 derives `expected` from `password` -/// using the salt `salt` and one iteration. +/// using the salt `salt` and one iteration, without application password-policy checks. pub fn diag_pbkdf2_sha512_verify_portable( password: &[u8; SHA512_OUTPUT_SIZE], expected: &[u8; SHA512_OUTPUT_SIZE], @@ -837,7 +797,7 @@ pub fn diag_pbkdf2_sha512_verify_portable( crate::hashes::crypto::sha512::kernels::Sha512KernelId::Portable, ); Pbkdf2Sha512::new_with_compress_for_test(password, compress) - .verify(b"salt", 1, expected) + .verify_primitive(b"salt", 1, expected) .is_ok() } @@ -1206,8 +1166,8 @@ fn pbkdf2_sha512_derive_key_fast_path( Ok(false) } -/// Test-only: one-shot SHA-512 digest using a specific compress function. -#[cfg(any(test, feature = "diag"))] +/// One-shot SHA-512 digest with a specific compress function for tests and internal proofs. +#[cfg(any(test, all(rscrypto_internal, feature = "diag")))] fn sha512_oneshot_with_compress(data: &[u8], compress: Sha512CompressBlocksFn) -> [u8; SHA512_OUTPUT_SIZE] { let mut state = SHA512_H0; let mut pos = 0usize; diff --git a/src/auth/rsa.rs b/src/auth/rsa.rs index 02acccf9..d0c79045 100644 --- a/src/auth/rsa.rs +++ b/src/auth/rsa.rs @@ -809,7 +809,7 @@ impl RsaSignatureProfile { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] impl RsaPkcs1v15Profile { fn digest_info_prefix(self) -> &'static [u8] { match self { @@ -820,7 +820,7 @@ impl RsaPkcs1v15Profile { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn diag_verify_pss_encoded( profile: RsaPssProfile, message: &[u8], @@ -850,7 +850,7 @@ fn diag_verify_pss_encoded( /// # Errors /// /// Returns an opaque [`VerificationError`] if the encoded message is invalid. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[must_use = "signature verification must be checked; a dropped Result silently accepts a forged signature"] pub fn diag_rsa_verify_pss_encoded( @@ -871,7 +871,7 @@ pub fn diag_rsa_verify_pss_encoded( /// # Errors /// /// Returns an opaque [`VerificationError`] if the encoded message is invalid. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[must_use = "signature verification must be checked; a dropped Result silently accepts a forged signature"] pub fn diag_rsa_verify_pss_encoded_with_scratch( @@ -893,7 +893,7 @@ pub fn diag_rsa_verify_pss_encoded_with_scratch( /// # Errors /// /// Returns an opaque [`VerificationError`] if the encoded message is invalid. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[must_use = "signature verification must be checked; a dropped Result silently accepts a forged signature"] pub fn diag_rsa_verify_pkcs1v15_encoded( @@ -918,7 +918,7 @@ pub fn diag_rsa_verify_pkcs1v15_encoded( /// /// Returns [`RsaPublicOpError`] if `input` or `out` is not exactly the modulus /// length, or if `input >= n`. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] pub fn diag_rsa_public_operation_bitserial( key: &RsaPublicKey, @@ -940,7 +940,7 @@ pub fn diag_rsa_public_operation_bitserial( /// /// Returns [`RsaPrivateOpError`] if the modulus is empty or even, if `exponent`, /// `input`, or `out` is not exactly the modulus length, or if `input >= n`. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] pub fn diag_rsa_private_exponentiate_fixed_width( modulus: &[u8], @@ -969,7 +969,7 @@ pub fn diag_rsa_private_exponentiate_fixed_width( /// Returns [`RsaPrivateOpError`] if `scratch` has another public modulus width, /// if `exponent`, `input`, or `out` is not exactly the first CRT component /// width, or if `input` is outside that component modulus. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] pub fn diag_rsa_private_exponentiate_fixed_width_with_scratch( key: &RsaPrivateKey, @@ -1000,7 +1000,7 @@ pub fn diag_rsa_private_exponentiate_fixed_width_with_scratch( /// /// Returns [`RsaPublicOpError`] if `input` or `out` is not exactly the modulus /// length, if `input >= n`, or if `scratch` was allocated for another key size. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] pub fn diag_rsa_public_operation_product( key: &RsaPublicKey, @@ -1021,7 +1021,7 @@ pub fn diag_rsa_public_operation_product( /// /// Returns [`RsaPublicOpError`] if `input` or `out` is not exactly the modulus /// length, if `input >= n`, or if `scratch` was allocated for another key size. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] pub fn diag_rsa_public_operation_comba_product( key: &RsaPublicKey, @@ -1045,7 +1045,7 @@ pub fn diag_rsa_public_operation_comba_product( /// /// Returns [`RsaPublicOpError`] if `input` or `out` is not exactly the modulus /// length, if `input >= n`, or if `scratch` was allocated for another key size. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] pub fn diag_rsa_public_operation_cios( key: &RsaPublicKey, @@ -1067,7 +1067,7 @@ pub fn diag_rsa_public_operation_cios( /// /// Returns [`RsaPublicOpError`] if `input` or `out` is not exactly the modulus /// length, if `input >= n`, or if `scratch` was allocated for another key size. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] pub fn diag_rsa_public_operation_cios_portable( key: &RsaPublicKey, @@ -1086,7 +1086,7 @@ pub fn diag_rsa_public_operation_cios_portable( /// Diagnostic-only benchmark baseline for exponentiation strategy. This forces /// the generic public-exponent loop even when production uses a specialized /// path for common Fermat exponents. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[inline] pub fn diag_rsa_public_operation_generic_exponent( @@ -1106,7 +1106,7 @@ pub fn diag_rsa_public_operation_generic_exponent( /// This diagnostic helper exists only to measure whether public-exponent /// windowing can beat the simpler square-and-multiply path. Normal callers /// should use [`RsaPublicKey::public_operation`]. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] pub fn diag_rsa_public_operation_window2_exponent( key: &RsaPublicKey, @@ -1129,7 +1129,7 @@ pub fn diag_rsa_public_operation_window2_exponent( /// # Errors /// /// Returns [`RsaKeyError`] if parsing or validation fails. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] pub fn diag_rsa_validate_spki_public_key_der( der: &[u8], @@ -1150,7 +1150,7 @@ pub fn diag_rsa_validate_spki_public_key_der( /// # Errors /// /// Returns [`RsaKeyError`] if the modulus violates the default public-key policy. -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] pub fn diag_rsa_precompute_public_montgomery_r2(modulus: &[u8]) -> Result { let policy = RsaPublicKeyPolicy::default(); @@ -1165,8 +1165,8 @@ pub fn diag_rsa_precompute_public_montgomery_r2(modulus: &[u8]) -> Result( Ok(components) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] pub fn diag_rsa_validate_pkcs8_private_key_der(der: &[u8], policy: &RsaPublicKeyPolicy) -> Result { let components = parse_pkcs8_private_key_der_parts_with_policy(der, policy)?; @@ -6925,7 +6925,7 @@ pub fn diag_rsa_validate_pkcs8_private_key_der(der: &[u8], policy: &RsaPublicKey Ok(components.modulus.len()) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] pub fn diag_rsa_validate_pkcs8_private_key_der_stage( der: &[u8], @@ -6938,7 +6938,7 @@ pub fn diag_rsa_validate_pkcs8_private_key_der_stage( Ok(components.modulus.len()) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] pub fn diag_rsa_import_pkcs8_private_key_der_stage( der: &[u8], @@ -9868,7 +9868,7 @@ fn private_select_window_power(out: &mut [u64], table: &[u64], window: u8) { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[inline(always)] /// Run the four-limb RSA private fixed-window selector for diagnostic harnesses. pub fn diag_rsa_private_select_window_power_4(table: &[u64; 64], window: u8) -> [u64; 4] { @@ -9877,7 +9877,7 @@ pub fn diag_rsa_private_select_window_power_4(table: &[u64; 64], window: u8) -> out } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[inline(always)] /// Evaluate the fixed-width RSA private-component checks used by diagnostic harnesses. pub fn diag_rsa_private_component_validation_32(component: &[u8; 32], upper_bound: &[u8; 32], other: &[u8; 32]) -> u8 { @@ -10414,7 +10414,7 @@ fn private_montgomery_r2(modulus: &[u8]) -> Result, RsaKeyError> { Ok(limbs) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn limb_checksum(limbs: &[u64]) -> u64 { limbs.iter().copied().fold(0u64, |acc, limb| acc.rotate_left(13) ^ limb) } @@ -10455,7 +10455,7 @@ fn subtract_modulus_if_needed(value: &mut [u64], modulus: &[u64], extra: u64) { add_modulus_masked(value, modulus, restore); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn add_mod_in_place(value: &mut [u64], addend: &[u64], modulus: &[u64]) { debug_assert_eq!(value.len(), addend.len()); debug_assert_eq!(value.len(), modulus.len()); @@ -10482,7 +10482,7 @@ fn double_mod_in_place(value: &mut [u64], modulus: &[u64]) { subtract_modulus_if_needed(value, modulus, carry); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn mul_mod_bitserial(out: &mut [u64], a: &[u64], b: &[u64], modulus: &[u64], addend: &mut [u64]) { debug_assert_eq!(out.len(), a.len()); debug_assert_eq!(out.len(), b.len()); @@ -10532,13 +10532,13 @@ fn mont_mul_in_place_left(left: &mut [u64], right: &[u64], tmp: &mut [u64], modu mont_mul(left, tmp, right, modulus, t); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn mont_square_comba_in_place(value: &mut [u64], tmp: &mut [u64], modulus: &RsaPublicModulus, t: &mut [u64]) { copy_limbs(tmp, value); mont_mul_comba(value, tmp, tmp, modulus, t); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn mont_mul_comba_in_place_left( left: &mut [u64], right: &[u64], @@ -10660,13 +10660,13 @@ fn mont_mul_cios_in_place_left( mont_mul_cios(left, tmp, right, modulus, t); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn mont_square_cios_portable_in_place(value: &mut [u64], tmp: &mut [u64], modulus: &RsaPublicModulus, t: &mut [u64]) { copy_limbs(tmp, value); mont_mul_cios_portable(value, tmp, tmp, modulus, t); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn mont_mul_cios_portable_in_place_left( left: &mut [u64], right: &[u64], @@ -10691,7 +10691,7 @@ fn use_public_cios_montgomery(modulus: &RsaPublicModulus) -> bool { modulus.limbs.len() <= 128 } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn mont_square_auto_in_place(value: &mut [u64], tmp: &mut [u64], modulus: &RsaPublicModulus, t: &mut [u64]) { if use_public_cios_montgomery(modulus) { mont_square_cios_in_place(value, tmp, modulus, t); @@ -10700,7 +10700,7 @@ fn mont_square_auto_in_place(value: &mut [u64], tmp: &mut [u64], modulus: &RsaPu } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn mont_mul_auto_in_place_left( left: &mut [u64], right: &[u64], @@ -10715,7 +10715,7 @@ fn mont_mul_auto_in_place_left( } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn mont_mul_auto(out: &mut [u64], a: &[u64], b: &[u64], modulus: &RsaPublicModulus, t: &mut [u64]) { if use_public_cios_montgomery(modulus) { mont_mul_cios(out, a, b, modulus, t); @@ -10724,7 +10724,7 @@ fn mont_mul_auto(out: &mut [u64], a: &[u64], b: &[u64], modulus: &RsaPublicModul } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn mont_reduce_auto(out: &mut [u64], value: &[u64], modulus: &RsaPublicModulus, t: &mut [u64]) { if use_public_cios_montgomery(modulus) { mont_reduce_cios(out, value, modulus, t); @@ -10918,7 +10918,7 @@ fn mont_reduce_cios_portable(out: &mut [u64], value: &[u64], modulus: &RsaPublic subtract_modulus_if_needed(out, &modulus.limbs, t[n]); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn mont_mul_comba(out: &mut [u64], a: &[u64], b: &[u64], modulus: &RsaPublicModulus, t: &mut [u64]) { let n = modulus.limbs.len(); debug_assert_eq!(out.len(), n); @@ -10952,7 +10952,7 @@ fn mont_mul_comba(out: &mut [u64], a: &[u64], b: &[u64], modulus: &RsaPublicModu subtract_modulus_if_needed(out, &modulus.limbs, extra); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn comba_mul_into(out: &mut [u64], a: &[u64], b: &[u64]) { debug_assert_eq!(a.len(), b.len()); let n = a.len(); @@ -14816,7 +14816,7 @@ f70203010001a3533051301d0603551d0e04160414fd0e576ce3f05b08884ad67ef3e8b4d39039c6 ); } - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn pkcs8_private_key_validation_stages_preserve_valid_result() { let pkcs1 = test_pkcs1_private_key(); @@ -15688,7 +15688,7 @@ f70203010001a3533051301d0603551d0e04160414fd0e576ce3f05b08884ad67ef3e8b4d39039c6 } } - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn diag_spki_public_key_validation_matches_import_metadata() { let spki = include_bytes!("../../benches/rsa_fixtures/rsa3072_spki.der"); @@ -15702,7 +15702,7 @@ f70203010001a3533051301d0603551d0e04160414fd0e576ce3f05b08884ad67ef3e8b4d39039c6 assert_eq!(exponent, key.public_exponent()); } - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn diag_montgomery_r2_precompute_matches_imported_key() { let spki = include_bytes!("../../benches/rsa_fixtures/rsa3072_spki.der"); @@ -15716,7 +15716,7 @@ f70203010001a3533051301d0603551d0e04160414fd0e576ce3f05b08884ad67ef3e8b4d39039c6 ); } - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn public_operation_generic_exponent_matches_specialized_fermat_paths() { let modulus = [0xff; 256]; @@ -15745,7 +15745,7 @@ f70203010001a3533051301d0603551d0e04160414fd0e576ce3f05b08884ad67ef3e8b4d39039c6 } } - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn diag_public_operation_backends_clear_output_on_error() { let modulus = [0xff; 256]; diff --git a/src/backend/curve25519.rs b/src/backend/curve25519.rs index c30a6e38..eb1743b0 100644 --- a/src/backend/curve25519.rs +++ b/src/backend/curve25519.rs @@ -4,8 +4,8 @@ //! //! # Arithmetic convention //! -//! Field arithmetic is modular math (mod 2²⁵⁵ − 19). Per CLAUDE.md rules, -//! `wrapping_*` is the correct choice for intentional modular arithmetic. +//! Field arithmetic is modular math (mod 2²⁵⁵ − 19). `wrapping_*` expresses +//! intentional modular arithmetic. //! Intermediate u128 accumulators are sized so that overflow is provably //! impossible — wrapping semantics are used for consistency, not because //! wrap-around actually occurs. @@ -288,7 +288,6 @@ impl FieldElement { #[cfg(all( feature = "x25519", any( - feature = "diag", test, miri, not(any( @@ -302,12 +301,7 @@ impl FieldElement { ) ))] pub(crate) fn conditional_swap(lhs: &mut Self, rhs: &mut Self, swap: u8) { - let mask = 0u64.wrapping_sub(u64::from(swap & 1)); - for (lhs_limb, rhs_limb) in lhs.0.iter_mut().zip(rhs.0.iter_mut()) { - let diff = mask & (*lhs_limb ^ *rhs_limb); - *lhs_limb ^= diff; - *rhs_limb ^= diff; - } + crate::backend::curve25519_swap::conditional_swap(&mut lhs.0, &mut rhs.0, swap); } /// Return the low-bit sign of the canonical encoding. @@ -397,16 +391,6 @@ impl FieldElement { } } -#[cfg(all(feature = "diag", feature = "x25519"))] -#[inline(always)] -pub fn diag_curve25519_conditional_swap(lhs: &mut [u64; FIELD_LIMBS], rhs: &mut [u64; FIELD_LIMBS], swap: u8) { - let mut lhs_element = FieldElement::from_limbs(*lhs); - let mut rhs_element = FieldElement::from_limbs(*rhs); - FieldElement::conditional_swap(&mut lhs_element, &mut rhs_element, swap); - *lhs = *lhs_element.limbs(); - *rhs = *rhs_element.limbs(); -} - impl Default for FieldElement { #[inline] fn default() -> Self { diff --git a/src/backend/curve25519/swap.rs b/src/backend/curve25519/swap.rs new file mode 100644 index 00000000..ddf5584a --- /dev/null +++ b/src/backend/curve25519/swap.rs @@ -0,0 +1,19 @@ +//! Conditional swap shared by the portable X25519 ladder and its internal evidence probe. + +/// Swap radix-51 limbs when the low bit of `swap` is set. +#[inline(always)] +pub(crate) fn conditional_swap(lhs: &mut [u64; 5], rhs: &mut [u64; 5], swap: u8) { + let mask = 0u64.wrapping_sub(u64::from(swap & 1)); + for (lhs_limb, rhs_limb) in lhs.iter_mut().zip(rhs.iter_mut()) { + let diff = mask & (*lhs_limb ^ *rhs_limb); + *lhs_limb ^= diff; + *rhs_limb ^= diff; + } +} + +/// Exercise the portable X25519 ladder's conditional swap. +#[cfg(all(rscrypto_internal, feature = "diag"))] +#[inline(always)] +pub fn diag_curve25519_conditional_swap(lhs: &mut [u64; 5], rhs: &mut [u64; 5], swap: u8) { + conditional_swap(lhs, rhs, swap); +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 19c46a98..0bd1858e 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -35,7 +35,6 @@ pub(crate) mod cache; any( test, miri, - feature = "diag", not(any( all( target_arch = "aarch64", @@ -47,20 +46,23 @@ pub(crate) mod cache; ) ) ))] -// Optimized X25519 targets compile the portable field backend in diagnostic builds only so the -// constant-time conditional-swap probe remains available. The rest of that backend is intentionally -// unreachable unless Ed25519 or a portable X25519 path also selects it. -#[cfg_attr( - all( - feature = "diag", - feature = "x25519", - not(feature = "ed25519"), - not(any(test, miri, feature = "portable-only")), - any( - all(target_arch = "aarch64", any(target_os = "macos", target_os = "linux")), - all(target_arch = "x86_64", target_os = "linux") - ) - ), - expect(dead_code, reason = "diagnostic builds retain the portable X25519 swap probe") -)] pub(crate) mod curve25519; + +#[cfg(all( + feature = "x25519", + any( + test, + miri, + all(rscrypto_internal, feature = "diag"), + not(any( + all( + target_arch = "aarch64", + any(target_os = "macos", target_os = "linux"), + not(feature = "portable-only") + ), + all(target_arch = "x86_64", target_os = "linux", not(feature = "portable-only")) + )) + ) +))] +#[path = "curve25519/swap.rs"] +pub(crate) mod curve25519_swap; diff --git a/src/checksum/common/portable.rs b/src/checksum/common/portable.rs index ba7dfc43..7428b68e 100644 --- a/src/checksum/common/portable.rs +++ b/src/checksum/common/portable.rs @@ -2,7 +2,7 @@ //! //! This module provides generic slice-by-N implementations for all CRC widths: //! - CRC-16: slice-by-4, slice-by-8 -//! - CRC-32: slice-by-8, slice-by-16 +//! - CRC-32: slice-by-16 //! - CRC-64: slice-by-8, slice-by-16 //! //! # Algorithm Overview @@ -28,7 +28,7 @@ macro_rules! tail_step { }; } -#[cfg(any(feature = "crc16", feature = "crc64", all(test, feature = "crc32")))] +#[cfg(any(feature = "crc16", feature = "crc64"))] macro_rules! tail8_body { ($crc:ident, $data:ident, $table:ident, $crc_ty:ty) => {{ // Unrolled processing for 0-7 bytes. Each arm is branchless after the match. @@ -128,13 +128,6 @@ fn tail8_64(mut crc: u64, data: &[u8], table: &[u64; 256]) -> u64 { tail8_body!(crc, data, table, u64) } -/// Process a small tail (0-7 bytes) for 32-bit CRC with unrolled lookups. -#[cfg(all(test, feature = "crc32"))] -#[inline(always)] -fn tail8_32(mut crc: u32, data: &[u8], table: &[u32; 256]) -> u32 { - tail8_body!(crc, data, table, u32) -} - /// Process a small tail (0-3 bytes) for 32-bit CRC with unrolled lookups. /// Used by slice16_32 which processes 4-byte chunks. #[cfg(feature = "crc32")] @@ -228,38 +221,6 @@ pub(crate) fn slice8_24(crc: u32, data: &[u8], tables: &[[u32; 256]; 8]) -> u32 // CRC-32 Portable Implementations -/// Update CRC-32 state using slice-by-8 algorithm. -/// -/// Processes 8 bytes per iteration (2× the CRC width in bytes). -/// -/// # Arguments -/// -/// * `crc` - Current CRC state (pre-inverted) -/// * `data` - Input data -/// * `tables` - 8 lookup tables (256 entries each) -#[cfg(all(test, feature = "crc32"))] -#[inline] -fn slice8_32(mut crc: u32, data: &[u8], tables: &[[u32; 256]; 8]) -> u32 { - let (chunks, remainder) = data.as_chunks::<8>(); - - for chunk in chunks { - let a = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]) ^ crc; - let b = u32::from_le_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]); - - crc = tables[7][(a & 0xFF) as usize] - ^ tables[6][((a >> 8) & 0xFF) as usize] - ^ tables[5][((a >> 16) & 0xFF) as usize] - ^ tables[4][(a >> 24) as usize] - ^ tables[3][(b & 0xFF) as usize] - ^ tables[2][((b >> 8) & 0xFF) as usize] - ^ tables[1][((b >> 16) & 0xFF) as usize] - ^ tables[0][(b >> 24) as usize]; - } - - // Process remaining bytes (0-7) with unrolled lookups - tail8_32(crc, remainder, &tables[0]) -} - /// Update CRC-32 state using slice-by-16 algorithm. /// /// Processes 16 bytes per iteration (4× the CRC width in bytes). @@ -537,15 +498,6 @@ mod tests { // CRC-32 Tests - #[test] - #[cfg(feature = "crc32")] - fn test_slice8_32_empty() { - let tables = - crate::checksum::common::tables::generate_crc32_tables_8(crate::checksum::common::tables::CRC32_IEEE_POLY); - let crc = slice8_32(!0, &[], &tables); - assert_eq!(crc, !0); - } - #[test] #[cfg(feature = "crc32")] fn test_slice16_32_empty() { @@ -555,16 +507,6 @@ mod tests { assert_eq!(crc, !0); } - #[test] - #[cfg(feature = "crc32")] - fn test_slice16_32_matches_slice8_32() { - let poly = crate::checksum::common::tables::CRC32_IEEE_POLY; - let tables8 = crate::checksum::common::tables::generate_crc32_tables_8(poly); - let tables16 = crate::checksum::common::tables::generate_crc32_tables_16(poly); - let data = b"The quick brown fox jumps over the lazy dog"; - assert_eq!(slice8_32(!0, data, &tables8), slice16_32(!0, data, &tables16)); - } - #[test] #[cfg(feature = "crc16")] fn test_slice8_16_empty() { diff --git a/src/checksum/crc16/mod.rs b/src/checksum/crc16/mod.rs index 210d31a2..626e9052 100644 --- a/src/checksum/crc16/mod.rs +++ b/src/checksum/crc16/mod.rs @@ -61,7 +61,7 @@ fn crc16_ibm_reference(crc: u16, data: &[u8]) -> u16 { crc16_bitwise(CRC16_IBM_POLY, crc, data) } -// Dispatch Functions (using new dispatch module) +// Dispatch Functions type Crc16DispatchFn = crate::checksum::dispatchers::Crc16Fn; #[cfg(feature = "std")] diff --git a/src/checksum/crc24/mod.rs b/src/checksum/crc24/mod.rs index 40c94eff..2c7e9df5 100644 --- a/src/checksum/crc24/mod.rs +++ b/src/checksum/crc24/mod.rs @@ -58,7 +58,7 @@ fn crc24_openpgp_reference(crc: u32, data: &[u8]) -> u32 { crc24_bitwise(CRC24_OPENPGP_POLY, crc, data) } -// Auto Dispatch Function (using new dispatch module) +// Auto Dispatch Function #[cfg(feature = "std")] type Crc24DispatchFn = crate::checksum::dispatchers::Crc24Fn; diff --git a/src/checksum/crc32/mod.rs b/src/checksum/crc32/mod.rs index 1794c048..a6e78a21 100644 --- a/src/checksum/crc32/mod.rs +++ b/src/checksum/crc32/mod.rs @@ -234,34 +234,13 @@ pub(crate) fn diag_crc32_ieee(len: usize) -> Crc32SelectionDiag { crate::checksum::diag::SelectionReason::Auto }; - let table = crate::checksum::kernel_table::active_table(); - let boundary = if !table.boundaries.is_empty() { - table.boundaries[0] - } else { - 64 - }; - Crc32SelectionDiag { polynomial: Crc32Polynomial::Ieee, len, arch: crate::platform::arch(), reason, effective_force: cfg.effective_force, - policy_family: "dispatch", selected_kernel, - selected_streams: 1, - portable_to_hwcrc: boundary, - hwcrc_to_fusion: boundary, - fusion_to_avx512: usize::MAX, - fusion_to_vpclmul: usize::MAX, - min_bytes_per_lane: usize::MAX, - memory_bound: false, - has_hwcrc: false, - has_fusion: false, - has_vpclmul: false, - has_avx512: false, - has_eor3: false, - has_sve2: false, } } @@ -278,38 +257,17 @@ pub(crate) fn diag_crc32c(len: usize) -> Crc32SelectionDiag { crate::checksum::diag::SelectionReason::Auto }; - let table = crate::checksum::kernel_table::active_table(); - let boundary = if !table.boundaries.is_empty() { - table.boundaries[0] - } else { - 64 - }; - Crc32SelectionDiag { polynomial: Crc32Polynomial::Castagnoli, len, arch: crate::platform::arch(), reason, effective_force: cfg.effective_force, - policy_family: "dispatch", selected_kernel, - selected_streams: 1, - portable_to_hwcrc: boundary, - hwcrc_to_fusion: boundary, - fusion_to_avx512: usize::MAX, - fusion_to_vpclmul: usize::MAX, - min_bytes_per_lane: usize::MAX, - memory_bound: false, - has_hwcrc: false, - has_fusion: false, - has_vpclmul: false, - has_avx512: false, - has_eor3: false, - has_sve2: false, } } -// Auto Kernels (using new dispatch module) +// Auto Kernels type Crc32DispatchFn = crate::checksum::dispatchers::Crc32Fn; #[cfg(feature = "std")] diff --git a/src/checksum/crc64/mod.rs b/src/checksum/crc64/mod.rs index b50fb167..6427c5a5 100644 --- a/src/checksum/crc64/mod.rs +++ b/src/checksum/crc64/mod.rs @@ -134,23 +134,13 @@ pub(crate) fn diag_crc64_xz(len: usize) -> Crc64SelectionDiag { crate::checksum::diag::SelectionReason::Auto }; - // Thresholds are now baked into dispatch tables; report dispatch boundaries - let table = crate::checksum::kernel_table::active_crc64_table(); - Crc64SelectionDiag { polynomial: Crc64Polynomial::Xz, len, arch: crate::platform::arch(), reason, effective_force: cfg.effective_force, - policy_family: "dispatch", selected_kernel, - selected_streams: 1, - portable_to_clmul: table.boundaries[0], // xs_max boundary - pclmul_to_vpclmul: table.boundaries[2], // m_max boundary - small_kernel_max_bytes: table.boundaries[1], // s_max boundary - use_4x512: false, - min_bytes_per_lane: usize::MAX, } } @@ -168,23 +158,13 @@ pub(crate) fn diag_crc64_nvme(len: usize) -> Crc64SelectionDiag { crate::checksum::diag::SelectionReason::Auto }; - // Thresholds are now baked into dispatch tables; report dispatch boundaries - let table = crate::checksum::kernel_table::active_crc64_table(); - Crc64SelectionDiag { polynomial: Crc64Polynomial::Nvme, len, arch: crate::platform::arch(), reason, effective_force: cfg.effective_force, - policy_family: "dispatch", selected_kernel, - selected_streams: 1, - portable_to_clmul: table.boundaries[0], // xs_max boundary - pclmul_to_vpclmul: table.boundaries[2], // m_max boundary - small_kernel_max_bytes: table.boundaries[1], // s_max boundary - use_4x512: false, - min_bytes_per_lane: usize::MAX, } } @@ -244,7 +224,7 @@ fn crc64_nvme_reference(crc: u64, data: &[u8]) -> u64 { #[cfg(feature = "alloc")] const CRC64_BUFFERED_THRESHOLD: usize = 64; -// Auto Kernels (using new dispatch module) +// Auto Kernels type Crc64DispatchFn = crate::checksum::dispatchers::Crc64Fn; #[cfg(feature = "std")] diff --git a/src/checksum/diag.rs b/src/checksum/diag.rs index ede99702..5e4a55ed 100644 --- a/src/checksum/diag.rs +++ b/src/checksum/diag.rs @@ -23,12 +23,8 @@ use crate::platform::Arch; #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum SelectionReason { - /// Below the algorithm's hard-coded tiny-size threshold (always portable). - BelowSmallThreshold, /// A forced mode was active (kernel selection bypassed normal thresholds). Forced, - /// Below the portable→SIMD transition threshold. - BelowSimdThreshold, /// Normal auto selection. Auto, } @@ -57,8 +53,7 @@ pub enum Crc64Polynomial { /// Snapshot explaining the CRC-32 kernel selected for one input length. /// -/// Capability booleans describe facts reported by the active selection policy; -/// use [`crate::platform`] when direct platform capability detection is needed. +/// Use [`crate::platform`] for direct platform capability detection. #[cfg(feature = "crc32")] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Crc32SelectionDiag { @@ -72,44 +67,8 @@ pub struct Crc32SelectionDiag { pub reason: SelectionReason, /// Force request after clamping it to detected platform capabilities. pub effective_force: crate::checksum::config::Crc32Force, - /// Name of the policy family that produced this snapshot. - pub policy_family: &'static str, /// Stable name of the selected kernel. pub selected_kernel: &'static str, - /// Stream count reported by the policy. - /// - /// The table-backed policy reports `1`; wider stream selection is encoded in - /// [`Self::selected_kernel`]. - pub selected_streams: u8, - /// First size boundary in the active CRC-32 dispatch table. - /// - /// The legacy field name does not guarantee that the next tier uses hardware - /// CRC instructions. - pub portable_to_hwcrc: usize, - /// Hardware-CRC-to-fusion boundary reported by the policy. - /// - /// The table-backed policy currently reports its first size boundary here. - pub hwcrc_to_fusion: usize, - /// Fusion-to-AVX-512 boundary, or [`usize::MAX`] when not modeled separately. - pub fusion_to_avx512: usize, - /// Fusion-to-VPCLMUL boundary, or [`usize::MAX`] when not modeled separately. - pub fusion_to_vpclmul: usize, - /// Minimum bytes per reported stream, or [`usize::MAX`] when unavailable. - pub min_bytes_per_lane: usize, - /// Whether the policy classified this selection as memory-bound. - pub memory_bound: bool, - /// Whether the policy reported a hardware CRC tier. - pub has_hwcrc: bool, - /// Whether the policy reported a fused CRC/CLMUL tier. - pub has_fusion: bool, - /// Whether the policy reported VPCLMUL support. - pub has_vpclmul: bool, - /// Whether the policy reported AVX-512 support. - pub has_avx512: bool, - /// Whether the policy reported an AArch64 EOR3 tier. - pub has_eor3: bool, - /// Whether the policy reported an AArch64 SVE2 tier. - pub has_sve2: bool, } /// Snapshot explaining the CRC-64 kernel selected for one input length. @@ -126,27 +85,8 @@ pub struct Crc64SelectionDiag { pub reason: SelectionReason, /// Force request after clamping it to detected platform capabilities. pub effective_force: crate::checksum::config::Crc64Force, - /// Name of the policy family that produced this snapshot. - pub policy_family: &'static str, /// Stable name of the selected kernel. pub selected_kernel: &'static str, - /// Stream count reported by the policy. - /// - /// The table-backed policy reports `1`; wider stream selection is encoded in - /// [`Self::selected_kernel`]. - pub selected_streams: u8, - /// Boundary between portable and carryless-multiply tiers, in bytes. - pub portable_to_clmul: usize, - /// Boundary between narrow and wide carryless-multiply tiers, in bytes. - /// - /// The legacy field name also represents equivalent non-x86 wide tiers. - pub pclmul_to_vpclmul: usize, - /// Largest input size assigned to the small-buffer kernel, in bytes. - pub small_kernel_max_bytes: usize, - /// Whether the policy selected the four-lane 512-bit VPCLMUL strategy. - pub use_4x512: bool, - /// Minimum bytes per reported stream, or [`usize::MAX`] when unavailable. - pub min_bytes_per_lane: usize, } /// Diagnose CRC-32 (IEEE) selection for `len`. diff --git a/src/checksum/kernel_table.rs b/src/checksum/kernel_table.rs index fc2302b8..33946090 100644 --- a/src/checksum/kernel_table.rs +++ b/src/checksum/kernel_table.rs @@ -1,7 +1,6 @@ //! Internal CRC kernel tables and one-shot helpers. //! -//! The public `checksum::dispatch` API was removed. What remains here is the -//! internal table-driven selector and its manually maintained kernel choices. +//! Table-driven selection with manually maintained kernel choices. #[cfg(feature = "crc16")] use crate::checksum::dispatchers::Crc16Fn; @@ -17,8 +16,7 @@ use crate::platform::Caps; /// Global cached kernel table, resolved once on first use. /// -/// This is the heart of the new dispatch system. Platform detection happens -/// exactly once, and all subsequent CRC calls use this pre-resolved table. +/// Platform detection runs once; subsequent CRC calls reuse the selected table. #[cfg(any(feature = "crc16", feature = "crc24", feature = "crc32", any(test, feature = "diag")))] static ACTIVE_TABLE: crate::backend::cache::OnceCache<&'static KernelTable> = crate::backend::cache::OnceCache::new(); #[cfg(feature = "crc64")] diff --git a/src/hashes/crypto/blake2b/mod.rs b/src/hashes/crypto/blake2b/mod.rs index b8a755f9..c62256ac 100644 --- a/src/hashes/crypto/blake2b/mod.rs +++ b/src/hashes/crypto/blake2b/mod.rs @@ -306,7 +306,7 @@ impl Core { ct::zeroize_words_no_fence(&mut h); } - #[cfg(any(test, all(feature = "diag", feature = "argon2")))] + #[cfg(any(test, all(rscrypto_internal, feature = "diag", feature = "argon2")))] fn new_with_compress_for_test( nn: u8, key: &[u8], @@ -882,7 +882,7 @@ impl Blake2b256 { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[must_use] /// Derive a keyed Blake2b-256 diagnostic digest with the portable backend. pub fn diag_blake2b256_keyed_digest_portable(key: &[u8; 32]) -> [u8; 32] { @@ -898,7 +898,7 @@ pub fn diag_blake2b256_keyed_digest_portable(key: &[u8; 32]) -> [u8; 32] { out } -#[cfg(all(feature = "diag", feature = "argon2"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "argon2"))] pub(crate) fn diag_hash_parts_portable(output_len: u8, parts: &[&[u8]], out: &mut [u8]) { assert_eq!( out.len(), @@ -1332,7 +1332,7 @@ mod tests { } #[test] - #[cfg(all(feature = "diag", feature = "argon2"))] + #[cfg(all(rscrypto_internal, feature = "diag", feature = "argon2"))] fn diag_hash_parts_portable_handles_multiblock_parts() { let first = [0x11u8; 4]; let second = [0x22u8; 1024]; diff --git a/src/hashes/crypto/blake2s/mod.rs b/src/hashes/crypto/blake2s/mod.rs index 0ebd52ce..ac5532c4 100644 --- a/src/hashes/crypto/blake2s/mod.rs +++ b/src/hashes/crypto/blake2s/mod.rs @@ -749,7 +749,7 @@ impl Blake2s256 { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Derive a keyed Blake2s-256 diagnostic digest with the portable backend. #[must_use] pub fn diag_blake2s256_keyed_digest_portable(key: &[u8; 32]) -> [u8; 32] { diff --git a/src/hashes/crypto/blake3/kernels.rs b/src/hashes/crypto/blake3/kernels.rs index 70ad4f61..0cfe6c9b 100644 --- a/src/hashes/crypto/blake3/kernels.rs +++ b/src/hashes/crypto/blake3/kernels.rs @@ -3,7 +3,7 @@ use super::{ }; #[cfg(any( test, - feature = "diag", + all(rscrypto_internal, feature = "diag"), target_arch = "x86_64", target_arch = "aarch64", target_arch = "s390x", @@ -88,13 +88,13 @@ pub(crate) struct Kernel { #[cfg(target_arch = "x86_64")] pub(crate) x86_compress_cv_bytes: X86CompressCvBytesFn, /// Diagnostic-only marker for x86 kernels that intentionally bypass asm CV compression. - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] pub(crate) owned_x86_compress: bool, /// Diagnostic-only marker for x86 kernels that intentionally bypass asm hash_many. - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] pub(crate) owned_x86_hash_many: bool, /// Diagnostic-only marker for measuring AVX-512 exact-block asm without AVX2 retargeting. - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] pub(crate) force_x86_avx512_exact_block_asm: bool, /// Kernel name for debugging/tuning. #[cfg(feature = "diag")] @@ -182,11 +182,11 @@ pub(crate) fn kernel(id: Blake3KernelId) -> Kernel { hash_many_contiguous: hash_many_contiguous_portable, #[cfg(target_arch = "x86_64")] x86_compress_cv_bytes: x86_compress_cv_portable_wrapper, - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] owned_x86_compress: false, - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] owned_x86_hash_many: false, - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] force_x86_avx512_exact_block_asm: false, #[cfg(feature = "diag")] name: id.as_str(), @@ -201,11 +201,11 @@ pub(crate) fn kernel(id: Blake3KernelId) -> Kernel { chunk_compress_blocks: chunk_compress_blocks_sse41_wrapper, hash_many_contiguous: hash_many_contiguous_sse41_wrapper, x86_compress_cv_bytes: x86_compress_cv_sse41_wrapper, - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] owned_x86_compress: false, - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] owned_x86_hash_many: false, - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] force_x86_avx512_exact_block_asm: false, #[cfg(feature = "diag")] name: id.as_str(), @@ -220,11 +220,11 @@ pub(crate) fn kernel(id: Blake3KernelId) -> Kernel { chunk_compress_blocks: chunk_compress_blocks_avx2_wrapper, hash_many_contiguous: hash_many_contiguous_avx2_wrapper, x86_compress_cv_bytes: x86_compress_cv_avx2_wrapper, - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] owned_x86_compress: false, - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] owned_x86_hash_many: false, - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] force_x86_avx512_exact_block_asm: false, #[cfg(feature = "diag")] name: id.as_str(), @@ -239,11 +239,11 @@ pub(crate) fn kernel(id: Blake3KernelId) -> Kernel { chunk_compress_blocks: chunk_compress_blocks_avx512_wrapper, hash_many_contiguous: hash_many_contiguous_avx512_wrapper, x86_compress_cv_bytes: x86_compress_cv_avx512_wrapper, - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] owned_x86_compress: false, - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] owned_x86_hash_many: false, - #[cfg(all(feature = "diag", target_arch = "x86_64"))] + #[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] force_x86_avx512_exact_block_asm: false, #[cfg(feature = "diag")] name: id.as_str(), @@ -291,7 +291,7 @@ pub(crate) fn kernel(id: Blake3KernelId) -> Kernel { } } -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] #[must_use] pub(crate) fn diag_kernel_owned_hash_many(id: Blake3KernelId) -> Option { match id { @@ -311,7 +311,7 @@ pub(crate) fn diag_kernel_owned_hash_many(id: Blake3KernelId) -> Option } } -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] #[must_use] pub(crate) fn diag_kernel_owned_compress(id: Blake3KernelId) -> Option { match id { @@ -1118,7 +1118,7 @@ fn reduce_parent_blocks_lanes( } } -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] pub(crate) fn diag_chunk_cvs_many_avx2_pair_from_bytes( input: &[u8], key_words: [u32; 8], @@ -1166,7 +1166,7 @@ pub(crate) fn diag_chunk_cvs_many_avx2_pair_from_bytes( } } -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] pub(crate) fn diag_parent_cvs_many_avx2_owned_from_bytes( children: &[[u8; OUT_LEN]], key_words: [u32; 8], @@ -1205,7 +1205,7 @@ pub(crate) fn diag_parent_cvs_many_avx2_owned_from_bytes( ); } -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] pub(crate) fn diag_parent_cvs_many_avx2_pair_from_bytes( children: &[[u8; OUT_LEN]], key_words: [u32; 8], @@ -1247,7 +1247,12 @@ pub(crate) fn diag_parent_cvs_many_avx2_pair_from_bytes( #[cfg(all( target_arch = "x86_64", - any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows") + any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" + ) ))] /// Hash one packed parent block through the serial AVX2 path. /// @@ -1292,7 +1297,7 @@ fn parent_block_ptr_from_children(children: &[[u8; OUT_LEN]], parent_idx: usize) children.as_ptr().wrapping_add(child_idx).cast::() } -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] pub(crate) fn diag_parent_cvs_many_avx512_owned_from_bytes( children: &[[u8; OUT_LEN]], key_words: [u32; 8], @@ -1724,7 +1729,7 @@ pub(crate) fn parent_cvs_many_from_bytes_inline( #[must_use] #[cfg(any( test, - feature = "diag", + all(rscrypto_internal, feature = "diag"), target_arch = "x86_64", target_arch = "aarch64", target_arch = "s390x", @@ -1774,7 +1779,7 @@ pub(crate) const fn required_caps(id: Blake3KernelId) -> Caps { } } -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] #[inline] #[must_use] pub(crate) const fn required_caps_owned_hash_many(id: Blake3KernelId) -> Caps { @@ -1790,7 +1795,7 @@ pub(crate) const fn required_caps_owned_hash_many(id: Blake3KernelId) -> Caps { } } -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] #[inline] #[must_use] pub(crate) const fn required_caps_owned_compress(id: Blake3KernelId) -> Caps { @@ -3270,7 +3275,7 @@ unsafe fn x86_compress_cv_avx512_wrapper( } } -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] /// Compress one block with the owned AVX-512 kernel. /// /// # Safety @@ -3354,7 +3359,7 @@ fn chunk_compress_blocks_avx512_wrapper( } } -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] fn chunk_compress_blocks_avx512_owned_wrapper( chaining_value: &mut [u32; 8], chunk_counter: u64, @@ -3518,7 +3523,12 @@ unsafe fn hash_many_contiguous_sse41_wrapper( #[cfg(all( target_arch = "x86_64", - any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows") + any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" + ) ))] /// Hash a sub-degree contiguous AVX2 chunk tail by duplicating the final lane. /// @@ -3584,7 +3594,12 @@ unsafe fn hash_many_avx2_owned_duplicate_tail( #[cfg(all( target_arch = "x86_64", - any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows") + any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" + ) ))] #[inline(always)] /// Hash one full chunk through the serial AVX2 compressor. @@ -3837,7 +3852,12 @@ unsafe fn hash_many_contiguous_avx2_wrapper( #[cfg(all( target_arch = "x86_64", - any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows") + any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" + ) ))] #[inline] fn avx512_owned_hash_many_available() -> bool { @@ -3853,7 +3873,12 @@ fn avx512_owned_hash_many_available() -> bool { #[cfg(all( target_arch = "x86_64", - any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows") + any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" + ) ))] /// Hash a sub-degree contiguous AVX-512 chunk tail with duplicate lanes. /// @@ -4073,7 +4098,7 @@ unsafe fn hash_many_contiguous_avx512_wrapper( } } -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] /// Hash contiguous chunks with the owned AVX2 diagnostic kernel. /// /// # Safety @@ -4140,7 +4165,7 @@ unsafe fn hash_many_contiguous_avx2_owned_wrapper( } } -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] /// Hash contiguous chunks with the owned AVX-512 diagnostic kernel. /// /// # Safety diff --git a/src/hashes/crypto/blake3/mod.rs b/src/hashes/crypto/blake3/mod.rs index 7224d34c..9cc9222e 100644 --- a/src/hashes/crypto/blake3/mod.rs +++ b/src/hashes/crypto/blake3/mod.rs @@ -245,7 +245,7 @@ fn with_subtree_scratch( }) } -#[cfg(all(feature = "parallel", feature = "diag"))] +#[cfg(all(feature = "parallel", all(rscrypto_internal, feature = "diag")))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -2505,7 +2505,7 @@ fn digest_public_oneshot(mut key_words: [u32; 8], flags: u32, input: &[u8]) -> [ digest } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Computes the constant-time evidence digest with the portable kernel. #[must_use] pub fn diag_blake3_keyed_digest_portable(key: &[u8; KEY_LEN]) -> Blake3KeyedHash { @@ -2516,7 +2516,7 @@ pub fn diag_blake3_keyed_digest_portable(key: &[u8; KEY_LEN]) -> Blake3KeyedHash digest } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -2527,7 +2527,7 @@ pub fn diag_zeroize_blake3_drop(mut key: [u8; KEY_LEN]) -> u8 { core::hint::black_box(state.finalize()[0]) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -2540,7 +2540,7 @@ pub fn diag_zeroize_blake3_reuse(mut key: [u8; KEY_LEN]) -> u8 { core::hint::black_box(state.finalize()[0]) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -2550,7 +2550,7 @@ pub fn diag_zeroize_blake3_xof_move(mut key: [u8; KEY_LEN]) -> u8 { diag_zeroize_blake3_xof_consume(reader) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -2560,7 +2560,7 @@ pub fn diag_zeroize_blake3_xof_consume(mut reader: Blake3XofReader) -> u8 { core::hint::black_box(output[0]) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// BLAKE3 kernels exposed for diagnostic comparison. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Blake3DiagKernel { @@ -2598,7 +2598,7 @@ pub enum Blake3DiagKernel { Aarch64Neon, } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] impl Blake3DiagKernel { #[inline] /// Returns the stable diagnostic label for this kernel. @@ -2716,7 +2716,7 @@ impl Blake3DiagKernel { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[inline] /// Returns whether the current CPU supports the diagnostic kernel. #[must_use] @@ -2734,7 +2734,7 @@ pub fn diag_blake3_kernel_available(kernel: Blake3DiagKernel) -> bool { crate::platform::caps().has(kernels::required_caps(kernel.kernel_id())) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[inline] #[must_use] fn diag_blake3_kernel(kernel: Blake3DiagKernel) -> Option { @@ -2763,7 +2763,7 @@ fn diag_blake3_kernel(kernel: Blake3DiagKernel) -> Option { Some(kernels::kernel(kernel.kernel_id())) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Computes a digest with a selected diagnostic kernel. #[must_use] pub fn diag_blake3_digest_with_kernel(kernel: Blake3DiagKernel, data: &[u8]) -> Option<[u8; OUT_LEN]> { @@ -2771,7 +2771,7 @@ pub fn diag_blake3_digest_with_kernel(kernel: Blake3DiagKernel, data: &[u8]) -> Some(digest_oneshot(kernel, IV, 0, data)) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Computes a keyed digest with a selected diagnostic kernel. #[must_use] pub fn diag_blake3_keyed_digest_with_kernel( @@ -2786,7 +2786,7 @@ pub fn diag_blake3_keyed_digest_with_kernel( Some(digest) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Fills XOF output with a selected diagnostic kernel. pub fn diag_blake3_xof_with_kernel(kernel: Blake3DiagKernel, data: &[u8], out: &mut [u8]) -> Option<()> { let kernel = diag_blake3_kernel(kernel)?; @@ -2805,7 +2805,7 @@ pub fn diag_blake3_xof_with_kernel(kernel: Blake3DiagKernel, data: &[u8], out: & Some(()) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Computes a streaming digest with a selected diagnostic kernel and update size. #[must_use] pub fn diag_blake3_streaming_digest_with_kernel( @@ -2828,7 +2828,7 @@ pub fn diag_blake3_streaming_digest_with_kernel( Some(hasher.finalize()) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Writes full-chunk chaining values with a selected diagnostic kernel. pub fn diag_blake3_chunk_cvs_with_kernel(kernel: Blake3DiagKernel, data: &[u8], out: &mut [u8]) -> Option<()> { #[cfg(target_arch = "x86_64")] @@ -2857,7 +2857,7 @@ pub fn diag_blake3_chunk_cvs_with_kernel(kernel: Blake3DiagKernel, data: &[u8], Some(()) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] /// Writes parent chaining values with a selected diagnostic kernel. pub fn diag_blake3_parent_cvs_with_kernel(kernel: Blake3DiagKernel, children: &[u8], out: &mut [u8]) -> Option<()> { #[cfg(target_arch = "x86_64")] @@ -4282,7 +4282,7 @@ fn use_x86_hash_many_exact_block_one_chunk_fast_path(kernel: Kernel, input_len: return false; } - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] if kernel.owned_x86_compress { return false; } @@ -4351,7 +4351,7 @@ unsafe fn avx2_owned_exact_block_chain( output } -#[cfg(all(feature = "diag", target_arch = "x86_64"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64"))] #[inline] /// Hashes one exact-block input through every AVX-512 lane. /// @@ -4453,7 +4453,7 @@ unsafe fn xof_oneshot_single_chunk_x86_exact_blocks( } } kernels::Blake3KernelId::X86Avx512 => { - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] if kernel.owned_x86_hash_many { let prefix_len = prefix_blocks.strict_mul(BLOCK_LEN); // SAFETY: Diagnostic availability checked the owned AVX-512 hash-many feature set, and the prefix @@ -4491,7 +4491,7 @@ unsafe fn xof_oneshot_single_chunk_x86_exact_blocks( return None; } } - #[cfg(not(feature = "diag"))] + #[cfg(not(all(rscrypto_internal, feature = "diag")))] { #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] let input_ptrs = [input.as_ptr()]; @@ -4596,7 +4596,7 @@ unsafe fn digest_one_chunk_root_hash_words_x86( let flags_u8 = u8::try_from(flags).expect("BLAKE3 flags fit in u8"); let flags_start_u8 = u8::try_from(flags_start).expect("BLAKE3 start flags fit in u8"); let flags_end_u8 = u8::try_from(flags_end).expect("BLAKE3 root flags fit in u8"); - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] if kernel.owned_x86_hash_many { // SAFETY: Diagnostic availability checked the owned AVX-512 hash-many feature set, and `input` is // one exact-block chunk prefix no longer than CHUNK_LEN. @@ -4606,9 +4606,9 @@ unsafe fn digest_one_chunk_root_hash_words_x86( } return output; } - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] let force_avx512_exact_block_asm = kernel.force_x86_avx512_exact_block_asm; - #[cfg(not(feature = "diag"))] + #[cfg(not(all(rscrypto_internal, feature = "diag")))] let force_avx512_exact_block_asm = false; if blocks == 4 && !force_avx512_exact_block_asm && use_avx512_four_block_avx2_fast_path() { // For the exact 4-block one-chunk case (256B input), the AVX2 path is a @@ -4899,7 +4899,7 @@ unsafe fn digest_one_chunk_root_hash_words_aarch64( #[cfg(test)] mod tests { - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] use super::CHUNK_LEN; use super::{Blake3, Blake3KeyedHash, OUT_LEN}; use crate::traits::{Digest, VerificationError, Xof}; @@ -4932,7 +4932,7 @@ mod tests { } } - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn diag_forced_kernels_match_normal_paths() { use super::{ @@ -4993,7 +4993,7 @@ mod tests { } } - #[cfg(feature = "diag")] + #[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn diag_raw_cv_helpers_match_portable() { use super::{ diff --git a/src/hashes/crypto/blake3/parallel.rs b/src/hashes/crypto/blake3/parallel.rs index d83fe213..ad7c6989 100644 --- a/src/hashes/crypto/blake3/parallel.rs +++ b/src/hashes/crypto/blake3/parallel.rs @@ -43,7 +43,7 @@ impl Drop for ParallelBatchScratch { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] diff --git a/src/hashes/crypto/blake3/x86_64.rs b/src/hashes/crypto/blake3/x86_64.rs index e7e64421..5a148882 100644 --- a/src/hashes/crypto/blake3/x86_64.rs +++ b/src/hashes/crypto/blake3/x86_64.rs @@ -178,7 +178,7 @@ pub(crate) unsafe fn compress_in_place_avx2_bytes( // On ASM-supported platforms, we prefer the handwritten assembly. This intrinsics // version is kept as fallback for other x86_64 platforms (e.g., FreeBSD, illumos). #[cfg(any( - feature = "diag", + all(rscrypto_internal, feature = "diag"), not(any(target_os = "linux", target_os = "macos", target_os = "windows")) ))] #[target_feature(enable = "avx512f,avx512vl,avx2,sse4.1,ssse3")] @@ -206,7 +206,7 @@ pub(crate) unsafe fn compress_in_place_avx512_bytes( } #[cfg(any( - feature = "diag", + all(rscrypto_internal, feature = "diag"), not(any(target_os = "linux", target_os = "macos", target_os = "windows")) ))] #[target_feature(enable = "avx512f,avx512vl,avx2,sse4.1,ssse3")] diff --git a/src/hashes/crypto/blake3/x86_64/avx2.rs b/src/hashes/crypto/blake3/x86_64/avx2.rs index 4401abc3..d63f8606 100644 --- a/src/hashes/crypto/blake3/x86_64/avx2.rs +++ b/src/hashes/crypto/blake3/x86_64/avx2.rs @@ -6,7 +6,12 @@ use core::arch::x86_64::*; -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] use super::super::{CHUNK_END, CHUNK_LEN, CHUNK_START, OUT_LEN, PARENT}; use super::{ super::{BLOCK_LEN, BLOCK_LEN_U32, IV, MSG_SCHEDULE}, @@ -393,14 +398,24 @@ unsafe fn load_counters(counter: u64, increment_counter: bool) -> (__m256i, __m2 } } -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] macro_rules! avx2_shuffle { ($z:expr, $y:expr, $x:expr, $w:expr) => { ($z << 6) | ($y << 4) | ($x << 2) | $w }; } -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] macro_rules! shuffle2 { ($a:expr, $b:expr, $c:expr) => { _mm256_castps_si256(_mm256_shuffle_ps(_mm256_castsi256_ps($a), _mm256_castsi256_ps($b), $c)) @@ -411,7 +426,12 @@ macro_rules! shuffle2 { /// /// AVX2 must be available, and both pointers must be readable for 16 bytes /// starting at `offset`. -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] #[inline(always)] unsafe fn load2x128(lo: *const u8, hi: *const u8, offset: usize) -> __m256i { // SAFETY: Loading two 128-bit halves into one YMM register because: @@ -428,7 +448,12 @@ unsafe fn load2x128(lo: *const u8, hi: *const u8, offset: usize) -> __m256i { /// # Safety /// /// AVX2 must be available. -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] #[inline(always)] unsafe fn set2x128(row: __m128i) -> __m256i { // SAFETY: Duplicating one 128-bit row into both halves because: @@ -440,7 +465,12 @@ unsafe fn set2x128(row: __m128i) -> __m256i { /// # Safety /// /// AVX2 must be available. -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] #[inline(always)] unsafe fn g1_2( row0: &mut __m256i, @@ -467,7 +497,12 @@ unsafe fn g1_2( /// # Safety /// /// AVX2 must be available. -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] #[inline(always)] unsafe fn g2_2( row0: &mut __m256i, @@ -494,7 +529,12 @@ unsafe fn g2_2( /// # Safety /// /// AVX2 must be available. -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] #[inline(always)] unsafe fn diagonalize_2(row0: &mut __m256i, row2: &mut __m256i, row3: &mut __m256i) { // SAFETY: Two-lane AVX2 diagonalization because: @@ -510,7 +550,12 @@ unsafe fn diagonalize_2(row0: &mut __m256i, row2: &mut __m256i, row3: &mut __m25 /// # Safety /// /// AVX2 must be available. -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] #[inline(always)] unsafe fn undiagonalize_2(row0: &mut __m256i, row2: &mut __m256i, row3: &mut __m256i) { // SAFETY: Two-lane AVX2 undiagonalization because: @@ -526,7 +571,12 @@ unsafe fn undiagonalize_2(row0: &mut __m256i, row2: &mut __m256i, row3: &mut __m /// # Safety /// /// AVX2 must be available. -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] #[inline(always)] unsafe fn compress2_pre( [mut row0, mut row1, mut row2, mut row3]: [__m256i; 4], @@ -630,7 +680,12 @@ unsafe fn compress2_pre( /// # Safety /// /// AVX2 must be available. -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] #[inline(always)] unsafe fn iv_row2x128() -> __m256i { // SAFETY: Duplicating the fixed BLAKE3 IV row because: @@ -649,7 +704,12 @@ unsafe fn iv_row2x128() -> __m256i { /// # Safety /// /// AVX2 must be available and `out` must be writable for two chaining values. -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] #[inline(always)] unsafe fn store2_cvs(row0: __m256i, row1: __m256i, out: *mut u8) { // SAFETY: Storing two 32-byte CVs from two independent 128-bit lanes because: @@ -670,7 +730,12 @@ unsafe fn store2_cvs(row0: __m256i, row1: __m256i, out: *mut u8) { /// # Safety /// /// AVX2 must be available. -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] #[inline(always)] unsafe fn compress2_parent_pre( key: &[u32; 8], @@ -710,7 +775,12 @@ unsafe fn compress2_parent_pre( /// 1. AVX2 is available on the current CPU. /// 2. `parents[0]` and `parents[1]` are each readable for one 64-byte parent block. /// 3. `out` is writable for two 32-byte CV outputs. -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] #[target_feature(enable = "avx2")] pub(crate) unsafe fn parent_cv2_owned(parents: &[*const u8; 2], key: &[u32; 8], flags: u32, out: *mut u8) { // SAFETY: Two-parent AVX2 CV reduction because: @@ -740,7 +810,12 @@ pub(crate) unsafe fn parent_cv2_owned(parents: &[*const u8; 2], key: &[u32; 8], /// 1. AVX2 is available on the current CPU. /// 2. `input` is readable for two full BLAKE3 chunks. /// 3. `out` is writable for two 32-byte CV outputs. -#[cfg(any(feature = "diag", target_os = "linux", target_os = "macos", target_os = "windows"))] +#[cfg(any( + all(rscrypto_internal, feature = "diag"), + target_os = "linux", + target_os = "macos", + target_os = "windows" +))] #[target_feature(enable = "avx2")] pub(crate) unsafe fn hash2_chunks_owned(input: *const u8, key: &[u32; 8], counter: u64, flags: u32, out: *mut u8) { // SAFETY: Two-chunk AVX2 CV reduction because: diff --git a/src/hashes/crypto/mod.rs b/src/hashes/crypto/mod.rs index 1f5ccc79..9c7133dd 100644 --- a/src/hashes/crypto/mod.rs +++ b/src/hashes/crypto/mod.rs @@ -34,15 +34,15 @@ pub(crate) mod sp800185; #[cfg(feature = "ascon-hash")] pub use ascon::{AsconCxof128, AsconCxof128Reader, AsconHash256, AsconXof, AsconXofReader}; -#[cfg(all(feature = "diag", feature = "blake2b"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "blake2b"))] pub use blake2b::diag_blake2b256_keyed_digest_portable; #[cfg(feature = "blake2b")] pub use blake2b::{Blake2b, Blake2b256, Blake2b512, Blake2bKey, Blake2bParams}; -#[cfg(all(feature = "diag", feature = "blake2s"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "blake2s"))] pub use blake2s::diag_blake2s256_keyed_digest_portable; #[cfg(feature = "blake2s")] pub use blake2s::{Blake2s128, Blake2s256, Blake2sKey, Blake2sParams}; -#[cfg(all(feature = "diag", feature = "blake3"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "blake3"))] pub use blake3::diag_blake3_keyed_digest_portable; #[cfg(feature = "blake3")] pub use blake3::{Blake3, Blake3KeyedHash, Blake3XofReader}; diff --git a/src/hashes/crypto/sha256/mod.rs b/src/hashes/crypto/sha256/mod.rs index f7f8c0df..5bcefebc 100644 --- a/src/hashes/crypto/sha256/mod.rs +++ b/src/hashes/crypto/sha256/mod.rs @@ -689,7 +689,10 @@ impl Sha256 { #[cfg(all(feature = "hmac", not(target_arch = "x86_64")))] fn reset_update_mode_to_aligned_prefix(&mut self, _prefix: Sha256Prefix) {} - #[cfg(all(feature = "hmac", any(feature = "diag", all(test, feature = "hkdf"))))] + #[cfg(all( + feature = "hmac", + any(all(rscrypto_internal, feature = "diag"), all(test, feature = "hkdf")) + ))] #[inline] pub(crate) fn new_with_compress_for_test(compress_blocks: CompressBlocksFn) -> Self { Self { diff --git a/src/hashes/crypto/sha3.rs b/src/hashes/crypto/sha3.rs index ca02f464..fbc695b1 100644 --- a/src/hashes/crypto/sha3.rs +++ b/src/hashes/crypto/sha3.rs @@ -90,7 +90,7 @@ impl MlKemShake256XofReader { } } -#[cfg(all(feature = "diag", feature = "ml-kem"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ml-kem"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -102,7 +102,7 @@ pub fn diag_zeroize_mlkem_sha3_512(mut seed: [u8; 32]) -> u8 { core::hint::black_box(observed) } -#[cfg(all(feature = "diag", feature = "ml-kem"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ml-kem"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -116,7 +116,7 @@ pub fn diag_zeroize_mlkem_shake256_scalar(mut seed: [u8; 32]) -> u8 { core::hint::black_box(observed) } -#[cfg(all(feature = "diag", feature = "ml-kem"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ml-kem"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -132,7 +132,7 @@ pub fn diag_zeroize_mlkem_shake256_pair(mut seed: [u8; 32]) -> u8 { core::hint::black_box(observed) } -#[cfg(all(feature = "diag", feature = "ml-kem"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "ml-kem"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] diff --git a/src/hashes/crypto/sha384.rs b/src/hashes/crypto/sha384.rs index 9c436c9e..ee531f12 100644 --- a/src/hashes/crypto/sha384.rs +++ b/src/hashes/crypto/sha384.rs @@ -241,7 +241,7 @@ impl Sha384 { digest } - #[cfg(all(feature = "hmac", any(test, feature = "diag")))] + #[cfg(all(feature = "hmac", any(test, all(rscrypto_internal, feature = "diag"))))] #[inline] pub(crate) fn new_with_compress_for_test(compress_blocks: CompressBlocksFn) -> Self { Self { diff --git a/src/hashes/crypto/sha512/mod.rs b/src/hashes/crypto/sha512/mod.rs index aa1fd69f..56c24099 100644 --- a/src/hashes/crypto/sha512/mod.rs +++ b/src/hashes/crypto/sha512/mod.rs @@ -510,7 +510,7 @@ impl Sha512 { digest } - #[cfg(all(feature = "hmac", any(test, feature = "diag")))] + #[cfg(all(feature = "hmac", any(test, all(rscrypto_internal, feature = "diag"))))] #[inline] pub(crate) fn new_with_compress_for_test(compress_blocks: CompressBlocksFn) -> Self { Self { diff --git a/src/hashes/introspect.rs b/src/hashes/introspect.rs index 0e7ea985..592e4f6c 100644 --- a/src/hashes/introspect.rs +++ b/src/hashes/introspect.rs @@ -43,10 +43,10 @@ pub fn kernel_for(len: usize) -> &'static str { /// Compress SHA-256 blocks through the currently selected kernel. /// -/// This is intentionally `diag`-only and hidden from docs. It exists so +/// This requires the internal compiler cfg and `diag`, and is hidden from docs. It exists so /// benchmark probes can isolate raw selected-kernel block compression from the /// public digest/update/finalize wrappers. -#[cfg(all(feature = "diag", feature = "sha2"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "sha2"))] #[doc(hidden)] #[inline] pub fn sha256_compress_blocks_for_bench(state: &mut [u32; 8], blocks: &[u8]) { diff --git a/src/hex.rs b/src/hex.rs index 19a55665..f6faa5ff 100644 --- a/src/hex.rs +++ b/src/hex.rs @@ -556,7 +556,7 @@ macro_rules! impl_getrandom { }; } -#[cfg(all(feature = "diag", feature = "aes-gcm"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-gcm"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -566,7 +566,7 @@ pub(crate) fn diag_zeroize_hex_success() -> bool { core::hint::black_box(parsed.is_ok()) } -#[cfg(all(feature = "diag", feature = "aes-gcm"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "aes-gcm"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] diff --git a/src/lib.rs b/src/lib.rs index 7decd2e5..eafdcf7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1675,3 +1675,399 @@ mod send_sync_assertions { assert_debug::, Sha256>>(); } } + +#[cfg(all( + doctest, + feature = "hmac", + feature = "hmac-sha3", + feature = "diag", + not(rscrypto_internal) +))] +#[doc(hidden)] +#[doc = r#" +```compile_fail,E0432 +use rscrypto::auth::diag_hmac_sha256_verify_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::diag_hmac_sha256_verify_truncated_64_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::diag_hmac_sha384_verify_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::diag_hmac_sha512_verify_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::hmac::diag_hmac_sha256_verify_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::hmac::diag_hmac_sha256_verify_truncated_64_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::hmac::diag_hmac_sha384_verify_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::hmac::diag_hmac_sha512_verify_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::hmac::diag_zeroize_hmac_sha256_finalize; +``` + +```compile_fail,E0432 +use rscrypto::auth::hmac_sha3::diag_zeroize_hmac_sha3_finalize; +``` +"#] +pub struct __HmacEvidenceBoundary; + +#[cfg(all(doctest, feature = "hkdf", feature = "diag", not(rscrypto_internal)))] +#[doc(hidden)] +#[doc = r#" +```compile_fail,E0432 +use rscrypto::auth::diag_hkdf_sha256_derive_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::diag_hkdf_sha384_derive_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::diag_hkdf_sha512_derive_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::hkdf::diag_hkdf_sha256_derive_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::hkdf::diag_hkdf_sha384_derive_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::hkdf::diag_hkdf_sha512_derive_portable; +``` +"#] +pub struct __HkdfEvidenceBoundary; + +#[cfg(all(doctest, feature = "pbkdf2", feature = "diag", not(rscrypto_internal)))] +#[doc(hidden)] +#[doc = r#" +```compile_fail,E0432 +use rscrypto::auth::diag_pbkdf2_sha256_verify_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::diag_pbkdf2_sha512_verify_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::pbkdf2::diag_pbkdf2_sha256_verify_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::pbkdf2::diag_pbkdf2_sha512_verify_portable; +``` +"#] +pub struct __Pbkdf2EvidenceBoundary; + +#[cfg(all(doctest, feature = "aegis256", feature = "diag", not(rscrypto_internal)))] +#[doc(hidden)] +#[doc = r#" +```compile_fail,E0432 +use rscrypto::aead::diag_aegis256_update_portable; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_aes_enc_round_portable; +``` +"#] +pub struct __AegisEvidenceBoundary; + +#[cfg(all(doctest, feature = "ascon-aead", feature = "diag", not(rscrypto_internal)))] +#[doc(hidden)] +#[doc = r#" +```compile_fail,E0432 +use rscrypto::aead::diag_ascon_aead128_tag_portable; +``` +"#] +pub struct __AsconEvidenceBoundary; + +#[cfg(all(doctest, feature = "aes-gcm", feature = "diag", not(rscrypto_internal)))] +#[doc(hidden)] +#[doc = r#" +```compile_fail,E0432 +use rscrypto::aead::diag_ghash_block_portable; +``` +"#] +pub struct __GhashEvidenceBoundary; + +#[cfg(all(doctest, feature = "aes-gcm-siv", feature = "diag", not(rscrypto_internal)))] +#[doc(hidden)] +#[doc = r#" +```compile_fail,E0432 +use rscrypto::aead::diag_polyval_reduce_portable; +``` +"#] +pub struct __PolyvalEvidenceBoundary; + +#[cfg(all(doctest, feature = "aes-gcm", feature = "diag", not(rscrypto_internal)))] +#[doc(hidden)] +#[doc = r#" +```compile_fail,E0432 +use rscrypto::aead::diag_aes128gcm_ctr32_be; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_aes128gcm_ghash; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_aes128gcm_tag_aes; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_aes256gcm_ctr32_be; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_aes256gcm_ghash; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_aes256gcm_tag_aes; +``` +"#] +pub struct __AesGcmEvidenceBoundary; + +#[cfg(all(doctest, feature = "aes-gcm-siv", feature = "diag", not(rscrypto_internal)))] +#[doc(hidden)] +#[doc = r#" +```compile_fail,E0432 +use rscrypto::aead::diag_aes128gcmsiv_derive_keys; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_aes128gcmsiv_raw_tag_aes; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_aes128gcmsiv_ctr32; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_aes256gcmsiv_derive_keys; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_aes256gcmsiv_raw_tag_aes; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_aes256gcmsiv_ctr32; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_aes128gcmsiv_polyval_digest; +``` +"#] +pub struct __AesGcmSivEvidenceBoundary; + +#[cfg(all(doctest, feature = "full", feature = "diag", not(rscrypto_internal)))] +#[doc(hidden)] +#[doc = r#" +```compile_fail,E0432 +use rscrypto::aead::diag_aes_siv_cmac256_s2v_portable; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_aes_siv_cmac256_open_portable; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_zeroize_aes_siv_cmac256; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_chacha20_xor_keystream_portable; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_poly1305_block_portable_digest; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_chacha20poly1305_encrypt_in_place_owned; +``` + +```compile_fail,E0432 +use rscrypto::aead::diag_zeroize_aes128_header_protection; +``` + +```compile_fail,E0432 +use rscrypto::auth::argon2::diag_hash_portable; +``` + +```compile_fail,E0432 +use rscrypto::auth::argon2::DIAG_BLOCK_WORDS; +``` + +```compile_fail,E0432 +use rscrypto::auth::diag_ecdsa_p256_nonce_reduce_limb_digest; +``` + +```compile_fail,E0432 +use rscrypto::auth::ecdsa::diag_ecdsa_p384_nonce_reduce_limb_digest; +``` + +```compile_fail,E0432 +use rscrypto::auth::DiagEd25519VerifyScalars; +``` + +```compile_fail,E0432 +use rscrypto::auth::ed25519::diag_ed25519_verify_scalars; +``` + +```compile_fail,E0432 +use rscrypto::auth::diag_curve25519_conditional_swap; +``` + +```compile_fail,E0432 +use rscrypto::auth::p256_ecdh::diag_p256_ecdh_select_window_limb_digest; +``` + +```compile_fail,E0432 +use rscrypto::auth::rsa::diag_rsa_verify_pss_encoded; +``` + +```compile_fail,E0432 +use rscrypto::auth::mlkem::diag_mlkem512_keygen_secret_noise_digest; +``` + +```compile_fail,E0432 +use rscrypto::auth::diag_mlkem_ntt_input_digest; +``` + +```compile_fail,E0432 +use rscrypto::hashes::crypto::diag_blake2b256_keyed_digest_portable; +``` + +```compile_fail,E0432 +use rscrypto::hashes::crypto::blake2s::diag_blake2s256_keyed_digest_portable; +``` + +```compile_fail,E0432 +use rscrypto::hashes::crypto::blake3::Blake3DiagKernel; +``` + +```compile_fail,E0432 +use rscrypto::hashes::crypto::blake3::diag_zeroize_blake3_drop; +``` + +```compile_fail,E0432 +use rscrypto::hashes::crypto::sha3::diag_zeroize_mlkem_sha3_512; +``` + +```compile_fail,E0432 +use rscrypto::hashes::introspect::sha256_compress_blocks_for_bench; +``` + +```compile_fail,E0599 +let _ = rscrypto::RsaPublicKey::diag_encrypt_pkcs1v15_with_seed; +``` + +```compile_fail,E0599 +let _ = rscrypto::RsaPublicKey::diag_encrypt_pkcs1v15_with_seed_and_scratch; +``` + +```compile_fail,E0599 +let _ = rscrypto::RsaPublicKey::diag_encrypt_oaep_with_seed; +``` + +```compile_fail,E0599 +let _ = rscrypto::RsaPublicKey::diag_encrypt_oaep_with_seed_and_scratch; +``` + +"#] +pub struct __RemainingEvidenceBoundary; + +#[cfg(all(doctest, feature = "full"))] +#[doc(hidden)] +#[doc = r#" +```compile_fail +use rscrypto::EcdsaP256SecretKey; +fn rejected(key: &EcdsaP256SecretKey) { + let _ = key.public_key_blinded(|_| {}); +} +``` + +```compile_fail +use rscrypto::EcdsaP256SecretKey; +fn rejected(key: &EcdsaP256SecretKey) { + let _ = key.try_sign_blinded(b"message", |_| {}); +} +``` + +```compile_fail +use rscrypto::EcdsaP256Keypair; +fn rejected(key: &EcdsaP256Keypair) { + let _ = key.try_sign_blinded(b"message", |_| {}); +} +``` + +```compile_fail +use rscrypto::EcdsaP384SecretKey; +fn rejected(key: &EcdsaP384SecretKey) { + let _ = key.public_key_blinded(|_| {}); +} +``` + +```compile_fail +use rscrypto::EcdsaP384SecretKey; +fn rejected(key: &EcdsaP384SecretKey) { + let _ = key.try_sign_blinded(b"message", |_| {}); +} +``` + +```compile_fail +use rscrypto::EcdsaP384Keypair; +fn rejected(key: &EcdsaP384Keypair) { + let _ = key.try_sign_blinded(b"message", |_| {}); +} +``` + +```compile_fail +use rscrypto::{Pbkdf2Sha256, Pbkdf2VerifyPolicy}; +fn rejected(key: &Pbkdf2Sha256, policy: &Pbkdf2VerifyPolicy) { + let _ = key.verify_with_policy(b"salt", 1, b"expected", policy); +} +``` + +```compile_fail +use rscrypto::{Pbkdf2Sha256, Pbkdf2VerifyPolicy}; +fn rejected(policy: &Pbkdf2VerifyPolicy) { + let _ = Pbkdf2Sha256::verify_password_with_policy(b"password", b"salt", 1, b"expected", policy); +} +``` + +```compile_fail +use rscrypto::{Pbkdf2Sha512, Pbkdf2VerifyPolicy}; +fn rejected(key: &Pbkdf2Sha512, policy: &Pbkdf2VerifyPolicy) { + let _ = key.verify_with_policy(b"salt", 1, b"expected", policy); +} +``` + +```compile_fail +use rscrypto::{Pbkdf2Sha512, Pbkdf2VerifyPolicy}; +fn rejected(policy: &Pbkdf2VerifyPolicy) { + let _ = Pbkdf2Sha512::verify_password_with_policy(b"password", b"salt", 1, b"expected", policy); +} +``` +"#] +pub struct __AuthenticationApiBoundary; diff --git a/src/secret.rs b/src/secret.rs index d3d47277..38f4c9a0 100644 --- a/src/secret.rs +++ b/src/secret.rs @@ -389,7 +389,7 @@ impl Drop for ZeroizingBytes { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -398,7 +398,7 @@ pub(crate) fn diag_zeroize_fixed_stack(input: [u8; 32]) -> u8 { core::hint::black_box(secret.as_bytes()[0]) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -410,7 +410,7 @@ pub(crate) fn diag_zeroize_fixed_move(input: [u8; 32]) -> u8 { output } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -422,7 +422,7 @@ pub(crate) fn diag_zeroize_early_return(input: [u8; 32], stop: bool) -> u8 { core::hint::black_box(secret.as_bytes()[0]) } -#[cfg(all(feature = "diag", feature = "alloc"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "alloc"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -431,7 +431,7 @@ pub(crate) fn diag_zeroize_variable_heap(input: Vec) -> usize { core::hint::black_box(secret.len()) } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -443,7 +443,7 @@ pub(crate) fn diag_zeroize_fixed_fill_error(value: u8) -> bool { .is_err() } -#[cfg(all(feature = "diag", feature = "alloc"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "alloc"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] @@ -456,7 +456,7 @@ pub(crate) fn diag_zeroize_variable_fill_error(len: usize, value: u8) -> bool { .is_err() } -#[cfg(all(feature = "diag", feature = "alloc"))] +#[cfg(all(rscrypto_internal, feature = "diag", feature = "alloc"))] #[doc(hidden)] #[unsafe(no_mangle)] #[inline(never)] diff --git a/tests/aead_kernel_equivalence.rs b/tests/aead_kernel_equivalence.rs index 7e46fb38..930eeda0 100644 --- a/tests/aead_kernel_equivalence.rs +++ b/tests/aead_kernel_equivalence.rs @@ -1,7 +1,7 @@ //! Forces every compiled ChaCha20 backend and compares it with the portable //! oracle. -#![cfg(all(feature = "diag", feature = "chacha20poly1305"))] +#![cfg(all(rscrypto_internal, feature = "diag", feature = "chacha20poly1305"))] #[cfg(target_arch = "aarch64")] use rscrypto::platform::caps::aarch64; diff --git a/tests/argon2_kernels.rs b/tests/argon2_kernels.rs index 92c291b1..70968141 100644 --- a/tests/argon2_kernels.rs +++ b/tests/argon2_kernels.rs @@ -12,7 +12,7 @@ //! specific input distribution (e.g. carries in the BlaMka multiply), a //! full hash with a non-trivial cost matrix surfaces it. -#![cfg(all(feature = "argon2", feature = "diag"))] +#![cfg(all(feature = "argon2", all(rscrypto_internal, feature = "diag")))] use rscrypto::{ Argon2Params, diff --git a/tests/ascon_official_vectors.rs b/tests/ascon_official_vectors.rs index d4a0757d..590cb9b1 100644 --- a/tests/ascon_official_vectors.rs +++ b/tests/ascon_official_vectors.rs @@ -6,7 +6,7 @@ use rscrypto::{ hashes::crypto::{AsconHash256, AsconXof}, traits::{Digest as _, Xof as _}, }; -use support::blobby_compat::BlobIterator; +use support::vector_blob::BlobIterator; #[test] fn ascon_hash256_official_vectors() { diff --git a/tests/blake2_official_vectors.rs b/tests/blake2_official_vectors.rs index de31adf0..2576fc01 100644 --- a/tests/blake2_official_vectors.rs +++ b/tests/blake2_official_vectors.rs @@ -7,7 +7,7 @@ use rscrypto::Digest; use rscrypto::{Blake2b512, Blake2bKey}; #[cfg(feature = "blake2s")] use rscrypto::{Blake2s256, Blake2sKey}; -use support::blobby_compat::BlobIterator; +use support::vector_blob::BlobIterator; fn run_blake2_vectors( data: &'static [u8], diff --git a/tests/blake3_official_vectors.rs b/tests/blake3_official_vectors.rs index faea6375..3f0a1775 100644 --- a/tests/blake3_official_vectors.rs +++ b/tests/blake3_official_vectors.rs @@ -3,7 +3,7 @@ mod support; use rscrypto::{Digest, hashes::crypto::Blake3, traits::Xof as _}; -use support::blobby_compat::BlobIterator; +use support::vector_blob::BlobIterator; fn update_input_pattern(hasher: &mut Blake3, len: usize) { let mut remaining = len; diff --git a/tests/chacha20poly1305.rs b/tests/chacha20poly1305.rs index cedec351..3ff2aa86 100644 --- a/tests/chacha20poly1305.rs +++ b/tests/chacha20poly1305.rs @@ -107,7 +107,7 @@ fn chacha20poly1305_rejects_modified_tag() { .expect_err("modified ChaCha20-Poly1305 tag must fail authentication"); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn chacha20poly1305_diag_owned_decrypt_large_inputs_match_normal_path() { const PLAINTEXT_LENS: &[usize] = &[1024, 1025, 4095, 4096, 4097, 16_384]; @@ -134,7 +134,7 @@ fn chacha20poly1305_diag_owned_decrypt_large_inputs_match_normal_path() { } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn chacha20poly1305_diag_owned_decrypt_zeroes_large_buffer_on_bad_tag() { let key = ChaCha20Poly1305Key::from_bytes([0x11; ChaCha20Poly1305::KEY_SIZE]); @@ -165,7 +165,7 @@ fn chacha20poly1305_diag_owned_decrypt_zeroes_large_buffer_on_bad_tag() { ); } -#[cfg(all(feature = "diag", target_arch = "x86_64", target_os = "linux"))] +#[cfg(all(rscrypto_internal, feature = "diag", target_arch = "x86_64", target_os = "linux"))] #[test] fn chacha20poly1305_diag_x86_asm_encrypt_matches_owned_path() { const PLAINTEXT_LENS: &[usize] = &[ diff --git a/tests/migration_aws_lc_rs.rs b/tests/interop_aws_lc_rs.rs similarity index 92% rename from tests/migration_aws_lc_rs.rs rename to tests/interop_aws_lc_rs.rs index ec6efb39..e898304e 100644 --- a/tests/migration_aws_lc_rs.rs +++ b/tests/interop_aws_lc_rs.rs @@ -27,10 +27,10 @@ use rscrypto::{ aead::{Nonce96, expert::AeadWithNonce}, }; -const DATA: &[u8] = b"migration equivalence data"; +const DATA: &[u8] = b"interoperability equivalence data"; const KEY_32: [u8; 32] = [0x42; 32]; const NONCE_12: [u8; 12] = [0x24; 12]; -const AAD: &[u8] = b"migration aad"; +const AAD: &[u8] = b"interoperability aad"; const RSA3072_SPKI: &[u8] = include_bytes!("../benches/rsa_fixtures/rsa3072_spki.der"); const RSA3072_PSS_SHA256: &[u8] = include_bytes!("../benches/rsa_fixtures/rsa3072_pss_sha256.sig"); @@ -47,7 +47,7 @@ impl aws_hkdf::KeyType for AwsHkdfLen { } #[test] -fn test_aws_lc_rs_digest_hmac_hkdf_and_pbkdf2_migration_examples_are_byte_equivalent() { +fn test_aws_lc_rs_digest_hmac_hkdf_and_pbkdf2_are_byte_equivalent() { let aws_digest = aws_digest::digest(&aws_digest::SHA256, DATA); assert_eq!(Sha256::digest(DATA).as_slice(), aws_digest.as_ref()); @@ -55,9 +55,9 @@ fn test_aws_lc_rs_digest_hmac_hkdf_and_pbkdf2_migration_examples_are_byte_equiva let aws_hmac = aws_hmac::sign(&aws_hmac_key, DATA); assert_eq!(HmacSha256::mac(&KEY_32, DATA).as_slice(), aws_hmac.as_ref()); - let salt = b"migration salt!!"; - let ikm = b"migration input key material"; - let info = b"migration context"; + let salt = b"interoperability salt!!"; + let ikm = b"interoperability input key material"; + let info = b"interoperability context"; let mut aws_okm = [0u8; 42]; aws_hkdf::Salt::new(aws_hkdf::HKDF_SHA256, salt) .extract(ikm) @@ -78,18 +78,18 @@ fn test_aws_lc_rs_digest_hmac_hkdf_and_pbkdf2_migration_examples_are_byte_equiva aws_pbkdf2::PBKDF2_HMAC_SHA256, iterations, salt, - b"migration password", + b"interoperability password", &mut aws_pbkdf2, ); let mut ours_pbkdf2 = [0u8; 32]; - Pbkdf2Sha256::derive_key(b"migration password", salt, iterations.get(), &mut ours_pbkdf2) + Pbkdf2Sha256::derive_key(b"interoperability password", salt, iterations.get(), &mut ours_pbkdf2) .expect("rscrypto PBKDF2 parameters must be valid"); assert_eq!(ours_pbkdf2, aws_pbkdf2); } #[test] -fn test_aws_lc_rs_aead_migration_examples_are_byte_equivalent() { +fn test_aws_lc_rs_aead_are_byte_equivalent() { let aws_aes = aws_aead_seal(&aws_aead::AES_256_GCM, &KEY_32, DATA); let aes = Aes256Gcm::new(&Aes256GcmKey::from_bytes(KEY_32)); let nonce = Nonce96::from_bytes(NONCE_12); @@ -121,7 +121,7 @@ fn test_aws_lc_rs_aead_migration_examples_are_byte_equivalent() { } #[test] -fn test_aws_lc_rs_ed25519_and_x25519_migration_examples_are_byte_equivalent() { +fn test_aws_lc_rs_ed25519_and_x25519_are_byte_equivalent() { use aws_lc_rs::signature::KeyPair as _; let seed = [0x13; 32]; @@ -180,7 +180,7 @@ fn test_aws_lc_rs_ed25519_and_x25519_migration_examples_are_byte_equivalent() { } #[test] -fn test_aws_lc_rs_rsa_verify_migration_examples_accept_the_same_fixtures() { +fn test_aws_lc_rs_rsa_verify_accept_the_same_fixtures() { let ours = RsaPublicKey::from_spki_der(RSA3072_SPKI).expect("embedded RSA-3072 SPKI fixture must parse"); ours .verify_pss(RsaPssProfile::Sha256, MESSAGE_PSS, RSA3072_PSS_SHA256) diff --git a/tests/migration_dryoc.rs b/tests/interop_dryoc.rs similarity index 69% rename from tests/migration_dryoc.rs rename to tests/interop_dryoc.rs index aa3558ea..c869e0c8 100644 --- a/tests/migration_dryoc.rs +++ b/tests/interop_dryoc.rs @@ -7,26 +7,26 @@ use dryoc::classic::{ }; use rscrypto::{Blake2b256, Blake2b512, Blake2bKey, Ed25519SecretKey, X25519SecretKey}; -const DATA: &[u8] = b"dryoc migration equivalence data"; +const DATA: &[u8] = b"dryoc interoperability equivalence data"; const KEY_32: [u8; 32] = [0x42; 32]; const KEY_64: [u8; 64] = [0x24; 64]; #[test] -fn test_dryoc_blake2b_migration_examples_are_byte_equivalent() { +fn test_dryoc_blake2b_are_byte_equivalent() { let mut dryoc_b256 = [0u8; 32]; - crypto_generichash(&mut dryoc_b256, DATA, None).expect("dryoc BLAKE2b-256 migration hash must succeed"); + crypto_generichash(&mut dryoc_b256, DATA, None).expect("dryoc BLAKE2b-256 interoperability hash must succeed"); assert_eq!(Blake2b256::digest(DATA), dryoc_b256); let mut dryoc_b512 = [0u8; 64]; - crypto_generichash(&mut dryoc_b512, DATA, None).expect("dryoc BLAKE2b-512 migration hash must succeed"); + crypto_generichash(&mut dryoc_b512, DATA, None).expect("dryoc BLAKE2b-512 interoperability hash must succeed"); assert_eq!(Blake2b512::digest(DATA), dryoc_b512); let mut dryoc_keyed_b256 = [0u8; 32]; crypto_generichash(&mut dryoc_keyed_b256, DATA, Some(&KEY_32)) - .expect("dryoc keyed BLAKE2b-256 migration hash must succeed"); + .expect("dryoc keyed BLAKE2b-256 interoperability hash must succeed"); assert_eq!( Blake2b256::keyed_digest( - Blake2bKey::new(&KEY_32).expect("32-byte BLAKE2b migration key must be valid"), + Blake2bKey::new(&KEY_32).expect("32-byte BLAKE2b interoperability key must be valid"), DATA, ), dryoc_keyed_b256 @@ -34,10 +34,10 @@ fn test_dryoc_blake2b_migration_examples_are_byte_equivalent() { let mut dryoc_keyed_b512 = [0u8; 64]; crypto_generichash(&mut dryoc_keyed_b512, DATA, Some(&KEY_64)) - .expect("dryoc keyed BLAKE2b-512 migration hash must succeed"); + .expect("dryoc keyed BLAKE2b-512 interoperability hash must succeed"); assert_eq!( Blake2b512::keyed_digest( - Blake2bKey::new(&KEY_64).expect("64-byte BLAKE2b migration key must be valid"), + Blake2bKey::new(&KEY_64).expect("64-byte BLAKE2b interoperability key must be valid"), DATA, ), dryoc_keyed_b512 @@ -45,12 +45,12 @@ fn test_dryoc_blake2b_migration_examples_are_byte_equivalent() { } #[test] -fn test_dryoc_ed25519_migration_examples_are_byte_equivalent() { +fn test_dryoc_ed25519_are_byte_equivalent() { let seed = [0x13; 32]; let (dryoc_public, dryoc_secret) = crypto_sign_seed_keypair(&seed); let mut dryoc_signature = [0u8; 64]; crypto_sign_detached(&mut dryoc_signature, DATA, &dryoc_secret) - .expect("dryoc Ed25519 migration signing must succeed"); + .expect("dryoc Ed25519 interoperability signing must succeed"); let ours = Ed25519SecretKey::from_bytes(seed); let ours_public = ours.public_key(); @@ -60,14 +60,14 @@ fn test_dryoc_ed25519_migration_examples_are_byte_equivalent() { assert_eq!(ours_signature.as_bytes(), &dryoc_signature); crypto_sign_verify_detached(ours_signature.as_bytes(), DATA, &dryoc_public) - .expect("dryoc must verify the rscrypto Ed25519 migration signature"); + .expect("dryoc must verify the rscrypto Ed25519 interoperability signature"); ours_public .verify(DATA, &ours_signature) - .expect("rscrypto must verify its Ed25519 migration signature"); + .expect("rscrypto must verify its Ed25519 interoperability signature"); } #[test] -fn test_dryoc_x25519_migration_examples_are_byte_equivalent() { +fn test_dryoc_x25519_are_byte_equivalent() { let alice_bytes = [0x18; 32]; let bob_bytes = [0x34; 32]; @@ -75,7 +75,7 @@ fn test_dryoc_x25519_migration_examples_are_byte_equivalent() { let ours_bob_public = X25519SecretKey::from_bytes(bob_bytes).public_key(); let ours_shared = ours_alice .diffie_hellman(&ours_bob_public) - .expect("rscrypto X25519 migration exchange must produce a nonzero secret"); + .expect("rscrypto X25519 interoperability exchange must produce a nonzero secret"); let mut dryoc_bob_public = [0u8; 32]; crypto_scalarmult_base(&mut dryoc_bob_public, &bob_bytes); @@ -83,6 +83,6 @@ fn test_dryoc_x25519_migration_examples_are_byte_equivalent() { let mut dryoc_shared = [0u8; 32]; crypto_scalarmult(&mut dryoc_shared, &alice_bytes, &dryoc_bob_public) - .expect("dryoc X25519 migration exchange must produce a nonzero secret"); + .expect("dryoc X25519 interoperability exchange must produce a nonzero secret"); assert_eq!(ours_shared.as_bytes(), &dryoc_shared); } diff --git a/tests/migration_ring.rs b/tests/interop_ring.rs similarity index 69% rename from tests/migration_ring.rs rename to tests/interop_ring.rs index 70edbb04..4e19781f 100644 --- a/tests/migration_ring.rs +++ b/tests/interop_ring.rs @@ -18,10 +18,10 @@ use rscrypto::{ aead::{Nonce96, expert::AeadWithNonce}, }; -const DATA: &[u8] = b"ring migration equivalence data"; +const DATA: &[u8] = b"ring interoperability equivalence data"; const KEY_32: [u8; 32] = [0x42; 32]; const NONCE_12: [u8; 12] = [0x31; 12]; -const AAD: &[u8] = b"ring migration aad"; +const AAD: &[u8] = b"ring interoperability aad"; const RSA3072_SPKI: &[u8] = include_bytes!("../benches/rsa_fixtures/rsa3072_spki.der"); const RSA3072_PSS_SHA256: &[u8] = include_bytes!("../benches/rsa_fixtures/rsa3072_pss_sha256.sig"); @@ -38,7 +38,7 @@ impl ring_hkdf::KeyType for RingHkdfLen { } #[test] -fn test_ring_digest_hmac_hkdf_and_pbkdf2_migration_examples_are_byte_equivalent() { +fn test_ring_digest_hmac_hkdf_and_pbkdf2_are_byte_equivalent() { let ring_digest = ring_digest::digest(&ring_digest::SHA256, DATA); assert_eq!(Sha256::digest(DATA).as_slice(), ring_digest.as_ref()); @@ -46,48 +46,53 @@ fn test_ring_digest_hmac_hkdf_and_pbkdf2_migration_examples_are_byte_equivalent( let ring_hmac = ring_hmac::sign(&ring_hmac_key, DATA); assert_eq!(HmacSha256::mac(&KEY_32, DATA).as_slice(), ring_hmac.as_ref()); - let salt = b"ring migration salt"; - let ikm = b"ring migration input key material"; - let info = b"ring migration context"; + let salt = b"ring interoperability salt"; + let ikm = b"ring interoperability input key material"; + let info = b"ring interoperability context"; let mut ring_okm = [0u8; 42]; ring_hkdf::Salt::new(ring_hkdf::HKDF_SHA256, salt) .extract(ikm) .expand(&[info], RingHkdfLen(ring_okm.len())) - .expect("ring HKDF must accept the migration output length") + .expect("ring HKDF must accept the interoperability output length") .fill(&mut ring_okm) - .expect("ring HKDF migration expansion must succeed"); + .expect("ring HKDF interoperability expansion must succeed"); let mut ours_okm = [0u8; 42]; HkdfSha256::new(salt, ikm) .expand(info, &mut ours_okm) - .expect("rscrypto HKDF migration expansion must succeed"); + .expect("rscrypto HKDF interoperability expansion must succeed"); assert_eq!(ours_okm, ring_okm); - let iterations = NonZeroU32::new(600_000).expect("migration iteration count must be nonzero"); + let iterations = NonZeroU32::new(600_000).expect("interoperability iteration count must be nonzero"); let mut ring_pbkdf2 = [0u8; 32]; ring_pbkdf2::derive( ring_pbkdf2::PBKDF2_HMAC_SHA256, iterations, salt, - b"ring migration password", + b"ring interoperability password", &mut ring_pbkdf2, ); let mut ours_pbkdf2 = [0u8; 32]; - Pbkdf2Sha256::derive_key(b"ring migration password", salt, iterations.get(), &mut ours_pbkdf2) - .expect("rscrypto PBKDF2 migration derivation must succeed"); + Pbkdf2Sha256::derive_key( + b"ring interoperability password", + salt, + iterations.get(), + &mut ours_pbkdf2, + ) + .expect("rscrypto PBKDF2 interoperability derivation must succeed"); assert_eq!(ours_pbkdf2, ring_pbkdf2); } #[test] -fn test_ring_aead_migration_examples_are_byte_equivalent() { +fn test_ring_aead_are_byte_equivalent() { let ring_aes = ring_aead_seal(&ring_aead::AES_256_GCM, &KEY_32, DATA); let aes = Aes256Gcm::new(&Aes256GcmKey::from_bytes(KEY_32)); let nonce = Nonce96::from_bytes(NONCE_12); let mut ours_aes = vec![0u8; DATA.len() + 16]; aes .encrypt(&nonce, AAD, DATA, &mut ours_aes) - .expect("rscrypto AES-GCM migration encryption must succeed"); + .expect("rscrypto AES-GCM interoperability encryption must succeed"); assert_eq!(ours_aes, ring_aes); let ring_chacha = ring_aead_seal(&ring_aead::CHACHA20_POLY1305, &KEY_32, DATA); @@ -95,17 +100,17 @@ fn test_ring_aead_migration_examples_are_byte_equivalent() { let mut ours_chacha = vec![0u8; DATA.len() + 16]; chacha .encrypt(&nonce, AAD, DATA, &mut ours_chacha) - .expect("rscrypto ChaCha20-Poly1305 migration encryption must succeed"); + .expect("rscrypto ChaCha20-Poly1305 interoperability encryption must succeed"); assert_eq!(ours_chacha, ring_chacha); } #[test] -fn test_ring_ed25519_and_rsa_verify_migration_examples_are_compatible() { +fn test_ring_ed25519_and_rsa_verify_are_compatible() { use ring::signature::KeyPair as _; let seed = [0x13; 32]; - let ring_ed25519 = - ring::signature::Ed25519KeyPair::from_seed_unchecked(&seed).expect("ring must accept the Ed25519 migration seed"); + let ring_ed25519 = ring::signature::Ed25519KeyPair::from_seed_unchecked(&seed) + .expect("ring must accept the Ed25519 interoperability seed"); let ours_ed25519 = Ed25519SecretKey::from_bytes(seed); let ours_public = ours_ed25519.public_key(); let ours_signature = ours_ed25519.sign(DATA); @@ -115,31 +120,31 @@ fn test_ring_ed25519_and_rsa_verify_migration_examples_are_compatible() { ring::signature::UnparsedPublicKey::new(&ring::signature::ED25519, ours_public.as_bytes()) .verify(DATA, ours_signature.as_bytes()) - .expect("ring must verify the rscrypto Ed25519 migration signature"); + .expect("ring must verify the rscrypto Ed25519 interoperability signature"); ours_public .verify(DATA, &ours_signature) - .expect("rscrypto must verify its Ed25519 migration signature"); + .expect("rscrypto must verify its Ed25519 interoperability signature"); - let ours = RsaPublicKey::from_spki_der(RSA3072_SPKI).expect("RSA migration SPKI fixture must parse"); + let ours = RsaPublicKey::from_spki_der(RSA3072_SPKI).expect("RSA interoperability SPKI fixture must parse"); let pkcs1 = ours.to_pkcs1_der(); ours .verify_pss(RsaPssProfile::Sha256, MESSAGE_PSS, RSA3072_PSS_SHA256) - .expect("rscrypto must verify the RSA-PSS migration fixture"); + .expect("rscrypto must verify the RSA-PSS interoperability fixture"); ours .verify_pkcs1v15(RsaPkcs1v15Profile::Sha256, MESSAGE_PKCS1V15, RSA3072_PKCS1V15_SHA256) - .expect("rscrypto must verify the RSA-PKCS1-v1_5 migration fixture"); + .expect("rscrypto must verify the RSA-PKCS1-v1_5 interoperability fixture"); ring::signature::UnparsedPublicKey::new(&ring::signature::RSA_PSS_2048_8192_SHA256, &pkcs1) .verify(MESSAGE_PSS, RSA3072_PSS_SHA256) - .expect("ring must verify the RSA-PSS migration fixture"); + .expect("ring must verify the RSA-PSS interoperability fixture"); ring::signature::UnparsedPublicKey::new(&ring::signature::RSA_PKCS1_2048_8192_SHA256, &pkcs1) .verify(MESSAGE_PKCS1V15, RSA3072_PKCS1V15_SHA256) - .expect("ring must verify the RSA-PKCS1-v1_5 migration fixture"); + .expect("ring must verify the RSA-PKCS1-v1_5 interoperability fixture"); } fn ring_aead_seal(algorithm: &'static ring_aead::Algorithm, key_bytes: &[u8], plaintext: &[u8]) -> Vec { let unbound_key = - ring_aead::UnboundKey::new(algorithm, key_bytes).expect("ring must accept the migration AEAD key length"); + ring_aead::UnboundKey::new(algorithm, key_bytes).expect("ring must accept the interoperability AEAD key length"); let key = ring_aead::LessSafeKey::new(unbound_key); let mut out = plaintext.to_vec(); key @@ -148,6 +153,6 @@ fn ring_aead_seal(algorithm: &'static ring_aead::Algorithm, key_bytes: &[u8], pl ring_aead::Aad::from(AAD), &mut out, ) - .expect("ring migration AEAD encryption must succeed"); + .expect("ring interoperability AEAD encryption must succeed"); out } diff --git a/tests/pbkdf2_evidence.rs b/tests/pbkdf2_evidence.rs new file mode 100644 index 00000000..3a72465b --- /dev/null +++ b/tests/pbkdf2_evidence.rs @@ -0,0 +1,33 @@ +#![cfg(all(rscrypto_internal, feature = "diag", feature = "pbkdf2"))] + +use rscrypto::{Pbkdf2Sha256, Pbkdf2Sha512, auth}; + +#[test] +fn sha256_proof_hook_verifies_the_derived_key() { + let password = [0x37; 32]; + let mut expected = [0; 32]; + pbkdf2::pbkdf2_hmac::(&password, b"salt", 1, &mut expected); + + assert!(auth::diag_pbkdf2_sha256_verify_portable(&password, &expected)); + assert!(Pbkdf2Sha256::new(&password).verify(b"salt", 1, &expected).is_err()); + for index in 0..expected.len() { + let mut wrong = expected; + wrong[index] ^= 1; + assert!(!auth::diag_pbkdf2_sha256_verify_portable(&password, &wrong)); + } +} + +#[test] +fn sha512_proof_hook_verifies_the_derived_key() { + let password = [0x93; 64]; + let mut expected = [0; 64]; + pbkdf2::pbkdf2_hmac::(&password, b"salt", 1, &mut expected); + + assert!(auth::diag_pbkdf2_sha512_verify_portable(&password, &expected)); + assert!(Pbkdf2Sha512::new(&password).verify(b"salt", 1, &expected).is_err()); + for index in 0..expected.len() { + let mut wrong = expected; + wrong[index] ^= 1; + assert!(!auth::diag_pbkdf2_sha512_verify_portable(&password, &wrong)); + } +} diff --git a/tests/rsa_public_key.rs b/tests/rsa_public_key.rs index 90b7023c..10280b31 100644 --- a/tests/rsa_public_key.rs +++ b/tests/rsa_public_key.rs @@ -34,7 +34,7 @@ use rsa::{ }; #[cfg(feature = "getrandom")] use rscrypto::RsaEncryptionError; -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] use rscrypto::auth::rsa::{ diag_rsa_private_exponentiate_fixed_width, diag_rsa_private_exponentiate_fixed_width_with_scratch, diag_rsa_public_operation_bitserial, diag_rsa_public_operation_cios, diag_rsa_public_operation_cios_portable, @@ -3587,7 +3587,7 @@ fn public_operation_boundary_representatives_match_independent_reference_across_ } #[test] -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] fn diagnostic_private_exponentiation_matches_independent_fixed_width_reference() { let len = 128; let modulus = vec![0xa5; len]; @@ -3790,7 +3790,7 @@ fn public_scratch_reuses_after_modulus_minus_one_operation() { .expect("PSS verification must succeed after repeated scratch reuse"); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn public_operation_bitserial_baseline_matches_montgomery_path() { let modulus = hex_to_vec( @@ -3866,7 +3866,7 @@ d5add90a8a212c10dd997b0a4efcb3df990808509dcb28c504e0649827a83ffd864395d1f62f2\ } } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn public_operation_montgomery_candidates_match_current_path() { for (name, spki, signature) in [ @@ -4622,7 +4622,7 @@ dd4fcc492a891d8536ef91cc228a3dbf66f0c70596f9cd101fe95d127550e7a4a9864430bd3\ assert!(key.verify_pkcs1v15(RsaPkcs1v15Profile::Sha256, message, &sig).is_err()); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn pss_encoded_message_oracle_failures_are_opaque() { let key = legacy_public_key_from_spki(&pss_fixture_public_key()); @@ -4702,7 +4702,7 @@ fn pss_encoded_message_oracle_failures_are_opaque() { )); } -#[cfg(feature = "diag")] +#[cfg(all(rscrypto_internal, feature = "diag"))] #[test] fn pkcs1v15_encoded_message_oracle_failures_are_opaque() { let key = legacy_public_key_from_spki(&pkcs1v15_fixture_public_key()); diff --git a/tests/sha256_official_vectors.rs b/tests/sha256_official_vectors.rs index b9aec0da..a1ce368d 100644 --- a/tests/sha256_official_vectors.rs +++ b/tests/sha256_official_vectors.rs @@ -3,7 +3,7 @@ mod support; use rscrypto::hashes::crypto::Sha256; -use support::blobby_compat::BlobIterator; +use support::vector_blob::BlobIterator; #[test] fn sha256_official_vectors() { diff --git a/tests/sha2_official_vectors.rs b/tests/sha2_official_vectors.rs index b84ce83a..0dacf969 100644 --- a/tests/sha2_official_vectors.rs +++ b/tests/sha2_official_vectors.rs @@ -3,7 +3,7 @@ mod support; use rscrypto::hashes::crypto::{Sha224, Sha384, Sha512, Sha512_256}; -use support::blobby_compat::BlobIterator; +use support::vector_blob::BlobIterator; fn run_fixed_vectors(data: &'static [u8], name: &str, mut digest: impl FnMut(&[u8]) -> [u8; OUT]) { for (i, row) in BlobIterator::<2>::new(data) diff --git a/tests/sha3_official_vectors.rs b/tests/sha3_official_vectors.rs index cb59ba2d..5673530d 100644 --- a/tests/sha3_official_vectors.rs +++ b/tests/sha3_official_vectors.rs @@ -7,7 +7,7 @@ use rscrypto::{ hashes::crypto::{Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256}, traits::Xof as _, }; -use support::blobby_compat::BlobIterator; +use support::vector_blob::BlobIterator; fn run_fixed_vectors(data: &'static [u8], name: &str, mut digest: impl FnMut(&[u8]) -> [u8; OUT]) { for (i, row) in BlobIterator::<2>::new(data) diff --git a/tests/support/mod.rs b/tests/support/mod.rs index db0af257..35d2bad6 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -3,4 +3,4 @@ //! This stays intentionally small. Right now it only exposes vector-corpus //! parsing helpers used by the official test suites. -pub(crate) mod blobby_compat; +pub(crate) mod vector_blob; diff --git a/tests/support/blobby_compat.rs b/tests/support/vector_blob.rs similarity index 97% rename from tests/support/blobby_compat.rs rename to tests/support/vector_blob.rs index fed8cfcf..22a262e7 100644 --- a/tests/support/blobby_compat.rs +++ b/tests/support/vector_blob.rs @@ -1,3 +1,5 @@ +//! Decode the vendored Blobby vector corpora used by the official-vector tests. + #[derive(Debug, Eq, PartialEq, Copy, Clone)] pub(crate) enum Error { InvalidVlq, diff --git a/tools/ct-dudect/src/main.rs b/tools/ct-dudect/src/main.rs index ab19088f..6c4d0401 100644 --- a/tools/ct-dudect/src/main.rs +++ b/tools/ct-dudect/src/main.rs @@ -679,8 +679,7 @@ aead_fixed_vs_random_key_open!( fn aes128_gcm_siv_diag_derive_fixed_vs_random_key(runner: &mut CtRunner, rng: &mut BenchRng) { let nonce = Nonce96::from_bytes([0x51; Nonce96::LENGTH]); let mut inputs = Vec::with_capacity(samples()); - for _ in 0..samples() { - let class = random_class(rng); + for class in balanced_classes(rng, samples()) { let key = if matches!(class, Class::Left) { [0x51; Aes128GcmSiv::KEY_SIZE] } else { @@ -697,8 +696,7 @@ fn aes128_gcm_siv_diag_derive_fixed_vs_random_key(runner: &mut CtRunner, rng: &m fn aes256_gcm_siv_diag_derive_fixed_vs_random_key(runner: &mut CtRunner, rng: &mut BenchRng) { let nonce = Nonce96::from_bytes([0x52; Nonce96::LENGTH]); let mut inputs = Vec::with_capacity(samples()); - for _ in 0..samples() { - let class = random_class(rng); + for class in balanced_classes(rng, samples()) { let key = if matches!(class, Class::Left) { [0x52; Aes256GcmSiv::KEY_SIZE] } else { @@ -1186,8 +1184,7 @@ mlkem_dudect_profile!( fn mlkem_arithmetic_ntt_fixed_vs_random_poly(runner: &mut CtRunner, rng: &mut BenchRng) { let mut inputs = Vec::with_capacity(samples()); - for _ in 0..samples() { - let class = random_class(rng); + for class in balanced_classes(rng, samples()) { let poly = if matches!(class, Class::Left) { mlkem_poly_from_seed(0x301) } else { @@ -1203,8 +1200,7 @@ fn mlkem_arithmetic_ntt_fixed_vs_random_poly(runner: &mut CtRunner, rng: &mut Be fn mlkem_arithmetic_inverse_ntt_fixed_vs_random_poly(runner: &mut CtRunner, rng: &mut BenchRng) { let mut inputs = Vec::with_capacity(samples()); - for _ in 0..samples() { - let class = random_class(rng); + for class in balanced_classes(rng, samples()) { let poly = if matches!(class, Class::Left) { mlkem_poly_from_seed(0x401) } else { @@ -1222,8 +1218,7 @@ fn mlkem_arithmetic_inverse_ntt_fixed_vs_random_poly(runner: &mut CtRunner, rng: fn mlkem_arithmetic_to_product_domain_fixed_vs_random_poly(runner: &mut CtRunner, rng: &mut BenchRng) { let mut inputs = Vec::with_capacity(samples()); - for _ in 0..samples() { - let class = random_class(rng); + for class in balanced_classes(rng, samples()) { let poly = if matches!(class, Class::Left) { mlkem_poly_from_seed(0x451) } else { @@ -1241,8 +1236,7 @@ fn mlkem_arithmetic_to_product_domain_fixed_vs_random_poly(runner: &mut CtRunner fn mlkem_arithmetic_from_product_domain_fixed_vs_random_poly(runner: &mut CtRunner, rng: &mut BenchRng) { let mut inputs = Vec::with_capacity(samples()); - for _ in 0..samples() { - let class = random_class(rng); + for class in balanced_classes(rng, samples()) { let poly = if matches!(class, Class::Left) { mlkem_poly_from_seed(0x471) } else { @@ -1260,8 +1254,7 @@ fn mlkem_arithmetic_from_product_domain_fixed_vs_random_poly(runner: &mut CtRunn fn mlkem_arithmetic_basemul_fixed_vs_random_operands(runner: &mut CtRunner, rng: &mut BenchRng) { let mut inputs = Vec::with_capacity(samples()); - for _ in 0..samples() { - let class = random_class(rng); + for class in balanced_classes(rng, samples()) { let (a, b, acc) = if matches!(class, Class::Left) { ( mlkem_poly_from_seed(0x501), @@ -1283,8 +1276,7 @@ fn mlkem_arithmetic_basemul_fixed_vs_random_operands(runner: &mut CtRunner, rng: fn mlkem1024_arithmetic_dot_fixed_vs_random_operands(runner: &mut CtRunner, rng: &mut BenchRng) { let mut inputs = Vec::with_capacity(samples()); - for _ in 0..samples() { - let class = random_class(rng); + for class in balanced_classes(rng, samples()) { let (a, b, acc) = if matches!(class, Class::Left) { ( mlkem_polyvec4_from_seed(0x801),