Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to formal verification of P-384 #141

Merged
merged 37 commits into from
Apr 22, 2024

Conversation

apetcher-amazon
Copy link
Contributor

@apetcher-amazon apetcher-amazon commented Mar 28, 2024

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

This PR has the following significant improvements to the formal verification of P-384

  • Constant values like the pre-computed table of multiples of the base point are no longer assumed to be correct. Instead, these values are tested using formally-verified tests, and we assume these tests pass in the Coq proof. These assumptions are sufficient to prove that the constant values are correct.
  • The ECDH output conversion, including the field inverse square operation and Jacobian to affine conversion, is formally verified. After this change, all field/point conversion operations are either verified in these proofs or provided by formally verified field arithmetic libraries.

The README file was updated to remove the two significant caveats associated with the changes above. I also added two new caveats:

  • ECDH_InfinityTestCorrect describes an assumption about the behavior of a particular subroutine used in ECDH output conversion. Previously, the entire conversion routine (not just this subroutine) was assumed to be correct.
  • SAW_EC_FieldGap is a caveat that should have been included in the last PR on this topic. This caveat makes it clear that there is a gap between the semantics and memory model used to verify the field arithmetic vs other P-384 operations.

There are also significant organizational changes to the Coq proof in this PR. These changes should improve the overall organization, and they were helpful in isolating Coq performance issues that arise from operating on actual values.

Copy link
Contributor

@pennyannn pennyannn left a comment

Choose a reason for hiding this comment

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

I reviewed the SAW proof part of this PR.

@apetcher-amazon apetcher-amazon changed the title Imrovements to formal verification of P-384 Improvements to formal verification of P-384 Apr 8, 2024
Copy link

@khieta khieta left a comment

Choose a reason for hiding this comment

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

I looked through the files in Coq/EC/ and the README and didn't find anything problematic -- the Coq changes are largely refactoring.

Some more high-level comments:

  1. I left a couple minor comments about documenting assumptions in the proofs when a comment wasn't readily available. For easier review in the future, you might consider adding a special comment string (e.g. "ASSUMPTION") to assumptions like Variable and Hypothesis.
    If you're in the mood for some hacking, you could even set up GitHub CI to check that all assumptions are documented, and nothing is admitted. We do something sort of similar in our Rust code, where we check that any function that can panic is prefixed by a comment with the string "PANIC SAFETY".

  2. How are the new table validation functions actually run? I see that they're assumed to pass in the proofs, but I didn't notice them being extracted anywhere.

Comment on lines -19 to +20
# First, apply some patches (TODO: remove them)...
# Start by patching in the constant validation tests and executing them
apply_patch "p384_validate"
Copy link

Choose a reason for hiding this comment

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

Out of curiosity: what are these patches for? Are they changes that need to be contributed back to the SAW tool? Are you doing any verification pre-patching, or is all testing/verification done on the patched code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The patches are mostly used to make trivial modifications to the code (that we believe produce semantically equivalent code) to work around tool limitations or otherwise allow the formal verification to work. Most patches are used to mark functions as noinline so they will not be inlined when compiling from C to LLVM, and so we can prove them correct and re-use these proofs when functions are called. All verification is done after patching the code.

This particular patch adds in the constant verification code. This test code uses symbols that are not visible outside the p384.c file, so the test code needs to be inside this file. The simple solution is to patch it in, and then invoke the test function from the existing test suite.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To answer your question about how the test is executed: it is patched in to the test suite by the patch on line 20 of this file, and then then the entire test suite is executed on line 30.

Copy link

Choose a reason for hiding this comment

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

But the code patched in here is C code, right? How does that relate to the Coq validation functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The patched in C functions are verified against a Cryptol spec using SAW. This is done in EC_P384_validate.saw, and the Cryptol spec was added to EC_mul.cry (e.g. validate_base_table). The Cryptol spec is converted to Gallina functions with the same name, and we prove the required correctness properties of these functions in Coq. We also assume that validate_base_table was executed on the pre-computed table on line 648 of EcEqProof.v, and this assumption is justified by the fact that we executed a C function that is proven to behave in the same way as this function.

Copy link

Choose a reason for hiding this comment

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

Got it, thanks for the clarification!

Comment on lines 836 to 839
Variable felem_from_bytes : Vector.t bool 384 -> F.
Variable felem_to_bytes : F -> Vector.t bool 384.
Hypothesis felem_from_to_bytes_inv : forall x, felem_from_bytes (felem_to_bytes x) = x.
Hypothesis felem_to_from_bytes_inv : forall x, felem_to_bytes (felem_from_bytes x) = x.
Copy link

Choose a reason for hiding this comment

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

Add explanation for these variables and hypotheses

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added comments. I also removed the second Hypothesis because it is not necessary, and also probably not true for the conversion routines supplied by fiat-crypto and s2n-bignum.

Comment on lines +847 to +849
Variable F_order_correct : nat -> Prop.
Context `{F_FiniteField : @FiniteField F_order_correct F Feq _ _ _ _ _ _ _ _ F_field}.
Hypothesis p384_field_order_correct : F_order_correct p384_field_order.
Copy link

Choose a reason for hiding this comment

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

Add explanation for these variables and hypotheses

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a comment.

Comment on lines +325 to +328
Theorem sub_eq_mono : forall x1 x2 y1 y2,
x1 = x2 ->
y1 = y2 ->
(x1 - y1)%nat = (x2 - y2)%nat.
Copy link

Choose a reason for hiding this comment

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

Several of the lemmas in this file seem like they could be discharged by builtin tactics like lia. Are you separating them out for performance reasons?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lemmas like this are only used to help me structure proofs of other facts. In the case of this lemma, if I have a goal like x1 - y1 = x2 - y2, I can apply this theorem to replace it with goals x1 = x2 and y1 = y2. I could accomplish the same thing with Ltac or rewriting (in some cases), but applying a theorem is often simpler.

Comment on lines 698 to 731
Variable point_double : point -> point.
Variable point_add : bool -> point -> point -> point.
Variable sign_extend_16_64 : (bitvector 16) -> (bitvector 64).
Variable point_id_to_limb : (bitvector 16) -> (bitvector 64).
Variable F_cmovznz : (bitvector 64) -> F -> F -> F.
Variable select_point_loop_body : (bitvector 64) -> point -> (bitvector 64) -> point -> point.
Definition double_add_body_abstract := @double_add_body_abstract Fopp F_cmovznz select_point_loop_body
point_add point_double sign_extend_16_64.
Definition select_point_abstract := @select_point_abstract select_point_loop_body.

Hypothesis F_cmovznz_equiv : forall x y z,
F_cmovznz x y z = if (bvEq _ x (intToBv 64 0)) then y else z.

Hypothesis select_point_loop_body_equiv : forall w x y z,
select_point_loop_body w x y z =
if (bvEq _ y w) then z else x.

Hypothesis point_add_jac_eq : forall (a b: Curve.point) a' b',
jac_eq (fromPoint a) (seqToProd a') ->
jac_eq (fromPoint b) (seqToProd b') ->
jac_eq (fromPoint (Curve.add a b)) (seqToProd (point_add false a' b')).

Hypothesis point_add_mixed_eq : forall (a b:Curve.point) a' b',
nth_order b' two_lt_three = Fone ->
jac_eq (fromPoint a) (seqToProd a') ->
jac_eq (fromPoint b) (seqToProd b') ->
jac_eq (fromPoint (Curve.add a b)) (seqToProd (point_add true a' b')).

Hypothesis point_double_minus_3_jac_eq : forall (p:Curve.point) p',
jac_eq (fromPoint p) (seqToProd p') ->
jac_eq (fromPoint (Curve.double_minus_3 p)) (seqToProd (point_double p')).

Hypothesis sbvToInt_sign_extend_16_64_equiv : forall x,
sbvToInt _ (sign_extend_16_64 x) = sbvToInt _ x.
Copy link

Choose a reason for hiding this comment

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

Add explanation for these variables and hypotheses

(Similar comment for other uses of Hypothesis in this file, but wanted to avoid spamming comments.)

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 added some comments and re-organized the file a bit. Many of these variables/hypotheses are internal to the proof, and they are replaced with concrete terms and proofs in ECEqProof.v. Let me know if more documentation is needed to make all this clear.

Copy link

Choose a reason for hiding this comment

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

Thanks, the new re-org and comments are useful.

@apetcher-amazon apetcher-amazon merged commit fbcf31b into awslabs:master Apr 22, 2024
4 checks passed
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.

3 participants