val lemma_sqr (a:int) (a0 a1 a2 a3
               r8 r9 r10 r11 r12 r13 rax rcx
               r8' r9' r10' r11' r12' r13' r14'
               d0 d1 d2 d3 d4 d5 d6 d7:nat64) (cf:bit)
  : Lemma
    (requires
      a = pow2_four a0 a1 a2 a3 /\
      pow2_six r8 r9 r10 r11 r12 r13 ==
        pow2_five (mul_nats a0 a1)
                  (mul_nats a0 a2)
                  (mul_nats a0 a3)
                  (mul_nats a1 a3)
                  (mul_nats a2 a3) /\
      pow2_64 * rcx + rax == a1 * a2 /\
      pow2_seven r8' r9' r10' r11' r12' r13' r14' ==
        pow2_six (2*r8)
                 (2*r9)
                 (2*(r10+rax))
                 (2*(r11+rcx))
                 (2*r12)
                 (2*r13) /\
      pow2_nine d0 d1 d2 d3 d4 d5 d6 d7 cf ==
        pow2_eight (mul_nats a0 a0)
                   r8'
                   ((mul_nats a1 a1) + r9')
                   r10'
                   ((mul_nats a2 a2) + r11')
                   r12'
                   ((mul_nats a3 a3) + r13')
                   r14')
  (ensures
    a*a == pow2_eight d0 d1 d2 d3 d4 d5 d6 d7)
let lemma_sqr a a0 a1 a2 a3
                r8 r9 r10 r11 r12 r13 rax rcx
                r8' r9' r10' r11' r12' r13' r14'
                d0 d1 d2 d3 d4 d5 d6 d7
              cf =
  assert (a < pow2_256); // PASSES
  assert_norm (pow2_256 == pow2 256); // PASSES
  pow2_plus 256 256;
  lemma_mul_pow2_bound 256 a a;
  assert (a*a < pow2_256 * pow2_256); // PASSES
  assert (cf = 1 ==> pow2_nine d0 d1 d2 d3 d4 d5 d6 d7 cf >= pow2_512);
  assert_by_tactic ((pow2_four a0 a1 a2 a3) * (pow2_four a0 a1 a2 a3) == pow2_seven (mul_nats a0 a0) (2*(mul_nats a0 a1)) (2*(mul_nats a0 a2) + (mul_nats a1 a1))
                                      (2*((mul_nats a0 a3) + (mul_nats a1 a2))) (2*(mul_nats a1 a3) + (mul_nats a2 a2)) (2*(mul_nats a2 a3)) (mul_nats a3 a3))
    (fun _ -> int_canon (); trefl(); qed ());
  let lhs:int = pow2_eight (mul_nats a0 a0) r8' ((mul_nats a1 a1) + r9') r10' ((mul_nats a2 a2) + r11') r12' ((mul_nats a3 a3) + r13') r14' in
  let squares = pow2_eight (mul_nats a0 a0) 0 (mul_nats a1 a1) 0 (mul_nats a2 a2) 0 (mul_nats a3 a3) 0 in
  let regs = pow2_eight 0 r8' r9' r10' r11' r12' r13' r14' in
  let rhs:int = squares + regs in
  assert (2*(mul_nats a0 a3 + a1*a2) >= 0);
  calc
    (eq2 #int) {
      pow2_eight
        (mul_nats a0 a0)
        r8'
        ((mul_nats a1 a1) + r9')
        r10'
        ((mul_nats a2 a2) + r11')
        r12'
        ((mul_nats a3 a3) + r13')
        r14';
    (eq2 #int ) { _ by (int_canon()) }
      pow2_eight
        (mul_nats a0 a0)
        0
        (mul_nats a1 a1)
        0
        (mul_nats a2 a2)
        0
        (mul_nats a3 a3)
        0
      + pow2_eight 0 r8' r9' r10' r11' r12' r13' r14';
    (eq2 #int) {
               calc
                 (eq2 #int) {
                    pow2_eight 0 r8' r9' r10' r11' r12' r13' r14';
                 (eq2 #int) { lemma_shift_pow2_eight r8' r9' r10' r11' r12' r13' r14' }
                    pow2_64 * pow2_six (2*r8) (2*r9) (2*(r10+rax)) (2*(r11+rcx)) (2*r12) (2*r13);
                 (eq2 #int) {
                              calc
                                (eq2 #int) {
                                   pow2_six (2*r8) (2*r9) (2*(r10+rax)) (2*(r11+rcx)) (2*r12) (2*r13);
                                (eq2 #int) { lemma_dbl_pow2_six r8 r9 (r10+rax) (r11+rcx) r12 r13 }
                                   2 * pow2_six r8 r9 (r10+rax) (r11+rcx) r12 r13;
                                (eq2 #int) { () }
                                           //   calc
                                           //     (eq2 #int) {
                                           //        pow2_six r8 r9 (r10+rax) (r11+rcx) r12 r13;
                                           //     (eq2 #int) { _ by (int_canon()) }
                                           //        pow2_six r8 r9 r10 r11 r12 r13 + pow2_six 0 0 rax rcx 0 0;
                                           //     (eq2 #int) { admit() }
                                           //        pow2_six (mul_nats a0 a1) (mul_nats a0 a2) (mul_nats a0 a3) (mul_nats a1 a3) (mul_nats a2 a3) 0
                                           //        + pow2_six 0 0 rax rcx 0 0;
                                           //     (eq2 #int) {
                                           //                   calc
                                           //                     (eq2 #int) {
                                           //                       pow2_six 0 0 rax rcx 0 0;
                                           //                     (eq2 #int) { () }
                                           //                       pow2_six 0 0 (a1*a2) 0 0 0;
                                           //                   }
                                           //                 }
                                           //        pow2_six (mul_nats a0 a1) (mul_nats a0 a2) (mul_nats a0 a3) (mul_nats a1 a3) (mul_nats a2 a3) 0
                                           //        + pow2_six 0 0 (a1*a2) 0 0 0;
                                           //     (eq2 #int) { admit () }
                                           //        pow2_six (mul_nats a0 a1) (mul_nats a0 a2) (mul_nats a0 a3 + a1*a2) (mul_nats a1 a3) (mul_nats a2 a3) 0;
                                           //   }
                                           // }
                                   2*pow2_six (mul_nats a0 a1) (mul_nats a0 a2) (mul_nats a0 a3 + a1*a2) (mul_nats a1 a3) (mul_nats a2 a3) 0;
                                (eq2 #int) {  () }
                                   pow2_six (2*(mul_nats a0 a1)) (2*(mul_nats a0 a2)) (2*(mul_nats a0 a3 + a1*a2)) (2*(mul_nats a1 a3)) (2*(mul_nats a2 a3)) 0;
                               }
                            }
                    pow2_64 * pow2_six (2*(mul_nats a0 a1)) (2*(mul_nats a0 a2)) (2*(mul_nats a0 a3 + a1*a2)) (2*(mul_nats a1 a3)) (2*(mul_nats a2 a3)) 0;
                 (eq2 #int) { () }
                    pow2_seven 0 (2*(mul_nats a0 a1)) (2*(mul_nats a0 a2)) (2*(mul_nats a0 a3 + a1*a2)) (2*(mul_nats a1 a3)) (2*(mul_nats a2 a3)) 0;
               }
             }
      pow2_eight
        (mul_nats a0 a0)
        (2*(mul_nats a0 a1))
        ((2*(mul_nats a0 a2)) + mul_nats a1 a1)
        (2*(mul_nats a0 a3 + a1*a2))
        ((2*(mul_nats a1 a3))
      + mul_nats a2 a2) (2*(mul_nats a2 a3)) (mul_nats a3 a3) 0;
    (eq2 #int) { () }
      pow2_seven
        (mul_nats a0 a0)
        (2*(mul_nats a0 a1))
        ((2*(mul_nats a0 a2)) + mul_nats a1 a1)
        (2*(mul_nats a0 a3 + a1*a2))
        ((2*(mul_nats a1 a3)) + mul_nats a2 a2)
        (2*(mul_nats a2 a3))
        (mul_nats a3 a3);
   };
  assert (pow2_nine d0 d1 d2 d3 d4 d5 d6 d7 cf == a*a);  // PASSES
  //assert (cf == 0);
  let ultimate_rhs:int = pow2_eight d0 d1 d2 d3 d4 d5 d6 d7 in
  assert_by_tactic (pow2_nine d0 d1 d2 d3 d4 d5 d6 d7 cf == ultimate_rhs) int_canon
val lemma_sqr (a:int) (a0 a1 a2 a3
               r8 r9 r10 r11 r12 r13 rax rcx
               r8' r9' r10' r11' r12' r13' r14'
               d0 d1 d2 d3 d4 d5 d6 d7:nat64) (cf:bit)
  : Lemma
    (requires
      a = pow2_four a0 a1 a2 a3 /\
      pow2_six r8 r9 r10 r11 r12 r13 ==
        pow2_five (mul_nats a0 a1)
                  (mul_nats a0 a2)
                  (mul_nats a0 a3)
                  (mul_nats a1 a3)
                  (mul_nats a2 a3) /\
      pow2_64 * rcx + rax == a1 * a2 /\
      pow2_seven r8' r9' r10' r11' r12' r13' r14' ==
        pow2_six (2*r8)
                 (2*r9)
                 (2*(r10+rax))
                 (2*(r11+rcx))
                 (2*r12)
                 (2*r13) /\
      pow2_nine d0 d1 d2 d3 d4 d5 d6 d7 cf ==
        pow2_eight (mul_nats a0 a0)
                   r8'
                   ((mul_nats a1 a1) + r9')
                   r10'
                   ((mul_nats a2 a2) + r11')
                   r12'
                   ((mul_nats a3 a3) + r13')
                   r14')
  (ensures
    a*a == pow2_eight d0 d1 d2 d3 d4 d5 d6 d7)
let lemma_sqr
      a
      a0
      a1
      a2
      a3
      r8
      r9
      r10
      r11
      r12
      r13
      rax
      rcx
      r8'
      r9'
      r10'
      r11'
      r12'
      r13'
      r14'
      d0
      d1
      d2
      d3
      d4
      d5
      d6
      d7
      cf
     =
assert (a < pow2_256);
assert_norm (pow2_256 == pow2 256);
pow2_plus 256 256;
lemma_mul_pow2_bound 256 a a;
assert (a * a < pow2_256 * pow2_256);
assert (cf = 1 ==> pow2_nine d0 d1 d2 d3 d4 d5 d6 d7 cf >= pow2_512);
assert_by_tactic ((pow2_four a0 a1 a2 a3) * (pow2_four a0 a1 a2 a3) ==
    pow2_seven (mul_nats a0 a0)
      (2 * (mul_nats a0 a1))
      (2 * (mul_nats a0 a2) + (mul_nats a1 a1))
      (2 * ((mul_nats a0 a3) + (mul_nats a1 a2)))
      (2 * (mul_nats a1 a3) + (mul_nats a2 a2))
      (2 * (mul_nats a2 a3))
      (mul_nats a3 a3))
  (fun _ ->
      int_canon ();
      trefl ();
      qed ());
let lhs:int =
  pow2_eight (mul_nats a0 a0)
    r8'
    ((mul_nats a1 a1) + r9')
    r10'
    ((mul_nats a2 a2) + r11')
    r12'
    ((mul_nats a3 a3) + r13')
    r14'
in
let squares =
  pow2_eight (mul_nats a0 a0) 0 (mul_nats a1 a1) 0 (mul_nats a2 a2) 0 (mul_nats a3 a3) 0
in
let regs = pow2_eight 0 r8' r9' r10' r11' r12' r13' r14' in
let rhs:int = squares + regs in
assert (2 * (mul_nats a0 a3 + a1 * a2) >= 0);
calc (eq2 #int) {
  pow2_eight (mul_nats a0 a0)
    r8'
    ((mul_nats a1 a1) + r9')
    r10'
    ((mul_nats a2 a2) + r11')
    r12'
    ((mul_nats a3 a3) + r13')
    r14';
  (eq2 #int) { FStar.Tactics.Effect.synth_by_tactic (fun _ -> (int_canon ())) }
  pow2_eight (mul_nats a0 a0) 0 (mul_nats a1 a1) 0 (mul_nats a2 a2) 0 (mul_nats a3 a3) 0 +
  pow2_eight 0 r8' r9' r10' r11' r12' r13' r14';
  (eq2 #int) { calc (eq2 #int) {
    pow2_eight 0 r8' r9' r10' r11' r12' r13' r14';
    (eq2 #int) { lemma_shift_pow2_eight r8' r9' r10' r11' r12' r13' r14' }
    pow2_64 * pow2_six (2 * r8) (2 * r9) (2 * (r10 + rax)) (2 * (r11 + rcx)) (2 * r12) (2 * r13);
    (eq2 #int) { calc (eq2 #int) {
      pow2_six (2 * r8) (2 * r9) (2 * (r10 + rax)) (2 * (r11 + rcx)) (2 * r12) (2 * r13);
      (eq2 #int) { lemma_dbl_pow2_six r8 r9 (r10 + rax) (r11 + rcx) r12 r13 }
      2 * pow2_six r8 r9 (r10 + rax) (r11 + rcx) r12 r13;
      (eq2 #int) { () }
      2 *
      pow2_six (mul_nats a0 a1)
        (mul_nats a0 a2)
        (mul_nats a0 a3 + a1 * a2)
        (mul_nats a1 a3)
        (mul_nats a2 a3)
        0;
      (eq2 #int) { () }
      pow2_six (2 * (mul_nats a0 a1))
        (2 * (mul_nats a0 a2))
        (2 * (mul_nats a0 a3 + a1 * a2))
        (2 * (mul_nats a1 a3))
        (2 * (mul_nats a2 a3))
        0;
    } }
    pow2_64 *
    pow2_six (2 * (mul_nats a0 a1))
      (2 * (mul_nats a0 a2))
      (2 * (mul_nats a0 a3 + a1 * a2))
      (2 * (mul_nats a1 a3))
      (2 * (mul_nats a2 a3))
      0;
    (eq2 #int) { () }
    pow2_seven 0
      (2 * (mul_nats a0 a1))
      (2 * (mul_nats a0 a2))
      (2 * (mul_nats a0 a3 + a1 * a2))
      (2 * (mul_nats a1 a3))
      (2 * (mul_nats a2 a3))
      0;
  } }
  pow2_eight (mul_nats a0 a0)
    (2 * (mul_nats a0 a1))
    ((2 * (mul_nats a0 a2)) + mul_nats a1 a1)
    (2 * (mul_nats a0 a3 + a1 * a2))
    ((2 * (mul_nats a1 a3)) + mul_nats a2 a2)
    (2 * (mul_nats a2 a3))
    (mul_nats a3 a3)
    0;
  (eq2 #int) { () }
  pow2_seven (mul_nats a0 a0)
    (2 * (mul_nats a0 a1))
    ((2 * (mul_nats a0 a2)) + mul_nats a1 a1)
    (2 * (mul_nats a0 a3 + a1 * a2))
    ((2 * (mul_nats a1 a3)) + mul_nats a2 a2)
    (2 * (mul_nats a2 a3))
    (mul_nats a3 a3);
};
assert (pow2_nine d0 d1 d2 d3 d4 d5 d6 d7 cf == a * a);
let ultimate_rhs:int = pow2_eight d0 d1 d2 d3 d4 d5 d6 d7 in
assert_by_tactic (pow2_nine d0 d1 d2 d3 d4 d5 d6 d7 cf == ultimate_rhs) int_canon
Vale.Curve25519.FastMul_helpers.fst.checked
Vale.Def.Words_s.fsti.checked Vale.Def.Types_s.fst.checked Vale.Curve25519.Fast_lemmas_internal.fsti.checked Vale.Curve25519.Fast_defs.fst.checked prims.fst.checked FStar.Tactics.Effect.fsti.checked FStar.Tactics.CanonCommSemiring.fst.checked FStar.Tactics.fst.checked FStar.Pervasives.fsti.checked FStar.Mul.fst.checked FStar.Math.Lemmas.fst.checked FStar.Calc.fsti.checked
Vale.Curve25519.FastMul_helpers.fst
Prims.int Vale.Def.Types_s.nat64 Vale.Curve25519.FastMul_helpers.bit FStar.Tactics.Effect.assert_by_tactic Prims.eq2 Vale.Curve25519.Fast_defs.pow2_nine Vale.Curve25519.FastMul_helpers.int_canon Prims.unit Vale.Curve25519.Fast_defs.pow2_eight Prims._assert FStar.Mul.op_Star FStar.Calc.calc_finish Vale.Curve25519.Fast_defs.mul_nats Prims.op_Addition Vale.Curve25519.Fast_defs.pow2_seven Prims.Cons FStar.Preorder.relation Prims.Nil FStar.Calc.calc_step FStar.Calc.calc_init FStar.Calc.calc_pack FStar.Tactics.CanonCommSemiring.semiring_reflect FStar.Tactics.CanonCommSemiring.int_cr FStar.Pervasives.Native.Mktuple2 Prims.list FStar.Pervasives.Native.tuple2 Prims.nat FStar.Pervasives.Native.snd Prims.b2t Prims.op_GreaterThanOrEqual FStar.Reflection.V2.Data.var FStar.Tactics.CanonCommSemiring.Pplus FStar.Tactics.CanonCommSemiring.Pmult FStar.Tactics.CanonCommSemiring.Pvar FStar.Tactics.CanonCommSemiring.Pconst Prims.op_Multiply Prims.squash Vale.Def.Words_s.pow2_64 Vale.Curve25519.Fast_defs.pow2_six Vale.Curve25519.FastMul_helpers.lemma_shift_pow2_eight Vale.Curve25519.FastMul_helpers.lemma_dbl_pow2_six Vale.Curve25519.Fast_defs.pow2_four FStar.Tactics.V1.Derived.qed FStar.Tactics.V1.Derived.trefl Prims.l_imp Prims.op_Equality Vale.Curve25519.Fast_defs.pow2_512 Prims.op_LessThan Vale.Curve25519.Fast_defs.pow2_256 Vale.Curve25519.FastMul_helpers.lemma_mul_pow2_bound FStar.Math.Lemmas.pow2_plus FStar.Pervasives.assert_norm Prims.pow2
module Vale.Curve25519.FastMul_helpers

open Vale.Def.Words_s
open Vale.Def.Types_s
open FStar.Mul
open FStar.Tactics
open FStar.Tactics.CanonCommSemiring
open Vale.Curve25519.Fast_defs
open Vale.Curve25519.Fast_lemmas_internal
open FStar.Math.Lemmas
#reset-options "--using_facts_from '* -FStar.Tactics -FStar.Reflection'"

#push-options "--z3rlimit 10"
let lemma_mul_pow2_bound (b:nat{b > 1}) (x y:natN (pow2 b))
  : Lemma (x * y < pow2 (2*b) - 1 /\
           x * y <= pow2 (2*b) - 2*pow2(b) + 1)
  = lemma_mul_bounds_le x (pow2 b - 1) y (pow2 b -1);
    pow2_plus b b;
    assert ( (pow2 b - 1) * (pow2 b -1) = pow2 (2*b) - 2*pow2(b) + 1)
#pop-options

let lemma_mul_bound64 (x y:nat64)
  : Lemma (x * y < pow2_128 - 1 /\ x * y <= pow2_128 - 2*pow2_64 + 1)
  = assert_norm (pow2 64 == pow2_64);
    assert_norm (pow2 128 == pow2_128);
    lemma_mul_pow2_bound 64 x y

(* Intel manual mentions this fact *)
let lemma_intel_prod_sum_bound (w x y z:nat64)
  : Lemma (w * x + y + z < pow2_128)
  = lemma_mul_bound64 w x

let lemma_prod_bounds (dst_hi dst_lo x y:nat64)
  : Lemma
    (requires
      pow2_64 * dst_hi + dst_lo == x * y)
    (ensures
      dst_hi < pow2_64 - 1 /\
      (dst_hi < pow2_64 - 2 \/
       dst_lo <= 1))
  = let result = x * y in
    lemma_div_mod result pow2_64;
    //assert (result = pow2_64 * (result / pow2_64) + result % pow2_64);
    //assert (result % pow2_64 == dst_lo);
    //assert (result / pow2_64 == dst_hi);
    lemma_mul_bound64 x y

let lemma_double_bound (x:nat64)
  : Lemma (add_wrap x x < pow2_64 - 1)
  = ()

type bit = b:nat { b <= 1 }

let lemma_offset_sum (a_agg:nat) (a0 a1 a2 a3 a4:nat64)
                     (b_agg:nat) (b0 b1 b2 b3 b4:nat64)
  : Lemma
    (requires
      a_agg = pow2_five a0 a1 a2 a3 a4 /\
      b_agg = pow2_five b0 b1 b2 b3 b4)
    (ensures
      a_agg + pow2_64 * b_agg =
      pow2_six a0 (a1 + b0) (a2 + b1) (a3 + b2) (a4 + b3) b4)
  = let lhs = a_agg + pow2_64 * b_agg in
    let rhs = pow2_six a0 (a1 + b0) (a2 + b1) (a3 + b2) (a4 + b3) b4 in
    assert_by_tactic (lhs == rhs) int_canon

#push-options "--z3rlimit 60"
let lemma_partial_sum
     (a0 a1 a2 a3 a4
      b0 b1 b2 b3 b4
      s1 s2 s3 s4 s5 c:nat64)
  : Lemma
    (requires
      (let s1', c1 = add_carry a1 b0 0 in
       let s2', c2 = add_carry a2 b1 c1 in
       let s3', c3 = add_carry a3 b2 c2 in
       let s4', c4 = add_carry a4 b3 c3 in
       let s5', c5 = add_carry 0 b4 c4 in
       s1 == s1' /\
       s2 == s2' /\
       s3 == s3' /\
       s4 == s4' /\
       s5 == s5' /\
       c == c5))
  (ensures
    pow2_six a0 (a1 + b0) (a2 + b1) (a3 + b2) (a4 + b3) b4 =
    pow2_seven a0 s1 s2 s3 s4 s5 c)
  = ()

let lemma_partial_sum_a2b
     (a0 a1 a2 a3 a4 a5
      b0 b1 b2 b3 b4
      s1 s2 s3 s4 s5:nat64)
  : Lemma
    (requires
      (let s1', c1 = add_carry a2 b0 0 in
       let s2', c2 = add_carry a3 b1 c1 in
       let s3', c3 = add_carry a4 b2 c2 in
       let s4', c4 = add_carry a5 b3 c3 in
       let s5', c5 = add_carry 0 b4 c4 in
       s1 == s1' /\
       s2 == s2' /\
       s3 == s3' /\
       s4 == s4' /\
       s5 == s5' /\
       0 == c5))
  (ensures
    pow2_seven a0 a1 (a2 + b0) (a3 + b1) (a4 + b2) (a5 + b3) b4 =
    pow2_seven a0 a1 s1 s2 s3 s4 s5)
  = ()

let lemma_partial_sum_a3b (
      a0 a1 a2 a3 a4 a5 a6
      b0 b1 b2 b3 b4
      s1 s2 s3 s4 s5:nat64) : Lemma
  (requires(let s1', c1 = add_carry a3 b0 0 in
            let s2', c2 = add_carry a4 b1 c1 in
            let s3', c3 = add_carry a5 b2 c2 in
            let s4', c4 = add_carry a6 b3 c3 in
            let s5', c5 = add_carry 0 b4 c4 in
            s1 == s1' /\
            s2 == s2' /\
            s3 == s3' /\
            s4 == s4' /\
            s5 == s5' /\
            0 == c5))
  (ensures pow2_eight a0 a1 a2 (a3 + b0) (a4 + b1) (a5 + b2) (a6 + b3) b4 =
           pow2_eight a0 a1 a2 s1 s2 s3 s4 s5)
  =
  ()
#pop-options

let lemma_sum_a1b
      (a0 a1:nat64)
      (a0b:nat) (a0b_0 a0b_1 a0b_2 a0b_3 a0b_4:nat64)
      (a1b:nat) (a1b_0 a1b_1 a1b_2 a1b_3 a1b_4:nat64)
      (b:nat)   (b0 b1 b2 b3:nat64)
      (s1 s2 s3 s4 s5
      c:nat64) : Lemma
  (requires a0b = pow2_five a0b_0 a0b_1 a0b_2 a0b_3 a0b_4 /\
            a1b = pow2_five a1b_0 a1b_1 a1b_2 a1b_3 a1b_4 /\
            b = pow2_four b0 b1 b2 b3 /\
            a0b = mul_nats a0 b /\
            a1b = mul_nats a1 b /\
           (let s1', c1 = add_carry a0b_1 a1b_0 0 in
            let s2', c2 = add_carry a0b_2 a1b_1 c1 in
            let s3', c3 = add_carry a0b_3 a1b_2 c2 in
            let s4', c4 = add_carry a0b_4 a1b_3 c3 in
            let s5', c5 = add_carry 0 a1b_4 c4 in
            s1 == s1' /\
            s2 == s2' /\
            s3 == s3' /\
            s4 == s4' /\
            s5 == s5' /\
            c5 == c))
  (ensures (pow2_two a0 a1) * b ==
           pow2_seven a0b_0 s1 s2 s3 s4 s5 c)
  =
  assert_by_tactic (
    (pow2_two a0 a1) * b ==
    pow2_two (mul_nats a0 b) (mul_nats a1 b)) int_canon;
  assert (
    pow2_two (mul_nats a0 b) (mul_nats a1 b) ==
    pow2_two a0b a1b);
  lemma_offset_sum a0b a0b_0 a0b_1 a0b_2 a0b_3 a0b_4
                   a1b a1b_0 a1b_1 a1b_2 a1b_3 a1b_4;
  assert (
    pow2_two a0b a1b ==
    pow2_six a0b_0 (a0b_1 + a1b_0) (a0b_2 + a1b_1) (a0b_3 + a1b_2) (a0b_4 + a1b_3) a1b_4);
  lemma_partial_sum a0b_0 a0b_1 a0b_2 a0b_3 a0b_4
                    a1b_0 a1b_1 a1b_2 a1b_3 a1b_4
                    s1 s2 s3 s4 s5 c;
  ()

#push-options "--z3rlimit 60"
let lemma_sum_a2b
      (a0 a1 a2:nat64)
      (a0a1b:nat) (a0a1b_0 a0a1b_1 a0a1b_2 a0a1b_3 a0a1b_4 a0a1b_5:nat64)
      (a2b:nat) (a2b_0 a2b_1 a2b_2 a2b_3 a2b_4:nat64)
      (b:nat)   (b0 b1 b2 b3:nat64)
      (s1 s2 s3 s4 s5:nat64) : Lemma
  (requires a0a1b = pow2_six a0a1b_0 a0a1b_1 a0a1b_2 a0a1b_3 a0a1b_4 a0a1b_5  /\
            a2b = pow2_five a2b_0 a2b_1 a2b_2 a2b_3 a2b_4 /\
            b = pow2_four b0 b1 b2 b3 /\
            a0a1b = mul_nats (pow2_two a0 a1) b /\
            a2b = mul_nats a2 b /\
           (let s1', c1 = add_carry a0a1b_2 a2b_0 0 in
            let s2', c2 = add_carry a0a1b_3 a2b_1 c1 in
            let s3', c3 = add_carry a0a1b_4 a2b_2 c2 in
            let s4', c4 = add_carry a0a1b_5 a2b_3 c3 in
            let s5', c5 = add_carry 0 a2b_4 c4 in
            s1 == s1' /\
            s2 == s2' /\
            s3 == s3' /\
            s4 == s4' /\
            s5 == s5' /\
            c5 == 0))
  (ensures (pow2_three a0 a1 a2) * b ==
           pow2_seven a0a1b_0 a0a1b_1 s1 s2 s3 s4 s5)
  =

  assert_by_tactic (
    (pow2_three a0 a1 a2) * b ==
    mul_nats (pow2_two a0 a1) b + pow2_128 * (mul_nats a2 b)) int_canon;
  assert (
    mul_nats (pow2_two a0 a1) b + pow2_128 * (mul_nats a2 b) ==
    a0a1b + pow2_128 * a2b);
  assert_by_tactic (
    a0a1b + pow2_128 * a2b ==
    pow2_seven a0a1b_0 a0a1b_1 (a0a1b_2 + a2b_0) (a0a1b_3 + a2b_1) (a0a1b_4 + a2b_2) (a0a1b_5 + a2b_3) a2b_4) int_canon;
  lemma_partial_sum_a2b
        a0a1b_0 a0a1b_1 a0a1b_2 a0a1b_3 a0a1b_4 a0a1b_5
        a2b_0 a2b_1 a2b_2 a2b_3 a2b_4
        s1 s2 s3 s4 s5;
  ()


let lemma_sum_a3b
      (a0 a1 a2 a3:nat64)
      (a0a1a2b:nat) (a0a1a2b_0 a0a1a2b_1 a0a1a2b_2 a0a1a2b_3 a0a1a2b_4 a0a1a2b_5 a0a1a2b_6:nat64)
      (a3b:nat) (a3b_0 a3b_1 a3b_2 a3b_3 a3b_4:nat64)
      (b:nat)   (b0 b1 b2 b3:nat64)
      (s1 s2 s3 s4 s5:nat64) : Lemma
  (requires a0a1a2b = pow2_seven a0a1a2b_0 a0a1a2b_1 a0a1a2b_2 a0a1a2b_3 a0a1a2b_4 a0a1a2b_5 a0a1a2b_6 /\
            a3b = pow2_five a3b_0 a3b_1 a3b_2 a3b_3 a3b_4 /\
            b = pow2_four b0 b1 b2 b3 /\
            a0a1a2b = mul_nats (pow2_three a0 a1 a2) b /\
            a3b = mul_nats a3 b /\
           (let s1', c1 = add_carry a0a1a2b_3 a3b_0 0 in
            let s2', c2 = add_carry a0a1a2b_4 a3b_1 c1 in
            let s3', c3 = add_carry a0a1a2b_5 a3b_2 c2 in
            let s4', c4 = add_carry a0a1a2b_6 a3b_3 c3 in
            let s5', c5 = add_carry 0 a3b_4 c4 in
            s1 == s1' /\
            s2 == s2' /\
            s3 == s3' /\
            s4 == s4' /\
            s5 == s5' /\
            c5 == 0))
  (ensures (pow2_four a0 a1 a2 a3) * b ==
           pow2_eight a0a1a2b_0 a0a1a2b_1 a0a1a2b_2 s1 s2 s3 s4 s5)
  =

  assert_by_tactic (
    (pow2_four a0 a1 a2 a3) * b ==
    mul_nats (pow2_three a0 a1 a2) b + pow2_192 * (mul_nats a3 b)) int_canon;
  assert (
    mul_nats (pow2_three a0 a1 a2) b + pow2_192 * (mul_nats a3 b) ==
    a0a1a2b + pow2_192 * a3b);
  assert_by_tactic (
    a0a1a2b + pow2_192 * a3b ==
    pow2_eight a0a1a2b_0 a0a1a2b_1 a0a1a2b_2 (a0a1a2b_3 + a3b_0) (a0a1a2b_4 + a3b_1) (a0a1a2b_5 + a3b_2) (a0a1a2b_6 + a3b_3) a3b_4) int_canon;
  lemma_partial_sum_a3b
        a0a1a2b_0 a0a1a2b_1 a0a1a2b_2 a0a1a2b_3 a0a1a2b_4 a0a1a2b_5 a0a1a2b_6
        a3b_0 a3b_1 a3b_2 a3b_3 a3b_4
        s1 s2 s3 s4 s5;
  ()

#pop-options

let lemma_dbl_pow2_six (z0 z1 z2 z3 z4 z5:nat) :
  Lemma (2*pow2_six z0 z1 z2 z3 z4 z5 == pow2_six (2*z0) (2*z1) (2*z2) (2*z3) (2*z4) (2*z5))
  =
  ()

let lemma_shift_pow2_eight (z0 z1 z2 z3 z4 z5 z6:nat) :
  Lemma (pow2_eight 0 z0 z1 z2 z3 z4 z5 z6 == pow2_64 * pow2_seven z0 z1 z2 z3 z4 z5 z6)
  = ()

#reset-options "--z3rlimit 100 --max_fuel 0 --max_ifuel 0 --using_facts_from 'FStar.Pervasives Prims Vale.Def.Words_s Vale.Curve25519.Fast_defs'"
val lemma_sqr (a:int) (a0 a1 a2 a3
               r8 r9 r10 r11 r12 r13 rax rcx
               r8' r9' r10' r11' r12' r13' r14'
               d0 d1 d2 d3 d4 d5 d6 d7:nat64) (cf:bit)
  : Lemma
    (requires
      a = pow2_four a0 a1 a2 a3 /\
      pow2_six r8 r9 r10 r11 r12 r13 ==
        pow2_five (mul_nats a0 a1)
                  (mul_nats a0 a2)
                  (mul_nats a0 a3)
                  (mul_nats a1 a3)
                  (mul_nats a2 a3) /\
      pow2_64 * rcx + rax == a1 * a2 /\
      pow2_seven r8' r9' r10' r11' r12' r13' r14' ==
        pow2_six (2*r8)
                 (2*r9)
                 (2*(r10+rax))
                 (2*(r11+rcx))
                 (2*r12)
                 (2*r13) /\
      pow2_nine d0 d1 d2 d3 d4 d5 d6 d7 cf ==
        pow2_eight (mul_nats a0 a0)
                   r8'
                   ((mul_nats a1 a1) + r9')
                   r10'
                   ((mul_nats a2 a2) + r11')
                   r12'
                   ((mul_nats a3 a3) + r13')
                   r14')
  (ensures
    a*a == pow2_eight d0 d1 d2 d3 d4 d5 d6 d7)
#set-options "--__temp_fast_implicits" //this flag should be the default for use with tactics, but isn't yet
let lemma_sqr a a0 a1 a2 a3
                r8 r9 r10 r11 r12 r13 rax rcx
                r8' r9' r10' r11' r12' r13' r14'
                d0 d1 d2 d3 d4 d5 d6 d7
val lemma_sqr (a:int) (a0 a1 a2 a3
               r8 r9 r10 r11 r12 r13 rax rcx
               r8' r9' r10' r11' r12' r13' r14'
               d0 d1 d2 d3 d4 d5 d6 d7:nat64) (cf:bit)
  : Lemma
    (requires
      a = pow2_four a0 a1 a2 a3 /\
      pow2_six r8 r9 r10 r11 r12 r13 ==
        pow2_five (mul_nats a0 a1)
                  (mul_nats a0 a2)
                  (mul_nats a0 a3)
                  (mul_nats a1 a3)
                  (mul_nats a2 a3) /\
      pow2_64 * rcx + rax == a1 * a2 /\
      pow2_seven r8' r9' r10' r11' r12' r13' r14' ==
        pow2_six (2*r8)
                 (2*r9)
                 (2*(r10+rax))
                 (2*(r11+rcx))
                 (2*r12)
                 (2*r13) /\
      pow2_nine d0 d1 d2 d3 d4 d5 d6 d7 cf ==
        pow2_eight (mul_nats a0 a0)
                   r8'
                   ((mul_nats a1 a1) + r9')
                   r10'
                   ((mul_nats a2 a2) + r11')
                   r12'
                   ((mul_nats a3 a3) + r13')
                   r14')
  (ensures
    a*a == pow2_eight d0 d1 d2 d3 d4 d5 d6 d7)

    a: Prims.int ->
    a0: Vale.Def.Types_s.nat64 ->
    a1: Vale.Def.Types_s.nat64 ->
    a2: Vale.Def.Types_s.nat64 ->
    a3: Vale.Def.Types_s.nat64 ->
    r8: Vale.Def.Types_s.nat64 ->
    r9: Vale.Def.Types_s.nat64 ->
    r10: Vale.Def.Types_s.nat64 ->
    r11: Vale.Def.Types_s.nat64 ->
    r12: Vale.Def.Types_s.nat64 ->
    r13: Vale.Def.Types_s.nat64 ->
    rax: Vale.Def.Types_s.nat64 ->
    rcx: Vale.Def.Types_s.nat64 ->
    r8': Vale.Def.Types_s.nat64 ->
    r9': Vale.Def.Types_s.nat64 ->
    r10': Vale.Def.Types_s.nat64 ->
    r11': Vale.Def.Types_s.nat64 ->
    r12': Vale.Def.Types_s.nat64 ->
    r13': Vale.Def.Types_s.nat64 ->
    r14': Vale.Def.Types_s.nat64 ->
    d0: Vale.Def.Types_s.nat64 ->
    d1: Vale.Def.Types_s.nat64 ->
    d2: Vale.Def.Types_s.nat64 ->
    d3: Vale.Def.Types_s.nat64 ->
    d4: Vale.Def.Types_s.nat64 ->
    d5: Vale.Def.Types_s.nat64 ->
    d6: Vale.Def.Types_s.nat64 ->
    d7: Vale.Def.Types_s.nat64 ->
    cf: Vale.Curve25519.FastMul_helpers.bit
  -> FStar.Pervasives.Lemma
      (requires
        a = Vale.Curve25519.Fast_defs.pow2_four a0 a1 a2 a3 /\
        Vale.Curve25519.Fast_defs.pow2_six r8 r9 r10 r11 r12 r13 ==
        Vale.Curve25519.Fast_defs.pow2_five (Vale.Curve25519.Fast_defs.mul_nats a0 a1)
          (Vale.Curve25519.Fast_defs.mul_nats a0 a2)
          (Vale.Curve25519.Fast_defs.mul_nats a0 a3)
          (Vale.Curve25519.Fast_defs.mul_nats a1 a3)
          (Vale.Curve25519.Fast_defs.mul_nats a2 a3) /\
        Vale.Def.Words_s.pow2_64 * rcx + rax == a1 * a2 /\
        Vale.Curve25519.Fast_defs.pow2_seven r8' r9' r10' r11' r12' r13' r14' ==
        Vale.Curve25519.Fast_defs.pow2_six (2 * r8)
          (2 * r9)
          (2 * (r10 + rax))
          (2 * (r11 + rcx))
          (2 * r12)
          (2 * r13) /\
        Vale.Curve25519.Fast_defs.pow2_nine d0 d1 d2 d3 d4 d5 d6 d7 cf ==
        Vale.Curve25519.Fast_defs.pow2_eight (Vale.Curve25519.Fast_defs.mul_nats a0 a0)
          r8'
          (Vale.Curve25519.Fast_defs.mul_nats a1 a1 + r9')
          r10'
          (Vale.Curve25519.Fast_defs.mul_nats a2 a2 + r11')
          r12'
          (Vale.Curve25519.Fast_defs.mul_nats a3 a3 + r13')
          r14') (ensures a * a == Vale.Curve25519.Fast_defs.pow2_eight d0 d1 d2 d3 d4 d5 d6 d7)