Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
152 commits
Select commit Hold shift + click to select a range
fe7a70c
Add to the spec
TheodoreEhrenborg Aug 9, 2025
7f2eac5
Make some asserts with correct syntax
TheodoreEhrenborg Aug 9, 2025
ede60fb
Remove unneeded code
TheodoreEhrenborg Aug 9, 2025
d75c942
Put in invariant and prove it's initially true
TheodoreEhrenborg Aug 9, 2025
d9bf793
Claude adds helpful lemma
TheodoreEhrenborg Aug 9, 2025
cea8650
Remove comments
TheodoreEhrenborg Aug 9, 2025
1292443
Claude adds a calc statement
TheodoreEhrenborg Aug 9, 2025
226f417
Fix syntax
TheodoreEhrenborg Aug 9, 2025
824c76c
Put in a bunch of assumes
TheodoreEhrenborg Aug 9, 2025
9dccb0e
Add asserts after the loop
TheodoreEhrenborg Aug 9, 2025
731d328
Some work on 2nd loop
TheodoreEhrenborg Aug 9, 2025
0d057e9
Claude updates subtle_assumes.rs
TheodoreEhrenborg Aug 9, 2025
a409fc5
Change variable names
TheodoreEhrenborg Aug 9, 2025
e61cdd3
IMPORTANT: Fix assumption that was wrong on main
TheodoreEhrenborg Aug 9, 2025
734329e
Hence fix up scalar.rs
TheodoreEhrenborg Aug 9, 2025
ffe9fac
Continue pushing easy case of loop 2
TheodoreEhrenborg Aug 9, 2025
589c87d
Continue easy part of loop 2
TheodoreEhrenborg Aug 9, 2025
e0fe40f
Clean a bit
TheodoreEhrenborg Aug 9, 2025
c203a81
Prove 2nd loop in easy case is a no-op
TheodoreEhrenborg Aug 9, 2025
fbb93fd
Almost done with case 1, and simplify spec
TheodoreEhrenborg Aug 9, 2025
fc1c61c
Use mod property and hence prove easy case
TheodoreEhrenborg Aug 9, 2025
dd2697c
Claude adds an invariant
TheodoreEhrenborg Aug 9, 2025
8fa7799
Claude writes an invariant
TheodoreEhrenborg Aug 9, 2025
dd6d82f
Remove dead code
TheodoreEhrenborg Aug 9, 2025
557e9cf
Simplify structure
TheodoreEhrenborg Aug 9, 2025
6586119
Prove invariant is true on entry
TheodoreEhrenborg Aug 9, 2025
6c0aed2
Prove invariant holds, up to assumes
TheodoreEhrenborg Aug 9, 2025
5a73321
Fix an easy assume
TheodoreEhrenborg Aug 9, 2025
a7c5cda
Start proving invariant
TheodoreEhrenborg Aug 9, 2025
f033d53
Another step
TheodoreEhrenborg Aug 9, 2025
141388d
Push another step
TheodoreEhrenborg Aug 9, 2025
15c9087
Push another step
TheodoreEhrenborg Aug 9, 2025
2d6126a
Claude adds 2 steps and I mostly fix it up
TheodoreEhrenborg Aug 9, 2025
7e7a24c
Reduce to a property of pow2
TheodoreEhrenborg Aug 9, 2025
4b6f0c2
Remove that assume
TheodoreEhrenborg Aug 9, 2025
2ffb7b5
Add assert after 2nd loop
TheodoreEhrenborg Aug 9, 2025
e52f2e6
Improve variable name
TheodoreEhrenborg Aug 9, 2025
e12f9c8
Add comment
TheodoreEhrenborg Aug 9, 2025
b419698
Add more asserts after 2nd loop
TheodoreEhrenborg Aug 9, 2025
9219b23
Mostly prove carry >> 52 == 1
TheodoreEhrenborg Aug 9, 2025
8cfd57a
Remove assume
TheodoreEhrenborg Aug 9, 2025
d85fd1e
Pull out common part of proof
TheodoreEhrenborg Aug 9, 2025
f125c7f
Assert nearly the goal
TheodoreEhrenborg Aug 9, 2025
cf0ebea
Try to reach final goal, but hangs
TheodoreEhrenborg Aug 9, 2025
9ea42a5
Move hanging bit to lemma
TheodoreEhrenborg Aug 9, 2025
5f53876
Prove lemma
TheodoreEhrenborg Aug 9, 2025
37f6fe0
Format
TheodoreEhrenborg Aug 9, 2025
b6d8986
Claude refactors, revealing a non-obvious part
TheodoreEhrenborg Aug 9, 2025
d81a76e
Make non-obvious assumption clearer
TheodoreEhrenborg Aug 9, 2025
8cb27dd
Reduce to simpler assertion
TheodoreEhrenborg Aug 9, 2025
8016706
Factor out into lemma
TheodoreEhrenborg Aug 9, 2025
f537584
Start proving lemma with more general case
TheodoreEhrenborg Aug 9, 2025
50087ae
Claude writes a proof with some assumes
TheodoreEhrenborg Aug 9, 2025
69d037f
On second thought, use Seq<u64>
TheodoreEhrenborg Aug 9, 2025
836be91
Claude finishes the lemma without assumes
TheodoreEhrenborg Aug 10, 2025
264b4cd
Clean up proof a little
TheodoreEhrenborg Aug 10, 2025
90d864b
Fix an assume
TheodoreEhrenborg Aug 10, 2025
e5bc3e7
Remove another assume
TheodoreEhrenborg Aug 10, 2025
7e7c9d1
Start work on last assume in loop 1
TheodoreEhrenborg Aug 10, 2025
eab2a53
Add defn of wrapping_sub
TheodoreEhrenborg Aug 10, 2025
27d765d
Simplify a little bit
TheodoreEhrenborg Aug 10, 2025
6b04a2a
Expand expression
TheodoreEhrenborg Aug 10, 2025
6e28b6d
Remove forall
TheodoreEhrenborg Aug 10, 2025
854cf1c
Rewrite assert as two asserts
TheodoreEhrenborg Aug 10, 2025
cd5f285
Figure out what `assume` is needed for easy case
TheodoreEhrenborg Aug 10, 2025
4a27f20
Prove bound on borrow
TheodoreEhrenborg Aug 10, 2025
1cc1685
Claude removes some assumes
TheodoreEhrenborg Aug 10, 2025
58e5204
Reduce assume to easier assumes
TheodoreEhrenborg Aug 10, 2025
9485942
Do some computations
TheodoreEhrenborg Aug 10, 2025
6381b2e
Reduce diff
TheodoreEhrenborg Aug 10, 2025
62f7d2c
Add skeleton proof for trickier part
TheodoreEhrenborg Aug 10, 2025
2f9efe1
Replace an assume with an easier one
TheodoreEhrenborg Aug 10, 2025
8227806
Make assumption about borrow's value
TheodoreEhrenborg Aug 10, 2025
b5ee959
Get the algebra working
TheodoreEhrenborg Aug 10, 2025
d89bed9
Remove step
TheodoreEhrenborg Aug 10, 2025
110cf9d
Remove another step
TheodoreEhrenborg Aug 10, 2025
ecf01d0
Hence remove assume(false)
TheodoreEhrenborg Aug 10, 2025
0b1ba17
Prove that borrow is large enough
TheodoreEhrenborg Aug 10, 2025
a01fbdb
Hence remove assume
TheodoreEhrenborg Aug 10, 2025
e5d4e09
Remove another assume
TheodoreEhrenborg Aug 10, 2025
321e756
Reduce assume to easier assumes
TheodoreEhrenborg Aug 10, 2025
08b5128
Prove 2 easy assumes
TheodoreEhrenborg Aug 10, 2025
ce13ef5
Verus can prove this assume
TheodoreEhrenborg Aug 10, 2025
8c3c6a6
Factor out into lemma
TheodoreEhrenborg Aug 10, 2025
6f1a629
Claude proves a lemma
TheodoreEhrenborg Aug 10, 2025
b5d85d5
Remove redundant assert
TheodoreEhrenborg Aug 10, 2025
b47c9c7
Pull first loop invariant proof into lemma
TheodoreEhrenborg Aug 10, 2025
933dfcb
Add some preconditions to lemma
TheodoreEhrenborg Aug 10, 2025
b3ebfca
Fix up preconditions but then verus hangs
TheodoreEhrenborg Aug 10, 2025
b7abd0b
Put in assert and it stops hanging
TheodoreEhrenborg Aug 10, 2025
a607824
Add more preconditions
TheodoreEhrenborg Aug 10, 2025
87d943a
Add more assertions to part that fails
TheodoreEhrenborg Aug 10, 2025
4ef0725
Add another assert to satisfy precondition
TheodoreEhrenborg Aug 10, 2025
39beca0
Shore up the lemma with assumes
TheodoreEhrenborg Aug 10, 2025
cfe83c1
Can use lemma_decompose without rlimit problem
TheodoreEhrenborg Aug 10, 2025
38f9126
Add more asserts to fix those 2 new assumes
TheodoreEhrenborg Aug 10, 2025
3b1ae0a
Fix two assumes
TheodoreEhrenborg Aug 10, 2025
e67cb37
Fix assume
TheodoreEhrenborg Aug 10, 2025
41ab36e
Fix other assume
TheodoreEhrenborg Aug 10, 2025
bb269f3
Mostly prove lemma
TheodoreEhrenborg Aug 10, 2025
6a1f76f
Clean up asserts a little
TheodoreEhrenborg Aug 10, 2025
af7a880
Try to prove base case
TheodoreEhrenborg Aug 10, 2025
71d9cde
Claude tries to prove base case
TheodoreEhrenborg Aug 10, 2025
bc1ab78
Put in an assume to stop proof hanging
TheodoreEhrenborg Aug 10, 2025
7625da9
Some progess on why it doesn't verify
TheodoreEhrenborg Aug 10, 2025
ee31ce1
Still hanging
TheodoreEhrenborg Aug 10, 2025
f2738fa
Fix hanging by keeping one assume
TheodoreEhrenborg Aug 10, 2025
317396f
Can prove that assert in a lemma
TheodoreEhrenborg Aug 10, 2025
e46d583
Hence remove final assume
TheodoreEhrenborg Aug 10, 2025
ba9ff09
Clean up last lemma
TheodoreEhrenborg Aug 10, 2025
08190af
Change spec to be more correct
TheodoreEhrenborg Aug 10, 2025
71a82b4
Add todo
TheodoreEhrenborg Aug 10, 2025
3ab992d
Weaken spec, but I think this is what add needs
TheodoreEhrenborg Aug 10, 2025
f8766c5
`sub` verifies with essentially no more effort
TheodoreEhrenborg Aug 10, 2025
660ba72
Make `add` happy
TheodoreEhrenborg Aug 10, 2025
b5426a8
Remove unused function
TheodoreEhrenborg Aug 10, 2025
1d17065
Remove cruft
TheodoreEhrenborg Aug 10, 2025
d7d8e07
Move lemma
TheodoreEhrenborg Aug 10, 2025
38fac50
Fix up imports
TheodoreEhrenborg Aug 10, 2025
c0a74cf
Move lemmas down
TheodoreEhrenborg Aug 10, 2025
b0d028a
Format
TheodoreEhrenborg Aug 10, 2025
378cdb5
Start silencing warnings
TheodoreEhrenborg Aug 10, 2025
5efc78c
Move code to lemma
TheodoreEhrenborg Aug 10, 2025
1255d8e
Small syntax fixes
TheodoreEhrenborg Aug 10, 2025
423d620
Add some preconditions and a post-condition
TheodoreEhrenborg Aug 10, 2025
22890c0
More preconditions
TheodoreEhrenborg Aug 10, 2025
e52fd05
Lemma now verifies
TheodoreEhrenborg Aug 10, 2025
4b006bd
Remove some asserts
TheodoreEhrenborg Aug 10, 2025
7ab61f7
Combine if statements
TheodoreEhrenborg Aug 10, 2025
49446f6
Combine if statements, again
TheodoreEhrenborg Aug 10, 2025
9cb6018
Move loop 2 proof into lemma
TheodoreEhrenborg Aug 10, 2025
28f303e
Fix syntax errors
TheodoreEhrenborg Aug 10, 2025
241738c
Add postconditions to lemma
TheodoreEhrenborg Aug 10, 2025
552989a
Add preconditions to lemma
TheodoreEhrenborg Aug 10, 2025
bcc015a
More preconditions
TheodoreEhrenborg Aug 10, 2025
fc7e60b
Down to 5 errors
TheodoreEhrenborg Aug 10, 2025
401ec4c
Tell the lemma what addend is
TheodoreEhrenborg Aug 10, 2025
4379c7b
Get lemma to verify
TheodoreEhrenborg Aug 10, 2025
b834c05
Format
TheodoreEhrenborg Aug 10, 2025
3e2c80c
Remove unneeded assert
TheodoreEhrenborg Aug 10, 2025
fd4eec6
More small tweaks
TheodoreEhrenborg Aug 10, 2025
07f1765
Remove unused imports
TheodoreEhrenborg Aug 10, 2025
0ef0f3d
Remove unused parameter
TheodoreEhrenborg Aug 10, 2025
b687b10
Remove unused import
TheodoreEhrenborg Aug 10, 2025
25bc694
Rename variable
TheodoreEhrenborg Aug 10, 2025
c3f8fbd
Reduce diff
TheodoreEhrenborg Aug 10, 2025
fff73f2
Fix my typo
TheodoreEhrenborg Aug 10, 2025
8d7e9e2
Add link to subtle docs
TheodoreEhrenborg Aug 10, 2025
435f0fd
Clarify comment
TheodoreEhrenborg Aug 11, 2025
856e203
Delete unneeded assert
TheodoreEhrenborg Aug 11, 2025
0ed1f5e
Add comments
TheodoreEhrenborg Aug 11, 2025
55d56c8
Small typo
TheodoreEhrenborg Aug 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions curve25519-dalek/src/backend/serial/u64/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ impl Scalar52 {

// subtract l if the sum is >= l
proof { lemma_l_value_properties(&constants::L, &sum); }
assume(to_nat(&sum.limbs) < 2 * group_order());
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I've added this assume in add so it can call sub. I think this can be propagated back to become another precondition for add

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

assert(to_nat(&sum.limbs) >= 0);

Isn't this a tautology based on types? to_nat returns a nat, which is always >= 0

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed

assert(group_order() > to_nat(&sum.limbs) - group_order() >= -group_order());
proof{lemma_l_equals_group_order();}
let result = Scalar52::sub(&sum, &constants::L);
assume(to_nat(&result.limbs) == (to_nat(&a.limbs) + to_nat(&b.limbs)) % group_order());
result
Expand All @@ -259,44 +262,96 @@ impl Scalar52 {
requires
limbs_bounded(a),
limbs_bounded(b),
// Without the following condition, all we can prove is something like:
// to_nat(&a.limbs) >= to_nat(&b.limbs) ==> to_nat(&s.limbs) == to_nat(&a.limbs) - to_nat(&b.limbs),
// to_nat(&a.limbs) < to_nat(&b.limbs) ==> to_nat(&s.limbs) == (to_nat(&a.limbs) - to_nat(&b.limbs) + pow2(260) + group_order()) % (pow2(260) as int),
// In the 2nd case, `sub` doesn't always do subtraction mod group_order
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It took me several tries to get the spec right

-group_order() <= to_nat(&a.limbs) - to_nat(&b.limbs) < group_order(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

How come this is necessary though?
If a and b are bounded, then to_nat(a) < order and a - b <= a, similar for b and -order.

Copy link
Copy Markdown
Collaborator Author

@TheodoreEhrenborg TheodoreEhrenborg Aug 11, 2025

Choose a reason for hiding this comment

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

limbs_bounded(a) guarantees that to_nat(a) < 2^260, but group_order ~ 2^252. So I tried requiring 0 <= a < group_order && 0 <= b < group_order, but add passes in values sometimes larger than group_order. I think add's call to sub satisfies 0 <= a < 2 * group_order && 0 <= b < 2 * group_order, which is simpler than the precondition I went with, but that precondition is too loose for sub to always be correct

Also montgomery_reduce calls sub, and I don't yet understand it well enough to know if it satisfies sub's preconditions.

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 that case, I'd consider relaxing this requirement, but changing ensures to

        to_nat(&s.limbs) % (group_order() as int) == (to_nat(&a.limbs) - to_nat(&b.limbs)) % (group_order() as int),

result limbs are bounded, but may represent a value greater than the order, in which case you have to take mod p on the left too.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The weird thing about sub is that

to_nat(&s.limbs) % (group_order() as int) == (to_nat(&a.limbs) - to_nat(&b.limbs)) % (group_order() as int),

isn't always true. If to_nat(&a.limbs) < to_nat(&b.limbs), we get

to_nat(&s.limbs) == (to_nat(&a.limbs) - to_nat(&b.limbs) + pow2(260) + group_order()) % (pow2(260) as int),

And then if to_nat(&a.limbs) - to_nat(&b.limbs) < -group_order, this simplifies to

to_nat(&s.limbs) == to_nat(&a.limbs) - to_nat(&b.limbs) + pow2(260) + group_order(),

And mod group_order, we get

s % group_order == (a - b + pow(260)) % group order  != (a - b) % group order

Here's a test demonstrating this: https://github.com/Beneficial-AI-Foundation/curve25519-dalek/tree/sub_test

ensures
to_nat(&s.limbs) == (to_nat(&a.limbs) + group_order() - to_nat(&b.limbs)) % (group_order() as int)
to_nat(&s.limbs) == (to_nat(&a.limbs) - to_nat(&b.limbs)) % (group_order() as int),
limbs_bounded(&s),
{
let mut difference = Scalar52 { limbs: [0u64, 0u64, 0u64, 0u64, 0u64] };
proof { assert(1u64 << 52 > 0) by (bit_vector);}
let mask = (1u64 << 52) - 1;

// a - b
let mut borrow: u64 = 0;
assert(seq_u64_to_nat(a.limbs@.subrange(0, 0 as int)) - seq_u64_to_nat(b.limbs@.subrange(0, 0 as int )) ==
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I've moved most of the proof of sub into lemmas, but I've left in the invariants and a few asserts. I could probably squish a few more asserts into lemmas, although some of them are needed to get Verus to recognize that a lemma has the right preconditions

seq_u64_to_nat(difference.limbs@.subrange(0, 0 as int )));
assert( (borrow >> 63) == 0 ) by (bit_vector)
requires borrow == 0;
assert(seq_u64_to_nat(a.limbs@.subrange(0, 0 as int)) - seq_u64_to_nat(b.limbs@.subrange(0, 0 as int )) ==
seq_u64_to_nat(difference.limbs@.subrange(0, 0 as int )) - (borrow >> 63) * pow2((52 * (0) as nat)));
for i in 0..5
invariant
limbs_bounded(b),
limbs_bounded(a),
forall|j: int| 0 <= j < i ==> difference.limbs[j] < (1u64 << 52),
mask == (1u64 << 52) - 1,
seq_u64_to_nat(a.limbs@.subrange(0, i as int)) - seq_u64_to_nat(b.limbs@.subrange(0, i as int )) ==
seq_u64_to_nat(difference.limbs@.subrange(0, i as int )) - (borrow >> 63) * pow2((52 * (i) as nat))
{
proof { assert ((borrow >> 63) < 2) by (bit_vector); }
let ghost old_borrow = borrow;
borrow = a.limbs[i].wrapping_sub(b.limbs[i] + (borrow >> 63));
let ghost difference_loop1_start = difference;
difference.limbs[i] = borrow & mask;
assert(difference_loop1_start.limbs@.subrange(0, i as int) == difference.limbs@.subrange(0, i as int));
assert(
seq_u64_to_nat(a.limbs@.subrange(0, i as int)) - seq_u64_to_nat(b.limbs@.subrange(0, i as int )) ==
seq_u64_to_nat(difference_loop1_start.limbs@.subrange(0, i as int )) - (old_borrow >> 63) * pow2((52 * (i) as nat)));
proof{
lemma_sub_loop1_invariant(difference, borrow, i, a, b, old_borrow, mask, difference_loop1_start);
}
proof { lemma_borrow_and_mask_bounded(borrow, mask); }
}

assert(seq_u64_to_nat(a.limbs@.subrange(0, 5 as int)) - seq_u64_to_nat(b.limbs@.subrange(0, 5 as int )) ==
seq_u64_to_nat(difference.limbs@.subrange(0, 5 as int )) - (borrow >> 63) * pow2((52 * (5) as nat)) );
// conditionally add l if the difference is negative
assert(borrow >> 63 == 1 || borrow >> 63 == 0) by (bit_vector);
let mut carry: u64 = 0;
let ghost difference_after_loop1 = difference;
assert(seq_u64_to_nat(difference_after_loop1.limbs@.subrange(0, 0 as int)) == 0);
assert(seq_u64_to_nat(constants::L.limbs@.subrange(0, 0 as int)) == 0);
assert(seq_u64_to_nat(difference.limbs@.subrange(0, 0 as int)) == 0);
assert(carry >> 52 == 0) by (bit_vector)
requires carry == 0;
for i in 0..5
invariant
forall|j: int| 0 <= j < 5 ==> difference.limbs[j] < (1u64 << 52), // from first loop
forall|j: int| i <= j < 5 ==> difference.limbs[j] == difference_after_loop1.limbs[j],
mask == (1u64 << 52) - 1,
i == 0 ==> carry == 0,
i >= 1 ==> (carry >> 52) < 2,
(i >=1 && borrow >> 63 == 0) ==> carry == difference.limbs[i-1],
borrow >> 63 == 0 ==> difference_after_loop1 == difference,
borrow >> 63 == 1 ==>
seq_u64_to_nat(difference_after_loop1.limbs@.subrange(0, i as int)) + seq_u64_to_nat(constants::L.limbs@.subrange(0, i as int)) ==
seq_u64_to_nat(difference.limbs@.subrange(0, i as int)) + (carry >> 52) * pow2(52 * i as nat)

{
let ghost old_carry = carry;
let underflow = Choice::from((borrow >> 63) as u8);
let addend = select(&0, &constants::L.limbs[i], underflow);
if borrow >> 63 == 0 {
assert(addend == 0);
}
if borrow >> 63 == 1 {
assert(addend == constants::L.limbs[i as int]);
}
proof {lemma_scalar_subtract_no_overflow(carry, difference.limbs[i as int], addend, i as u32, &constants::L);}
carry = (carry >> 52) + difference.limbs[i] + addend;
let ghost difference_loop2_start = difference;
difference.limbs[i] = carry & mask;
proof { lemma_carry_bounded_after_mask(carry, mask); }
proof {
lemma_carry_bounded_after_mask(carry, mask);
assert(difference_loop2_start.limbs@.subrange(0, i as int) == difference.limbs@.subrange(0, i as int));
lemma_sub_loop2_invariant(difference, i, a, b, mask, difference_after_loop1, difference_loop2_start, carry, old_carry, addend, borrow);
}
}
assume(to_nat(&difference.limbs) == (to_nat(&a.limbs) + group_order() - to_nat(&b.limbs)) % (group_order() as int));
proof { lemma_sub_correct_after_loops(difference, carry, a, b, difference_after_loop1, borrow);}
difference
}

Expand Down
Loading