Taypsi: Static Enforcement of Privacy Policies for Policy-Agnostic Oblivious Computation

Secure multiparty computation (MPC) techniques enable multiple parties to compute joint functions over their private data without sharing that data with other parties, typically by employing powerful cryptographic protocols to protect individual's data. One challenge when writing such functions is that most MPC languages force users to intermix programmatic and privacy concerns in a single application, making it difficult to change or audit a program's underlying privacy policy. Prior policy-agnostic MPC languages relied on dynamic enforcement to decouple privacy requirements from program logic. Unfortunately, the resulting overhead makes it difficult to scale MPC applications that manipulate structured data. This work proposes to eliminate this overhead by instead transforming programs into semantically equivalent versions that statically enforce user-provided privacy policies. We have implemented this approach in a new MPC language, called Taypsi; our experimental evaluation demonstrates that the resulting system features considerable performance improvements on a variety of MPC applications involving structured data and complex privacy policies.


INTRODUCTION
Secure multiparty computation (MPC) techniques allow multiple parties to jointly compute a function over their private data while keeping that data secure.A variety of privacy-focused applications can be formulated as MPC problems, including secure auctions, voting, and privacy-preserving machine learning [Evans et al. 2018;Hastings et al. 2019;Laud and Kamm 2015].MPC solutions typically depend on powerful cryptographic techniques, e.g., Yao's Garbled Circuits [Yao 1982] or secret sharing [Beimel 2011], to provide strong privacy guarantees.These cryptographic techniques can be difficult for non-experts to use, leading to the creation of several high-level languages that help programmers write MPC applications [Acay et al. 2021;Darais et al. 2020;Hastings et al. 2019;Liu et al. 2015;Malkhi et al. 2004;Rastogi et al. 2014Rastogi et al. , 2019;;Sweet et al. 2023;Ye and Delaware 2022;Zahur and Evans 2015;Zhang et al. 2013].While raising the level of abstraction, almost all of these languages intermix privacy and programmatic concerns, requiring the programmers to explicitly enforce the high-level privacy policies within the logic of the application itself, using the secure operations provided by the language.As a consequence, the entire application must be examined language is equipped with a security type system that offers the same guarantees as Taype: after jointly computing a well-typed function, neither party can learn more about the other's private data than what can be gleaned from their own data and the output of the function.
• We develop an algorithm that combines a program written in the non-secure fragment of Taypsi with a privacy policy to produce a secure private version that statically enforces the desired policy.We prove that this algorithm generates well-typed (and hence secure) target programs that are additionally guaranteed to preserve the semantics of the source programs.• We evaluate our approach on a range of case studies and microbenchmarks.Our experimental results demonstrate exponential performance improvements over the previous state-of-the-art (Taype) on several complicated benchmarks, while simultaneously showing no performance regression on the remaining benchmarks.Before presenting the full details of our approach, we begin with an overview of Taypsi's strategy for building privacy-preserving applications.Consider the simple filter function in Figure 1, which drops all the elements in a list above a certain bound.1 Suppose Alice owns some integers, and wants to know which of those integers are less than some threshold integer belonging to Bob, but neither party wants to share their data with the other.MPC protocols allow Alice and Bob to encrypt their data and then jointly compute filter using secure operations, without leaking information about the encrypted data beyond what they can infer from the final disclosed output.One (insecure) implementation strategy is to simply encrypt everyone's integers and use a secure version of the ≤ operation to compute the resulting list.Under a standard semi-honest threat model,2 however, this naive strategy can reveal private information, via the shape of the input and the intermediate program states.
As an example, assume Alice's input list is Cons [2] (Cons [7] (Cons [3] Nil)), and Bob's input is [5], where square brackets denote secure (encrypted) numbers, i.e., only the owner of the integer can observe its value.By observing that Alice's private data is built from three Conss, Bob can already tell Alice owns exactly 3 integers, information that Alice may want to keep secret.In addition, both parties can learn information from the control flow of the execution of filter: by observing which branch of if is executed, for example, Bob can infer that the second element of Alice's list is greater than 5. Thus, even if the integers are secure, both parties can still glean information about the other's private data.
The particular policy that a secure application enforces can greatly impact the performance of that application, since the control flow of an application cannot depend on private data.In the case of our example, this means that the number of recursive calls to filter depends on the public information Alice is willing to share.If Alice only wants to share the maximum length of her list, for example, its encrypted version must be padded with dummy encrypted values, and a secure version of filter must recurse over these dummy elements, in order to avoid leaking information to Bob through its control flow.On the other hand, if Alice does not mind sharing the exact number of integers she owns, the joint computation will not have to go over these values, allowing a secure version of filter to be computed more efficiently.Taypsi allows Alice and Bob to use types to describe what public information can be shared about their private data (i.e., the policy governing that data), and its type system guarantees that these policies are not violated when jointly computing a function over that data.At a high-level, a privacy policy for a structured (i.e., algebraic) datatype specifies which of its components are private and which can be freely shared.We call this publicly shared information a public view, reflecting that it is some projection of the full data.Formally, policies in Taypsi are encoded as oblivious algebraic data types (OADTs) [Ye and Delaware 2022], dependent types that take a public view as a parameter.The body of an OADT is the type of the private components of a data type, which are built using oblivious (i.e., secure) type formers, e.g., oblivious fixed-width integer ( Z) and oblivious sum ( +). 3 An oblivious sum is similar to a standard sum, but both its tag and "payload" (i.e., component) are obfuscated, so that an attacker cannot distinguish between a left and right injection.Essentially, an OADT is a type-level function that maps the public view of a value to its private representation, i.e., the shape of its private component.
Figure 2 shows two OADTs for the type list: list ≤ , whose public view is the maximum length of a list, and list = , whose public view is the exact length.A public view can be any public data type.We say list is the public type or public counterpart of the OADTs list ≤ and list = .The key invariant of OADTs is that private values with the same public view are indistinguishable to an attacker, as their private representation is completely determined by the public view.For example, all private lists of type list ≤ 2 have the same private representation, regardless of the actual length of the list: list ≤ 2 ≡  + Z × ( + Z × ).Thus, an attacker cannot learn anything about the structure of an OADT, outside of what is entailed by its public view: an empty list, singleton list, or a list with two elements of type list ≤ 2 all appear the same to an attacker.

Fig. 3. Public and oblivious types
Conceptually, OADTs generalize the notion of secure fixed-width integers to secure structured data, as illustrated in Figure 3. Every fixed-width integer (of type Z) can be sent to its secure value in Z by "encryption", and a secure integer can be converted back to Z by "decryption".In Taypsi, these conversion functions are called section (e.g., Z#s) and retraction (e.g., Z#r).The names reflect their expected semantics: applying retraction to the section of a value should produce the same value.Importantly, while the oblivious integer type Z does not appear to have much structure, it nonetheless has an implicit policy: the public view of an integer is its bit width.If we use 32-bit integers, for example, Z is the set of all integers whose bit width is 32, and Z is the set of their "encrypted" values, related by a pair of conversion functions.Similarly, list ≤ k consists of the secure encodings of lists that have at most k elements.Like Z, list ≤ is equipped with a section function, list ≤ #s, and a retraction function, list ≤ #r, which convert public values of list to their oblivious counterparts and back.Crucially, just as the oblivious integers in Z are indistinguishable, the elements of list ≤ k are also indistinguishable.
In the implementation of Taypsi, oblivious values are represented using arrays of secure values.To ensure that attackers cannot learn anything from the "memory layout" of an OADT value, the size of this array is the same for all values of a particular OADT.As an example, the encoding of the list Cons 10 (Cons 20 Nil) as an oblivious list of type list ≤ 2 is inr ([10], inr ([20], ())), where inr ( inl) is the oblivious counterpart of standard sum injection inr (inl)."Under the hood" this oblivious value is represented as an array holding four secure values; in the remainder of this section, we will informally write this value as [Cons,10,Cons,20], where [Cons] is a synonym of the tag [inr] for readability.As another example, the empty list Nil is encoded as inl (); it is also represented using an array with four elements, [Nil,−,−,−], where the last three elements are dummy encrypted values (denoted by −).Our compiler uses the type of inl to automatically pad this array with these values, in order to ensure that it is indistinguishable from other private values of list ≤ 2.

Enforcing Policies
Although using OADTs ensures that the representation of private information does not leak anything, both parties can still learn information by observing the control flow of a program.In order to protect private data from control flow channels, Taypsi provides oblivious operations to manipulate private data safely.One such operation is the atomic conditional mux,4 a version of if that fully evaluates both branches before producing its final result.To understand why this evaluation strategy is necessary, consider the following example of what would happen if we were to evaluate mux like a standard if expression: Even when all the private data (i.e., the integers in square brackets) is hidden, an attacker can infer that the private condition is true by observing that mux evaluates to the expression in its then branch.
With the secure semantics of mux, however, the following execution trace does not reveal any private information: Since both branches are evaluated regardless of the private condition, an attacker cannot infer that condition from this execution trace (again, all secure values are indistinguishable to an attacker).Thus, falsifying the condition produces an equivalent trace, modulo the encrypted data: The security-type system of Taypsi ensures all operations on private data are done in a way that does not reveal any private information, outside the public information specified by the policies.

Automatically Enforcing Policies
Users can directly implement privacy-preserving applications in Taypsi using OADTs and secure operations, but this requires manually instrumenting programs so that their control flow only depends on public information.Under this discipline, the implementation of a secure function intertwines program logic and privacy policies: the secure version of filter requires a different implementation depending on whether Alice is willing to share the exact length of her list, or an upper bound on that length.Taype [Ye and Delaware 2023] decouples these concerns by allowing programs to include unsafe computations and repairing unsafe computations at runtime, using a novel form of semantics called tape semantics.As an example of this approach, in Taype, a secure implementation of filter that allows Alice to only share an upper bound on the size of her list can be written as: The type signature of filter ≤ specifies the policy it must follow.Intuitively, its implementation first "decrypts" the private inputs, applying the standard filter function to those values, and then "re-encrypts" the filtered list.In this example, the retractions of the private inputs xs and y are unsafe computations that would violate the desired policy if they were computed naively.Fortunately, using the tape semantics prevents this from occurring by deferring these computations until it is safe to do so.Less fortunately, the runtime overhead of dynamic policy enforcement makes it hard to scale private applications manipulating structured data.As one data point, the secure version of filter produced by Taype takes more than 5 seconds to run with an oblivious list list ≤ with sixteen elements, and its performance grows exponentially worse as the number of elements increases.
To understand the source of this slowdown, consider a computation that filters a private list containing 10 and 20 with integer 15: filter ≤ 2 [Cons,10,Cons,20] [15].The first step in evaluating this function is to compute list ≤ #r 2 [Cons,10,Cons,20].Completely reducing this expression leaks information, so tape semantics instead stops evaluation after producing the following computation:5 The two [false]s are the results of securely checking if the two constructors in the input list are Nil.Observe that evaluating either mux or Z#r would reveal private information, so the evaluation of these operations is deferred.This delayed computation can be thought of as an "if-tree" whose internal nodes are the private conditions needed to compute the final results, and whose leaves hold the result of the computation along each corresponding control flow path.To make progress, tape semantics distributes the context surrounding a delayed computation, filter and then list ≤ #s in this example, into each of its leaves; having done so, those leaves can be further evaluated.Importantly, in our example, the leaves of this if-tree are eventually re-encrypted using list ≤ #s.The tape semantics does so in a secure way, so that Z#r [10] becomes [10] again, and each result list is converted to a secure value of the expected OADT.Once the branches of a mux node have been reduced to oblivious values of the same type, the node itself can be securely reduced using the secure semantics of mux.Unfortunately, the if-tree produced by the tape semantics can grow exponentially large before its mux nodes can be reduced.For example, after applying filter to the if-tree produced by list ≤ #r, the resulting if-tree has a leaf corresponding to every possible list that filter could produce; the number of these leaves is exponential in the maximum length of the input list.As any surrounding computation, i.e., list ≤ #s in our example, can be distributed to each of these leaves, an exponential number of computations may need to be performed before the if-tree can be collapsed.
To remedy these limitations, this paper proposes to instead compile an insecure program into a secure version that statically enforces a specified policy.To do so, we extend Taype, the secure language of Ye and Delaware [2023] with Ψ-types, a form of dependent sums (or dependent pairs) that packs public views and the oblivious data into a uniform representation.For example, Ψ list ≤ is the oblivious list list ≤ with its public view: ⟨2, inr ([10], inr ([20], ()))⟩ and ⟨2, inl ()⟩ are elements of type Ψ list ≤ , corresponding to the examples in the previous section.The first component of this pair-like syntax is a public view and the second component is an OADT whose public view is exactly the first component.This allows users to again derive a private filter function from its type signature: Users no longer need to explicitly provide the public views for either the output or any intermediate subroutines: both are automatically inferred.As a result, the policy specification of filter ≤ more directly corresponds to the type signature of filter.In addition, specifying policies using Ψ-types avoids mistakes in the supplied public views: using Taype, if the programmer mistakenly specifies the return type list ≤ (k−1) for a secure version of filter, for example, the resulting implementation may truncate the last element of the result list.A keyword %lift is used to translate the standard non-secure function filter to a private version that respects the policy specification.
To understand how this translation works, consider a naive approach where each algebraic data type (ADT) is thought of as an abstract interface, whose operations correspond to the introduction and elimination forms of the algebraic data type6 .An ADT, e.g., list, as well as any corresponding Ψ-type, e.g., Ψ list ≤ and Ψ list = , are implementations or instances of this interface.For example, an interface for list operations is: As long as Ψ list ≤ and Ψ list = implement this interface, we could straightforwardly translate filter to a secure version: This strategy does not rely on unsafe retractions like list ≤ #r, as private data always remains in its secure form, eliminating the need to defer unsafe computations, which is the source of exponential slowdowns in Taype.Unfortunately, there are several obstacles to directly implementing this strategy.First, an ADT and an OADT may not agree on the type signatures of the abstract interface.ListLike fixes the argument types of operations like Cons and match, meaning that list is not an instance of this abstract interface, despite list being a very reasonable (albeit very permissive) policy!In general, different OADTs may only be able to implement operations with specific signatures.Second, a private function may involve a mixture of oblivious types.Thus, some functions may need to coerce from one type to a "more" secure version.For example, if the policy of filter ≤ is Ψ list ≤ → Z → Ψ list ≤ , its second argument y will need to be converted to Z in order to evaluate x ≤ y.A secure list that discloses its exact length may similarly need to be converted to one disclosing its maximum length.Third, this naive translation results in ill-typed programs, because the branches of a mux may have mismatched public views.In filter ≤ , for example, the branches of mux may evaluate to ⟨2, [Cons,10,Cons,20]⟩ and ⟨1, [Cons,20]⟩, respectively.Thus, Taypsi's secure type system will (rightly) reject filter ≤ as leaky.Lastly, the signatures that should be ascribed to any subsidiary function calls may not be obvious.Consider the following client of filter: fn filter5 : list → list = xs ⇒ filter xs 5 If filter5 is given a signature Ψ list ≤ → Ψ list ≤ , we would like to use a secure version of the filter function with the type Ψ list ≤ → Z → Ψ list ≤ , as the threshold argument is publicly known.In general, a function may have many private versions, and we should infer which version to use at each callsite: a recursive function may even recursively call a different "version" of itself.
To solve these challenges, we generalize the abstract interface described above into a set of more flexible structures, which we collectively refer to as Ψ-structures (Section 4).Intuitively, each category of Ψ-structures solves one of the challenges described above.Our translation algorithm (Section 5) generates a set of typing constraints for the intermediate expressions in a program.These constraints are then solved using the set of available Ψ-structures, resulting in multiple private versions of the necessary functions and ruling out the infeasible ones, e.g., filter = . OADT-structure Intro/elim-structure presents the methods of each category of Ψ-structures of list ≤ .The first two methods, list ≤ #s and list ≤ #r, are its section and retraction functions, belonging to the OADT-structure category.Unlike Taype, these two functions are not directly used to derive secure implementations of functions.In fact, our type system guarantees that retraction functions are never used in a secure computation, because Taypsi does not rely on tape semantics to repair unsafe computation (the unsafe fn keyword tells our type checker that list ≤ #r is potentially leaky).Our implementation of Taypsi exposes section and retraction functions as part of the API of the secure library it generates, however, so that client programs can conceal their private input and reveal the output of secure computations.This structure also includes a view method, which our translation uses to select the public view needed to safely convert a list into a Ψ list ≤ .Figure 4 does not show coercion methods, but the programmers can define a coercion from Ψ list = to Ψ list ≤ , for example.
The next set of methods belong to the intro-structure and elim-structure category.These introduction ( list ≤ #Nil and list ≤ #Cons) and elimination ( list ≤ #match) methods construct and destruct private list, respectively.As we construct and manipulate data, these methods build the private version, calculate its public view, and record that view in Ψ-types.Their type signatures are specified by the programmers, as long as the signatures are compatible with Z × list (Section 4).
The join and reshape methods in the join-structure category enable translated programs to include private conditionals whose branches return OADT values with different public views.As an example, consider the following private conditional whose branches have Ψ-types: To build a version of this program that does not reveal [true], Taypsi uses join to calculate a common public view that "covers" both branches.In this example, list ≤ #join chooses a public view of 2, as a list with at most one element also has at most two elements.Our translation then uses the reshape method to convert both branches to use this common public view.In our example, [Cons,20], an oblivious list of maximum length 1, is converted into the list [Cons,20,Nil,, which has maximum length 2. Since both branches in the resulting program have the same public view, it is safe to evaluate mux: the resulting list is equivalent to ⟨2, mux [true] [Cons,10,Cons,20] [Cons,20,Nil,⟩.As we will see later, not all OADTs admit join structures, e.g., list = , but our translation generates constraints that take advantage of any that are available, failing when these constraints cannot be resolved in a way that guarantees security.Note that these two methods are key to avoiding the slowdown exhibited by Taype's enforcement strategy: they allow functions that may return different private representations to be eagerly evaluated, instead of being lazily deferred in a way that requires an exponential number of subcomputations to resolve.
In summary, to develop a secure application in Taypsi, programmers first implement its desired functionality, e.g., filter, in the public fragment of Taypsi, independently of any particular privacy policy.Policies are separately defined as oblivious algebraic data types, e.g., list ≤ , and their Ψstructures.Users can then automatically derive a secure version of their application by providing the desired policy in the form of a type signature involving Ψ-types, relying on Taypsi's compiler to produce a privacy-preserving implementation.The type system of Taypsi, like Taype's, provides a strong security guarantee in the form of an obliviousness theorem (Theorem 3.1).This obliviousness theorem is a variant of noninterference [Goguen and Meseguer 1982], and ensures that well-typed programs in Taypsi are secure by construction: no private information can be inferred even by an attacker capable of observing every state of a program's execution.Our compilation algorithm is further guaranteed to generate a secure implementation that preserves the behavior of the original program (Theorem 4.8). 7he following three sections formally develop the language Taypsi, the Ψ-structures, and our translation algorithm.

TAYPSI, FORMALLY
This section presents  OADTΨ , the core calculus for secure computation that we will use to explain our translation.This calculus extends the existing  OADT [Ye and Delaware 2022] calculus with Ψ-types, and uses ML-style ADTs in lieu of explicit fold and unfold operations.8

Syntax
Figure 5 presents the syntax of  OADTΨ .Types and expressions are in the same syntax class, as  OADTΨ is dependently typed, but we use e for expressions and τ for types when possible.A  OADTΨ program consists of a set of global definitions of data types, functions and oblivious types.Definitions in each of these classes are allowed to refer to themselves, permitting recursive types and general recursion in both function and oblivious type definitions.We use x for variable names, C for constructor names, T for type names, and T for oblivious type names.Each constructor of an ADT definition takes exactly one argument, but this does not harm expressivity: this argument is  for constructors that take no arguments, e.g., Nil, and a tuple of types for constructors that have more than one argument, e.g., Cons takes an argument of type Z × list.

e,τ
Expressions  In addition to standard types and dependent function types (Π),  OADTΨ includes oblivious booleans ( B) and oblivious sum types ( +).The elimination forms of these types are oblivious conditionals mux and oblivious case analysis match, respectively.The branches of both expressions must be private and each branch has to be fully evaluated before the expression can take an atomic step to a final result.Boolean section B#s is a primitive operation that "encrypts" a boolean expression to an oblivious version.Oblivious injection inl and inr are the oblivious counterparts of the standard constructors for sums.Other terms are mostly standard, although let bindings (let), conditionals (if) and pattern matching (match) are allowed to return a type, as  OADTΨ supports type-level computation.
The key addition over  OADT is the Ψ-type, Ψ T. It is constructed from a pair expression ⟨•,•⟩ that packs the public view and the oblivious data together, and has the same eliminators π 1 and π 2 as normal products.As an example, ⟨3, list ≤ #s 3 (Cons 1 Nil)⟩ creates a Ψ-pair of type Ψ list ≤ with public view 3, using the section function from Figure 4. Projecting out the second component of a pair using π 2 produces a value of type list ≤ 3. A Ψ-type is essentially a dependent sum type (Σx:τ, T x), with the restriction that τ is the public view of T, and that T x is an oblivious type.
Since  OADTΨ has type-level computation, oblivious types have normal forms; oblivious type values ( ω) are essentially polynomials formed by primitive oblivious types.We also have the oblivious values of oblivious boolean and sum type.Note that these "boxed" values only appear at runtime, our semantics use these to model encrypted booleans and tagged sums.

Semantics
Figure 6 shows a selection of the small-step semantics rules of  OADTΨ (the full rules are included in the appendix), with judgment Σ ⊢ e −→ e ′ .The global context Σ is a map from names to a global definition, which is elided for brevity as it is fixed in these rules.The semantics of  OADTΨ is similar to  OADT , with the addition of S-PsiProj 1 (and S-PsiProj 2 ) to handle the projection of dependent pairs, which is simply the same as normal projection.S-Ctx reduces subterms according to the evaluation contexts defined in Figure 6.The first few contexts take care of the type-level reduction of product and oblivious sum type.The type annotation of oblivious injection inl and inr is reduced to a type value first, before reducing the payload.The evaluation contexts for mux capture the intuition that all components of a private conditional have to be normalized to values first to avoid leaking the private condition through control flow channels.
S-OMatchL (and S-OMatchR) evaluates a pattern matching expression for oblivious sums.Similar to mux, oblivious pattern matching needs to ensure the reduction does not reveal private information about the discriminee, e.g., whether it is the left injection or right injection.To do so, we reduce a match to an oblivious conditional that uses the private tag.The pattern variable in

Type System
Similar to  OADT , types in  OADTΨ are classified by kinds which specify how protected a type is, in addition to ensuring the types are well-formed.For example, an oblivious type, e.g., B, kinded by * O , can be used as branches of an oblivious conditional, but not as a public view, which can only be kinded by * P .A mixed kind * M is used to classify function types and types that consist of both public and oblivious components, e.g., B× B. A type with a mixed kind cannot be used as a public view or in private context.
The type system of  OADTΨ is defined by a pair of typing and kinding judgments, Σ; Γ ⊢ e : τ and Σ; Γ ⊢ τ :: κ, with global context Σ (which is again elided for brevity) and the standard typing context Γ. Figure 7 presents a subset of our typing and kinding rules; the full rules are in appendix.
The security type system [Sabelfeld and Myers 2003] of  OADTΨ enforces a few key invariants.First, oblivious types can only be constructed from oblivious types, which is enforced by the kinding rules, such as K-OSum.Otherwise, the attacker could infer the private tag of an oblivious sum, e.g., B +, by observing its public payload.Second, oblivious operations, e.g., mux, require their subterms to be oblivious, to avoid leaking private information via control flow channels.T-Mux, for example, requires both branches to be typed by an oblivious type, otherwise an attacker may infer the private condition by observing the result, as in mux [true] true false.Third, type-level computation is only defined for oblivious types and cannot depend on private information.Thus, K-If requires both branches to have oblivious kinds, and the condition to be a public boolean.The type mux [true]  B is ill-typed, since the "shape" of the data reveals the private condition.The typing rules for Ψ-types are defined similarly to the rules of standard dependent sums.T-PsiPair introduces a dependent pair, where the type of the second component depends on the first component.In contrast to standard dependent sum type, Ψ-type has the restriction that the first component must be public, and the second component must be oblivious.This condition is implicitly enforced by the side condition that T is an OADT with public view type τ. Figure 8 shows the typing rules for global definitions; DT-OADT prescribes exactly this restriction.The rules for the first and second projection of Ψ-type, T-PsiProj 1 and T-PsiProj 2 , are very similar to the corresponding rules for standard dependent sum types.Observe that a Ψ-type always has mixed kind, as in K-Psi, because it consists of both public and oblivious components.
T-Conv allows conversion between equivalent types, such as if true then B else  and B. The equivalence judgment τ ≡ τ ′ is defined by a set of parallel reduction rules, which we elide here.The converted type is nonetheless required to be well-kinded.
Note that these rules cannot be used to type check retraction functions, e.g., list ≤ #r from Figure 4, and for good reason: these functions reveal private information.Nevertheless, we still want to check that these sorts of "leaky" functions have standard type safety properties, i.e., progress and preservation.To do so, we use a version of these rules that simply omit some security-related side-conditions about oblivious kinding: removing Γ ⊢ τ :: * O from T-Mux allows the branches of a mux to disclose the private condition, for example.The implementation of Taypsi's type checker uses a "mode" flag to indicate whether security-related side-conditions should be checked.Our implementation ensures that secure functions never use any leaky functions.

Metatheory
With our addition of Ψ-types,  OADTΨ enjoys the standard type safety properties (i.e., progress and preservation), and, more importantly, the same security guarantees as  OADT : Theorem 3.1 (Obliviousness).If e 1 ≈ e 2 and • ⊢ e 1 : τ 1 and • ⊢ e 2 : τ 2 , then (1) Here, e ≈ e ′ means the two expressions are indistinguishable, i.e., they only differ in their unobservable oblivious values, and e −→  e ′ means e reduces to e ′ in exactly  steps.This obliviousness theorem provides a strong security guarantee: well-typed programs that are indistinguishable produce traces that are pairwise indistinguishable.In other words, an attacker cannot infer any private information even by observing the execution trace of a program.All these results are mechanized in the Coq theorem prover, including the formalization of the core calculus and the proofs of soundness and obliviousness theorems.

Ψ-STRUCTURES AND DECLARATIVE LIFTING
While our secure language makes it possible to encode structured data and privacy policies, and use them in a secure way, it does not quite achieve our main goal yet, i.e., to decouple privacy policies and programmatic concerns.To do so, we allow the programmers to implement the functionality of their secure application in a conventional way, that is using only the public, nondependent fragment of Taypsi.We make this fragment explicit by requiring such programs to have simple types, denoted by η, defined in Figure 9.For example, filter has simple type list → Z → list.Programs of simple types are the source programs to our lifting process that translates them to a private version against a policy, which stipulates the public information allowed to disclose in the program input and output.This policy on private functionality is specified by a specification type, denoted by θ, defined also in Figure 9.For example, filter ≤ has specification type Ψ list ≤ → Z → Ψ list ≤ .Note that dependent types are not directly allowed in specifications, they are instead encapsulated in Ψ-types.Simple types and specification types are additionally required to be well-kinded under empty local context, i.e., all ADTs and OADTs appear in them are defined.
However, not all specification types are valid with respect to a simple type.It is nonsensical to give filter the specification type Z → B, for example.The specification types should still correspond to the simple types in some way: the specification type corresponding to list should at least be "list-like".This correspondence is formally captured in the erasure function in Figure 9, which maps a specification type to the "underlying" simple type.For example, Ψ list ≤ is erased to list.This function clearly induces an equivalence relation: the erasure ⌊θ⌋ is the representative of the equivalence class.We call this equivalence class a compatibility class, and say two types are compatible if they belong to the same compatibility class.For example, list, Ψ list ≤ and Ψ list = are in the same compatibility class [list].This erasure operation is straightforwardly extended to typing contexts, ⌊Γ ⌋, by erasing every specification type in Γ and leaving other types untouched.
Our translation transforms source programs with simple types into target programs with the desired (compatible) specification types.As mentioned in Section 2, this lifting process depends on a set of  -structures which explain how to translate certain operations associated with an OADT.

OADT Structures
Every global OADT definition T must be equipped with an OADT-structure, defined below.Definition 4.1 (OADT-structure).An OADT-structure of an OADT T, with public view type τ, consists of the following (Taypsi) type and functions: • A public type T :: * P , which is the public counterpart of T. We say T is an OADT for T.
• A section function s : Πk:τ,T→ T k, which converts a public type to its oblivious counterpart.
• A retraction function r : Πk:τ, T k→T, which converts an oblivious type to its public version.
• A public view function  : T→τ, which creates a valid view of the public type.
• A binary relation ≼ over values of types T and τ; v ≼ k reads as v has public view k, or k is a valid public view of v.These operations are required to satisfy the following axioms: • (A-O 1 ) s and r are a valid section and retraction, i.e., r is a left-inverse for s, given a valid public view: for any values v : T, k : given any values v : T and k : τ.
For example, list ≤ is equipped with the OADT-structure with the public type list, section function list ≤ #s, retraction function list ≤ #r and view function list ≤ #view, all of which are shown in Figure 4. Taypsi users do not need to explicitly give the public type of an OADT-structure, as it can be inferred from the types of the other functions.The binary relation ≼ is only used in the proof of correctness of our translation, so Taypsi users can also elide it.In the case of list ≤ , ≼ simply states the length of the list is no larger than the public view.

Join Structures
In order for Ψ-types to be flexibly used in the branches of secure control flow structures, our translation must be able to find a common public view for both branches, and to convert an OADT to use this view.To do so, an OADT can optionally be equipped with a join-structure.Definition 4.2 (join-structure).A join-structure of an OADT T for T, with public view type τ, consists of the following operations: • A binary relation ⊑ on τ, used to compare two public views.
) the reshape function produces equivalent value, as long as the new public view is valid: for any values v : Figure 4 defines the join and reshape functions list ≤ #join and list ≤ #reshape.The partial order for this join structure is simply the total order on integers, and the join is simply the maximum of the two numbers.Not all OADTs have a sensible join-structure: oblivious lists using their exact length as a public view cannot be combined if they have different lengths.If such lists are the branches of an oblivious conditional, lifting will either fail or coerce both to an OADT with a join-structure.
Join structures induce a mergeability relation, defined in Figure 10, that can be used to decide if a specification type can be used in oblivious conditionals.We say θ is mergeable if θ ▷ ite, with witness ite of type B→θ→θ→θ.We will write θ when we do not care about the witness.This witness can be thought of as a generalized, drop-in replacement of mux: we simply translate mux to the derived ite if the result type is mergeable.The case of Ψ-type captures this intuition: we first join the public views, and reshape all branches to this common public view, before we select the correct one privately using mux.This rule looks up the necessary methods from the context of join structures S ⊔ .Other cases are straightforward: we simply fall back to mux for primitive types, and the derivation for product and function types are done congruently.

Introduction and Elimination Structures
An ADT is manipulated by its introduction and elimination forms.To successfully lift a public program using ADTs, we need structures to explain how the primitive operations of its ADTs are handled in their OADT counterparts.Thus, an OADT T can optionally be equipped with an introduction-structure (intro-structure) and an elimination-structure (elim-structure), defined below.These structures are optional because some programs only consume ADTs, without constructing any new ADT values (and vice versa): a function that checks membership in a list only requires an elim-structure on lists, for example.Intuitively, the axioms of these structures require the introduction and elimination methods of an OADT to behave like those of the corresponding ADT.This is formalized using a pair of logical refinement relations on values (V  • ) and expressions (E  • ); these relations are formally defined in Section 4.6.Definition 4.3 (intro-structure).An intro-structure of an OADT T for ADT T, with global definition data T = C η, consists of a set of functions C  , each corresponding to a constructor C  .The type of C  is θ  →Ψ T, where ⌊θ  ⌋ = η  (note that DT-ADT guarantees that η  is a simple type).The particular θ  an intro-structure uses is determined by the author of that structure.Each C  is required to logically Definition 4.4 (elim-structure).An elim-structure of an OADT T for ADT T, with global definition data T = C η, consists of a family of functions match α , indexed by the possible return types.The type of match α is Ψ T→(θ→α)→α, where ⌊θ  ⌋ = η  for each θ  in the function arguments corresponding to alternatives.Each match α is required to logically refine the pattern matching expression, specialized with ADT T and return type α.The sole axiom of this structure (A-E 1 ) only considers return type α being a specification type: given values v  : η  , ⟨k, v⟩ : T k, x⇒e  : ⌊θ  ⌋→⌊α⌋ and x⇒e ′  : The types of the oblivious introduction and elimination forms in these structures are only required to be compatible with the public counterparts.The programmers can choose which specific OADTs to use according to their desired privacy policy.Figure 4 shows the constructors and pattern matching functions for list ≤ .
The elim-structure of an OADT consists of a family of destructors, whose return type α does not necessarily range over all types.For example, match α of list ≤ , list ≤ #match in Figure 4, requires α to be a mergeable type, due to the use of match, which imposes a restriction similarly to mux.Such constraints on α are automatically inferred and enforced.

Coercion Structures
As discussed in Section 2, we may need to convert an oblivious type to another, either due to a mismatch from input to output, or due to its lack of certain structures.For example, list = does not have join structure, so if the branches of an oblivious conditional has type Ψ list = , they should be coerced to Ψ list ≤ , when such a coercion is available.
Two compatible OADTs may form a coercion-structure, shown below.
Definition 4.5 (coercion-structure).A coercion-structure of a pair of compatible OADTs T and T ′ for T, with public view type τ and τ ′ respectively, consists of a coercion function ↑ of type Ψ T→Ψ T ′ .The coercion should produce an equivalent value (A-C 1 ): given values v : T, ⟨k, v⟩ : This structure only defines the coercion between two Ψ-types.Figure 11 generalizes the coercion relation to any (compatible) specification types.We say θ is coercible to θ ′ if θ ↣ θ ′ ▷ ↑, with witness ↑ of type θ→θ ′ .We may write θ ↣ θ ′ when we do not care about the witness.The rules of this relation are straightforward.The context of coercion structures S ↑ and the context of OADT structures S ω are used to look up the necessary methods in the corresponding rules.The rule for coercing a function type is contravariant.Note that we can always coerce a public type to an OADT by running the section function, and the public view can be selected by the view function in the OADT structure. 144:17 Figure 12 shows a selection of rules of the declarative lifting relation (the full rules are in appendix).We elide most contexts as they are fixed, and simply write Γ ⊢ e : θ ▷ e for brevity.Most rules are simply congruences and similar to typing rules.L-Fun outsources the lifting of a function call to the lifting context.L-If 2 handles the case when the condition is lifted to an oblivious boolean by delegating the translation to the mergeability relation.Similarly, L-Ctor 2 and L-Match 2 query the contexts of the intro-structures and elim-structures, and use the corresponding instances as the drop-in replacement, when we are constructing or destructing Ψ-types.Lastly, L-Coerce coerces an expression nondeterministically using the coercion relation.
This lifting relation in Figure 12 only considers one expression.In practice, the users specify a set of functions and their target types to lift.The result of our lifting procedure is a lifting context L which maps these functions and target types to the corresponding generated functions, as well as any other functions and the inferred target types that these functions depend on.The global context Fig. 13.A logical relation for refinement Σ is also extended with the definitions of the generated functions.To make this more clear, we say a lifting context is derivable, denoted by ⊢ L, if and only if, for any x : θ ▷ x ∈ L, fn x:⌊θ⌋ = e ∈ Σ and fn x:θ = e ∈ Σ for some e and e, such that S; L; Σ; • ⊢ e : θ ▷ e.In other words, any definitions of the lifted functions in L can be derived from the lifting relation in Figure 12.Note that the derivation of a function definition is under a lifting context with possibly an entry of this function itself.This is similar to the role of global context in type checking, as Taypsi supports mutually recursive functions.The goal of our algorithm (Section 5) is then to find such a derivable lifting context that includes the user-specified liftings.

Logical Refinement
The correctness of the lifting procedure is framed as a logical refinement between expressions of specification types and those of simple types; this relationship is defined as a step-indexed logical relation [Ahmed 2006].As is common, this relation is defined via a pair of set-valued type denotations: a value interpretation V  θ and an expression interpretation E  θ .We say an expression e ′ of type θ refines e of type ⌊θ⌋ (within  steps) if (e, e ′ ) ∈ E  θ .In other words, e ′ preserves the behavior of e, in that if e ′ terminates at a value, e must terminate at an equivalent value.The equivalence between values is dictated by V  θ .
Figure 13 shows the complete definition of the logical relation.All pairs in the relations must be closed and well-typed, i.e., their interpretations have the forms: For brevity, we leave this requirement implicit in Figure 13.
The definitions are mostly standard.The most interesting case is the value interpretation of Ψ-type: we say the pair of a public view and an oblivious value of an OADT is equivalent to a public value of the corresponding ADT when the oblivious value can be retracted to the public value.Intuitively, an encrypted value is equivalent to the value it decrypts to.The base cases of the value interpretation are also guarded by the condition that we still have steps left, i.e., greater than 0. This requirement maintains the pleasant property that the interpretations V 0 θ and E 0 θ are total relations on closed values and expressions, respectively, of type θ.The proof also uses a straightforward interpretation of typing context, G  Γ , whose definition is in appendix.
This relation also gives rise to a semantic characterization of the lifting context.We say a lifting context is -valid, denoted by ⊨  L, if and only if, for any x : θ ▷ x ∈ L, (x, x) ∈ E  θ .If ⊨  L for any , we say L is valid, denoted by ⊨ L. The validity is essentially a semantic correctness of L.
Our lifting relation ensures that lifted expressions refine source expressions in fewer than  steps, as long as every lifted program in L is also semantically correct in fewer than  steps.As is common in logical relation proofs, this proof requires a more general theorem about open terms.
Finally, Theorem 4.8 provides a strong result of the correctness of our translation.Any lifting context that is derived using the rules of Figure 12 is semantically correct.In other words, if every pair of source program and lifted program in L are in our lifting relation, they also satisfy our refinement criteria.Theorem 4.8 (Correctness of declarative lifting).⊢ L implies ⊨ L.
Our notion of logical refinement only provides partial correctness guarantees, as can be seen in the definition of E  • .As a result, the lifting relation does not guarantee equi-termination: it is possible that a lifted program will diverge when the source program terminates.This can occur when an if is replaced by a mux: since the latter fully executes both branches, this effectively changes the semantics of a conditional from a lazy evaluation strategy to an eager strategy.Using a public value to bound the recursion depth in order to guarantee termination is a common practice in data-oblivious computation, for the reasons discussed in Section 2. While the public view of an OADT naturally serves as a measure in many cases, including all of the case studies and benchmarks in our evaluation, in theory it is possible for a user to provide a policy to a function that results in a nonterminating lifted version.In this situation, users must either specify a different policy, or rewrite the functions to recurse on a different argument, e.g., a fuel value.

ALGORITHMIC LIFTING
Figure 14 presents the overall workflow of our lifting algorithm.This algorithm starts with a set of goals, i.e., pairs of source functions tagged with the %lift keywords and their desired specification types.We then run our lifting algorithm on all the functions in these goals, as well as any functions they depend on, transforming each of these functions to an oblivious version parameterized by typed macros and type variables, along with a set of constraints over these type variables.After solving the constraints, we obtain a set of type assignments for each function.Note that a single function may have multiple type assignments, one for each occurence in a goal and callsite.For example, filter may have the type assignment for the goal Ψ list ≤ → Z → Ψ list ≤ generated by %lift, and the assignment for Ψ list ≤ → Z → Ψ list ≤ generated by the call in filter5 from Section 2. Finally, we generate the private versions of all the lifted functions by instantiating their type variables and expanding away any macros.The lifting context from the last section is simply these lifted functions and their generated private versions.
The lifting algorithm is defined using the judgment Σ; Γ ⊢ e : η ∼ X ▷ e | C. It reads as the source expression e of type η is lifted to the target expression e whose type is a type variable X as a placeholder for the specification type, and generates constraints C. The source expression e is required to be in administrative normal form (ANF) [Flanagan et al. 1993], which is guaranteed by our type checker.In particular, type annotations are added to let-bindings, and the body of every let is either another let or a variable.Importantly, this means the last expression of a sequence of let must be a variable.The output of this algorithm is an expression e containing macros (which will be discussed shortly), and the constraints C. Unlike the declarative rules, this algorithm keeps track of the source type η, which is used to restrict the range of the type variables.Consequently, every entry of the typing context Γ has the form x : η ∼ X, meaning that local variable x has type η in the source program and type X in the target program.For example, after the lifting algorithm has processed the function arguments of filter in Figure 1, the typing context contains entries xs : list ∼ X 1 and y : Z ∼ X 2 , with freshly generated type variables X 1 and X 2 .The typed macros, defined in Figure 15, are an essential part of the output of the lifting algorithm, and permit a form of ad-hoc polymorphism, that allows the algorithm to cleanly separate constraint solving from program generation.These macros take types as parameters and elaborate to expressions, under the contexts S, L and Σ implicitly.These macros are effectively thin "wrappers" of their corresponding language constructs and the previously defined relations.The conditional macro %ite, for example, corresponds to the if expression, but the condition may be oblivious.The constructor macro %C is a "smart" constructor that may construct a Ψ-type.The pattern matching macro %match is similar to %C but for eliminating a type compatible with an ADT.Lastly, %↑ and %x is simply a direct wrapper of the mergeable relation and the lifting context L, respectively.Note that the derivation of these macro are completely determined by the type parameters.
Figure 16 defines the constraints used in the algorithm, where θ + is the specification types extended with type variables.The constraint X ∈ [η] means type variable X belongs to the compatibility class of η.In other words, ⌊X⌋ = η.Each macro is accompanied by a constraint on its type parameters.These constraints mean that the corresponding macros are resolvable.More formally, this means they can elaborate to some expressions according to the rules in Figure 15 for any expression arguments.As a result, after solving all constraints and concretizing the type variables, all macros in the lifted expression e can be fully elaborated away.
Figure 17 shows a selection of lifting algorithm rules.Coercions only happen when we lift variables, as in A-Var.This works because the source program is in ANF, so each expression is bound to a variable which has the opportunity to get coerced.For example, the argument to a function or constructor, in A-App and A-Ctor, is always a variable in ANF, and recursively lifting it allows the application of A-Var.On the other hand, the top-level program is always in let-binding form, whose last expression is always a variable too, allowing coercion of the whole program.However, not all variables are subject to coercions: the function x 2 in A-App, the condition x 0 in A-If and the discriminee x 0 in A-Match are kept as they are, for example.Coercing these variables would be unnecessary and undesirable.For example, coercing the condition in a conditional only makes the generated program more expensive: there is no reason to coerce from B to B, and use mux instead of if.Another key invariant we enforce in our algorithmic rules is that every fresh variable is "guarded" by a compatibility class constraint.For example, in A-Abs, the freshly generated variables X 1 and X 2 belong to the classes η 1 and η 2 , respectively.This constraint ensures that every type variable can be finitely enumerated, as every compatibility class is a finite set, bounded by the number of available OADTs.As a result, constraint solving in our context is decidable.Finally, if an expression is translated to a macro, a corresponding constraint is added to ensure this macro is resolvable.
We use the judgment S; L; Σ;  ⊨ C to mean the assignment  satisfies a set of constraints C, under the context of Ψ-structure, lifting context and global definition context.The constraints generated by our lifting algorithm use type variables X as placeholders for the target type of the function being lifted.To solve a goal with a particular target type θ, we add a constraint to C that equates the placeholder with the stipulated type, i.e., X = θ.Our constraint solver then attempts to find type assignments that satisfy the constraints in C; the resulting assignment is used to generate private versions of all the functions in the set of goals, as well as the accompanying lifting context.
At a high level,10 our solver reduces all constraints, except for function call constraints (%x), to quantifier-free formulas in a finite domain theory, which can be efficiently solved using an off-theshelf solver.Function call constraints are recursively solved once their type arguments have been concretized by discharging the other constraints.When a function call constraint is unsatisfiable, we add a new refutation constraint and invoke the solver again to find a new instantiation of type parameters.As an example of this process, in order to ascribe filter the type Ψ list = → Z → Ψ list ≤ , we first add the constraint X = Ψ list = → Z → Ψ list ≤ to the constraints generated by the lifting algorithm Solving the other constraints may concretize the type variable of function call constraint %filter(X), i.e., the type of the recursive call to filter, to %filter(Ψ list = → Z → Ψ list ≤ ).Recursively solving this subgoal assuming the original goal is solved, i.e., extending the lifting context with the original goal, results in immediate success, as the subgoal is simply in the lifting context.On the other hand, if the type of the recursive call is instantiated as %filter(Ψ list = → Z → Ψ list = ), the same constraints generated by lifting filter are solved, with an additional constraint X = Ψ list = → Z → Ψ list = .However, this set of constraints is unsatisfiable, as list = has no join structure, so we add a refutation constraint to the context Fig. 17.Selected algorithmic lifting rules that forces the solver to not generate this assignment again.In general, the type of the recursive call to filter may be concretized to any types compatible with list → Z → list.The number of such compatible types is bounded, as the number of arguments of this function and the number of OADTs are themselves bounded.The function filter has 3 × 2 × 3 = 18 possible type assignments.In the worst case scenario, the algorithm eventually terminates after exhausting all 18 combinations.The lifting algorithm enjoys a soundness theorem with respect to the declarative lifting relation.As a result, our algorithm inherits the well-typedness and correctness properties of the declarative version.The statement of this theorem follows how the algorithm is used: if the generated constraints, equating the function type variable with the specification type, are satisfiable by the type assignment , instantiating the lifted expression with  and elaborating the macros results in a target expression that is valid under the declarative lifting relation: The proof of this theorem is available in the appendix.

IMPLEMENTATION AND EVALUATION
Our compilation pipeline takes as input a source program, including any OADTs, Ψ-structures, and macros (e.g., %lift), in the public fragment of Taypsi and privacy policies (i.e., security-type signatures) for all target functions.After typing the source program using a bidirectional type checker, our lifting pass generates secure versions of the specified functions and their dependencies, using Z3 [de Moura and Bjørner 2008] as its constraint solver.The resulting Taypsi functions are translated into Oil [Ye and Delaware 2023], an ML-style functional language equipped with oblivious arrays and secure array operations: OADTs are converted to serialized versions which are stored in secure arrays, and all oblivious operations are translated into secure array operations.After applying some optimizations, our pipeline outputs an OCaml library providing secure implementations of all the specified functions, including section and retraction functions for encrypting private data and decrypting the results of a joint computation.After linking this library to a driver that provides the necessary cryptographic primitives (i.e., secure integer arithmetic), programmers can build secure MPC applications on top of this API.The following evaluation uses a driver implemented using the popular open-source EMP toolkit [Wang et al. 2016].
Optimizations.Our implementation of Taypsi implements three optimizations which further improve the performance of the programs it generates. 11The reshape guard optimization instruments reshape instances to first check if the public views of two private values are identical, omitting the reshape operation if so.The memoization optimization caches the sizes of the private representation of data in order to avoid recalculating this information, which is needed to create and slice oblivious arrays.The final, smart array optimization supports zero-cost array slicing and concatenation, and eliminates redundant operations over the serialized representation of oblivious data.One observation underlying this optimization is that evaluating a mux whose branches are encrypted versions of publicly-known values is unnecessary: for example.This situation frequently occurs in map-like functions, where the constructor used in each branch of a function is publicly known.Under the hood, the serialized encoding of the result of map uses a boolean tag to indicate which constructor was used to build it, i.e., Nil or Cons; this boolean is determined by the tag of the input list, e.g., mux [tag] [true] [false].Of course, the tag used in each branch is publicly known: map always returns Nil if the input list is empty, and returns a Cons otherwise.Thus, we can safely reuse the [tag] of the input list to label the result of map, for similar reasons as the previous example.The smart array optimization exploits this observation by marking when section functions are applied to public values instead of, for example, immediately evaluating B#s true to the encrypted value [true].Then, when performing a mux, the smart array first checks if both branches are "fake" private values, safely reducing the mux to its private condition if so, without actually performing any cryptographic operations.
Our evaluation considers the following research questions: RQ1 How does the performance of Taypsi's transformation-based approach compare to the dynamic enforcement strategy of Taype?RQ2 What is the compilation overhead of Taypsi's translation strategy?

Microbenchmark Performance
To answer RQ1, we have evaluated the performance of a set of microbenchmarks compiled with both Taypsi and Taype.Both approaches are equipped with optimizations that are unique to their enforcement strategies: Taypsi's reshape guard optimization is not applicable to Taype, and Taype features an early tape optimization that does not make sense for Taypsi.12Our evaluation also includes a version of Taype that implements Taypsi's smart array optimization (Taype-SA), in order to provide a comparison of the two approaches at their full potential.Our benchmarks are a superset of the benchmarks from Ye and Delaware [2023].Figure 18 presents the experimental results. 13These experiments fix the public views of private lists and trees to be their maximum length and maximum depth, respectively; the suffix of each benchmark name indicates the public view used.The benchmarks annotated with † simply traverse the data type in order to produce a primitive value, e.g., an integer; these include membership (elem), hamming distance (hamming), minimum euclidean distance (euclidean), dot product (dot_prod), secure index look up (nth) and computing the probability of an event given a probability tree diagram (prob).The programs generated by Taype, Taype-SA and Taypsi all exhibit similar performance on these benchmarks.The remaining benchmarks all construct structured data values, i.e., the sort of application on which Taypsi is expected to shine.In addition to standard list operations, the list benchmarks include insertion into a sorted list (insert) and insertion of a list of elements into a sorted list (insert_list) (both lists have public view 100).The tree examples include a filter function that removes all nodes (including any subtrees) greater than a given private integer (filter), swapping subtrees if the node matches a private integer (swap), computing a subtree reached following a list of "going left" and "going right" directions (path), insertion into a binary search tree (insert), replacing the leaves of a tree with a given tree (bind), and collecting all nodes smaller than a private integer into a list (collect).Dynamic policy enforcement either fails to finish within 5 minutes or exceeds an 8 GB memory bound on almost half of the last set of benchmarks, due to the exponential blowup discussed in Section 2. For those benchmarks that do finish, Taypsi's enforcement strategy results in a fraction of the total execution time compared to Taype.Compared to the version of Taype using smart arrays, Taypsi still performs comparably or better, although the gap is somewhat narrowed: functions like map do not suffer from exponential blowup, so these benchmarks benefit mostly from the smart array optimization.In summary, these results demonstrate that a static enforcement strategy performs considerably better than a dynamic one on many benchmarks, and works roughly as well on the remainder.

Impact of Optimization
To evaluate the performance impact of Taypsi's three optimizations, we conducted an ablation study on their effect.The results, shown in Figure 19, indicate that our smart array optimization is the most important, providing up to almost 800x speedup in the best case.As suggested by Figure 18, this optimization also helps significantly with the performance of Taype, although not enough to outweigh the exponential blowup innate in its dynamic approach.The other optimizations also improve performance, albeit not as significantly.As our memoization pass caches public views of arbitrary type, we have also conducted an ablation study for these list and tree examples using ADT public views instead, e.g., using Peano number to encode the maximum length of a list.In this study, we observe up to 9 times speed up in list examples, with minimal regression in tree examples.The full results of this study are included in the appendix.

Compilation Overhead
To measure the overhead of Taypsi's use of an external solver to resolve constraints, we have profiled the compilation of a set of larger programs drawn from Taype's benchmark suite, plus an additional secure dating application. 14The first two benchmark suites (List and Tree) in Figure 20 include all the microbenchmarks from previous section.The next benchmark, List (stress), consists of the same microbenchmarks as List with 5 additional list OADTs.The purpose of this synthetic suite is to examine the impact of the number of OADTs on the search space.The remaining benchmarks represent larger, more realistic applications which demonstrate the expressivity and usability of Taypsi.The last three columns of Figure 20 report the results of these experiments: total compilation time (Tot), time spent on constraint solving (Slv) and the number of solver queries (#Qu).The group of columns in the middle of the table describes features that can impact the performance of our constraint-based approach: the number of functions (#Fn) being translated, the number of atomic types (#Ty), and the total number of atomic types used in function types (#At).For example, the List benchmark features 7 atomic types: public and oblivious booleans, integers and lists, as well as an unsigned integer type (i.e.natural numbers).The number of atomic types in the function filter : list → Z → list is 3.In the worst case scenario, our constraint solving algorithm will explore every combination of types that are compatible with this signature, resulting in the constraints associated with filter being solved 2 * 2 * 2 = 8 times.Exactly how many compatible types the constraint solving algorithm explores depends on many factors: the user-specified policies, the complexity of the functions, the calls to other functions and so on.We chose these 3 metrics as a coarse approximation of the solution space.Our results show that the solver overhead is quite minimal for most benchmarks, and in general solving time per query is low thanks to our encoding of constraints in an efficiently decidable logic.

RELATED WORK
The problem of secure computation was first formally introduced by Yao [1982], who simultaneously proposed garbled circuits as a solution.Subsequently, a number of other solutions have been proposed [Evans et al. 2018;Hazay and Lindell 2010].Solutions categorized as multiparty computation are usually based on cryptographic protocols, e.g., secret-sharing [Beimel 2011;Goldreich et al. 1987;Maurer 2006].Outsourced computation is another type of secure computation that includes both cryptographic solutions, e.g., fully homomorphic encryption [Acar et al. 2018;Gentry 2009], and solutions based on virtualization [Barthe et al. 2014[Barthe et al. , 2019] ] or secure processors [Hoekstra 2015].
Taypsi features a security-type system [Sabelfeld and Myers 2003;Zdancewic 2002] based on the type system of  OADT .While most security-type systems tag types with labels classifying the sensitivity of data, our dependent type system tags kinds instead.The obliviousness guarantee provided by Theorem 3.1 is a form of noninterference [Goguen and Meseguer 1982] that generalizes 14 The full details of the additional case study can be found in the appendix.memory trace obliviousness (MTO) [Liu et al. 2013].MTO considers traces of memory accesses, while the traces in Theorem 3.1 include every intermediate program state under a small-step operational semantics.This reflects MPC's stronger threat model, in which all parties can observe the complete execution of a program, including each instruction executed.As a consequence, our type system also protects against timing channels, similar to other constant-time languages [Cauligi et al. 2019].
Numerous high-level programming languages for writing secure multiparty computation applications have been proposed [Hastings et al. 2019].Most prior languages either do not support structured data, or require all structural information to be public, e.g., Obliv-C [Zahur and Evans 2015] and ObliVM [Liu et al. 2015].To the best of our knowledge Taype is the only existing language for MPC applications that natively supports decoupling privacy policies from program logic.On the other hand, there are many aspects of MPC tackled by prior languages that we do not consider here.Wysteria and Wys * [Rastogi et al. 2014[Rastogi et al. , 2019]], for example, focus on mixed-mode computation which allows certain computation to be executed locally.Symphony [Sweet et al. 2023], a successor of Wysteria, supports first-class shares and first-class party sets for coordinating many parties, enabling more reactive applications.Darais et al. [2020] developed  obliv , a probabilistic functional language for oblivious computations that can be used to safely implement a variety of cryptography algorithms, including Oblivious RAM.
Several prior works have considered how to compile secure programs into more efficient secure versions.Viaduct [Acay et al. 2024[Acay et al. , 2021] ] is a compiler that transforms high-level programs into secure distributed versions by intelligently selecting an efficient combination of protocols for subcomputations.The HyCC toolchain [Büscher et al. 2018] similarly transforms a C program into a version that combines different MPC protocols to optimize performance.The HACCLE toolchain [Bao et al. 2021] uses staging to generate efficient garbled circuits from a high-level language.Compiler techniques, e.g., vectorization, have been studied for optimizing fully homomorphic encryption (FHE) applications [Cowan et al. 2021;Dathathri et al. 2020;Malik et al. 2023Malik et al. , 2021;;Viand et al. 2023].
Jeeves [Yang et al. 2012] and Taypsi have a shared goal of decoupling security policies from program logic.While they both employ a similar high-level strategy of relying on the language to automatically enforce policies, their different settings result in very different solutions.In Jeeves' programming model, each piece of data is equipped with a pair of high-and low-level views: a username, for example, may have a high confidentiality view of "Alice", but a low view of "Anonymous".The language then uses the view stipulated by the privacy policy and current execution context, ensuring that information is only visible to observers with the proper authority.In the MPC setting, however, no party is allowed to observe the private data of other parties.Thus, no party can view all the data necessary for the computation, making it impossible to compute a correct result by simply replacing data with some predetermined value, like "Anonymous".

CONCLUSION
Secure multiparty computation allows joint computation over private data from multiple parties, while keeping that data secure.Previous work has considered how to make languages for MPC more accessible by allowing privacy requirements to be decoupled from functionality, relying on dynamic enforcement of polices.Unfortunately, the resulting overhead of this strategy made it difficult to scale applications manipulating structured data.This work presents Taypsi, a policyagnostic language for oblivious computation that transforms programs to instead statically enforce a user-provided privacy policy.The resulting programs are guaranteed to be both well-typed, and hence secure, and equivalent to the source program.Our experimental results show this strategy yields considerable performance improvements over prior approaches, while maintaining a clean separation between privacy and programmatic concerns.

DATA-AVAILABILITY STATEMENT
An artifact containing our implementation of Taypsi, its source code, and the source for all the benchmarks in our experiments with instructions is publicly available [Ye and Delaware 2024].The appendix is included in the auxiliary material.
Fig. 1.Filtering a list

Fig. 2 .
Fig. 2. Oblivious lists with maximum and exact length public views

Fig. 18 .
Fig.18.Running times for each benchmark in milliseconds.The Taypsi column also reports the percentage of running time relative to Taype and Taype-SA.A failed entry indicates the benchmark either timed out after 5 minutes or exceeded the memory bound of 8 GB.List and tree benchmarks appear above and below the double line, respectively.

Fig. 19 .
Fig. 19.Impact of turning off the smart array (No Smart Array), reshape guard (No Reshape Guard), and public view memoization (No Memoization) optimizations.Each column presents running time in milliseconds and the slowdown relative to that of the fully optimized version reported in Figure 18.