proofdafnyproof-of-correctness

Dafny GCD lemma Proof


I'd like to use dafny to prove the following lemma about GCD: For all k natural numbers, if k|a and k|b, then k|gcd(a,b). I have the following code so far:

// Euclid's algorithm for computing the greatest common divisor
function gcd(a: nat, b: nat): nat
    requires a > 0 && b > 0
{
    if a == b then a else 
    if b > a then gcd(a, b - a) else 
    gcd(a - b, b) 
}

predicate divides(a: nat, b:nat)
    requires a > 0
{
exists k: nat :: b == k * a
}

lemma dividesLemma(a: nat, b: nat)
//k|a && k|b ==> k|gcd(a,b)
requires a > 0 && b > 0
    ensures gcd(a,b) > 0
    ensures forall k: nat :: divides(a,k) && divides(b,k) ==> divides(gcd(a,b),k)
{
    if(a == b) {
        assert a * 1 == gcd(a,b);
        assert b * 1 == gcd(a,b);
    } else if b > a {
        if(divides(a, b)) {
            assert divides(a,a);
            assert divides(a,b);
            assert divides(a, gcd(a,b));
        } else {
            dividesLemma(a, b - a);
        }
    } else {
        if(divides(b, a)) {
            assert divides(b,b);
            assert divides(b,a);
            assert divides(b, gcd(a,b));
        } else {
            dividesLemma(a, a - b);
        }
    }
}

I know how to do the proof for this by hand. I would consider the prime factorization of a and b and say that gcd(a,b) was the combined prime factorization such that we take the minimal number of primes from each prime factorization. For instance if a = 9 and b = 15, the prime factorization of 9 = 3x3 and the prime factorization of 15 = 3x5, so the gcd(9,5) = 3 since that's the minimal combination of their prime factoizations. Using this fact it should be clear that if k|b and k|a, k must contain those minimal primes. How can I express this using dafny? Currently, I'm considering the base case if a=b and if a|b or b|a, but not sure how to incorporate the fact that it's possible for a and b to not share common primes in their prime factorizations.

Any help would be much appreciated for this!


Solution

  • There is problem in how divides is being called. I think in ensures clauses you meant divides(k, a) instead of divides(a, k) similarly for divides(b, k) and divides(gcd(a, b), k).

    One way to go about this after recursive call to dividesLemma(a, b - a) is to use postcondition of method. Here we know forall k such that k divides a and k divides b - a implies k divides gcd(a, b-a). Using this information we try to prove required postcondition (code or proof is straightforward to follow)

    dividesLemma(a, b - a);
    assert gcd(a, b) == gcd(a, b-a);
    assert forall k : nat :: k > 0 && divides(k, a) && divides(k, b-a) ==> divides(k, gcd(a, b));
    forall k : nat | k > 0 && divides(k, a) && divides(k, b) ensures divides(k, gcd(a, b)) {
        var m :| a == m * k;
        var n :| b == n * k;
        assert (b - a) == (n - m) * k;
        assume n >= m;
        assert divides(k, a);
        assert divides(k, b-a);
        // Implied from last assert forall
        assert divides(k, gcd(a, b)); 
    }
    

    Here I am assuming n >= m because divides requires n-m to be nat, which can proved separately.

    Also second recursive call should be dividesLemma(a - b, b).

    function gcd(a: nat, b: nat): nat
      requires a > 0 && b > 0
    {
      if a == b
        then a
      else if a < b
        then gcd(a, b-a)
      else gcd(a-b, b)
    }
    
    predicate divides(a: nat, b: nat)
      requires a > 0 && b > 0
    {
      exists k: nat :: b == k * a
    }
    
    lemma helper(a: nat, b: nat, k : nat)
      requires a > 0 && b > 0 && k > 0
      requires divides(k, a) && divides(k, b)
      requires b >= a
      ensures exists m, n :: a == m * k && b == n * k && m <= n;
    { }
        
    lemma dividesLemma(a: nat, b: nat)
      decreases a + b
      requires a > 0 && b > 0
      ensures gcd(a, b) > 0
      ensures forall k: nat :: k > 0 && divides(k, a) && divides(k, b) ==> divides(k, gcd(a, b))
    {
      if (a == b){
      }
      else if (b > a){
        dividesLemma(a, b - a);
        assert gcd(a, b) == gcd(a, b-a);
        assert forall k : nat :: k > 0 && divides(k, a) && divides(k, b-a) ==> divides(k, gcd(a, b));
        forall k : nat | k > 0 && divides(k, a) && divides(k, b) ensures divides(k, gcd(a, b)) {
          helper(a, b, k);
          var m, n :| a == m * k && b == n * k && m <= n;
          assert b - a == (n - m) * k;
          assert divides(k, b-a);
        }
      }
      else {
        dividesLemma(a - b, b);
        assert gcd(a, b) == gcd(a - b, b);
        assert forall k : nat :: k > 0 && divides(k, a-b) && divides(k, b) ==> divides(k, gcd(a, b));
        forall k : nat | k > 0 && divides(k, a) && divides(k, b) ensures divides(k, gcd(a, b)) {
          helper(b, a, k);
          var m, n :| b == m * k && a == n * k && m <= n;
          assert a - b == (n - m) * k;
          assert divides(k, a-b);
        }
      }
    }