Assumptions and trusted components

Often times, it’s not possible to verify every line of code and some things need to be assumed. In such cases, the ultimate correctness of the code is dependent not just on verification but on the assumptions being made.

Assumptions can be introduced through the following mechanisms:

  • As assume statement
  • An axiom - any proof function introduced with #[verifier::external_body]
  • An axiomatic specification - any exec function introduced with #[verifier::external_body] or #[verifier::external_fn_specification]
  • #[verifier::external] (See below.)

Types (structs and enums) can also be marked as #[verifier::external_body], though to be pedantic, this does not introduce a new assumption per se. In practice, though, such types are usually associated with additional assumptions to make them useful.

The #[verifier::external] attribute

The #[verifier::external] annotation tells Verus to ignore an item entirely. It can be applied to any item - a function, trait, trait implementation, type, etc.

For many items (functions, types, trait declarations), this does not, on its own, introduce any new “assumptions” about that item. Attempting to call an external function from a verified function, for example, will result in an error from Verus. In practice, a developer will often call an external function (say f) from an external_body function (say g), in which case, the external_body attribute introduces assumptions about g, thus indirectly introducing assumptions about f.

Furthermore, adding #[verifier::external] to a trait implementation requires even more careful consideration, as Verus relies on rustc’s trait-checking for some things, so trait implementations can sometimes affect what code gets accepted or rejected.

For example:

#[verifier::external]
unsafe impl Send for X { }