From 4e41478c9dbc193118a0b99de629f27a5754a446 Mon Sep 17 00:00:00 2001 From: LoadingALIAS Date: Sat, 12 Sep 2026 19:37:33 -0400 Subject: [PATCH 1/6] build: restore CT runner catalog references --- .github/workflows/ct.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ct.yml b/.github/workflows/ct.yml index 3780d5e2..c36e8af8 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=cross-build/env=production timeout-minutes: 45 name: Prepare ${{ matrix.target }} CT strategy: From 5c4a7400bedea0dc51051708ba0c21ea8419137b Mon Sep 17 00:00:00 2001 From: LoadingALIAS Date: Sun, 13 Sep 2026 18:22:03 -0400 Subject: [PATCH 2/6] crypto: remove obsolete APIs and confine evidence hooks Remove deprecated ECDSA and unbounded PBKDF2 wrappers, stale CRC diagnostics, and migration scaffolding while preserving active protocol support. Keep proof operations behind the internal compiler boundary and retain independent interoperability coverage. Align evidence harnesses and CI runner configuration with the cleaned surface. Consolidate v0.10 release intent, refresh documentation, and include standalone workspace lockfiles in release preparation. --- .changes/checksum-diagnostics.md | 10 + .changes/docs-cleanup.md | 5 +- .changes/ecdsa-secret-selection.md | 9 + .changes/edwards-msrv-build.md | 5 - .changes/examples-workflows.md | 5 - .changes/fallible-ecdsa-secret-owners.md | 8 +- .changes/feature-target-builds.md | 8 + .changes/internal-evidence-boundary.md | 14 + .changes/keep-aarch64-ecdsa-point-e959.md | 5 - .changes/keep-portable-p-256-4870.md | 5 - .changes/keep-standalone-aead-feature-c4b6.md | 5 - .changes/markdown-accuracy.md | 5 - .../preserve-unconditional-risc-v-1146.md | 5 - .changes/release-lockfiles.md | 5 + .../remove-unbounded-pbkdf2-verification.md | 7 + ...e-e70c.md => retired-performance-chart.md} | 0 ...-sha-and-sse4-53de.md => sha2-dispatch.md} | 0 .changes/wasm-scalar-artifacts.md | 5 - .config/rail.toml | 45 ++ .github/runs-on.yml | 65 +- .github/workflows/bench.yml | 2 +- .github/workflows/ci.yml | 7 +- .github/workflows/ct.yml | 2 +- .github/workflows/fuzz.yml | 2 +- CONTRIBUTING.md | 300 ++++----- Cargo.toml | 1 + README.md | 106 ++-- benches/aead.rs | 16 +- benches/aead_kernels.rs | 29 +- benches/auth.rs | 36 +- benches/blake3.rs | 32 +- benches/rsa.rs | 70 +-- benches/sha2.rs | 6 +- docs/benchmarking.md | 536 ++++++++-------- docs/constant-time.md | 124 ++-- docs/features.md | 75 ++- docs/migration.md | 113 ---- docs/platforms.md | 154 ++--- docs/test-vector-coverage.md | 35 +- examples/README.md | 16 +- justfile | 7 +- scripts/README.md | 585 ++++++++++-------- scripts/bench/execution.py | 11 +- scripts/bench/run_test.py | 6 + scripts/bench/runner.py | 9 +- scripts/bench/transfer.py | 3 +- scripts/check/check_runner_test.py | 35 ++ scripts/check/compat_test.py | 20 +- scripts/check/lint-independent-workspaces.sh | 9 +- scripts/check/macos.sh | 1 + scripts/check/macos_test.py | 2 +- scripts/ct/artifacts.sh | 3 + scripts/ct/binsec.py | 12 +- scripts/ct/ci.py | 2 +- scripts/ct/ci_test.py | 10 + scripts/ct/dudect.sh | 6 +- scripts/ct/internal.py | 50 ++ scripts/ct/internal_test.py | 88 +++ scripts/ct/provenance.py | 14 +- scripts/ct/smoke_test.py | 14 +- scripts/ct/test.sh | 2 + scripts/lib/ci_platforms.py | 4 +- scripts/test/cross.py | 38 +- scripts/test/evidence_suite.py | 20 + scripts/test/test-musl.sh | 1 + scripts/test/test-rsa-asm.sh | 4 +- scripts/test/test_runner_test.py | 38 ++ scripts/test/transfer_test.py | 48 +- src/aead/aegis256.rs | 2 +- src/aead/aes.rs | 2 +- src/aead/aes128gcm.rs | 8 +- src/aead/aes128gcmsiv.rs | 10 +- src/aead/aes256gcm.rs | 8 +- src/aead/aes256gcmsiv.rs | 8 +- src/aead/aes_round.rs | 22 +- src/aead/aes_siv_cmac256.rs | 10 +- src/aead/ascon128.rs | 2 +- src/aead/chacha20.rs | 21 +- src/aead/chacha20poly1305.rs | 25 +- src/aead/ghash.rs | 2 +- src/aead/header_protection.rs | 6 +- src/aead/mod.rs | 46 +- src/aead/poly1305.rs | 26 +- src/aead/polyval.rs | 2 +- src/auth/argon2/mod.rs | 79 +-- src/auth/curve25519_edwards.rs | 20 +- src/auth/ecdsa.rs | 193 +----- src/auth/ed25519.rs | 13 +- src/auth/ed25519/field_avx2.rs | 4 +- src/auth/ed25519/point.rs | 2 +- src/auth/ed25519/point_avx2.rs | 4 +- src/auth/hkdf.rs | 12 +- src/auth/hmac.rs | 22 +- src/auth/hmac_sha3.rs | 2 +- src/auth/mlkem.rs | 82 ++- src/auth/mlkem/portable.rs | 88 ++- src/auth/mod.rs | 26 +- src/auth/p256_ecdh.rs | 5 +- src/auth/p256_portable.rs | 2 +- src/auth/pbkdf2.rs | 68 +- src/auth/rsa.rs | 112 ++-- src/backend/curve25519.rs | 22 +- src/backend/curve25519/swap.rs | 19 + src/backend/mod.rs | 36 +- src/checksum/common/portable.rs | 62 +- src/checksum/crc16/mod.rs | 2 +- src/checksum/crc24/mod.rs | 2 +- src/checksum/crc32/mod.rs | 44 +- src/checksum/crc64/mod.rs | 22 +- src/checksum/diag.rs | 62 +- src/checksum/kernel_table.rs | 6 +- src/hashes/crypto/blake2b/mod.rs | 8 +- src/hashes/crypto/blake2s/mod.rs | 2 +- src/hashes/crypto/blake3/kernels.rs | 93 ++- src/hashes/crypto/blake3/mod.rs | 52 +- src/hashes/crypto/blake3/parallel.rs | 2 +- src/hashes/crypto/blake3/x86_64.rs | 4 +- src/hashes/crypto/blake3/x86_64/avx2.rs | 105 +++- src/hashes/crypto/mod.rs | 6 +- src/hashes/crypto/sha256/mod.rs | 5 +- src/hashes/crypto/sha3.rs | 8 +- src/hashes/crypto/sha384.rs | 2 +- src/hashes/crypto/sha512/mod.rs | 2 +- src/hashes/introspect.rs | 4 +- src/hex.rs | 4 +- src/lib.rs | 396 ++++++++++++ src/secret.rs | 14 +- tests/aead_kernel_equivalence.rs | 2 +- tests/argon2_kernels.rs | 2 +- tests/ascon_official_vectors.rs | 2 +- tests/blake2_official_vectors.rs | 2 +- tests/blake3_official_vectors.rs | 2 +- tests/chacha20poly1305.rs | 6 +- ...tion_aws_lc_rs.rs => interop_aws_lc_rs.rs} | 22 +- .../{migration_dryoc.rs => interop_dryoc.rs} | 30 +- tests/{migration_ring.rs => interop_ring.rs} | 61 +- tests/pbkdf2_evidence.rs | 33 + tests/rsa_public_key.rs | 12 +- tests/sha256_official_vectors.rs | 2 +- tests/sha2_official_vectors.rs | 2 +- tests/sha3_official_vectors.rs | 2 +- tests/support/mod.rs | 2 +- .../{blobby_compat.rs => vector_blob.rs} | 2 + 143 files changed, 2896 insertions(+), 2126 deletions(-) create mode 100644 .changes/checksum-diagnostics.md create mode 100644 .changes/ecdsa-secret-selection.md delete mode 100644 .changes/edwards-msrv-build.md delete mode 100644 .changes/examples-workflows.md create mode 100644 .changes/feature-target-builds.md create mode 100644 .changes/internal-evidence-boundary.md delete mode 100644 .changes/keep-aarch64-ecdsa-point-e959.md delete mode 100644 .changes/keep-portable-p-256-4870.md delete mode 100644 .changes/keep-standalone-aead-feature-c4b6.md delete mode 100644 .changes/markdown-accuracy.md delete mode 100644 .changes/preserve-unconditional-risc-v-1146.md create mode 100644 .changes/release-lockfiles.md create mode 100644 .changes/remove-unbounded-pbkdf2-verification.md rename .changes/{remove-the-retired-performance-e70c.md => retired-performance-chart.md} (100%) rename .changes/{require-sha-and-sse4-53de.md => sha2-dispatch.md} (100%) delete mode 100644 .changes/wasm-scalar-artifacts.md delete mode 100644 docs/migration.md create mode 100644 scripts/ct/internal.py create mode 100644 scripts/ct/internal_test.py create mode 100644 scripts/test/evidence_suite.py create mode 100644 src/backend/curve25519/swap.rs rename tests/{migration_aws_lc_rs.rs => interop_aws_lc_rs.rs} (92%) rename tests/{migration_dryoc.rs => interop_dryoc.rs} (69%) rename tests/{migration_ring.rs => interop_ring.rs} (69%) create mode 100644 tests/pbkdf2_evidence.rs rename tests/support/{blobby_compat.rs => vector_blob.rs} (97%) 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/.github/runs-on.yml b/.github/runs-on.yml index a7d21bd5..6cba60c6 100644 --- a/.github/runs-on.yml +++ b/.github/runs-on.yml @@ -1,4 +1,5 @@ -# AWS runner shapes. 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] @@ -7,18 +8,36 @@ runners: spot: price-capacity-optimized volume: 40gb:gp3 miri: + family: [c7a.large, c8a.large] + cpu: 2 + image: ubuntu24-minimal-x64 + spot: price-capacity-optimized + volume: 150gb:gp3 + package: family: [c7a.xlarge, c7i.xlarge, c8a.xlarge, c8i.xlarge] cpu: 4 image: ubuntu24-minimal-x64 spot: price-capacity-optimized + volume: 100gb:gp3 + compatibility: + family: [c7a.2xlarge, c7i.2xlarge, c8a.2xlarge, c8i.2xlarge] + cpu: 8 + image: ubuntu24-minimal-x64 + spot: price-capacity-optimized volume: 150gb:gp3 - package: + ci-cross-build: + family: [c7a.4xlarge, c7i.4xlarge, c8a.4xlarge, c8i.4xlarge] + cpu: 16 + image: ubuntu24-minimal-x64 + spot: price-capacity-optimized + volume: 150gb:gp3 + ct-cross-build: family: [c7a.2xlarge, c7i.2xlarge, c8a.2xlarge, c8i.2xlarge] cpu: 8 image: ubuntu24-minimal-x64 spot: price-capacity-optimized - volume: 100gb:gp3 - cross-build: + volume: 150gb:gp3 + bench-cross-build: family: [c7a.8xlarge, c7i.8xlarge, c8a.8xlarge, c8i.8xlarge] cpu: 32 image: ubuntu24-minimal-x64 @@ -37,12 +56,44 @@ 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 - # Fixed, non-Flex, On-Demand hardware for timing and benchmark evidence. + # Eight concurrent fuzz targets need eight physical cores, including on x86. + fuzz-x86_64-linux: + family: [c7a.2xlarge, c8a.2xlarge] + cpu: 8 + image: ubuntu24-minimal-x64 + spot: price-capacity-optimized + volume: 150gb:gp3 + fuzz-aarch64-linux: + family: [c7g.2xlarge, c8g.2xlarge, c9g.2xlarge] + cpu: 8 + 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 + image: ubuntu24-minimal-x64 + spot: false + volume: 100gb:gp3 + ct-x86_64-win-intel: + family: [c8i.xlarge] + cpu: 4 + image: windows25-full-x64 + spot: false + volume: 100gb:gp3 + ct-aarch64-linux: + family: [c9g.2xlarge] + cpu: 8 + image: ubuntu24-minimal-arm64 + spot: false + volume: 100gb:gp3 + # 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 c36e8af8..747ade3b 100644 --- a/.github/workflows/ct.yml +++ b/.github/workflows/ct.yml @@ -100,7 +100,7 @@ jobs: cross-build: needs: select if: needs.select.outputs.cross == 'true' - runs-on: runs-on=${{ github.run_id }}-${{ matrix.target }}/runner=cross-build/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: 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/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..d47adb61 --- /dev/null +++ b/scripts/ct/internal.py @@ -0,0 +1,50 @@ +#!/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 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(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..5842535a --- /dev/null +++ b/scripts/ct/internal_test.py @@ -0,0 +1,88 @@ +#!/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_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/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..33c90567 100644 --- a/src/aead/aes128gcmsiv.rs +++ b/src/aead/aes128gcmsiv.rs @@ -227,14 +227,14 @@ 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]) { 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 +246,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 +264,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 +307,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..21e8eee1 100644 --- a/src/aead/aes256gcmsiv.rs +++ b/src/aead/aes256gcmsiv.rs @@ -235,14 +235,14 @@ 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]) { 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 +260,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 +303,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..e94d4921 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,7 +1216,7 @@ 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; @@ -5816,7 +5864,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 +6421,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, From 1098a47d1f03f08f9ad3739f0c86226fe1c4805a Mon Sep 17 00:00:00 2001 From: LoadingALIAS Date: Sun, 13 Sep 2026 19:34:31 -0400 Subject: [PATCH 3/6] security: bind CT snapshots to the confined API surface Audit the compiler-derived public API inventory on every release-evidence target. The boundary cleanup adds no entries and removes only the retired or internalized APIs, so record the resulting common snapshot. --- ct.toml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ct.toml b/ct.toml index 2f626df6..0332ed1e 100644 --- a/ct.toml +++ b/ct.toml @@ -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" From ea9e48d887ef25aee3c3a996207e001409dac5d1 Mon Sep 17 00:00:00 2001 From: LoadingALIAS Date: Sun, 13 Sep 2026 20:12:57 -0400 Subject: [PATCH 4/6] build: provision Nextest for CT evidence tests --- .config/tooling.toml | 2 +- scripts/tooling/catalog.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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'})): From ebe6919cb3b4a933b986644909e6b060add25f80 Mon Sep 17 00:00:00 2001 From: LoadingALIAS Date: Sun, 13 Sep 2026 22:04:28 -0400 Subject: [PATCH 5/6] build: repair native evidence execution Run repository shell wrappers through Bash on Windows so Python does not invoke them as Win32 executables.\n\nOn AArch64, make the GCM-SIV timing probe exercise the fused production derivation path and balance class ordering against short-lived host drift. --- ct.toml | 8 ++++---- scripts/ct/internal.py | 9 ++++++++- scripts/ct/internal_test.py | 10 ++++++++++ src/aead/aes128gcmsiv.rs | 11 +++++++++++ src/aead/aes256gcmsiv.rs | 11 +++++++++++ tools/ct-dudect/src/main.rs | 6 ++---- 6 files changed, 46 insertions(+), 9 deletions(-) diff --git a/ct.toml b/ct.toml index 0332ed1e..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 diff --git a/scripts/ct/internal.py b/scripts/ct/internal.py index d47adb61..d4c997df 100644 --- a/scripts/ct/internal.py +++ b/scripts/ct/internal.py @@ -18,6 +18,13 @@ 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] @@ -43,7 +50,7 @@ def main() -> int: if not command: parser.error("a Cargo command is required after --") environment, _ = build_environment(args.target) - return subprocess.run(command, cwd=ROOT, env=environment, check=False).returncode + return subprocess.run(host_command(command), cwd=ROOT, env=environment, check=False).returncode if __name__ == "__main__": diff --git a/scripts/ct/internal_test.py b/scripts/ct/internal_test.py index 5842535a..455ed136 100644 --- a/scripts/ct/internal_test.py +++ b/scripts/ct/internal_test.py @@ -17,6 +17,16 @@ 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() diff --git a/src/aead/aes128gcmsiv.rs b/src/aead/aes128gcmsiv.rs index 33c90567..8a46fda8 100644 --- a/src/aead/aes128gcmsiv.rs +++ b/src/aead/aes128gcmsiv.rs @@ -231,6 +231,17 @@ fn compute_tag( /// 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) } diff --git a/src/aead/aes256gcmsiv.rs b/src/aead/aes256gcmsiv.rs index 21e8eee1..fc2f96ad 100644 --- a/src/aead/aes256gcmsiv.rs +++ b/src/aead/aes256gcmsiv.rs @@ -239,6 +239,17 @@ fn compute_tag( /// 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) } diff --git a/tools/ct-dudect/src/main.rs b/tools/ct-dudect/src/main.rs index ab19088f..51b4b686 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 { From 8cc75f47d71be2e50dcfb51299a564bffca83989 Mon Sep 17 00:00:00 2001 From: LoadingALIAS Date: Sun, 13 Sep 2026 23:11:01 -0400 Subject: [PATCH 6/6] build: make native CT evidence self-checking Probe every catalog-selected Cargo tool and retain standard downloadable artifacts. Balance ML-KEM timing classes and remove the multiply-based diagnostic sink so DudeCT measures the production kernel it names. --- .github/workflows/ct.yml | 1 - scripts/tooling/aarch64-macos.sh | 7 +++++++ scripts/tooling/install_test.py | 10 +++++++++- scripts/tooling/linux.sh | 13 ++++++++++--- scripts/tooling/windows.ps1 | 10 +++++++++- src/auth/mlkem/portable.rs | 5 ++--- tools/ct-dudect/src/main.rs | 18 ++++++------------ 7 files changed, 43 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ct.yml b/.github/workflows/ct.yml index 747ade3b..6ac6eb30 100644 --- a/.github/workflows/ct.yml +++ b/.github/workflows/ct.yml @@ -184,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/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/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/auth/mlkem/portable.rs b/src/auth/mlkem/portable.rs index e94d4921..fcb3ff9c 100644 --- a/src/auth/mlkem/portable.rs +++ b/src/auth/mlkem/portable.rs @@ -1220,9 +1220,8 @@ fn fill_diag_seed(out: &mut [u8; SEED_BYTES], seed: u8) { #[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 } diff --git a/tools/ct-dudect/src/main.rs b/tools/ct-dudect/src/main.rs index 51b4b686..6c4d0401 100644 --- a/tools/ct-dudect/src/main.rs +++ b/tools/ct-dudect/src/main.rs @@ -1184,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 { @@ -1201,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 { @@ -1220,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 { @@ -1239,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 { @@ -1258,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), @@ -1281,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),