Skip to content

Correctness Proofs for SHA-3 (Part 1): A Rust Specification for SHA-3#1399

Open
karthikbhargavan wants to merge 8 commits into
mainfrom
sha3-spec-upstream
Open

Correctness Proofs for SHA-3 (Part 1): A Rust Specification for SHA-3#1399
karthikbhargavan wants to merge 8 commits into
mainfrom
sha3-spec-upstream

Conversation

@karthikbhargavan

Copy link
Copy Markdown
Contributor

This PR adds a specification of SHA-3 in a purely functional subset of Rust (sometimes called hacspec).

The spec uses some specific proof-friendly styles.

  1. For code that creates or updates arrays point-wise, it uses createi (array::from_fn) as a way of creating arrays using an initialization function instead of initializing the array using a loop.
  2. For code that loops over unbounded inputs, it prefers recursion over loops
  3. When reasoning about integer arithmetic, it sometimes uses hax_lib::int

The spec itself verifies in F* (hax.sh extract; hax.sh prove) and is tested against the typical test vectors.
We also added cross-spec tests to the SHA-3 implementation in libcrux, testing that the code matches the spec.
As a drive-by change, we aligned the array layout in the implementation state to match that of the specification state.
This eases all subsequent proofs.

Adds a Hacspec specification of FIPS 202 (SHA-3 / SHAKE) under
specs/sha3, with hax extraction to F* and verification of the
four root modules (Hacspec_sha3, .Keccak_f, .Sha3, .Sponge).
The spec compiles against hax-lib 0.3.6 and verifies at the
default rlimit (no Proof_Utils dependency, no fstar! escape
hatches): properties beyond well-typedness and bound checks
are deferred to a follow-up that brings the implementation
proofs.

Adds the new crate to the specs/ sub-workspace.

Spec authored by Karthik Bhargavan. Claude (Opus 4.7) was used
to review and test the spec; it did not author the spec content.
The KeccakState helpers used 5*j+i but the rest of the impl
(and the corresponding hacspec spec, see specs/sha3) treats
the lane layout as 5*i+j. Aligns with FIPS 202 §3.1.2 lane
indexing.

Authored by Karthik Bhargavan with Claude (Opus 4.7) used to
review and test.
Adds crates/algorithms/sha3/tests/cross_spec.rs comparing the
public SHA-3/SHAKE API (including AVX2 x4 SHAKE256 and Neon x2
parallel variants) against hacspec_sha3 on boundary input sizes.

Adds an embedded #[cfg(test)] mod cross_spec_tests in
generic_keccak.rs that compares each permutation step (theta,
rho, pi, chi, iota, keccakf1600) and the simd::portable
load_block / load_last / store_block helpers against the spec.
Exposes generic_keccak::constants as pub(crate) for the test mod.

Adds hacspec_sha3 as a [dev-dependencies] entry in the sha3
crate's Cargo.toml.

Tests authored by Karthik Bhargavan. Claude (Opus 4.7) was used
to review and test.
@karthikbhargavan karthikbhargavan requested a review from a team as a code owner May 6, 2026 07:28
@abentkamp

abentkamp commented May 7, 2026

Copy link
Copy Markdown

I ran hax into aeneas-lean on these new Rust specs. Here is what I found:

One issue with extraction: Aeneas says Unimplemented for xor_block_into_state in sponge.rs (AeneasVerif/aeneas#924). An easy workaround is to use core::array::from_fn directly instead of createi.

The extracted Lean code cannot be compiled yet, mostly because of missing defs in core models:
- Insts.Core_modelsOpsIndexIndexMut.index_mut
- Slice.Insts.Core_modelsOpsIndexIndex.index
- Array.Insts.Core_modelsOpsIndexIndex.index

There is also one slightly more serious issue with Lean compilation: AeneasVerif/aeneas#984
This can probably be worked around by editing the Lean file (e.g., with a python script).

So overall, I think these specs are suitable for hax into aeneas-lean, even though we might have to make some small corrections in the future. I'll now proceed to read the code and review it.

Comment thread specs/sha3/src/lib.rs
: t_Array v_T v_N
= Rust_primitives.Arrays.createi v_N f
"#
)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to use createi instead of using array::from_fn directly for F*, right?

Maybe we could call it array_from_fn instead of createi? That would be more self-explanatory.

Comment thread specs/sha3/src/sponge.rs
Comment on lines +97 to +104
pub fn absorb_rec(state: State, rate: usize, delim: u8, message: &[u8]) -> State {
if message.len() < rate {
absorb_final(state, message, 0, message.len(), rate, delim)
} else {
let state = absorb_block(state, &message[0..rate], rate);
absorb_rec(state, rate, delim, &message[rate..])
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this form, a recursive function doesn't seem to be more useful for verification than a for-loop.

It would help if the recursive function would only call absorb_block, and leave the absorb_final call to be implemented in the absorb function below. Then we could use absorb_rec to write down a loop invariant on the implementation's for-loop, I think.

Comment on lines +425 to +430
let state = test_state();
let mut impl_out = [0u8; 200];
crate::simd::portable::store_block::<136>(&state, &mut impl_out, 0, 136);
let mut spec_out = [0u8; 200];
spec_out = spec_sponge::squeeze_state(&state, spec_out, 0, 136);
assert_eq!(impl_out[..136], spec_out[..136]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can even assert assert_eq!(impl_out, spec_out);, can't we?

Comment on lines +482 to +486
assert_eq!(
impl_out[..len],
spec_out[..len],
"store_block mismatch at len={len}"
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, can't we test equality of the entire array?

Comment on lines +499 to +502
assert_eq!(
impl_out[offset..offset + len],
spec_out[offset..offset + len]
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, too.

Comment thread specs/sha3/tests/cavp.rs
}

// ---------------------------------------------------------------------------
// Path to the test vector files (shared with reference implementation)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I read "reference implementation", I'd think of the hacspec version. So maybe better:

Suggested change
// Path to the test vector files (shared with reference implementation)
// Path to the test vector files (shared with implementation)

@abentkamp

Copy link
Copy Markdown

Other than the small nitpicks above, this looks good! I haven't compared the hacspec implementation with the official FIPS spec, though. Let me know if I should do that.

@jschneider-bensch jschneider-bensch self-assigned this May 12, 2026
@jschneider-bensch jschneider-bensch added the waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on-review Status: Awaiting review from the assignee but also interested parties.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants