Skip to content

ML-KEM Proofs Cleanup#1291

Draft
karthikbhargavan wants to merge 22 commits intomainfrom
proofs-cleanup
Draft

ML-KEM Proofs Cleanup#1291
karthikbhargavan wants to merge 22 commits intomainfrom
proofs-cleanup

Conversation

@karthikbhargavan
Copy link
Contributor

This PR does a few things:

  • Switches all the annotations in traits, polynomial, ntt, and invert_ntt to mostly use Rust (with a few F*-specific annotations)
  • Simplifies the proofs by using opaque predicates, removing the need for a lot of F* annotations in modules above the vector abstraction

We should discuss this style and stabilize it before going further.
One design point is the use of cfg(hax) for specs.
I was doing that uniformly before, but it is inconvenient when we start using Rust annotations more uniformly.

But maybe now the code is less cluttered, we don't mind having hax enabled everywhere? Dunno.
So some discussion is needed around how and whether to use conditional flags.

Copy link
Collaborator

@jschneider-bensch jschneider-bensch left a comment

Choose a reason for hiding this comment

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

Can you say more about the inconvenience of cfg(hax) when using Rust specs?

v (Seq.index ${_lhs0}.f_elements i) + v (Seq.index ${rhs}.f_elements i))"
);
hax_lib::fstar!(
r#"reveal_opaque (`%Spec.Utils.is_i16b_array_opaque) (Spec.Utils.is_i16b_array_opaque)"#
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this related to the introduction of opaque predicates? Does it mean while the higher-level modules will have less F* annotations, we will need to reveal_opaque in the lower-levels?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the observation is that the verification of higher-level modules was taking an unnecessarily long time because the underlying math was not opaque, even if the math itself is not needed for the proof. So we make it opaque at (or near) the trait boundary, so modules above can be verified quickly and the modules below will need to reveal these predicates when needed.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The spec functions return hax_lib::Prop instead of bool, which means you use .and here as the connective. That could be cumbersome in case there are a larger number of preconditions that have to be .anded together. Would it be possible to implement e.g. BitAnd for Prop to be able to & two of them. (The short-circuiting && is sadly not overloadable.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that would be a good feature upgrade. For now, I am trying to live within the constraints of the current hax-lib until libcrux is upgraded to latest hax.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This invariant is much more readable (to me)! Any reason to use the fstar_prop_expr! instead of (*zeta_i == _zeta_i_init - round * 4).to_prop()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no good reason, we should change to your version (earlier, I may have been trying to avoid to_prop but it was not a good reason).

(Libcrux_ml_kem.Vector.Traits.f_to_i16_array (re.f_coefficients.[ sz i ])))"#
hax_lib::fstar_prop_expr!(r#"v $zeta_i == v $_zeta_i_init - v $round * 4"#).and(
hax_lib::forall(|i: usize| {
if i < 16 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe there could be a convenience macro for loop invariants like this, e.g. pivot!(array, round, prop_lower_equal, prop_higher) that states prop_lower_equal for array[i] with i in 0..=round and prop_higher for i in (round + 1).. array.len().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, yes, once we have a bunch of annotations like these, we can try to find common patterns. (Copilot is already able to suggest these patterns to me now.)

#[hax_lib::fstar::options("--z3rlimit 200 --ext context_pruning")]
#[hax_lib::requires(
spec::is_bounded_poly(3328, re).and(
match layer {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's neat to be able to use a match here, but it could also be written in terms of layer, i.e. *zeta_i == (1 << (8 - layer)) (keeping the boundedness condition for layer). Does it make a difference for the proofs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't like using shifts in specs. Maybe you mean pow2(8 - layer) which may be nicer if that is what the NIST spec says e.g.

@karthikbhargavan
Copy link
Contributor Author

Can you say more about the inconvenience of cfg(hax) when using Rust specs?

I undid that, it basically needs a few more cfg(hax) sprinkled around to make sure specs are only referred to in hax mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments