Proof-Carrying Code in a
Session-Typed Process Calculus
Luis Caires 2 , Frank Pfenning 1 , and Bernardo Toninho 1,2
1
Computer Science Department,
Carnegie Mellon University,
Pittsburgh, PA, USA
2
Faculdade de Ciencias e Tecnologia,
Universidade Nova de Lisboa,
Lisboa, Portugal
Abstract. Dependent session types allow us to describe not only properties
of the I/O behavior of processes but also of the exchanged data.
In this paper we show how to exploit dependent session types to express
proof-carrying communication. We further introduce two modal operators
into the type theory to provide detailed control about how much
information is communicated: one based on traditional proof irrelevance
and one integrating digital signatures.
Keywords: Process calculus, session types, proof irrelevance, proofcarrying
code
1 Introduction
Session types [9] provide high-level specifications for the communication behavior
of interacting processes along bidirectional channels. Recently, logical
foundations for session types have been established via Curry-Howard correspondences
with linear logic [4, 10]. Besides clarifying and unifying concepts in
session types, such logical underpinnings provide simple means for generalization.
One such extension to dependent session types [3, 17] allows us to express
and enforce complex properties of data transmitted during sessions.
In this paper we build upon dependent session types to model various aspects
of systems employing certified code. Already, just dependent session types
can model basic proof-carrying code since a dependent type theory uniformly
integrates proofs and programs. Briefly, a process with session type ∀x:τ.A(x)
will input a data value M of type τ and then behave as A(M), while ∃x:τ.A(x)
will output a data value M of type τ and then behave as A(M). The data values
are taken from an underlying functional layer which is dependently typed. The
session type 1 indicates termination of the session. For example, the following
is the specification of a server process that accepts the code for a function on
natural numbers, a proof that the function is decreasing, and returns a fixed
point of that function.
∀f:nat → nat. ∀p:(Πx:nat. f(x) ≤ x). ∃y:nat. ∃q:(y = f(y)). 12 Luis Caires, Frank Pfenning, and Bernardo Toninho
In a session of the type above, two proof objects will be transmitted: one (labeled
p) showing that the function f is decreasing will be passed to the server, and a
second one (labeled q), that the returned value y is indeed a fixed point, will be
passed back to the client. Note that the propositions n ≤ m and n = m act as
types of their proofs, according to the usual Curry-Howard correspondence.
The client may easily check that the returned value y is indeed a fixed point
by computing f(y) itself, so we would like to avoid transmitting a proof q of
y = f(y). But we do not want to erase this requirement entirely, of course, just
avoid sending a proof term. We can do this by using the type-theoretic concept
of proof irrelevance [14, 13, 1]. Generally, a type [A] (pronounced “bracket A”)
is the type inhabited by proofs of A, all of which are identified. This is only
meaningful if such proofs play no computational role, so there is some subtlety
to the type system presented in Section 3. The revised specification would be:
∀f:nat → nat. ∀p:(Πx:nat. f(x) ≤ x). ∃y:nat. ∃q:[y = f(y)]. 1
Irrelevant proofs terms are eliminated in the operational semantics of our type
theory, so just a unit element would be communicated instead of a proof. The
residual communication overhead can also be optimized away using two different
techniques (see Section 3).
The proof that a given function is decreasing may be complex, so the server
may try to avoid checking this proof, delegating it instead to a trusted verifier.
This verifier would sign a digital certificate to the effect that there is a proof
that the function is decreasing. We integrate this into our type theory with a
type constructor 〈K〉A (read “K says A”), where K is a principal and A is a
proposition. We want this certificate not to contain the proof, so the proof itself
is marked as irrelevant. We obtain:
∀f:nat → nat. ∀p:〈verif〉[Πx:nat. f(x) ≤ x]. ∃y:nat. ∃q:[y = f(y)]. 1
In the implementation, we assume a public key infrastructure so that the verifier
can sign a certificate containing the proposition [Πx:nat. f(x) ≤ x] and the
server can reliably and efficiently check the signature. Our experience with a
proof-carrying file system [7] shows that digitally signed certificates are much
more compact and can be checked much more quickly than proofs themselves
and are one of the cornerstones to make the architecture practical. In this paper
we show that they can be accomodated elegantly within session types, based on
logical grounds.
We begin in Section 2 with an overview of dependent session types in a term
passing variant of the π-calculus, as formulated in previous work by the authors.
In Section 3 we define proof irrelevance and how it is used in our operational
model, followed by a discussion of affirmation as a way of integrating digitally
signed certificates into sessions in Section 4. We sketch some standard metatheoretic
results regarding progress and preservation in Section 5 and conclude
in Section 6.Proof-Carrying Code in a Session-Typed Process Calculus 3
2 Dependent session types
In this section we will briefly review dependent session types and the facilities
they provide in terms of proof-carrying communication. Dependent session types
[3, 17] are a conservative extension of session types [9, 4, 5, 8] that allow us to
not only describe the behavior of processes in terms of their input and output
behavior but also enable us to describe rich properties of the communicated data
themselves.
In [17], the authors investigated a natural interpretation of linear type theory
as a dependent session typed π-calculus.
Definition 1 (Types). Types in linear type theory are freely generated by the
following grammar, given types τ from a standard dependent type theory:
A, B ::= 1 | A ⊸ B | A ⊗ B | A � B | A ⊕ B | !A
∀x:τ.A | ∃x:τ.A
A process P offering a service A along a channel z is typed as P :: x : A and we
obtain an interpretation of the types as follows:
P :: x : 1 inaction
P :: x : A ⊸ B input a channel of type A along x and continue as B
P :: x : A ⊗ B output a fresh channel y of type A along x and continue as B
P :: x : A � B offer the choice between A and B along x
P :: x : A ⊕ B provide either A or B along x
P :: x : !A provide a persistent (replicating) service A along x
P :: x : ∀y:τ. A input a value M of type τ along x and continue as A{M/y}
P :: x : ∃y:τ. A output a value M of type τ along x and continue as A{M/y}
As an example consider the following type:
T � ∀n:nat. ∀p:(n > 0). ∃y:nat. ∃q:(y > 0). 1
The type T specifies a process that inputs a positive number n and outputs
another positive number y. A process that inhabits this type (for channel x) is:
P :: x : T � x(n).x(p).x〈n + 1〉.x〈incp n p〉. 0
where incp n p denotes a proof term of type n + 1 > 0, computed by a function:
incp : Πm : int.(m > 0) → (m + 1 > 0)
The properties of the communicated data (in this case, the positivity of both
numbers) are made explicit by the exchange of terms that act as proof certificates
for the properties, by inhabiting the appropriate types.
Our type system arises as a direct interpretation of the rules of linear logic
as typing rules for processes, thus our typing judgment is essentially the same
as that for a linear logic sequent calculus with a proof term assignment, but4 Luis Caires, Frank Pfenning, and Bernardo Toninho
singling out a specific channel in which the considered session is being offered.
The typing judgment for our system is written as Ψ; Γ ; ∆ ⇒ P :: z : A, where Ψ
consists of assumptions of the form x:τ, Γ consists of persistent assumptions of
the form u:A, and ∆ consists of linear assumptions of the form x:A. We assume
all variables in these contexts to be distinct.
The typing judgment above denotes that process P implements session A
along channel z, provided it is placed in a process environment that offers the
sessions and values specified in contexts Ψ, Γ and ∆. The typing rules for our
system are given below in Fig. 1, and are defined modulo structural congruence of
processes. Following standard sequent calculus presentations of logic, our system
is made up of so called right and left rules that define the types, and structural
rules that denote sound reasoning principles in logic. In our interpretation, right
rules define how to implement a session of a particular type, while left rules define
how to use such a session. The standard reasoning principles of cut and identity
correspond to process composition and channel forwarding (i.e., communication
along a channel being replaced by communication on another).
As previously mentioned, our process calculus is a π-calculus where processes
can communicate terms from a functional language.
Definition 2 (Processes). Processes are defined by the following grammar,
where P, Q range over processes, x, y over names and N over terms.
P, Q ::= 0 | P |Q | (νy)P | x〈y〉.P | x(y).P | x〈N〉.P
!x(y).P | x.inl; P | x.inr; P | x.case(P, Q) | [y ↔ x]
Most constructs are standard. We highlight the term output construct x〈N〉.P ,
the binary guarded choice constructs x.inl; P and x.inr; P with the corresponding
case construct; the channel forwarding or renaming construct [y ↔ x] that links
the channels x and y.
Processes are equated up to a structural congruence ≡, defined below.
Definition 3 (Structural Congruence). Structural congruence is defined as
the least congruence relation closed under the following rules:
P | 0 ≡ P
P ≡α Q ⇒ P ≡ Q
P | (Q|R) ≡ (P |Q) | R
P |Q ≡ Q|P
x ̸∈ fn(P ) ⇒ P |(νx)Q ≡ (νx)(P |Q) (νx)0 ≡ 0
(νx)(νy)P ≡ (νy)(νx)P [y ↔ x] ≡ [x ↔ y]
The operational semantics for the process calculus are standard. The semantics
for the [y ↔ x] construct, as informed by the proof theory, consist of channel
renaming.Proof-Carrying Code in a Session-Typed Process Calculus 5
Ψ; Γ ; · ⇒ 0 :: z : 1 1R
Ψ; Γ ; · ⇒ P :: y : A
Ψ; Γ ; · ⇒ !z(y).P :: z : !A !R
Ψ; Γ ; ∆ ⇒ P :: z : C
Ψ; Γ ; ∆, x : 1 ⇒ P :: z : C 1L
Ψ; Γ, u : A; ∆ ⇒ P :: z : C
Ψ; Γ ; ∆, x : !A ⇒ P {x/u} :: z : C !L
Ψ; Γ ; ∆ ⇒ P :: z : A Ψ; Γ ; ∆ ⇒ Q :: z : B
Ψ; Γ ; ∆ ⇒ z.case(P, Q) :: z : A � B
Ψ; Γ ; ∆, x : A ⇒ P :: z : C
Ψ; Γ ; ∆, x : A � B ⇒ x.inl; P :: z : C �L1
Ψ; Γ ; ∆, x : B ⇒ P :: z : C
Ψ; Γ ; ∆, x : A � B ⇒ x.inr; P :: z : C �L2
�R
Ψ; Γ ; ∆1 ⇒ P :: y : A Ψ; Γ ; ∆2 ⇒ Q :: z : B
Ψ; Γ ; ∆1, ∆2 ⇒ (νy)z〈y〉.(P | Q) :: z : A ⊗ B ⊗R
Ψ; Γ ; ∆ ⇒ P :: z : A
Ψ; Γ ; ∆, y : A, x : B ⇒ P :: z : C
Ψ; Γ ; ∆, x : A ⊗ B ⇒ x(y).P :: z : C ⊗L
Ψ; Γ ; ∆ ⇒ z.inl; P :: z : A ⊕ B ⊕R1
Ψ; Γ ; ∆ ⇒ P :: z : B
Ψ; Γ ; ∆ ⇒ z.inr; P :: z : A ⊕ B ⊕R2
Ψ; Γ ; ∆, x : A ⇒ P :: z : C Ψ; Γ ; ∆, x : B ⇒ Q :: z : C
Ψ; Γ ; ∆, x : A ⊕ B ⇒ x.case(P, Q) :: z : C
Ψ; Γ ; x : A ⇒ [x ↔ z] :: z : A id
Ψ ⊢ N : τ
⊕L
Ψ, x : τ; Γ ; ∆ ⇒ P :: z : A
Ψ; Γ ; ∆ ⇒ z(x).P :: z : ∀x : τ.A ∀R
Ψ; Γ ; ∆, x : A{N/y} ⇒ P :: z : C
Ψ; Γ ; ∆, x : ∀y : τ.A ⇒ x〈N〉.P :: z : C
Ψ ⊢ N : τ Ψ; Γ ; ∆ ⇒ P : A{N/x}
Ψ; Γ ; ∆ ⇒ z〈N〉.P :: z : ∃x : τ.A ∃R
Ψ; Γ, u : A; ∆, y : A ⇒ P :: z : C
Ψ; Γ, u : A; ∆ ⇒ (νy)u〈y〉.P :: z : C
∀L
Ψ, y : τ; Γ ; ∆, x : A ⇒ P :: z : C
Ψ; Γ ; ∆, x : ∃y : τ.A ⇒ x(y).P :: z : C ∃L
copy
Ψ; Γ ; ∆1 ⇒ P :: x : A Ψ; Γ ; ∆2, x : A ⇒ Q :: z : C
Ψ; Γ ; ∆1, ∆2 ⇒ (νx)(P | Q) :: z : C
Ψ; Γ ; · ⇒ P :: x : A Ψ; Γ, u : A; ∆ ⇒ Q :: z : C
Ψ; Γ ; ∆ ⇒ (νu)((!u(x).P ) | Q) :: z : C
cut !
cut
Fig. 1. Dependent Session Types.6 Luis Caires, Frank Pfenning, and Bernardo Toninho
Definition 4 (Reduction). The reduction relation on processes, P → Q is
defined by the following rules:
x〈y〉.Q | x(z).P → Q | P {y/z}
x〈y〉.Q | !x(z).P → Q | P {y/z} | !x(z).P
x〈N〉.Q | x(z).P → Q | P {N/z}
(νx)([x ↔ y] | P ) → P {y/x}
x.inl; P | x.case(Q, R) → P | Q
x.inr; P | x.case(Q, R) → P | R
Q → Q ′ ⇒ P | Q → P | Q ′
P → Q ⇒ (νy)P → (νy)Q
P ≡ P ′ , P ′ → Q ′ , Q ′ ≡ Q ⇒ P → Q
A labeled transition system can be defined in a somewhat standard manner,
where a label denotes a silent action, an output or input of a (bound) name or
of a term (note that terms do not contain channel names, so no issues of scope
extrusion arise).
The language of terms is intentionally left open-ended. We only suppose that
they contain no (π-calculus) names, and that it satisfies substitution, progress,
and preservation properties as we usually suppose for functional languages. In
the next section we will postulate some particular constructs that allow us to
specify different versions of proof-carrying code protocols.
3 Proof irrelevance
In a dependent type theory, proofs are represented as terms. Even with basic
dependent function types we already have the means to model proof-carrying
code, as explained in the introduction and the previous section. This assumes
that data values transmitted along channels are type-checked when received
before we continue to compute with them in a type-safe way.
Under which circumstances can we avoid type-checking a proof object, or
perhaps even avoid transmitting it entirely? One class of examples is provided
by cases where the property of the objects we specified is (easily) decidable.
Then we can check the property itself without the need to obtain an explicit
proof object. However, this only works if the proof object is also of no actual
operational significance, that is, it is computationally irrelevant. The previous
section (e.g., ∀p:(n > 0)) and the introduction (e.g., ∃q:(y = f(y))) contain
examples of this kind. But we do not want to presuppose or “bake in” any
particular analysis or strategy, but formulate the type theory so that we can
seemlessly move between different specifications. This is what a modality for
proof irrelevance [14, 13, 1] in the type theory allows us to do.
Proof irrelevance is a technique that allows us to selectively hide portions of
a proof (and by the proofs-as-programs principle, portions of a program). The
idea is that these “irrelevant” proof objects are required to exist for the purposeProof-Carrying Code in a Session-Typed Process Calculus 7
of type-checking, but they must have no bearing on the computational outcome
of the program. This means that typing must ensure that these hidden proofs
are never required to compute something that is not itself hidden.
We internalize proof irrelevance in our functional language by requiring a
modal type constructor, [τ] (read bracket τ), meaning that there is a term of
type τ, but the term is deemed irrelevant from a computational point of view. We
give meaning to [τ] by adding an introduction form for irrelevant terms, written
[M], that states that M is not available computationally; and a new class of
assumptions x ÷ τ, meaning that x stands for a term of type τ that is not
computationally available; we then define a promotion operation on contexts
that transforms computationally irrelevant hypotheses into ordinary ones, to
account for type-checking within the bracket operator.
Definition 5 (Promotion).
(·) ⊕ � ·
(Ψ, x : τ) ⊕
� Ψ ⊕ , x : τ
(Ψ, x ÷ τ) ⊕ � Ψ ⊕ , x : τ
The introduction and elimination forms of proof irrelevant terms are defined by
the following rules:
Ψ ⊕ ⊢ M : τ
Ψ ⊢ [M] : [τ] []I
Ψ ⊢ M : [τ]
Ψ, x ÷ τ ⊢ N : σ
Ψ ⊢ let [x] = M in N : σ
[]E
The introduction rule states that any term M (that may use irrelevant hypotheses)
of type τ is also a proof irrelevant term [M] of type [τ]. The elimination
rule states that we can unwrap the bracket operator only by binding its contents
to a variable classified as proof irrelevant. This new class of variables is given
meaning by an appropriate substitution principle.
Theorem 1 (Irrelevant substitution). If Ψ ⊕ ⊢ M : τ and Ψ, x÷τ, Ψ ′ ⊢ N : σ
then Ψ, Ψ ′ ⊢ N{M/x} : σ
Proof. By structural induction on the derivation of Ψ, x ÷ τ, Ψ ′ ⊢ N : σ
We generally prefer a call-by-value operational semantics for the type theory
so that we can restrict communication to values without complications. We
first extend this to a version that computes explicit evidence for inhabitation
of type [τ], although the intent is to actually erase rather than compute irrelevant
objects. The single-step reduction relation would then contain the following
congruence and reduction rules (treating irrelevant terms lazily):
M −→ M ′
let [x] = M in N −→ let [x] = M ′ in N
let [x] = [M] in N −→ N{M/x}8 Luis Caires, Frank Pfenning, and Bernardo Toninho
As motivated above, the next step is to check that irrelevant terms do not
need to be computed at the functional level or communicated at the process level.
We formalize this through a notion of erasure that replaces computationally
irrelevant types by a unit type unit and irrelevant terms by corresponding unit
elements 〈〉.
Definition 6 (Erasure). The erasure operation † is defined on contexts, types,
processes and terms. It is compositional everywhere, with the following special
cases.
(Ψ, x:τ) † � Ψ † , x:τ †
(Ψ, x÷τ) † � Ψ †
[τ] †
[M] †
� unit
� 〈〉
(let [x] = M in N) † � N †
The erasure from the definition above does not affect the process structure. It
simply traverses processes down to the functional terms they manipulate and
replaces bracketed terms by the unit element as specified above.
Theorem 2 (Erasure correctness).
If Ψ; Γ ; ∆ ⇒ P :: z : A then Ψ † ; Γ † ; ∆ † ⇒ P † :: z : A † .
Proof. Straightforward, by induction on the typing derivation. Note that in the
case for the let-binding for bracket types we rely on the fact that the variable
[x] can only occur in a bracketed term (which is itself replaced by 〈〉 in †).
Note that the lack of computational significance of proof irrelevant terms
ensures us that the meanings of programs are preserved. Since erasure does not
affect the structure of processes, we need only focus on the functional language
itself (which we fix to be well-behaved in terms of the standard properties of
progress and preservation). We can establish that erasure and evaluation commute,
in the following sense (where ≡ is a standard notion of equality).
Theorem 3 (Erasure Preservation). If Ψ ⊢ M : τ and M −→ N, then there
exists M ′ such that M † −→ ∗ M ′ and N † ≡ M ′ .
Proof. By induction on the operational semantics.
However, the erasure operation is just a step in the optimization mentioned
above, since the processes in the image of the erasure still perform some communication
(of unit elements) in the same places where proof objects were previously
exchanged. To fully remove the potentially unnecessary communication, we consistently
appeal to type isomorphisms regarding the interaction of unit with the
universal and existential quantifiers:
∀x:unit.A ∼ = A
∃x:unit.A ∼ = AProof-Carrying Code in a Session-Typed Process Calculus 9
Since we only allow for types of the functional language in the universal and
existential quantifiers (and terms in the appropriate process constructs), the
isomorphisms above allow us to remove a communication step. For example, if
we revisit our initial example of Section 2, we can reformulate the type and
process as:
T1 � ∀n:nat. ∀p:[n > 0]. ∃y:nat. ∃q:[y > 0]. 1
P1 :: x : T1 � x(n).x(p).x〈n + 1〉.x〈[incp n p)]〉.0
By bracketing the types for the universally and existentially quantified variables
p and q, we are effectively stating that we only require some proof that p and
y are positive, but the content of the proof itself does not matter. Of course,
since determining the positivity of an integer is easily decidable, and the form of
the proof is irrelevant, we can erase the proofs using †, obtaining the following
process (and type):
T †
1 � ∀n:nat. ∀p:unit. ∃y:nat. ∃q:unit. 1
P †
1 :: x : T1 � x(n).x(p).x〈n + 1〉.x〈〈〉〉.0
By consistently appealing to the type isomorphisms mentioned above, we obtain
the process below that simply inputs a number n and outputs its increment:
P1 † ∼ =
� x(n).x〈n + 1〉.0
An alternative technique familiar from type theories is to replace sequences
of data communications by a single communication of pairs. When proof objects
are involved, these become Σ-types which are inhabited by pairs. For example,
we can rewrite the example above as
T2 � ∀p:(Σn:nat. [n > 0]). ∃q:(Σy:nat. [y > 0]). 1
P2 :: x : T2 � x(〈n, p〉). x〈 〈n + 1, [incp n p]〉 〉. 0
where we have take the liberty of using pattern matching against 〈n, p〉 instead
of writing first and second projections. Applying erasure here only simplifies
the communicated terms without requiring us to change the structure of the
communication.
T †
2 � ∀p:(Σn:nat. unit). ∃q:(Σy:nat. unit). 1
P †
2 :: x : T2 � x(〈n, 〉). x〈 〈n + 1, 〈〉〉 〉. 0
This solution is popular in type theory, where Σx:τ. [σ] is a formulation of a
subset type [14], {x:τ | σ}. Conversely, bracket types [σ] can be written as
{x:unit | σ}, except that the proof object is always erased. Under some restrictions
on σ, subset types can be seen as predicate-based type refinement as
available, for example, in Fine [16] used for secure communication in distributed
computation.10 Luis Caires, Frank Pfenning, and Bernardo Toninho
4 Affirmation
In many distributed communicating systems there are tradeoffs between trust
and explicit proofs. For example, when we download a large application we may
be willing to trust its safety if it is digitally signed by a reputable vendor. On
the other hand, if we are downloading and running a piece of Javascript code
embedded in a web page, we may insist on some explicit proof that it is safe and
adheres to our security policy. The key to making such tradeoffs explicit in session
types is a notion of affirmation (in the sense of [6]) of propositions and proofs by
principals. Such affirmations can be realized through explicit digital signatures
on proofs by principals, based on some underlying public key infrastructure.
An affirmation judgment, written K affirms τ, means that principal K attests
a proof M for τ. As in prior work [6], this may be realized by a digitally signed
certificate, although in our case it will be both the proof and the propositions
that are signed by a principal K, written as 〈M:τ〉K.
We add the affirmation judgment to the type system of our functional language
through the following rule:
Ψ ⊢ M : τ
Ψ ⊢ 〈M:τ〉K : K affirms τ (affirms)
The rule states that any principal can affirm the property τ by virtue of a proof
M. In the implementation, a process wishing to create such an affirmation must
have access to K’s private key so it can sign the pair consisting of the term M
and its type τ.
Such an affirmation may seem redundant: after all, the certificate contains
the term M which can be type-checked. However, checking a digitally signed
certificate may be faster than checking the validity of a proof, so we may speed
up the system if we trust K’s signature. More importantly, if we have proof
irrelevance, and some parts of M have been erased, then we have in general no
way to reconstruct the proofs. In this case we must trust the signing principal
K to accept the τ as true, because we cannot be sure if K played by the rules
and did indeed have a proof. Therefore, in general, the affirmation of τ by K is
weaker than the truth of τ, for which we demand explicit evidence. Conversely,
when τ is true K can always sign it and be considered as “playing by the rules”,
as the inference rule above shows.
Now, to actually be able to use affirmation in a meaningful way, we internalize
the judgment as a modal operator. We write 〈K〉τ for the type that internalizes
the judgment K affirms τ, and let 〈x:τ〉K = M in N for the corresponding
destructor.
Ψ ⊢ M : K affirms τ
Ψ ⊢ M : 〈K〉τ
〈〉I
Ψ ⊢ M : 〈K〉τ Ψ, x:τ ⊢ N : K affirms σ
Ψ ⊢ let 〈x:τ〉K = M in N : K affirms σ 〈〉E
The introduction rule simply internalizes the affirmation judgment. The elimination
rule requires the type we are determining to be an affirmation of theProof-Carrying Code in a Session-Typed Process Calculus 11
same principal K, adding an assumption of τ – we can assume the property τ
from an affirmation made by K only if we are reasoning about affirmations of
K. Affirmation in this sense works as a principal-indexed monad. The reduction
rules for affirmation are straightforward:
M −→ M ′
let 〈x:τ〉K = M in N −→ let 〈x:τ〉K = M ′ in N
let 〈x:τ〉K = 〈M:τ〉K in N −→ N{M/x}
Returning now to the example in the introduction, the type
fpt : ∀f:nat → nat. ∀p:〈verif〉[Πx:nat. f(x) ≤ x]. ∃y:nat. ∃q:[y = f(y)]. 1
expresses the type of a server that inputs a function f, accepts a verifier’s word
that it is decreasing, and returns a fixed point of f to the client. A client that
passes the identity function to fpt may be written as follows:
fpt〈λx. x〉. fpt〈〈[λx. refl x]:[Πx:nat. f(x) ≤ x]〉verif〉. fpt(y). fpt(q). 0.
If we want to explicate that the digital signature is supplied by another process
associated with access to the private key with the principal verif, we could write
a polymorphic process with type
which could be
v : ∀α:type. ∀x:α. ∃y:〈verif〉[α]. 1
v(α). v(x). v〈〈[x]:[α]〉verif〉 :: v : ∀α:type. ∀x:α. ∃y:〈verif〉[α]. 1
The client would then call upon this service and pass the signed certificate (without
the proof term) on to fpt.
fpt〈λx. x〉. v〈nat → nat〉. v〈λx. x〉. v(c). fpt〈c〉. fpt(y). fpt(q). 0.
In fact, the implementation of the proof-carrying file system [7] (PCFS) provides
such a generic trusted service. In PCFS, the access control policy is presented
as a logical theory in the access control logic. Access to a file is granted if
a proof of a corresponding access theorem can be constructed with the theory in
access control logic and is presented to the file system. Such proofs are generally
small when compared to proof-carrying code in the sense of Necula and Lee [12,
11] in which the type safety and memory safety of binary code is certified, but
they are still too big to be transmitted and checked every time a file is accessed.
Instead, we call upon the trusted verification service to obtain a digitally signed
certificate of type 〈verif:[α]〉 called a procap (for proven capability). Procaps are
generally very small and fast to verify, leading to an acceptably small overhead
when compared to checking access control lists.12 Luis Caires, Frank Pfenning, and Bernardo Toninho
As another example, we consider a toy scenario where the customer of a store
uses a paying machine to make a purchase. The machine must communicate with
the bank to make sure the client has enough money for the purchase (realistically
the bank would decide if the client has enough money, not the machine, but this
suits our illustrative purposes best), if that is not the case it must abort the
transaction, otherwise the purchase goes through. We can model this system in
our setting by specifying a type for the bank and a type for the machine. We
abbreviate ∀x:τ. A as τ ⊃ A and ∃x:τ. A as τ ∧ A when x is not free in A:
TBank � ∀s:string. 〈M〉[uid(s)] ⊃
(Σn:int. 〈B〉[bal(s, n)]) ∧ ((∀m:nat. 〈M〉[charge(s, m)] ⊃ 1) � 1)
The type for the bank describes part of the protocol we wish this system to
observe: the bank will receive a string and a signed certificate from the paying
machine (we use M and B as the principal identifiers for the machine and for
the bank, respectively), that asserts the client’s identification data. It then sends
back the account balance to the machine, attaching a signed certificate that it
is indeed the appropriate balance information. It will wait for the decision of
the machine to charge the account or not. This is embodied in the use of the
additive conjunction (�), that allows the bank to branch on doing nothing (1)
or inputing the appropriate charge information. The type for the interface of the
machine with the client is as follows:
TMClient � ∀s:string. ((〈M〉[ok] ∧ 1) ⊕ (〈M〉[nok] ∧ 1))
The client inputs his pin number in the machine and then simply waits for the
machine to inform him if the transaction went through or not. An inhabitant of
the bank type is the process (implementing the session along channel x):
Bank � x(s).x(u).x〈sign 2(db getbal(s))〉.
x.case(x(m).x(c).0; 0)
We assume a function db getbal that interacts with the bank database to fetch
the appropriate balance information and a generic function sign2 (make type
arguments implicit) which is like the earlier generic verifier and uses the bank’s
private key.
db getbal : Πs:string. Σn:int.bal(s, n)
sign2 : (Σn:α. β) → (Σn:α. 〈B〉[β])
The machine process is typed in an environment containing the bank session
along channel x and implementing the interface with the client along channel z,
as follows:
Machine � z(s). x〈s〉. x〈〈[gen uid]:[uid(s)]〉M〉. x(n). x(b). Pdecide
We assume a function gen uid of type Πs:string. uid(s) that takes the clients
input and generates the appropriate uid object. We abstract away the details
of deciding if the client has enough money for the purchase in process Pdecide.Proof-Carrying Code in a Session-Typed Process Calculus 13
This process will simply perform the check and then either terminate and send
to the client the nok signal, if the client has insufficient balance, or send the
charge information to the bank and inform the client that the transaction went
through.
5 Progress and Preservation
In [17] we established the type safety results of progress and preservation for
our dependent session type theory for an unspecified functional language. In
fact, we made no mention of when reduction of the functional terms happens.
Here, we work under the assumption that processes always evaluate a term to a
value before communication takes place, and therefore progress and preservation
are contingent on the functional layer also being type safe in this sense (which
can easily be seen to be the case for the connectives we have presented in this
development).
The proof of type preservation then follows the same lines of [17], using a
series of reduction lemmas that relate process reductions with parallel composition
through an instance of the cut rule and appealing to the type preservation
of the functional layer when necessary.
Theorem 4 (Type Preservation).
Ψ; Γ ; ∆ ⇒ Q :: z : A
If Ψ; Γ ; ∆ ⇒ P :: z : A and P → Q then
Proof. By induction on the typing derivation. When the last rule is an instance
of cut, we appeal to the reduction lemmas mentioned above (and to type preservation
of the functional language when the premises of cut are of existential or
universal type), which are presented in more detail in [17].
The case for the proof of progress is identical. The result in [17] combined
with progress of the functional language establishes progress for the system of
this paper. For the purpose of having a self-contained document, we will sketch
the proof here as well.
Definition 7 (Live Process).
live(P ) � P ≡ (νn)(Q | R)
for some Q, R, n
where Q ≡ π.Q ′ (π is a non-replicated prefix) or Q ≡ [x ↔ y]
We begin by defining the form of processes that are live. We then establish a
contextual progress theorem from which progress follows (Theorem 5 relies on
several inversion lemmas that relate types to action labels). Given an action
label α, we denote by s(α) the subject of the action α (i.e., the name through
which the action takes place).
Theorem 5 (Contextual Progress). Let Ψ; Γ ; ∆ ⇒ P :: z : C. If live(P )
then there is Q such that one of the following holds:
(a) P → Q,14 Luis Caires, Frank Pfenning, and Bernardo Toninho
(b) P α → Q for some α where s(α) ∈ z, Γ, ∆ and s(α) ∈ Γ, ∆ if C = !A,
(c) P ≡S [x ↔ z], for some x ∈ ∆.
Proof. By induction on typing, following [17].
The theorem above states that live processes are either able to reduce outright,
are able to take an action α or are equivalent to a channel forwarding (modulo
structural congruence extended with a “garbage collection rule” for replicated
processes that are no longer usable).
Theorem 6 (Progress). If ·; ·; · ⇒ P :: x : 1, and live(P ), then there exists a
process Q such that P → Q.
Finally, Theorem 6 follows straightforwardly from Theorem 5 since P can never
offer an action α along x, due to its type. Note that 1 types not just the inactive
process but also all closed processes (i.e. processes that consume all ambient
sessions).
6 Concluding remarks
In this paper, we have built upon previous work on dependent session types to
account for a flexible notion of proof-carrying code, including digitally signed
certificates in lieu of proof objects. To this end, we integrated proof irrelevance
and affirmations to the underlying functional language, giving the session type
language fine control over which code and data are accompanied by explicit proof,
which are supported by digital signature only, and which are trusted outright.
We had previously considered proof irrelevance only as a means of optimizing
communication in trusted or decidable settings. In a concrete implementation,
the operational semantics must be supported by cryptographic infrastructure to
digitally sign propositions and proofs and check such signatures as authentic.
Ours is one amongst several Curry-Howard interpretations connecting linear
logic to concurrency. Perhaps closest to session types is work by Mazurak and
Zdancewic [10] who develop a Curry-Howard interpretation of classical linear
logic as a functional programming language with explicit constructs for concurrency.
Their system is based on natural deduction and is substantially different
from ours, and they consider neither dependent types nor unrestricted sessions.
The work on Fine [16], F7 [2], and more recently F* [15] has explored the
integration of dependent and refinement types in a suite of fully fledged functional
programming languages, with the aim of statically checking assertions
about data and state, and enforcing security policies. In our line of research,
investigating how closely related mechanisms may be essentially extracted from
a Curry-Howard interpretation of fragments of linear and affirmation logics,
building on proof irrelevance to express a counterpart of the so-called ghost
refinements in F*.
For future work, we wish to explore the applications of proof irrelevance and
affirmation in the process layer. Proof irrelevance at the process level is not well
understood since it interacts with linearity (if a channel is linear, it must beProof-Carrying Code in a Session-Typed Process Calculus 15
used, but because it it irrelevant it may not) and communication, considered as
an effect. The monadic flavor of affirmation seems to enforce a very strong notion
of information flow restrictions on processes, where a process that provides a
session of type 〈K〉A is only able to do so using public sessions, or other sessions
of type 〈K〉T . It would nevertheless be very interesting to investigate how more
flexible information flow disciplines might be expressed in our framework, based
on modal logic interpretations.
References
1. S. Awodey and A. Bauer. Propositions as [types]. Journal of Logic and Computation,
14(4):447–471, 2004.
2. J. Bengtson, K. Bhargavan, C. Fournet, A. D. Gordon, and S. Maffeis. Refinement
types for secure implementations. In 21st Computer Security Foundations
Symposium (CSF’08), pages 17–32, Pittsburgh, Pennsylvania, June 2008. IEEE
Computer Society.
3. E. Bonelli, A. Compagnoni, and E. L. Gunter. Correspondence Assertions for Process
Synchronization in Concurrent Communications. J. of Func. Prog., 15(2):219–
247, 2005.
4. L. Caires and F. Pfenning. Session types as intuitionistic linear propositions. In
21st International Conference on Concurrency Theory, CONCUR’10, pages 222–
236. Springer LNCS 6269, 2010.
5. M. Dezani-Ciancaglini and U. de’ Liguoro. Sessions and Session Types: an
Overview. In 6th Intl Workshop on Web Services and Formal Methods WS-FM’09,
Lecture Notes in Computer Science. Springer-Verlag, 2010.
6. D. Garg, L. Bauer, K. Bowers, F. Pfenning, and M. Reiter. A linear logic of
affirmation and knowledge. In Proceedings of the 11th European Symposium on
Research in Computer Security, ESORICS’06, pages 297–312. Springer LNCS 4189,
Sept. 2006.
7. D. Garg and F. Pfenning. A proof-carrying file system. In D.Evans and G.Vigna,
editors, Proceedings of the 31st Symposium on Security and Privacy (Oakland
2010), Berkeley, California, May 2010. IEEE. Extended version available as Technical
Report CMU-CS-09-123, June 2009.
8. K. Honda. Types for dyadic interaction. In 4th International Conference on Concurrency
Theory, CONCUR’93, pages 509–523. Springer LNCS 715, 1993.
9. K. Honda, V. T. Vasconcelos, and M. Kubo. Language primitives and type discipline
for structured communication-based programming. In 7th European Symposium
on Programming Languages and Systems, ESOP’98, pages 122–138. Springer
LNCS 1381, 1998.
10. K. Mazurak and S. Zdancewic. Lolliproc: To concurrency from classical linear logic
via Curry-Howard and control. In P.Hudak and S.Weirich, editors, Proceedings of
the 15th International Conference on Functional Programming (ICFP’10), pages
39–50, Baltimore, Maryland, Sept. 2010. ACM.
11. G. C. Necula. Proof-carrying code. In N. D. Jones, editor, Conference Record of
the 24th Symposium on Principles of Programming Languages (POPL’97), pages
106–119, Paris, France, Jan. 1997. ACM Press.
12. G. C. Necula and P. Lee. Safe kernel extensions without run-time checking. In
Proceedings of the Second Symposium on Operating System Design and Implementation
(OSDI’96), pages 229–243, Seattle, Washington, Oct. 1996.16 Luis Caires, Frank Pfenning, and Bernardo Toninho
13. F. Pfenning. Intensionality, extensionality, and proof irrelevance in modal type
theory. In J. Halpern, editor, Proceedings of the 16th Annual Symposium on Logic
in Computer Science (LICS’01), pages 221–230, Boston, Massachusetts, June 2001.
IEEE Computer Society Press.
14. A. Salvesen and J. M. Smith. The strength of the subset type in Martin-Löf’s type
theory. In 3rd Annual Symposium on Logic in Computer Science (LICS’88), pages
384–391, Edinburgh, Scotland, July 1988. IEEE.
15. N. Swamy, J. Checn, C. Fournet, P.-Y. Strub, K. Bhargavan, and J. Yang. Secure
distributed programming with value-dependent types. In O.Danvy, editor, International
Conference on Functional Programming (ICFP’11), Tokyo, Japan, Sept.
2011. ACM. To appear.
16. N. Swamy, J. Chen, and R. Chugh. Enforcing stateful authorization and information
flow policies in fine. In A.D.Gordon, editor, 19th European Symposium
on Programming (ESOP’10), pages 529–549, Paphos, Cyprus, Mar. 2010. Springer
LNCS 6012.
17. B. Toninho, L. Caires, and F. Pfenning. Dependent session types via intuitionistic
linear type theory. In Proceedings of the 13th International ACM SIGPLAN
Symposium on Principles and Practice of Declarative Programming, 2011.