diff --git a/ct.toml b/ct.toml index cf2cb14a..854f2c81 100644 --- a/ct.toml +++ b/ct.toml @@ -2737,8 +2737,8 @@ name = "x86_64-unknown-linux-gnu" group = "linux" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2230 -compiler_api_sha256 = "671da8ccb1855d450418dc6fc3e79c9b9a2d50c518f98761d8c0fda6abb7c3b3" +compiler_api_item_count = 2236 +compiler_api_sha256 = "ec37f397970ac2d9bce6c5188c48c0e1dfbd9b1d321674bce13395acdd6781f8" claim = "ct-intended" physical_timing = "required" binsec = "required" @@ -2749,8 +2749,8 @@ name = "aarch64-unknown-linux-gnu" group = "linux" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2224 -compiler_api_sha256 = "2bc45c4c1aa47db7c9786ca5aae449cfc7e4370899841c96efbaeb7b67a909ae" +compiler_api_item_count = 2230 +compiler_api_sha256 = "2f64f9277c6acbb0510f9b6bb2753b6adcf862f6e8f2945831c5b7607d0a9240" claim = "ct-intended" physical_timing = "required" binsec = "required" @@ -2809,8 +2809,8 @@ name = "aarch64-apple-darwin" group = "macos" backend = "llvm" linker = "apple-ld-unpinned" -compiler_api_item_count = 2224 -compiler_api_sha256 = "2bc45c4c1aa47db7c9786ca5aae449cfc7e4370899841c96efbaeb7b67a909ae" +compiler_api_item_count = 2230 +compiler_api_sha256 = "2f64f9277c6acbb0510f9b6bb2753b6adcf862f6e8f2945831c5b7607d0a9240" claim = "ct-intended" physical_timing = "required" binsec = "unsupported" @@ -2834,8 +2834,8 @@ name = "s390x-unknown-linux-gnu" group = "ibm" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2223 -compiler_api_sha256 = "e54c2a119d506935fda2279f7d7f9f75506ce86fe5b0b6fc72fd08a7e8090883" +compiler_api_item_count = 2229 +compiler_api_sha256 = "8c5e606f4792a7a5052da0e00093fbaa01f56f2223264b7df67bc3ada774d4d5" claim = "ct-intended" physical_timing = "required" binsec = "unsupported" @@ -2847,8 +2847,8 @@ name = "powerpc64le-unknown-linux-gnu" group = "ibm" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2223 -compiler_api_sha256 = "e85c77d60d67a4648efd50cda101b8248026eb57d9008ceaae2511e319b502d9" +compiler_api_item_count = 2229 +compiler_api_sha256 = "24448b0c3dc339211793edc2999bcb77b57956ddde4c1d1111247c52067b9c9f" claim = "ct-intended" physical_timing = "required" binsec = "unsupported" @@ -2860,8 +2860,8 @@ name = "riscv64gc-unknown-linux-gnu" group = "linux" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2223 -compiler_api_sha256 = "119e6aef49beac534d7155cba8b15a7774c7a4fb6990c9e304b07998844a0858" +compiler_api_item_count = 2229 +compiler_api_sha256 = "3d1b7cfa425696e8d060773d2f2778119ce27b8aac3f69daaef1c24ddfb6e677" claim = "ct-intended" physical_timing = "required" binsec = "unsupported" diff --git a/docs/constant-time.md b/docs/constant-time.md index b3503a5e..ec70e11d 100644 --- a/docs/constant-time.md +++ b/docs/constant-time.md @@ -87,9 +87,10 @@ opaque authentication success/failure result may leak. Secret-bearing fixed-size keys, shared secrets, authentication tags, keypairs, and keyed outputs do not implement `PartialEq` or `Eq`. Their inherent `ct_eq` methods return `CtDecision`, an opaque, non-`Copy` value with no public -constructor, formatting, equality, or implicit boolean conversion. Decisions -can be composed with bitwise `&`, `|`, and `!`; the consuming `declassify()` -method is the only public route to a branchable equality bit. +constructor, equality, or implicit boolean conversion. Its `Debug` output is +the fixed redacted string `CtDecision(..)` and does not expose the decision. +Decisions can be composed with bitwise `&`, `|`, and `!`; the consuming +`declassify()` method is the only public route to a branchable equality bit. Verification APIs keep that boundary inside the primitive and return one opaque `Result`. Public keys, nonces, signatures, and ciphertext containers are public diff --git a/scripts/ct/artifacts.sh b/scripts/ct/artifacts.sh index b3beff05..4fefea37 100755 --- a/scripts/ct/artifacts.sh +++ b/scripts/ct/artifacts.sh @@ -114,7 +114,7 @@ fi OUT_DIR="$ROOT/target/ct/$TARGET/$PROFILE" ARTIFACT_DIR="$OUT_DIR/artifacts" BUILD_TARGET_DIR="$ROOT/target/ct-build/$TARGET/$PROFILE" -DEPS_DIR="$BUILD_TARGET_DIR/$TARGET/$PROFILE/deps" +EMIT_ROOT="$BUILD_TARGET_DIR/$TARGET/$PROFILE" rm -rf "$OUT_DIR" rm -rf "$BUILD_TARGET_DIR" mkdir -p "$ARTIFACT_DIR" @@ -138,7 +138,7 @@ LIB_EMITTED=() while IFS= read -r artifact; do LIB_EMITTED+=("$artifact") done < <( - find "$DEPS_DIR" -maxdepth 1 -type f \ + find "$EMIT_ROOT" -type f \ \( -name 'rscrypto_ct_harness*.ll' \ -o -name 'rscrypto_ct_harness*.s' \ -o -name 'rscrypto_ct_harness*.o' \ @@ -147,7 +147,7 @@ done < <( ) if [[ ${#LIB_EMITTED[@]} -eq 0 ]]; then - echo "no CT harness emitted artifacts found in $DEPS_DIR" >&2 + echo "no CT harness emitted artifacts found under $EMIT_ROOT" >&2 exit 1 fi if [[ ${#LIB_EMITTED[@]} -ne 3 ]]; then @@ -201,7 +201,7 @@ BIN_EMITTED=() while IFS= read -r artifact; do BIN_EMITTED+=("$artifact") done < <( - find "$DEPS_DIR" -maxdepth 1 -type f \ + find "$EMIT_ROOT" -type f \ \( -name 'rscrypto_ct_evidence*.ll' \ -o -name 'rscrypto_ct_evidence*.s' \ -o -name 'rscrypto_ct_evidence*.o' \ diff --git a/scripts/ct/evidence_validation_test.py b/scripts/ct/evidence_validation_test.py index 1fe86bad..da32508c 100644 --- a/scripts/ct/evidence_validation_test.py +++ b/scripts/ct/evidence_validation_test.py @@ -142,6 +142,36 @@ def main() -> None: configure_target_environment("powerpc64le-unknown-linux-gnu", unrelated_environment) assert unrelated_environment == {} + captured_rustdoc: dict[str, object] = {} + original_run = manifest_validation.subprocess.run + + def capture_rustdoc(command, **kwargs): + captured_rustdoc["command"] = command + captured_rustdoc["env"] = kwargs.get("env") + return subprocess.CompletedProcess(command, 1, "", "rustdoc unavailable") + + manifest_validation.subprocess.run = capture_rustdoc + try: + with tempfile.TemporaryDirectory() as temporary: + inventory_errors: list[str] = [] + assert ( + manifest_validation.compiler_public_api_snapshot( + Path(temporary), + "aarch64-apple-darwin", + ("rscrypto::auth",), + inventory_errors, + ) + is None + ) + assert inventory_errors == ["compiler public-API inventory failed: rustdoc unavailable"] + finally: + manifest_validation.subprocess.run = original_run + + rustdoc_env = captured_rustdoc["env"] + assert isinstance(rustdoc_env, dict) + assert rustdoc_env["RUSTC_BOOTSTRAP"] == "rscrypto" + assert captured_rustdoc["command"][-4:] == ["-Z", "unstable-options", "--output-format", "json"] + commit = "a" * 40 validate_exact_candidate("1.2.3", commit, "1.2.3", commit) expect_failure(lambda: validate_exact_candidate("1.2.3", commit, "1.2.4", commit)) diff --git a/scripts/ct/validate.py b/scripts/ct/validate.py index 1ca854a8..6a0b3946 100755 --- a/scripts/ct/validate.py +++ b/scripts/ct/validate.py @@ -6,6 +6,7 @@ import argparse import hashlib import json +import os import re import subprocess import sys @@ -242,7 +243,11 @@ def compiler_public_api_snapshot( "--output-format", "json", ] - completed = subprocess.run(command, cwd=root, capture_output=True, text=True, check=False) + rustdoc_env = os.environ.copy() + # rustdoc JSON is unstable even when the selected release compiler supports it. Scope the escape hatch to + # rscrypto so the inventory describes the exact compiler used by this CT evidence lane. + rustdoc_env["RUSTC_BOOTSTRAP"] = "rscrypto" + completed = subprocess.run(command, cwd=root, capture_output=True, text=True, check=False, env=rustdoc_env) if completed.returncode != 0: detail = completed.stderr.strip().splitlines() suffix = f": {detail[-1]}" if detail else "" diff --git a/src/auth/mlkem/portable/aarch64.rs b/src/auth/mlkem/portable/aarch64.rs index 3e7f76e8..26763db5 100644 --- a/src/auth/mlkem/portable/aarch64.rs +++ b/src/auth/mlkem/portable/aarch64.rs @@ -62,9 +62,9 @@ unsafe extern "C" { input2: *const u8, caps: *const usize, ) -> u64; - #[cfg(any(test, feature = "diag"))] + #[cfg(test)] fn rscrypto_mlkem_rej_uniform_3blocks_aarch64_linux(out: *mut u16, input: *const u8) -> usize; - #[cfg(any(test, feature = "diag"))] + #[cfg(test)] fn rscrypto_mlkem_basemul_accumulate_aarch64_linux( acc: *mut u16, a: *const u16, @@ -187,7 +187,7 @@ pub(super) unsafe fn sample_ntt_rej_uniform_triple_block_bounded_asm( unpack_triple_counts(packed) } -#[cfg(all(any(test, feature = "diag"), target_os = "linux"))] +#[cfg(all(test, target_os = "linux"))] #[inline] pub(super) unsafe fn sample_ntt_rej_uniform_3blocks_asm(out: *mut u16, input: *const u8) -> usize { // SAFETY: Linux aarch64 three-block SampleNTT rejection parser call because: @@ -200,7 +200,7 @@ pub(super) unsafe fn sample_ntt_rej_uniform_3blocks_asm(out: *mut u16, input: *c } #[inline] -#[cfg(any(test, feature = "diag", target_os = "macos"))] +#[cfg(any(test, target_os = "macos"))] /// # Safety /// /// The active platform must provide the matching ML-KEM AArch64 assembly diff --git a/src/checksum/common/tests.rs b/src/checksum/common/tests.rs index 06c86df8..40cb73fd 100644 --- a/src/checksum/common/tests.rs +++ b/src/checksum/common/tests.rs @@ -33,6 +33,7 @@ where /// Test that combine produces crc(A || B) from crc(A) and crc(B). /// /// This is the fundamental combine property that enables parallel CRC computation. + #[cfg(not(miri))] #[inline] pub(in crate::checksum) fn test_combine_property(data: &[u8], split: usize) { let split = split.checked_rem(data.len()).unwrap_or(0); @@ -63,6 +64,7 @@ where } /// Test combine with empty second part (identity case). + #[cfg(not(miri))] #[inline] pub(in crate::checksum) fn test_combine_empty_suffix(data: &[u8]) { let crc_data = C::checksum(data); @@ -73,6 +75,7 @@ where } /// Test combine with empty first part. + #[cfg(not(miri))] #[inline] pub(in crate::checksum) fn test_combine_empty_prefix(data: &[u8]) { let crc_empty = C::checksum(&[]); @@ -98,6 +101,7 @@ where } /// Test streaming with byte-at-a-time updates. + #[cfg(not(miri))] #[inline] pub(in crate::checksum) fn test_streaming_byte_at_a_time(data: &[u8]) { let oneshot = C::checksum(data); @@ -112,6 +116,7 @@ where } /// Test streaming across a specific chunk size boundary. + #[cfg(not(miri))] #[inline] pub(in crate::checksum) fn test_streaming_chunked(data: &[u8], chunk_size: usize) { if chunk_size == 0 { @@ -167,6 +172,7 @@ where /// 1. Streaming first half /// 2. Computing second half separately /// 3. Combining the results + #[cfg(not(miri))] #[inline] pub(in crate::checksum) fn test_streaming_and_combine(data: &[u8]) { if data.is_empty() { diff --git a/src/checksum/crc64/portable.rs b/src/checksum/crc64/portable.rs index d2ccccc9..34fe9cba 100644 --- a/src/checksum/crc64/portable.rs +++ b/src/checksum/crc64/portable.rs @@ -10,14 +10,14 @@ use crate::checksum::common::portable; /// CRC-64-XZ slice-by-8 computation. #[inline] -#[cfg(all(test, any(target_arch = "x86_64", target_arch = "aarch64")))] +#[cfg(all(test, not(miri), any(target_arch = "x86_64", target_arch = "aarch64")))] pub(super) fn crc64_slice8_xz(crc: u64, data: &[u8]) -> u64 { crc64_slice8(crc, data, &kernel_tables::XZ_TABLES_8) } /// CRC-64-NVME slice-by-8 computation. #[inline] -#[cfg(all(test, any(target_arch = "x86_64", target_arch = "aarch64")))] +#[cfg(all(test, not(miri), any(target_arch = "x86_64", target_arch = "aarch64")))] pub(super) fn crc64_slice8_nvme(crc: u64, data: &[u8]) -> u64 { crc64_slice8(crc, data, &kernel_tables::NVME_TABLES_8) } diff --git a/src/hashes/crypto/keccak.rs b/src/hashes/crypto/keccak.rs index 74dc4ea6..bac2aa97 100644 --- a/src/hashes/crypto/keccak.rs +++ b/src/hashes/crypto/keccak.rs @@ -434,7 +434,7 @@ fn read_unaligned_block_lane(block: &[u8; RATE], lane: usize) /// /// Since `RATE` is a const generic, `RATE / 8` is compile-time known and LLVM /// eliminates all `if lane < lanes` branches — the result is straight-line code. -#[cfg(target_arch = "aarch64")] +#[cfg(all(target_arch = "aarch64", not(miri)))] #[inline] fn keccakf_absorb_portable(state: &mut [u64; 25], block: &[u8; RATE]) { debug_assert_eq!(RATE % 8, 0); diff --git a/src/hashes/fast/xxh3/kernels.rs b/src/hashes/fast/xxh3/kernels.rs index f47c06f1..30ecca12 100644 --- a/src/hashes/fast/xxh3/kernels.rs +++ b/src/hashes/fast/xxh3/kernels.rs @@ -43,7 +43,7 @@ impl Xxh3KernelId { /// Long-path-only entry for 64-bit hash (>240B, no ≤240B length checks). #[cfg(any( - test, + all(test, not(miri)), not(any( all(target_arch = "x86_64", any(target_feature = "avx512f", target_feature = "avx2")), all(target_arch = "aarch64", target_feature = "neon"), @@ -69,7 +69,7 @@ pub(crate) fn hash64_long_fn(id: Xxh3KernelId) -> fn(&[u8], u64) -> u64 { /// Long-path-only entry for 128-bit hash (>240B, no ≤240B length checks). #[cfg(any( - test, + all(test, not(miri)), not(any( all(target_arch = "x86_64", any(target_feature = "avx512f", target_feature = "avx2")), all(target_arch = "aarch64", target_feature = "neon"), diff --git a/src/platform/detect/arch/aarch64.rs b/src/platform/detect/arch/aarch64.rs index 7bd534bb..f8bae080 100644 --- a/src/platform/detect/arch/aarch64.rs +++ b/src/platform/detect/arch/aarch64.rs @@ -379,6 +379,7 @@ fn runtime_aarch64() -> Caps { /// Apple Silicon chip generation. #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(test, feature = "crc16", feature = "crc24", feature = "crc32", feature = "crc64"), @@ -401,6 +402,7 @@ enum AppleSiliconGen { /// Microarchitecture family used for aarch64 kernel-table selection. #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(feature = "crc16", feature = "crc24", feature = "crc32", feature = "crc64") @@ -426,6 +428,7 @@ pub(crate) enum Aarch64TuneFamily { /// /// Returns `None` for unknown/future chips or A-series (pre-M1) processors. #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(test, feature = "crc16", feature = "crc24", feature = "crc32", feature = "crc64"), @@ -508,6 +511,7 @@ fn detect_apple_silicon_gen() -> Option { /// Detect the aarch64 microarchitecture family used by runtime table selection. #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(feature = "crc16", feature = "crc24", feature = "crc32", feature = "crc64") @@ -612,6 +616,7 @@ fn detect_apple_sme_features() -> Caps { // MIDR_EL1 Detection (Linux aarch64) #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -619,6 +624,7 @@ fn detect_apple_sme_features() -> Caps { ))] const MIDR_IMPLEMENTER_SHIFT: u32 = 24; #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -627,6 +633,7 @@ const MIDR_IMPLEMENTER_SHIFT: u32 = 24; const MIDR_PARTNUM_SHIFT: u32 = 4; #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -634,6 +641,7 @@ const MIDR_PARTNUM_SHIFT: u32 = 4; ))] const ARM_CPU_IMP_ARM: u32 = 0x41; #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -641,6 +649,7 @@ const ARM_CPU_IMP_ARM: u32 = 0x41; ))] const ARM_CPU_PART_NEOVERSE_N1: u32 = 0xD0C; #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -648,6 +657,7 @@ const ARM_CPU_PART_NEOVERSE_N1: u32 = 0xD0C; ))] const ARM_CPU_PART_NEOVERSE_V1: u32 = 0xD40; #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -656,6 +666,7 @@ const ARM_CPU_PART_NEOVERSE_V1: u32 = 0xD40; const ARM_CPU_PART_NEOVERSE_V2: u32 = 0xD4F; #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -667,6 +678,7 @@ fn midr_implementer(midr: u32) -> u32 { } #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -678,6 +690,7 @@ fn midr_partnum(midr: u32) -> u32 { } #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -696,6 +709,7 @@ fn parse_u32_auto_radix(value: &str) -> Option { } #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -719,6 +733,7 @@ fn read_linux_midr_sysfs() -> Option { } #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -751,6 +766,7 @@ fn read_linux_midr_cpuinfo() -> Option { } #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -771,6 +787,7 @@ fn map_linux_midr_to_tune_family(midr: u32) -> Option { } #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"),