Verifying and Synthesizing Constant-Resource Implementations with Types

01/05/2018 ∙ by Van Chan Ngo, et al. ∙ Carnegie Mellon University 0

We propose a novel type system for verifying that programs correctly implement constant-resource behavior. Our type system extends recent work on automatic amortized resource analysis (AARA), a set of techniques that automatically derive provable upper bounds on the resource consumption of programs. We devise new techniques that build on the potential method to achieve compositionality, precision, and automation. A strict global requirement that a program always maintains constant resource usage is too restrictive for most practical applications. It is sufficient to require that the program's resource behavior remain constant with respect to an attacker who is only allowed to observe part of the program's state and behavior. To account for this, our type system incorporates information flow tracking into its resource analysis. This allows our system to certify programs that need to violate the constant-time requirement in certain cases, as long as doing so does not leak confidential information to attackers. We formalize this guarantee by defining a new notion of resource-aware noninterference, and prove that our system enforces it. Finally, we show how our type inference algorithm can be used to synthesize a constant-time implementation from one that cannot be verified as secure, effectively repairing insecure programs automatically. We also show how a second novel AARA system that computes lower bounds on resource usage can be used to derive quantitative bounds on the amount of information that a program leaks through its resource use. We implemented each of these systems in Resource Aware ML, and show that it can be applied to verify constant-time behavior in a number of applications including encryption and decryption routines, database queries, and other resource-aware functionality.

READ FULL TEXT VIEW PDF
POST COMMENT

Comments

There are no comments yet.

Authors

page 1

page 2

page 3

page 4

This week in AI

Get the week's most popular data science and artificial intelligence research sent straight to your inbox every Saturday.

I Introduction

Side-channel attacks extract sensitive information about a program’s state through its observable use of resources such as time, network, and memory. These attacks pose a realistic threat to the security of systems in a range of settings, in which the attacker has local access to the native host [1], through multi-tenant virtualized environments [2, 3], or remotely over the network [4]. Side channels have revealed highly-sensitive data such as cryptographic keys [1, 4, 5, 6, 7] and private user data [8, 9, 10, 11, 12].

These attacks are mounted by taking repeated measurements of a program’s resource behavior, and comparing the resulting observations against a model that relates the program’s secret state to its resource usage. Unlike direct information flow channels that operate over the input/output semantics of a program, the conditions that give rise to side channels are oftentimes subtle and therefore difficult for programmers to identify and mitigate. This also poses a challenge for automated tool support aimed at addressing such problems—whereas direct information flow can be described in terms of standard program semantics, a similar precise treatment of side channels requires incorporating the corresponding resource into the semantics and applying quantitative reasoning.

This difficulty has led previous work in the area to treat resource use indirectly, by reasoning about the flow of secret information into branching control flow or other operations that might affect resource use [13, 14, 15, 16]. These approaches can limit the expressiveness of secure programs and further complicate the development. For example, by requiring programmers to write code using a “constant-time discipline” that forbids the use of variables influenced by secret state in statements that could affect the program’s control path [13].

Verifiable constant-resource language

In this paper, we present a novel type system that gives developers the ability to certify that their code is secure against resource side-channel attacks w.r.t. a high-level attack model, in which the resource consumption of each language construct is modeled by a constant. Our approach reduces constraints on the expressiveness of programs that can be verified, and does not introduce general stylistic guidelines that must be followed in order to ensure constant-resource behavior. Programmers write code in typical functional style and annotate variables with standard types. Thus, it does not degrade the readability of the code. At compile time, our verifier performs a quantitative analysis to infer additional type information that characterizes the resource usage. From this, constant-resource behavior w.r.t. the high-level model on all executions of the program is determined automatically.

The granularity with which our resource guarantees hold against an attacker who can measure the total quantity of consumed resources is roughly equivalent to what can be obtained by adhering to a strict constant-time programming discipline. The certified constant-resource programs prevent side-channels that are inherent in implementing algorithms w.r.t. the provided high-level attack model. For example, if the resource under consideration is execution time, measured by the number of language constructs executed by the program (e.g., the total number of arithmetic operations, function calls, etc.), then our system provides a defense against attackers that can observe the same resource measure. To have a stronger guarantee, e.g., against cache side channels, our resource model could in principle incorporate memory-access patterns and instruction caches. Other types of side channels arising from low-level behaviors, such as branch prediction or instructions whose resource usage is influenced by argument values, require corresponding changes to the resource model. Our technique does not currently model such timing differences, so is not a defense against such attacks.

In general, requiring that a program always consumes constant resources is too restrictive. In most settings, it is sufficient to ensure that the resource behavior of a program does not depend on selected confidential parts of the program’s state. To account for this, our type system tracks information flow using standard techniques, and uses this information to reason about an adversary who can observe and manipulate public state as well as resource usage through public outputs. Intuitively, resource-aware noninterference—the guarantee enforced by this type system—requires that the parts of the program that are both affected by secret data and can influence public outputs, can only make constant use of resources.

To accomplish this without limiting expressiveness or imposing stylistic requirements, the type system must be allowed to freely switch between local and global reasoning. One extreme would be to ignore the information flow of the secret values and prove that the whole program has global constant resource consumption. The other extreme would be to ensure that every conditional that branches on a secret value (critical conditionals) uses a constant amount of resources. However, there are constant-resource programs in which individual conditionals are not locally constant-resource (see Section III). As a result, we allow different levels of global and local reasoning in the type system to ensure that every critical conditional occurs in a constant-resource block.

Finally, we show that our type-inference algorithm can be used to automatically repair programs that make inappropriate non-constant use of resources, by synthesizing constant-resource ones whose input/output behavior is equivalent. To this end, we introduce a consume

expression that performs resource padding. The amount of resource padding that is needed is automatically determined by the type system and is parametric in the values held by program variables. An advantage of this technique over prior approaches 

[17, 18] is that it does not change the worst-case resource behavior of many programs. Of course, it would be possible to perform this transformation by padding resource usage dynamically at the end of the program execution, but this would require instrumenting the program to track at runtime the actual resource usage of the program.

Novel resource type systems

In order to verify constant resource usage, as well as to produce quantitative upper and lower-bounds on information leakage via resource behavior, this work extends the theory behind automatic amortized resource analysis (AARA) [19, 20, 21] to automatically derive lower-bound and constant-resource proofs.

Previous AARA techniques are limited to deriving upper bounds. To this end, the resource potential is used as an affine quantity: it must be available to cover the cost of the execution, but excess potential is simply discarded. We show that if potential is treated as a linear resource, then corresponding type derivations prove that programs have constant resource consumption, i.e., the resource consumption is independent of the execution path. Intuitively, this amounts to requiring that all potential must be used to cover the cost and that excess potential is not wasted. Furthermore, we show that if potential is treated as a relevant resource then we can derive lower bounds on the resource usage. Following a similar intuition, this requires that all potential is used, but the available potential does not need to be sufficient to cover the remaining cost of the execution.

We implemented these type systems in Resource Aware ML (RAML) [21], a language that supports user-defined data types, higher-order functions, and other features common to functional languages. Our type inference uses efficient LP solving to characterize resource usage for general-purpose programs in this language. We formalized soundness proofs for these type systems, as well as the one of classic linear AARA [19], in the proof assistant Agda. The soundness is proved w.r.t. an operational cost semantics and, like the type systems themselves, is parametric in the resource of interest.

Contributions

We make the following contributions:

  • A security type system that incorporates our novel lower-bound and constant-time type systems to prevent and quantify leakage of secrets through resource side channels, as well as an LP-based method that automatically transforms programs into constant-resource versions.

  • An implementation of these systems that extends RAML. We evaluate the implementation on several examples, including encryption routines and data processing programs that were previously studied in the context of timing leaks in differentially-private systems [8].

  • A mechanization of the soundness proofs the two new type systems and classic AARA for upper bounds in Agda. To the best our knowledge, this is also the first formalization of the soundness of linear AARA for worst-case bounds.

Technical details including the complete proofs and inference rules can be found on the RAML website [21].

Ii Language-level constant-resource programs

In this section, we define our notion of a constant-resource program. We start with an illustrative example: a login with a username and password. During the login process, the secret password with a high security level is compared with the low-security user input, and the result is sent back to the user. As a result, the pure noninterference property [22, 23] is violated because data flows from high to low. Nevertheless, such a program is often considered secure because it satisfies the relaxed noninterference property [24, 25, 26].

Fig. 1 shows an implementation of the login process in a monomorphically-typed purely-functional language. The arguments and are lists of integers that are the bytes of the password and the user input (characters of the hashes). The function returns true if the input is valid and false otherwise.

This implementation is vulnerable against an attacker who measures the execution time of the login function. Because the function returns false immediately on finding a mismatched pair of bytes, the resource usage depends on the size of the longest matching prefix. Based on this observation, the attacker can mount an efficient attack to recover the correct password byte-by-byte. For example, if we assume that there is no noise in the measurements, it requires at most calls to the function to reveal one byte of the secret password. Thus, at most runs are needed to recover a secret password of bytes. If noise is added to the measurements then the number of necessary guesses is increased but the attack remains feasible [1, 4].

One method to prevent this sort of attack is to develop a constant-resource implementation of the compare function that minimizes the information that an attacker can learn from the resource-usage information. Ideally, the resource usage should not be dependent on the content of the secret password, which means it is constant for fixed sizes of all public parameters.

Syntax and semantics

We use the purely-functional first-order language defined in Fig. 2 to formally define the notion of a language-level constant-time implementation. The grammar is written using abstract binding trees [27]. However, equivalent expressions in OCaml syntax are used for examples. The expressions are in let normal form, meaning that they are formed from variables whenever it is possible. It makes the typing rules and semantics simpler without losing expressivity. The syntactic form share has to be use to introduce multiple occurrences of a variable in an expression. A value is a boolean constant, an integer value , the empty list , a list of values , or a pair of values .

| [] -> match l with | [] -> true
                     | y::ys -> false
| x::xs -> match l with
  | [] -> false
  | y::ys -> if (x = y) then compare(xs,ys)
             else false
Fig. 1: The list comparison function compare is not constant resource w.r.t. and . This implementation is insecure against an attacker who measures its resource usage.
Fig. 2: Syntax of the language

To reason about the resource consumption of programs, we first define the operational cost semantics of the language. It is standard big-step semantics instrumented with a non-negative resource counter that is incremented or decremented by a constant at every step. The semantics is parametric in the cost that is used at each step and we call a particular set of such cost parameters a cost model. The constants can be used to indicate the costs of storing or loading a value in the memory, evaluating a primitive operation, binding of a value in the environment, or branching on a Boolean value. It is possible to further parameterize some constants to obtain a more precise cost model. For example, the cost of calling a function may vary according to the number of the arguments. In the following, we will show that the soundness of type systems does not rely on any specific values for these constants. In the examples, we use a cost model in which the constants are for all steps except for calls to the tick function where tick(q) means that we have resource usage . A negative number specifies that resources (such as stack space) become available.

The cost semantics is formulated using an environment that is a finite mapping from a set of variable identifiers to values. Evaluation judgements are of the form where . The intuitive meaning is that under the environment and available resources, evaluates to the value without running out of resources and resources are available after the evaluation. The evaluation consumes resource units. Fig. 12 presents some selected evaluation rules. In the rule E:Fun for function applications, is an expression defining the function’s body and is the argument.

Fig. 3: Selected evaluation rules of the operational cost semantics
let rec aux(r,h,l) = match h with
| [] -> match l with | [] -> tick(1.0); r
        | y::ys -> tick(1.0); false
| x::xs -> match l with
  | [] -> tick(1.0); false
  | y::ys -> if (x = y) then
      tick(5.0); aux(r,xs,ys)
    else tick(5.0); aux(false,xs,ys)
in aux(true,h,l)
Fig. 4: The manually padded function p_compare is constant resource w.r.t. and . However, it is not constant resource w.r.t. only .

Constant-resource programs

Let be a context that maps variable identifiers to base types . We write to denote that is a well-formed value of type . The typing rules for values are standard [19, 20, 28] and we omit them here. An environment is well-formed w.r.t. , denoted , if . Below we define the notation of size equivalence, written , which is a binary relation relating two values of the same type.

Informally, a program is constant resource if it has the same quantitative resource consumption under all environments in which values have the same sizes. Let be a set of variables and , be two well-formed environments. Then and are size-equivalent w.r.t. , denoted , when they agree on the sizes of the variables in , that is, .

Definition 1.

An expression is constant resource w.r.t. , written , if for all well-formed environments and such that , the following statement holds.

We say that a function is constant resource w.r.t. if where is the expression defining the function body. We have the following lemma.

Lemma 1.

For all , , and , if then .

Example.

The function p_compare in Fig. 4 is a manually padded version of compare, in which the cost model is defined using tick annotations. It is constant resource w.r.t. and . However, it is not constant resource w.r.t. . For instance, p_compare([1;2;3],[0;1;2]) has cost but p_compare([1;2;1],[0;1]) has cost . If the nil case of the second match on is padded with tick(5.0); aux(false,xs,[]) then the function is constant resource w.r.t. .

Intuitively, this implementation is constant w.r.t. the given cost model for fixed sizes of all public parameters, e.g., the lengths of argument lists. However, it might be not constant resource at a lower level, e.g., machine code on modern hardware, because the cost model does not precisely capture the resource consumption of the instructions executed on the hardware. Moreover, the compilation process can interfere with the resource behavior. It may introduce a different type of leakage that could reveal the secret data on the lower level. For instance, memory accesses would allow an attacker with access to the full trace of memory addresses accessed to infer the content of the password. This leakage can be exploited via cache-timing attacks [29, 30]. In addition, in some modern processors, execution time of arithmetic operations may vary depending on the values of their operands and the execution time of conditionals is affected by branch prediction.

Iii A resource-aware security type system

In this section we introduce a new type system that enforces resource-aware noninterference to prevent the leakage of information in high-security variables through low-security channels. In addition to preventing leakage over the usual input/output information flow channels, our system incorporates the constant-resource type system discussed in Section IV to ensure that leakage does not occur over resource side channels.

The notion of security addressed by our type system considers an attacker who wishes to learn information about secret data by making observations of the program’s public outputs and resource usage. We assume an attacker who is able to control the value of any variable she is capable of observing, and thus to influence the program’s behavior and resource consumption. However, in our model the attacker can only observe the program’s total resource usage upon termination, and cannot distinguish between intermediate states or between terminating and non-terminating executions.

Iii-a Security types

To distinguish parts of the program under the attacker’s control from those that remain secret, we annotate types with labels ranging over a lattice . The elements of correspond to security levels partially-ordered by with a unique bottom element . The corresponding basic security types take the form:

A security context is a partial mapping from variable identifiers and the program counter pc to security types. The context assigns a type to pc to track information that may propagate through control flow as a result of branching statements. The security type for lists contains a label for the elements, as well as a label for the list’s length.

As in other information flow type systems, the partial order indicates that the class is at least as restrictive as , i.e., is allowed to flow to . We assume a non-trivial security lattice that contains at least two labels: (low security) and (high security), with . Following the convention defined in FlowCaml [31], we also make use of a guard relation , which denotes that all of the labels appearing in are at least as restrictive as . The definition is given in Figure 13 along with its dual notion , called the collecting relation, and the standard subtyping relation .

To refer to sets of variables by security class, we write to denote the set of variable identifiers in the domain of such that , and define similarly. This gives us the set of variables upper- and lower-bounded by , respectively. Conversely, we define , the set of variables more restrictive than . To refer to the set of variables strictly bounded below by and above by , we write . Given two well-formed environments and , we say that they are k-equivalent w.r.t if they agree on all variables with label at most :

This relation captures the attacker’s observational equivalence between the two environments.

Fig. 5: Guards, collecting security labels, and subtyping ()

The first-order security types take the following form. The annotation pc indicates the security level of the program counter, i.e., a lower-bound on the label of any observer who is allowed to learn that a given function has been invoked. The const annotation denotes that the function body respects resource-aware noninterference.

A security signature is a finite partial mapping from a set of function identifiers to a non-empty sets of first-order security types.

Iii-B Resource-aware noninterference

We consider an adversary associated with label , who can observe and control variables in . Intuitively, we say that a program satisfies resource-aware noninterference at level w.r.t , where , if 1) the behavior of does not leak any information about the contents of variables more sensitive than , and 2) does not leak any information about the contents or sizes of variables more sensitive than . The definition follows.

Definition 2.

Let and be two well-formed environments and be a security context sharing their domain. An expression satisfies resource-aware noninterference at level for , if whenever and are:

  1. observationally equivalent at : ,

  2. size equivalent w.r.t. :

then it follows from and that and .

The final condition in Defintion 2 ensures two properties. First, requiring that provides noninterference [22], given that and are observationally equivalent. Second, the requirement ensures that the program’s resource consumption will remain constant w.r.t changes in variables from the set This establishes noninterference w.r.t the program’s final resource consumption, and thus prevents the leakage of secret information.

Before moving on, we point out an important subtlety in this definition. We require that all variables in begin with equivalent sizes, but not those in . By fixing this quantity in the initial environments, we assume that an attacker is able to control and observe it, so it is not protected by the definition. This effectively establishes three classes of variables, i.e., those whose size and content are observable to the -adversary, those whose size (but not content) is observable, and those whose size and content remain secret. In the remainder of the text, we will simplify the technical development by assuming that the third and most-restrictive class is empty, and that all of the secret variables reside in

Iii-C Proving resource-aware noninterference

There are two extreme ways of proving resource-aware noninterference. Assume we already have established classic noninterference, the first way is to additionally prove constant-resource usage globally by forgetting the security labels and showing that the program has constant-resource usage. This is a sound approach but it requires us to reason about parts of the programs that are not affected by secret data. It would therefore result in the rejection of programs that have the resource-aware noninterference property but are not constant resource. The second way is to prove constant resource usage locally by ensuring that every conditional that branches on secret values is constant time. However, this local approach is problematic because it is not compositional. Consider the following examples where rev is the reverse function.

  let z = if b then x else [] in rev z
let f2(b,x,y) =
  let z = if b then let _ = rev y in x
          else let _ = rev x in y in rev z

If we assume a cost model in which we count the number of function calls then the cost of rev(x) is . So rev is constant resource w.r.t. its argument. Moreover, the expression if b then x else [] is constant resource. However, f1 is not constant resource. In contrast, the conditional in f2 is not constant resource. But f2 is a constant-resource function. The function f2 can be automatically analyzed with the constant-resource type system from Section IV while f1 is correctly rejected.

The idea of our type system for resource-aware noninterference is to allow both global and local reasoning about resource consumption as well as arbitrary intermediate levels. We ensure that every expression that is typed in a high security context is part of a constant-resource expression. In this way, we get the benefits of local reasoning without losing compositionality.

Iii-D Typing rules and soundness

We combine our type system for constant resource usage with a standard information flow type system which based on FlowCaml [32]. The interface between the two type systems is relatively light and the idea is applicable to other cost-analysis methods as well as other security type systems.

In the type judgement, an expression is typed under a type context and a label pc. The pc label can be considered an upper bound on the security labels of all values that affect the control flow of the expression and a lower bound on the labels of the function’s effects [32]. As mentioned earlier, we will simplify the technical development by assuming that the third and most-restrictive class is empty, that is, the typing rules here guarantee that well-typed expressions provably satisfy the resource-aware noninterference property w.r.t. changes in variables from the set , say . We define two type judgements of the form

The judgement with the const annotation states that under a security configuration given by and the label pc, has type and it satisfies resource-ware noninterference w.r.t. changes in variables from . The second judgement indicates that satisfies the noninterference property but does not make any guarantees about resource-based side channels. Selected typing rules are given in Fig. 14. We implicitly assume that the security types and the resource-annotated counterparts have the same base types. We write to denote it is optional, and if is well-typed in the constant-resource type system w.r.t. (i.e., , , and ). We will discuss the constant-resource type system in Section IV.

Fig. 6: Selected security typing rules

Note that the standard information flow typing rules [33, 32] can be obtained by removing the const annotations from all judgements. Consider for instance the rule SR:If for conditional expressions. By executing the true or false branches, an adversary could gain information about the conditional value whose security label is . Therefore, the conditional expression must be type-checked under a security assumption at least as restrictive as pc and . This is a standard requirement in any information flow type system. In the following, we will focus on explaining how the rules restrict the observable resource usage instead of these classic noninterference aspects.

The most interesting rules are SR:C-Gen and the rules for let and if expressions, which block leakage over resource usage when branching on high security data. SR:C-Gen allows us to globally reason about constant resource usage for an arbitrary subexpression that has the noninterference property. For example, we can apply SR:If, the standard rule for conditionals, first and then SR:C-Gen to prove that its super-expression is constant resource. Alternatively, we can use rules such as SR:L-If and SR:L-Let to locally reason about resource use. The rule SR:L-Let reflects the fact that if both and have the resource-aware noninterference property and the size of only depends on low security data then respects resource-aware noninterference. The reasoning is similar for SR:L-If where we require that the variable does not depend on high security data.

Leaf expressions such as and have constant resource usage. Thus their judgements are always associated with the qualifier const as shown in the rule SR:B-Op. The rule SR:C-Fun states that if a function’s body has the resource-aware noninterference property then the function application has the resource-aware noninterference property too. If the argument’s label is low security data, bounded below by , then the function application has the resource-aware noninterference property since the value of the argument is always the same under any -equivalent environments. It is reflected by rule SR:L-Arg.

Example.

Recall the functions compare and p_compare in Fig. 1. Suppose the content of the first list is secret and the length is public. Thus it has type . While the second list controlled by adversaries is public, hence it has type . Assume that the pc label is and = . The return value’s label depends on the content of the elements of the first list whose label is . Thus it must be assigned the label to make the functions well-typed.

Here, both functions satisfy the noninterference property at security label . However, only p_compare is a resource-aware noninterference function w.r.t. , or the secret list.

Example.

Consider the following function cond_rev in which rev is the standard reverse function.

  let r = if b2 then rev l1; l2
  else rev l2; l1 in rev r; () else ()

Assume that , , and have types , , , and , respectively. Given the rev function is constant w.r.t. the argument, the inner conditional does not satisfy resource-aware noninterference. However, the let expression satisfies resource-aware noninterference w.r.t. = . We can derive this by applying the rule SR:C-Gen. By the rule SR:L-If, the outer conditional on low security data satisfies resource-aware noninterference w.r.t. at level . We derive the following type.

We now prove the soundness of the type system w.r.t. the resource-aware noninterference property. It states that if is a well-typed expression with the const annotation then is a resource-aware noninterference expression at level .

The following two lemmas are needed in the soundness proof. The first lemma states that the type system satisfies the standard simple security property [34] and the second shows that the type system prove classic noninterference.

Lemma 2.

Let or . For all variables in , if then .

Lemma 3.

Let or , , , and . Then if .

Theorem 1.

If , , and then is a resource-aware noninterference expression at .

Proof.

The proof is done by induction on the structure of the typing derivation and the evaluation derivation. Let be the set of variables . For all environments , such that and , if and . We then show that and if . We illustrate one case of the conditional expression. Suppose is of the form , thus the typing derivation ends with an application of either the rule SR:L-If or SR:C-Gen. By Lemma 3, if then .

  • Case SR:L-If. By the hypothesis we have . Assume that , by the evaluation rule E:If-True, and . By induction for we have . It is similar for .

  • Case SR:C-Gen. Since w.r.t. , we have w.r.t. . By the hypothesis we have . Thus by Theorem 3, it follows .

Iv Type systems for lower bounds and constant resource usage

We now discuss how to automatically and statically verify constant resource usage, upper bounds, and lower bounds. For upper bounds we rely on existing work on automatic amortized resource analysis [19, 21]. This technique is based on an affine type system. For constant resource usage and lower bounds we introduce two new sub-structural resource-annotated type systems: The type system for constant resource usage is linear and the one for lower bounds is relevant.

Iv-a Background

Amortized analysis

To statically analyze a program with the potential method [35], a mapping from program points to potentials must be established. One has to show that the potential at every program point suffices to cover the cost of any possible evaluation step and the potential of the next program point. The initial potential is then an upper bound on the resource usage of the program.

Linear potential for upper bounds

To automate amortized analysis, we fix a format of the potential functions and use LP solving to find the optimal coefficients. To infer linear potential functions, inductive data types are annotated with a non-negative rational numbers  [19]. For example, the type of Boolean lists with potential defines potential , where is the number of list’s elements.

This idea is best explained by example. Consider the function filter_succ below that filters out positive numbers and increments non-positive numbers. As in RAML, we use OCaml syntax and tick commands to specify resource usage. If we filter out a number then we have a high cost ( resource units) since x is, e.g., sent to an external device. If x is incremented we have a lower cost of resource units. As a result, the worst-case resource consumption of filter_succ() is (where is for the cost that occurs in the nil case of the match). The function fs_twice() applies filter_succ twice, to and to the result of filter_succ(). The worst-case behavior appears if no list element is filtered out in the first call and all elements are filtered out in the second call. The worst-case behavior is thus .

match l with
| [] -> tick(1.0); []
| x::xs -> if x > 0 then
    tick(8.0); filter_succ(xs)
  else tick(3.0); (x+1)::filter_succ(xs)
let fs_twice(l) =
  filter_succ(filter_succ(l))
Fig. 7: Two OCaml functions with linear resource usage. The worst-case number of ticks executed by fitler_succ() and fs_twice() is and respectively. In the best-case the functions execute and ticks, respectively. The resource consumption is not constant.

These upper bounds can be expressed with the following annotated function types, which can be derived using local type rules in Fig. 8.

Intuitively, the first function type states that an initial potential of is sufficient to cover the cost of filter_succ() and there is potential left where is the result of the computation. This is just one possible potential annotation of many. The right choice of the potential annotation depends on the use of the function result. For example, for the inner call of filter_succ in fs_twice we need the following annotation.

It states that the initial potential of is sufficient to cover the cost of filter_succ() and there is potential left to be assigned to the returned list . The potential of the result can then be used with the previous type of filter_succ to pay for the cost of the outer call.

We can summarize all possible types of filter_succ with a linear constraint system. In the type inference, we generate such a constraint system and solve it with an off-the-shelf LP solver. To obtain tight bounds, we perform a whole-program analysis and minimize the coefficients in the input potential.

Surprisingly, this approach—as well as the new concepts we introduce here—can be extended to polynomial bounds [36], higher-order functions [37, 21], polymorphism [38], and user-defined inductive types [38, 21].

Iv-B Resource annotations

The resource-annotated types are base types in which the inductive data types are annotated with non-negative rational numbers, called resource annotations.

A type context, , is a partial mapping from variable identifiers to resource-annotated types. The underlying base type and context denoted by , and respectively can be obtained by removing the annotations. We extend all definitions such as , and for base data types to resource-annotated data types by ignoring the annotations.

We now formally define the notation of potential. The potential of a value of type , written , is defined by the function as follows.

Example.

The potential of a list of type is . Similarly, a list of lists of Booleans of type , where , has the potential .

Let be a context and be a well-formed environment w.r.t. . The potential of under is defined as . The potential of is . Note that if then . The following lemma states that the potential is the same under two well-formed size-equivalent environments.

Lemma 4.

If then .

Annotated first-order data types are given as follows, where and are rational numbers.

A resource-annotated signature is a partial mapping from function identifiers to a non-empty sets of annotated first-order types. That means a function can have different resource annotations depending on the context. The underlying base types are denoted by And the underlying base signature is denoted by where .