In propositional logic, the statements we are proving are completely abstract. To be able to prove programs correct, we need a logic that can talk about the things that programs compute on: integers, strings, tuples, datatype constructors, and functions. We'll enrich propositional logic with the ability to talk about these things, obtaining a version of predicate logic.
The syntax extends propositional logic with a few new expressions, shown in blue:
Predicates P,Q,R ::= ⊤ (* true *)  ⊥ (* false *)  ¬P (* complement; equivalent to P ⇒ ⊥ *)  P ∧ Q (* conjunction (and) *)  P ∨ Q (* disjunction (or) *)  P ⇒ Q (* implication (ifthen) *)  ∀x.P (* P is true for all x. P can mention x*)  ∃x.P (* There exists some x such that P is true *)  t_{1} = t_{2} (* t_{1} is equal to t_{2} *)  P(t_{1},...,t_{n}) (* nary predicate P is true for t_{1},...,t_{n} *) Terms t ::= c (* constants (integers, tuples, other values) *)  x (* variables *)  f(t_{1},...,t_{n}) (* result of applying nary function f to t_{1},...,t_{n} *)
Terms t stand for individual elements of some domain of objects we are reasoning about, such as the natural numbers. Predicates P are of type Boolean.
The formula ∀x.P means that the formula P is true for any choice of x. This is called universal quantification, and ∀ is the universal quantifier. The formula ∃x.P denotes existential quantification. It means that the formula P is true for some choice of x, though there may be more than one such x. Existential and universal quantifiers can be turned into each other using negation. The formula ∃x.P(x) implies ¬∀x.¬P(x), because if P is true of some x, then P cannot be false for all x. The converse is valid classically, but not intuitionistically. Similarly, the formula ∀x.P is equivalent to ¬∃x.¬P. These equivalences are generalizations of DeMorgan's laws to existential and universal quantifiers.
It is possible to restrict the range of quantifiers to quantify over some subset of the domain of possible values. For universal quantifiers, we use an implication ⇒, and for existential quantifiers, we use conjunction ∧. For example, if we wanted to say that all positive numbers x satisfy some property Q(x), we could write ∀x.x > 0 ⇒ Q(x). This works because the quantified formula is vacuously true for numbers not greater than 0. To say that there exists a positive number that satisfies Q, we can write ∃x.x > 0 ∧ Q(x).
Using quantifiers, we can express some interesting statements. For example, we can express the idea that a number n is prime in various logically equivalent ways:
Prime(n)  ⇔  ∀m. 1 < m ∧ m < n ⇒ ¬∃k. k*m = n 
⇔  ¬∃m. 1 < m ∧ m < n ∧ ∃k. k*m = n  
⇔  ¬∃m. ∃k. 1 < m ∧ m < n ∧ k*m = n 
Introduction and elimination rules can be defined for universal and existential quantifiers. In the following rules, P(t) and P(a) refer to P(x) with all free occurrences of the variable x replaced by the term t and variable a, respectively.
 (∀intro) 
 (∀elim) 
 (∃intro) 
 (∃elim) 
(*) provided a does not appear free in any undischarged assumption or in Q in the (∃elim) rule.
The proviso (*) in the (∀intro) and (∃elim) rules is a restriction on the use of the rule. This restriction prevents us from doing unsound reasoning like the following:
a_{0}: x > 10 
∀x. x > 10 
x > 10 ⇒ ∀x. x > 10 
where the first step is an application of (∀intro) and the second is an application of (⇒intro) with assumption a_{0}.
However, it is fine for the variable a to appear in an assumption that is discharged above the point where (∀intro) is applied. For example,
a_{0}: x > 10 ∧ x < 20 
x > 10 
x > 10 ∧ x < 20 ⇒ x > 10 
∀x. x > 10 ∧ x < 20 ⇒ x > 10 
The rule (∀elim) specializes the formula P(x) to a particular value t of x. (We require implicitly that t be of the right type to be substituted for x.) Since P holds for all x, it should hold for any particular choice of x, including t. The (∀intro) rule formalizes the type of argument that starts, "Let a be an arbitrary element..." If one can prove a fact P(a) for arbitrarily chosen a, then P(x) holds for all x.
The rule (∃intro) derives ∃x.P(x) because a witness t to the existential has been produced. Intuitively, if P(t) holds for some t, then certainly there exists an x such that P(x) holds. The idea behind rule (∃elim) is that if Q can be shown without using any information about the witness a other than P(a), then the mere existence of an element satisfying P is enough to imply Q.
Predicate logic allows the use of arbitary predicates P. Equality (=) is such a predicate. It applies to two arguments; we can read t_{1}=t_{2} as a predicate =(t_{1},t_{2}). But in addition to the rules above for arbitrary predicates, equality has some special properties. The following three rules capture that equality is an equivalence relation: it is reflexive, symmetric, and transitive.
 (reflexivity) 
 (symmetry) 
 (transitivity) 
Beyond being an equivalence relation, equality preserves meaning under substitution. If two things are equal, substituting one for the other in equal terms results in equal terms. This is known as Leibniz's rule (substitution of equals for equals):

Leibniz's rule can also be applied to show propositions are logically equivalent :

The same idea can be applied completely at the propositional level as well. If we can prove that two formulas are equivalent, they can be substituted for one another within any other formula.

This admissible rule can be very convenient for writing proofs, though anything we can prove with it can be proved using just the basic rules. It can be very handy when there is a large library of logical equivalences to draw upon, because it allows rewriting of deeply nested subformulas.
For reasoning about specific kinds of values, we need axioms that describe how those values behave. For example, the following axioms partly describe the integers and can be used to prove many facts about integers. In fact, they define a more general structure, a commutative ring, so anything proved with them holds for any commutative ring. These axioms are all considered to be implicitly universally quantified.
(x+y)+z = x+(y+z)  (associativity of +) 
x+y = y+x  (commutativity of +) 
(x*y)*z = x*(y*z)  (associativity of *) 
x*y = y*x  (commutativity of *) 
x*(y+z) = x*y+x*z  (distributivity of * over +) 
x + 0 = x  (additive identity) 
x + (x) = 0  (additive inverse) 
x*1 = x  (multiplicative identity) 
x*0 = 0  (annihilation) 
These rules use a number of functions: +, *, , 0, and 1 (we can think of 0 and 1 as functions that take zero arguments). These symbols are represented by the metavariable f in the grammar earlier.
Proving facts about arithmetic can be tedious. For our purposes, we will write proofs that do reasonable algebraic manipulations as a single step, e.g.:
 (algebra) 
This proof step can be done explicitly using the rules and axioms above, but it takes several steps.