HACCLE: An Ecosystem for Building Secure Multi-Party Computations
Yuyan Bao, Kirshanthan Sundararajah, Raghav Malik, Qianchuan Ye, Christopher Wagner, Nouraldin Jaber, Fei Wang, Mohammad Hassan Ameri, Donghang Lu, Alexander Seto, Benjamin Delaware, Roopsha Samanta, Aniket Kate, Christina Garman, Jeremiah Blocki, Pierre-David Letourneau, Benoit Meister, Jonathan Springer, Tiark Rompf, Milind Kulkarni
HHACCLE: An Ecosystem for Building Secure Multi-Party Computations
Yuyan Bao
Purdue University
Kirshanthan Sundararajah
Purdue University
Raghav Malik
Purdue University
Qianchuan Ye
Purdue University
Christopher Wagner
Purdue University
Fei Wang
Purdue University
Mohammad Hassan Ameri
Purdue University
Donghang Lu
Purdue University
Alexander Seto
Purdue University
Benjamin Delaware
Purdue University
Roopsha Samanta
Purdue University
Aniket Kate
Purdue University
Christina Garman
Purdue University
Jeremiah Blocki
Purdue University
Pierre-David Letourneau
Reservoir Labs
Benoit Meister
Reservoir Labs
Jonathan Springer
Reservoir Labs
Tiark Rompf
Purdue University
Milind Kulkarni
Purdue University
Abstract
Cryptographic techniques have the potential to enable dis-trusting parties to collaborate in fundamentally new ways, buttheir practical implementation poses numerous challenges. Animportant class of such cryptographic techniques is knownas secure multi-party computation (MPC). Deploying secureMPC protocols in realistic scenarios requires extensive knowl-edge spanning multiple areas of cryptography and systemseven for seemingly simple applications. And while the steps toarrive at a solution for a particular application are pedestrian,it remains difficult to make the implementation efficient, andcumbersome to apply those same steps to a slightly differentapplication from scratch. Hence, it is an important problemto design an ecosystem for building secure MPC applica-tions with minimum effort and using techniques accessible tonon-experts in cryptography.In an effort to provide such an ecosystem for building se-cure MPC applications using higher degrees of automation,we present the HACCLE (High Assurance CompositionalCryptography: Languages and Environments) toolchain. TheHACCLE toolchain contains an embedded domain-specificlanguage (Harpoon) for software developers without crypto-graphic expertise to write MPC-based programs. Harpoonprograms are compiled into acyclic circuits represented inHACCLE’s Intermediate Representation (HIR) that serves asan abstraction for implementing a computation using differentcryptographic protocols such as secret sharing, homomorphicencryption, or garbled circuits. Implementations of differentcryptographic protocols serve as different backends of ourtoolchain. The extensible design of HIR allows cryptographicexperts to plug in new primitives and protocols to realizecomputations. We have implemented HACCLE, and used it to programinteresting algorithms and applications (e.g., secure auction,matrix-vector multiplication, and merge sort). We show thatthe performance is improved by using our optimization strate-gies and heuristics.
Secure multi-party computation (MPC) enables a group ofdistrusting parties to jointly perform computations withoutrevealing any participant’s private data that they do not wishto share with others. It has broad practical applications, e.g.,Yao’s millionaires problem [42], secure auctions [4], voting,privacy-preserving network security monitoring [6], privacy-preserving genomics [22, 41], private stable matching [15],ad conversion [23], spam filtering on encrypted email [19]and privacy-preserving machine learning [13]. Secure MPCapplications are generally realized as circuits communicatinginformation – both private and public – among parties.Although MPC techniques and protocols have seen muchsuccess in the cryptography community, it is still challeng-ing to build practical MPC applications by utilizing thesetechniques. Executing cryptographic protocols is notoriouslyslow, due to the encryption and communication overhead. Thelargest benchmark reported in Fairplay [28] – a secure two-party computation system – was finding the median of twosorted input arrays containing ten 16-bit numbers from eachparty. Running the benchmark required execution of 4383gates, and took over 7 seconds on a local area network. Whileimproving computing capabilities and network bandwidth,implementation techniques can contribute to 3-4 orders ofmagnitude improvements [17]. These techniques include op-1 a r X i v : . [ c s . P L ] S e p imizations that reduce the number of gates and the depth ofa circuit and reduce the computational costs of executing acryptographic protocol. However, such optimizations do notexist in general-purpose compiler frameworks.While several MPC frameworks have been proposed [3,7, 12, 14, 21, 24, 26, 27, 30, 31, 39, 40, 43–45], they either pro-vide low-level cryptographic primitives or provide high-levelabstractions like traditional programming languages. The low-level frameworks provide high degrees of customized protocolexecution, but the users are generally expected to be expertsin either one or both of cryptography and optimizing circuits.These MPC frameworks provide little or no type safety to pre-vent semantic errors, and it is difficult to write applications in away that is portable across different protocols. The high-levelframeworks provide traditional programming abstractionsthat hide the data-oblivious nature of secure computationsfrom programmers. But these frameworks are tied to only oneor a few protocols and their compilation procedures – fromhigh-level abstractions to low-level primitives – are not easyto extend to perform application specific optimizations [43]. Contributions
The main intellectual contribution of thispaper is a toolchain for building secure MPC applicationscalled HACCLE (High Assurance Compositional Cryptogra-phy: Languages and Environments). Our framework containsan embedded domain-specific language (eDSL)
Harpoon fordesigning MPC-based applications. Allowing seamless con-struction of MPC-based applications by software developerswithout expertise in advanced cryptography is the main pur-pose of providing such a high-level programming language.A Harpoon program is compiled to an acyclic combinationalcircuit, which is described in a HACCLE Intermediate Rep-resentation (HIR). HIR exposes the essential data-obliviousnature of MPC, and allows cryptography experts to experi-ment with new primitives and protocols. Our framework alsoprovides a specialized backend for estimating the resource us-age ( e.g. compute time and memory space) prior to execution.This paper makes the following specific contributions: • HACCLE Toolchain : A compilation framework tobuild and execute MPC applications written in Harpoon –an embedded domain-specific language (eDSL) in Scala. • HACCLE Intermediate Representation (HIR) : Ex-tensible circuit-like intermediate representation tailoredto abstract cryptographic primitives used in MPC. • Optimization Strategies : Methods for optimizing theMPC application by specialization as it flows througheach stage of our HACCLE toolchain.The rest of the paper is organized as follows. Section 2provides background on cryptographic protocols involved insecure MPC and motivates the need for an ecosystem to buildMPC-based applications. We describe the key impedimentsfor building practical MPC applications with the example ofsecure auctions. Section 3 describes the HACCLE toolchain and associated workflow. Section 4 illustrates HIR, the keycomponent of our compiler. Section 5 describes the optimiza-tions implemented in our compiler toolchain. Section 6 dis-cusses our toolchain on three case studies in detail. Section 7summarizes related work and Section 8 concludes the paper.
As an example of secure MPC, consider online auctions. On-line auctions have great practical importance and differentmodels are widely used by eBay, Google AdWords and Face-book. In general, a secure online auction works as follows.Buyers place their sealed bids on items and the highest bid-der is chosen to buy the item. In this setup, parties are notpermitted to know others’ bids, unlike in an English auctionwhere the auctioneer starts with a minimum price and buyersopenly shout out their bids. Hence, conducting successfulsecret auctions in the absence of a trusted authority requirescryptographic techniques to preserve the secrecy of bids whileperforming necessary computations such as finding the high-est bidder. One of the significant use cases of secure auctionsis procurement via a competitive bidding process, where noparticipant trusts each other, including the auctioneer. Whilea trusted third party handling the auction may be acceptablewhen the items under auction have low value, this is generallya less desirable option in high-value and corruption-proneenvironments such as procurement for public constructioncontracts.There are many different types of auction policies studiedby economists and game theorists. An auction where the high-est bidder is chosen to buy the item by paying the highest bidis known as a first-price auction. A second-price or Vickreyauction [38] is a well-known auction policy where the highestbidder is chosen to buy the item by paying the second highestprice. Second-price auctions provide buyers with the incentiveto bid their true valuation and does not allow for price discov-ery ( i.e. , no ramping up prices). Hence, second-price auctionsare especially suitable for high-value low-trust environmentssuch as public procurements. Second-price auctions also ap-ply to settings where multiple items are auctioned and bidsmay have additional structure, such as if/then conditions, thatneed to be taken into account for comparison. Such settingsare described as generalized second-price auctions. Giventhat secrecy of the bids is preserved, the computations re-quired when a single item is auctioned are simpler than whenmultiple items are auctioned. Hence it is desirable both fromprogrammability and efficiency viewpoints that the onlineauction application is written once for the general case andgets automatically and correctly specialized for the desirednumber of items, number of bidders, comparison logic, etc.We will discuss an implementation in our HACCLE toolchainin more detail in Section 6.1.To continue with necessary background on cryptography,we first take a look at
Secret Sharing – a cryptographic tech-2ique that allows to perform computations on secret data.
Secret sharing
Secret sharing [35] is a cryptographic tech-nique that distributes a piece of secret data amongst a groupof parties and allows the secret to be reconstructed only whena sufficient portion of shares are combined. A ( t , n ) -secretsharing allows the secret s to be split into n shares. Any t − s , while any t sharesallow complete reconstruction of the secret s .The SPDZ [12] and HoneyBadgerMPC [27] frameworksserve as our secret sharing backends and provide Python-style programming environments for writing custom MPCprograms. These frameworks let developers express MPC pro-grams ( e.g. , second-price auction) as arithmetic expressions.Constructing the most efficient MPC programs is the majorchallenge faced by developers. First, developers constantlythink about the depth of the overall circuit results from theirarithmetic expressions ( e.g. , performing a linear reductionover a list of elements can be realized as a balanced tree re-duction with smaller depth). Second, developers must have agood understanding of the cost of every primitive operation( e.g. , logically similar usage of different comparison oper-ators may yield different costs). Challenges faced here aresignificantly different from writing an efficient program inthe traditional setting and can be successfully overcome by acompiler. Homomorphic Encryption
Cloud computing is a scenariowhere one’s privacy may be violated. In this scenario, oneparty wants to perform computations by outsourcing to an-other (possibly untrusted) party, e.g. , training machine learn-ing models on private data on a public cloud server. This canbe achieved by homomorphic encryption, another importantcryptographic primitive.
Homomorphic encryption enablesoperations on encrypted data without access to the secret key.The existing fully homomorphic encryption (FHE) schemesare lattice-based constructions, and their security mainly relieson hardness assumptions of Learning With Error (LWE) prob-lems. PALISADE [1], TFHE [10], and HElib [34] serve asour FHE backends. They all implement asymmetric protocolsthat use a pair of public and private keys for encryption anddecryption. TFHE implements a very fast gate-by-gate boot-strapping mechanism [8, 9]. The TFHE library allows to eval-uate an arbitrary Boolean circuit composed of binary gates,over encrypted data, without revealing any information. HE-lib implements the Brakerski-Gentry-Vaikuntanathan (BGV)scheme [5], along with many optimizations to make homomor-phic evaluation run faster, focusing mostly on effective useof the Smart-Vercauteren cipher text packing techniques [36]and the Gentry-Halevi-Smart optimizations [18]. The PAL-ISADE library supports the BGV, BFV, and CKKS schemesand a more secure variant of the TFHE scheme, includingbootstrapping. In cryptography, ciphertext and plaintext meanprivate information and public information respectively. Inthis paper, we may use those terms interchangeably.
Garbled Circuits
Yao’s Garbled circuits [42] is a two-partysecure computation scheme for Boolean circuits against semi-honest adversaries. The main idea is that for each of theBoolean gates, one party (the circuit generator) generatesa list of keys corresponding to all possible outputs, each en-crypted by two keys corresponding to the two inputs thatgenerates that output. This way, when the other party (theevaluator) reaches the gate, the evaluator should have twokeys corresponding to the values of the two inputs, whichmeans the evaluator will be able to correctly decrypt one ofthe values, and be able to continue on to the next gate. Obliv-C [43] is the library that we use to support Yao’s GarbledCircuits protocols.
System Model and Communication Model
There are twopopular system models for multi-party computation. TheMPC-as-a-service setting allows some parties to play therole of servers and provide MPC services to clients with pri-vate input. The other setting is where the parties runningthe MPC protocols are the participants who provide the in-put. The HACCLE toolchain does not force users to pickone specific setting; instead, the user should pick the propersetting and keep that setting in mind when developing pro-grams. Similarly, the HACCLE toolchain does not enforceany communication model. The parties/machines could befully connected, could form a star network structure, or couldbe any specified structure. As long as the network structure issupported by one of HACCLE’s backends, HACCLE shouldbe able to compile the programs.
Adversary Models
Moreover, building MPC applicationsrequires one to understand the security assumptions of anMPC library, e.g. , the adversary models. There are two majoradversary models: semi-honest and malicious. A semi-honest adversary follows the protocol, but tries to learn as much aspossible from the messages they receive from other parties.A malicious adversary has the same powers of a semi-honestone in analyzing the protocol execution. In addition to that, itmay also control, manipulate, or arbitrarily inject messages tothe network. In HACCLE, programmers are only required toprovide a model of choice and the toolchain will pick propersub-protocols to build up the MPC programs satisfying theadversary model described.
Offline Phase
The offline/online paradigm is applied bymany MPC protocols and frameworks, where the online phasemakes use of a buffer of preprocessed input-independent val-ues that are created during the offline phase. Due to the factthat these values are independent of the input of the program,the MPC framework can run the offline phase to prepare thembefore the online program begins. The online phase is thenthe actual phase where clients/users provide their input andget expected output; this online phase can gain a significantspeed-up with the help of the offline phase. A number ofpreprocessing values are required for doing multiplication3nd comparison. For example, each multiplication betweensecret shares consumes one random "Beaver’s triple", andeach comparison would costs some triples and random sharedbits. The volume of preprocessing data depends on the on-line phase, and, as such, it would be hard for programmerswith no security expertise to work out those requirements. InHACCLE, the programmer need not care about those secretparameters, but rather only describes the function computedby the program and the intended private information, and theHACCLE toolchain will parse the program and work out theproper settings for the offline phase.
Scala Program withHarpoon embeddedHarpoon programHIR (protocol-independent)
HIR (…)Backend Resource Estimates … Resource Estimation Graph Obliv-Ccode SPDZ codeHIR (garbled circuits) HIR (secret sharing)
Compiler stage
Input params
123 4
Figure 1: HACCLE Compilation Framework
In this section, we describe the flow of our HACCLE com-pilation framework. Figure 1 shows an overview of the com-pilation flow. The secure multiparty programs are written inScala and the core portion of the computation is written inHarpoon, an eDSL. In the very first stage of the compilationflow, the input program is staged to a complete Harpoon pro-gram that consists of an entry point for passing the inputsprovided by the parties, computations on those inputs andnecessary revealing of results to the parties. The Harpoonprogram is compiled to a protocol independent multi-levelintermediate representation, HACCLE Intermediate Repre-sentation (HIR). Computation represented by HIR is one bigacyclic circuit. Section 4 illustrates attributes of HIR withexamples. The program represented in HIR is further loweredto include protocol specific instructions. Finally, from the low-level HIR program code generation happens for respectiveprotocol specific backends. Resource estimation is one of thespecial note-worthy backends that instead of executing thecomputation, it estimates the amount of resources required to realize the computation using a specific protocol. The follow-ing subsections illustrates the stages of our toolchain fromwriting the MPC application as a program to executing itusing different cryptographic protocols.
A programmer starts by providing a Scala program that em-beds a secure computation. The secure computation is writtenin
Harpoon , our language for expressing multiparty computa-tions (Section 4.2). The Scala program runs at client locations,and is responsible for processing input, setting up communi-cation channels, etc. The Harpoon program actually performsthe secure computation. This secure computation is writtenparametrically: effectively, a Harpoon program is a functionthat accepts the number of parties and their inputs as parame-ters.
Stage 1
The first stage of compilation transforms the Scala+ Harpoon program into a pure Harpoon program. In otherwords, executing the Scala program stages away the non-Harpoon portions of the code: local input files are read andrepresented in memory, connections are set up to the relevantservers.What is left after the stage 1 compilation is a Harpoonprogram that represents just the secure computation that mustbe performed . This program will eventually be transformedinto a circuit that performs the desired secure processing.However, the secure computation is not ready for executionyet. Any publicly known information about the inputs ( e.g. ,the bitwidths, or a maximum number input size) has not yetbeen incorporated into the circuit, and, of course, the inputvalues are not yet known.Crucially, a Harpoon program can have loops and recursivefunctions, but a programmer must provide upper bounds onthe running time of those constructs that are derivable frompublicly-known information. Note that a common piece ofpublicly available information about the input that may beused to define the bounds is the bitwidth of the input; but anyother information about the input that is publicly known to allparticipants may also be used to generate the HIR program.The key typing guarantee that Harpoon provides is thatprivate data will not leak via public channels.An important note here is that each Harpoon program repre-sents a single secure computation that will compile to a singlecircuit. Hence, the Harpoon program must compile down to acircuit whose size is determined only by the publicly availableinformation about the inputs (to avoid leaking information).In many applications, there are multiple secure computationsthat must occur ( e.g. , in database application, there may bemultiple queries; each query represents a different secure com-putation). Here, we leverage the blurred distinction between4ompile time and runtime. Generating a Harpoon programhappens at what programmers traditionally consider run time :the Scala program is actually running to produce the Harpoonprogram. Hence, the
Scala program can include a loop overthe set of queries, and for each query, a new Harpoon programwill be produced and compiled and executed. The abstrac-tion in Scala has no runtime overhead for the generated codesince it is executed at the Scala runtime, offering the so-called“abstraction without regret” (Section 4.1).
Stage 2
The next step is to generate an abstract circuit : theHarpoon program is compiled down to the HACCLE Interme-diate Representation (HIR, Section 4), which is, essentially, afunctional, bounded-size representation of the program. Here,the bounds information in the Harpoon program is used to un-roll loops and inline recursive functions, leading to a function-and loop-free representation of the program. Because thereare no longer any looping constructs, the HIR program is nat-urally single-assignment and functional. HIR supports scalarsand arrays of bits, integers, and floats. An HIR program atthis stage is still independent of a particular protocol. Hence,it is essentially a direct translation of the Harpoon programinto HIR without considering the abilities of any particularbackend.The key typing guarantee that HIR provides at this levelis that the appropriate HIR operation will be used based onwhether inputs to an operation are private or public. For exam-ple, indexing into an array of private data using a public indexproduces different code (essentially, copying a particular wire)from indexing into an array of private data using a private in-dex (essentially, using a selector construct to securely choosethe appropriate element).
Stage 3
The next compilation stage specializes an HIRcircuit to a specific protocol. The choice of protocol is de-termined by the security specification file. Here, we do notchange the language representation of the program—the re-sulting program is still in HIR. Instead, this stage rewritesHIR to limit the use of HIR operations to those supported bya particular backend. For example, a backend that only sup-ports Boolean operations requires translating all operationson integers and floating point into bit-level operations. Simi-larly, a backend that only supports operations on integers willrequire translating floating point operations into decomposedoperations on the component parts (mantissa and exponent).Here, HIR switches to the use of backend-specific type sys-tems that enforces the following property: a backend-specificHIR circuit that type checks will correctly enforce the re-quirements of that backend for security (e.g., ensuring thatthe set of sharers matches up when performing operations ina secret-sharing backend).
Stage 4
The final step of generating a circuit is specific tothe backend implementation the HACCLE framework is in-stantiated with. Here, the HIR circuit is translated to work with the particular backend (this is the key module interfaceprovided by our system). This may require translating thecircuit to a set of API calls (e.g., our TFHE backend), ortransliterating the circuit into a different programming lan-guage ( e.g. , translating to Obliv-C for the garbled-circuit back-end, or Scale-Mamba for the secret-sharing backend). Thebackend is configured based on the information in the securityspecification file.At this point, the circuit is in an executable form, and itwill execute, using the actual inputs from the various parties,to perform the desired secure computation.
Resource estimation is implemented as another backend. Here,protocol-specific HIR code, rather than being translated towork with a specific backend, will be translated into a graphrepresentation of the circuit. This graph representation isthen incorporated into a resource estimation program thattraverses this graph in various ways to compute differentresource estimates: total amount of work, critical path length,FHE levels, etc.
HACCLE toolchain uses lightweight modular staging (LMS)to support our towers of abstractions. Staging is a techniquefor building extensible, flexible DSLs by providing code gen-erators that successively lower higher-level abstractions tolower-level abstractions, and, ultimately, to executable code.Importantly, staging allows optimization to be performed atevery level of the lowering process. Hence, some optimiza-tions can be performed at high levels of abstraction ( e.g. , op-timization on plaintext computations (Section 5), while otheroptimizations can be performed at lower levels of abstrac-tion. As a result, abstraction penalties are minimized. Anotherbenefit to staging is that because the translation is written interms of generators, it is simple to add new abstractions atany given level.
Multi-Stage Programming (or staging) is the programminglanguage technique that executes programs in multiple stages.In Lightweight Modular Staging (LMS), higher order type(Rep[T]) labels the program fragments of the next stage. Inthe current stage, all Rep-typed variables (and computations)form computation graphs ( i.e. , LMS IR). Staging is similar toand more general than the construction of TensorFlow com-putation graphs using TensorFlow APIs, since it is designedto handle more programming language constructs such asfunctions and closures. The key benefit of staging is that thepresent-stage code can be written in a high-level style, yetgenerates future-stage code that is very low-level and effi-cient. Figure 2 illustrates an end-to-end compilation path in5ACCLE. The compiler takes a Scala program with Harpoonannotations, constructs a computation graph that expresses anabstract circuit. Given a backend specification, the compilerwill generate a target program for it. Currently, our compileris not able to automatically choose an appropriate backendand initialize all the parameters that are needed for it. Thus, abackend specification is needed. It is a file that contains a setof parameters that are needed for translating an abstract circuitto a concrete backend program. For testing and development,we currently hard-coded those information in the program.There are no technique difficulties to read the informationfrom a file.
Harpoon+Scala StagedComputationGraph BackendProgramTransform TransformLMSBackend Specification
Figure 2: Compilation in HACCLE
Generative Programming and Lightweight ModularStaging (LMS)
As mentioned previously, the HACCLEcompiler uses LMS for code generation due to its multi-staging capabilities. In LMS, a special type constructor
Rep[T] is used to denote a staged expression, which willcause an expression of type T to become part of the generatedprogram. For example, the expression Rep[SNum] denotes anencrypted integer. Given two
Rep[SNum] values a and b , eval-uating the expression a + b will generate code for a givenbackend. For the Helib backend, the generated code will be Ctxt r = a; r += b; , where
Ctxt is the type of a ciphertext in the Helib library. For the TFHE backend, the generatedcode will be:
LweSample* x5 =new_gate_bootstrapping_ciphertext_array(64, x2->params);fhe_add(x5, a, b, 64, bk); where
LweSample is the type of a cipher text in the TFHElibrary. As a TFHE program denotes a Boolean circuit, itdoes not provide arithmetic expressions and operations. Thecompiler expresses an integer as a bit-array of size 64. Thefunction fhe _ add is part of our HACCLE library of the TFHElibrary. HAccle Rich Representation for Program OperatiON (Har-poon) language is an expressive subset of Scala for writingMPC programs. It is an imperative and monomorphic lan-guage, featuring standard control flow operations: loops, func-tion calls, conditionals, and recursion. The language is de-signed to be expressive enough that programmers could easilywrite Harpoon code directly, while being constrained enoughto ensure that Harpoon programs can be implemented viatranslation to secure low-level computations. In practice, Har- poon serves as the top-level IR for the HACCLE pipeline, andis the language for end-user programs.Harpoon language is not only able to access all the Scala li-braries, but also provides a set of cryptographic data structures.For example,
HArray[T] is an encrypted array that allowsone to index on cipher texts. It also provides a set of securityannotations that are forms of syntactic metadata added to theScala source code. Annotations are read via reflection and areused to direct code generation. They are agnostic to the targetbackend, and are used by subsequent stages of the HACCLEpipeline.For example, the annotation sec is used to mark theprovider (also the owner) of the private data. Recursive func-tions and loops may be annotated with an expression, placingan upper bound on the number of recursive calls and iterations.This expression can reference the parameters of the function,allowing this bound to vary according to the context in whicha function is called. As an example, consider the signature of merge function: @bound(a.length + b.length) def merge(a: HArray[
Int ], b: HArray[
Int ]): Harray[
Int ] The upper bound on the number of recursive calls is the sumof the length of the two input arrays. Note that the semanticsof function calls in Harpoon is not impacted by this bound;rather it is used by subsequent stages of the pipeline to boundthe invocation of a recursive function call. See Section 4.4 fordetails.The annotated program is also equipped with a type systemwhich ensures that information about private data cannot beleaked; this provides the first-layer guarantees that the pro-grams can be successfully compiled by the later stages ofthe pipeline. Consider the statement println(a) , where a is annotated as private data. The compiler will report a typeerror, as encrypted data is not understandable or meaningfulto users. But the assignment @sec(alice) val r = a willnot cause a type error, because the annotation indicates thatthe variable r stores encrypted data. While the type systemat this stage does not make use of fine-grained ownershipinformation, this information will be passed down throughthe pipeline. HACCLE intermediate representation (HIR) serves as an in-terface between high-level programming language and cryp-tographic backends. HIR is a domain-specific intermediatelanguage, and gains benefits from LMS to support towersof abstractions. It encompasses all the primitive operationswhich we have supported so far, e.g., encryption, decryption,sharing, and combining.
Multi-level IR
Different backends may support differentsets of operations in HIR—no backend is “complete” in thatthere is a direct implementation of each HIR operation in that6ackend. For example, the TFHE backend provides a com-plete set of logical operations, but does not support arithmeticoperations. In contrast, other backends may support arithmeticoperations but not Boolean operations. The compiler’s job isto rewrite
HIR circuits to be compatible with backends.As shown in Figure 3, HIR is a multi-level IR. The compilercan thus use rewrites to target the subset of operations that agiven backend supports. For example, arithmetic operations(adds, multiplies) can be rewritten into bit-level implemen-tations (as, e.g., ripple-carry adders, or bit-level implementa-tions), or Boolean operations can be represented as arithmeticoperations that happen to operate over Z . We are developinga set of these rewrite rules for our various backends (and,indeed, rely on exactly this type of rewrite to support floatingpoint operations).A key task for integrating a new backend is identifying whatset of HIR operations that module supports, hence directingthe compiler to perform appropriate rewrites. Notably, if thecompiler cannot rewrite an HIR circuit to target the set ofoperations a backend supports, this will manifest as a typeerror, providing feedback to the user. Float, FloatArrayUNum, UNumArray Num, NumArrayBit, BitArray
Figure 3: Example of multi-level HIRs
For example, in the scenario of using a FHE scheme, aninteger is represented by a
Num node in HIR shown below,where the field provider is an abstraction of the party whoprovides the value, and the field value is an abstraction ofthe encrypted value. case class
Num( val provider: Set[Rep[SOwner]], // who provides it val value: Rep[SNum] // encrypted value)
In this case, a variable declaration statement in Harpoon, i.e., @sec(alic) val x = 5; , is transformed to val o = newOwner(alice); val x = Num(o, 5); in HIR.In the scenario of using a secret sharing scheme, an integeris represented by a
ShareNum node in HIR shown below. Itdescribes a general secret sharing protocol. The provider isthe one who contributes the value that are shared among aset of players with threshold . And the set of observers are allowed to access the value once it gets combined. case class
ShareNum( val provider: Set[Rep[SOwner]], // who provides it val players: Set[Rep[SOwner]], // players val observers: Set[Rep[SOwner]], // who observes it val threshold:
Int , // threshold val value: Rep[SShareNum] // shares)
In addition, the HIR provides libraries for implementingsecure computations. Those libraries are not supported ingeneral-purpose compilers, but are essential to build inter-esting multi-party applications with security guarantees. Forexample, the HIR provides array indexing on cipher texts. Letus assume that arr is an HIR array. The following lists itsoperations: • arr(i) - array index, where i is either a plaintext or acipher text. • arr.update(i, v) - update the i th element with the value v , where i is either a plaintext or a cipher text. • arr.slice(i, j) - array slicing from the i th element untilthe j the element, where i and j are plaintext. • arr.length - the length of the arrayThe way these array operations with secure indices arecurrently implemented is through, essentially, a naive ORAM:to index into an array with a cipher text index, the compilerwill generate a circuit that accesses every array element, thenuses a secure selector (multiplexer) to output the desired arrayelement. This is equivalent to a set of if-then-elses to choosethe desired array element, except with log depth instead oflinear depth. Writing to an array element with a cipher textindex is the equivalent of an array copy, where each elementof the new array performs a check for whether the old elementof the array should be copied, or the “update” value shouldbe copied.As implementation details of cryptographic backends areabstracted away from the HIR, our framework can be easilyextended to support more advanced cryptographic backends,for example, a backend with oblivious RAM (ORAM). Here,we would leverage HIR’s ability to provide backend-specificrewrite rules, and would directly rewrite array operations toORAM operations Type system.
HIR also abstracts away the implementationdetails of cryptographic primitives and protocols. For exam-ple, an addition operation does not specify how a secure ad-dition is achieved as different protocols perform in differentways. But the type rules provide an approximation of dataaccess policy that specifies how data is provided, accessed,and shared. For example, an addition operation on two sharednumbers is only allowed on the same set of players with thesame threshold. And the result is provided by either one ofits operand’s providers with the same set of players with thesame threshold, and is allowed to be accessed by either of onethe operands’ observers. def +(x: ShareNum, y: ShareNum) = { assert (x.players.equals(y.players)) assert (x.threshold == y.threshold)ShareNum(x.provider | y.provider, players,x.observers | y.observers, threshold, value.+(y.value))}
Given a cryptographic backend, HIR is further rewritten toa program with the corresponding cryptographic semantics.7nd the HIR type system is refined to provide more preciseinformation on data access policy. For example, the type ruleof the addition operation is refined to the following whenusing the additive secret sharing scheme. def +(x: ShareNum, y: ShareNum) = { assert (x.players.equals(y.players)) assert (x.players.size == x.threshold) assert (x.threshold == y.threshold)ShareNum(x.provider | y.provider, players,x.observers & y.observers, threshold, value.+(y.value))}
The type rule checks it is a n -out-of- n secret sharing scheme,i.e., x.players.size == x.threshold . This type rule pro-vides a stronger security guarantee. The refined type systemguarantees that the generated HIR program complies withthe semantics of the backend. For example, an FHE targetprogram is not transformed into a program that may invokesecret sharing primitives. In addition to bridging the semantic gap between a high anda low-level language, our compiler also bridges the semanticgap of obliviousness. Boolean circuits and Arithmetic circuitsare oblivious in the sense that they perform the same sequenceof operations independently of the input. A program withoutprivacy concern change their flow of control according tothe input: they execute statements conditionally, loop for avariable number of steps, etc. The following transformationsmay seem quite inefficient at first sight, but they are absolutelyrequired in order to maintain obliviousness.
Encrypted Array Indexing
Indexing an array with a ci-pher text translates to a multiplexer circuit which takes everyelement of the array as an input and outputs the element inthe position that matches the indexing cipher text. This multi-plexer circuit consists integer comparators and selectors.
Conditional Execution
After a type-checked Harpoon pro-gram is translated to the program written in HIR, there aretwo types of if-constructs allowed. One is the standard if-statement where its condition depends on plaintext compar-ison, and the two branches consist of a sequence of state-ments that may have side effects. The other has the form z= if (b){x} else {y} , where the value of b is the result ofprivate comparisons. Obliviousness is effectively guaranteedby executing both the consequent and alternative branches. Ifthe backend is a Boolean circuit, this if-construct is furtherimplemented as a selector. If the backend is an arithmetic cir-cuit, the program is transformed to z = b * x + (1 - b)* y .Consider the following Harpoon code snippet. The variable arr stores a sequence of shared numbers, the comparisonresult of max < arr(i) is a shared number as well. So theprogram if (max < arr(i)) { max = arr(i) } is transformed to val b = max < arr(i)max = b * arr(i) + (1 - b) * max Note that such a program transformation is non-trivial for aprogram allowing shared mutable states, due to the complex-ity of identifying potential side effects for the two branches.Currently, an if-statement will be transformed if the side ef-fects of its two branches can be syntactically detected.
Loops and Recursion
All function calls are treated asmacros and simply inlined where they are called. All loops aresimply unfolded as the number of iterations is a compile-timeconstant). Figure 4 demonstrates our treatment for recursivecalls, where the obliviousness is achieved by using the extraplaintext parameter d in the right side of the figure. In thistransformed program, the value d is initialized by the Harpoonannotation and decreases with each iteration. That makes surethe recursive calls only iterates d times. Cryptographic Backends
In the context of building cir-cuits, LMS is used to specialized a circuit with respect to atarget backend. The outcome of such a programmatic special-ization is a compiled target of the circuit.The code generator transforms an abstract circuit into aconcrete one for a given backend. For example, the followingadder expressed by HIR is specialized to the Boolean circuitshown in Figure 12 and arithmetic circuit shown in Figure 13in the Appendix 9. val o1 = Owner();output((Num(o1, 10).+(Num(o1, 5))).eval(o1))
The essence of multi-stage programming is to generate effi-cient programs using high-level constructs without runtimepenalty [37]. The example in Figure 5 a shows a code snippetthat generates a for loop. Note that the if condition is com-posed of a plaintext Boolean type, so this code is executed atcode generation time as shown Figure 5 b. Resource Estimation
Resource Estimation acts as anotherbackend for the compiler, which generates a graphical repre-sentation of the HIR nodes circuit and feeds it to a generic"Evaluator" that walks the graph and accumulates some in-formation at each node. The estimator is parameterized onthe given resource model, which is an assignment of costs toeach node and edge type in the graph, as well as a depth ofeach edge.At the most basic level, the resource estimation frameworkexpects an enumeration of the abstract "gates" for the particu-lar cost model, as well as a description of how each HIR nodetype affects these gates and depths. The gates are what thetotal cost is tallied in terms of. As an example, a cost modelfor one of the secret sharing backends might have round com-plexity and communication complexity as gates, whereas a8 cala Program val a = 5; val b = 15; def gcd(x:
Int , y:
Int ): Int = { if (x == 0) y else gcd(y % x, x)} println (gcd(a, b)) Harpoon Program @sec(alice) val a = 5;@sec(alice) val b = 15;@bound(5) def gcd(x:
Int @sec, y:
Int @sec):
Int @sec = { if (x == 0) y else gcd(y % x, x)}@reveal(alice) val r = gcd(a, b) println (r) HIR Program val o = Owner(alice) val a = Num(o, 5); val b = Num(o, 15);topFun gcd(d: Rep[
Int ], x: Rep[SNum], y: Rep[SNum]): Rep[SNum] = { if (d == 0) y elseif (x == 0) gcd(d-1, x, y) else gcd(d - 1, y % x, x)} val r = Num(o, gcd(5, a.value, b.value)).eval(o) println (r) Figure 4: An example: Computing the Greatest Common Divisor(GCD) of two numbers. The one on the left is the Scala textbook implementation. The one inthe middle is the Harpoon program. The annotations mean that a user, alice , owns the data a and b , and wants to compute the GCD of them. A different partyreceives and stores the data in an encrypted form, performs computation on the encrypted data, and provides the encrypted results to alice . Note that the bound annotation indicates maximum number of times the function gets called. The one on right is the HIR program translated from the Harpoon program in the middle.The translated gcd function has one extra parameter d initialized by 5, and decreases with each iteration. (a) HIR code example: val sum = topFun((x: Rep[SNumArray], len: Rep[ Int ]) => { var n = 0; val b = true var res = Num(o1, 0).value while (n < len) { if (b) {res = res + x(n)}n += 1}res}) (b) Generated C code of TFHE backend: const LweSample* x3( const
LweSample* x4, int x5){ int x6 = 0; const
LweSample* x7 = num_init(0, 64 ,x2); while (x6 < x5) {x7 = add(x7, array_index(x4, x6, 64, x0), 64, x0);x6 = x6 + 1;} return x7;}
Figure 5: (a) HIR code example. (b) Generated C code of TFHE backend. circuit backend might literally have
AND , OR , and NOT asits gates. The evaluator then walks the HIR graph and accu-mulates the abstract gate costs produced by each node, andtracks the maximum total depth encountered for critical pathestimation. In the case of a secret sharing scheme, walkingthe graph will (potentially) increment round and communica-tion complexity as new computation nodes are encountered,whereas a circuit backend will increment gate costs. Thesegate costs are then instantiated with specific costs (in termsof lower-level operations) based on the resource estimatesdetermined by the cryptographic experts.This framework can also be easily extended to evaluatecosts that do not follow this simple model. All that is neededis a definition for some data structure to be accumulated ateach HIR node (by default, this consists of a mapping fromgate type to gate count and an integer for current depth), anda transfer function for how each node type accumulates intothis structure.Each cost model can also be parameterized on some valueswhich are configurable but known at compile time (such as integer bitwidth), the prime modulus which can be determinedfrom the security specifications, and specific edge costs.The ability to estimate the cost of a program in multiplebackends is useful when selecting a target, as well. Programsare sometimes inherently better suited to execution in oneprotocol instead of another. If the cost for these two protocolsare comparable (e.g. both models measure “communicationcomplexity”), then we can generate a resource estimate forthe program in each available backend to determine whichbackend would be best suited for execution.
Our compiler contains a sequence of optimization transfor-mations. For example, the peephole optimization, e.g. , x &&true is optimized to just x ; common subexpression elim-ination (CSE), e.g. , x = a + b; y = (a + b) * c is op-timized to x = a + b; y = x * c ; constant folding, anddead code elimination (DCE). In addition to those optimiza-tions that a general purpose compiler has, we identify sev-eral optimizations involving encrypted values. Given an in-memory representation of a Boolean circuit or an arithmeticcircuit, the goal of the optimization is to reduce the depth ofthe circuit and the number of costly gates. The multiplicative depth of circuits is the main practical limi-tation in performing computations over encrypted data. Weidentify the opportunity when one of the operands of a mul-tiplication is plain text. For example, the multiplication iseliminated if the plain text is 0 or 1. Moreover, consider thecase of calculating pow ( x , n ) , where x is an encrypted data.The compiler divides the computation into subproblems ofsize n / pow ( , ) , where 2 is private. TheHarpoon program is transformed to its corresponding HIR9rogram, and is generated to the TFHE program, where thefunction unum_mul multiplies two 64-bit encrypted integers.Our generate program only needs O ( log n ) multiplies. Al-though this optimization is simple, but, as you can see fromthis example, has a dramatic impact on performance. Harpoon Program: @sec ( a l i c e ) val a = 2;scala . math .pow(2 , 8 ) ; HIR Program: val o1 = Owner ( ) ;UNum(o1 , 2 ) .pow(8)Generated TFHE program: const
LweSample* x3 = unum_init(2, 64, x2);LweSample* x4 = unum_mul(x3, x3, 64, x0);LweSample* x5 = unum_mul(x4, x4, 64, x0); return unum_mul(x5, x5, 64, x0);
Figure 6: Example of computing pow ( , ) , where 2 is private. The effect of the optimization is clearly demonstrated inFigure 7, which shows the graphs of the generated circuitsafter and before optimization respectively. The left is a depth-3 circuit, and contains three multiply gates. The right is adepth-7 circuit, and contains 7 multiply gates.The generated graphs show an abstract model of execu-tion cost where each operation is treated as atomic; How-ever, the resource estimation framework can be specialized toparticular backends by providing the corresponding modelsof execution cost (in terms of communication complexity,number of logic gates, etc.) These backend-specific resourceestimates can be used to compare different optimization strate-gies and intelligently select the appropriate one based on theexecution semantics of the targeted backend. As mentionedin Section 4.5, in addition to selecting the right set of opti-mizations to use, these specialized estimates even let us pickthe most optimal backend to target.
The private comparison is a major bottleneck in MPC pro-tocols due to their inherent non-arithmetic structure [11]. Itprivately determines whether a < b , where a and b are con-sidered as private information. Operators involve private com-parisons include < , <= , > , >= , == and != . One operator may beencoded by two or more other operators, e.g., a != b is equiv-alent to a > b || a < b . However, the two expressions mayhave different costs. We identify some implementation heuris-tics that help us generate efficient programs.We take the HoneyBadgerMPC library as an exampleto demonstrate our implementation technique. HoneyBad-gerMPC provides two comparison protocols: LessThan andEquality. As their names indicated, they allow us to evaluate a < b and a == b on shared values. The result is 1 if it istrue, otherwise is 0. What’s more, the result is also secretshared due to the fact that the result of comparison leaks in-formation about input. Building an MPC compiler requiresus to implement other operators in terms of these two. Taking x0: Number(64, 2) (13)x1: Multiply(x0, x0) (16)x2: Multiply(x1, x0) (16)x3: Multiply(x2, x0) (16)x4: Multiply(x3, x0) (16)x5: Multiply(x4, x0) (16)x6: Multiply(x5, x0) (16)x7: Multiply(x6, x0) (16) x0: Number(64, 2) (13)x1: Multiply(x0, x0) (16)x2: Multiply(x1, x1) (16)x3: Multiply(x2, x2) (16) Figure 7: Graphs of computing pow ( , ) : before (left) and after applyingoptimizations (right). Encoding a >= b
Execution Time (b < a) + (a == b)
Table 1: Execution time of evaluating a >= b for 100 times, where a and b are randomly generated number ranging from 1 to 100. the operator >= as an example. A naive and intuitive imple-mentation is encoding a >= b as (b < a) + (a == b) . Analternative implementation is encoding a >= b as . Our abstract resource estimator will generate one LEQgate, one ADD gate and one EQUAL gate for the first imple-mentation, and one SUB gate and LEQ gate for the secondimplementation. As mentioned in Section 4.5, each backendhas its own resource model. In the HoneyBadgerMPC re-source model, the costs of addition and subtraction are trivialsince they require no communication, and the multiplicationtakes one rounds and one multicast to finish. When it comesto comparison, the round complexity of comparison is seventimes more than the cost of multiplication [32], the communi-cation cost is even more expensive. Also, the cost of equalitycheck is higher than less than operation. Thus, we believe thesecond implementation is optimal due to the reduced numberof comparisons. This is how we experiment with optimizedimplementations with the help of our resource estimators.To verify the above observation, we perform a set of privatecomparisons in a HoneyBadgerMPC program (on a singleUbuntu 18.04 LTS machine of 16 GB RAM with 8 Intel Corei7 Processors). Our tests execute 100 times of the greateror equal comparisons on two randomly generated numbers.Table 1 compares the running time of the two encoding. We have implemented our HACCLE framework in Scala. Toassess our framework, we present three experiments. The first10ocuses on Harpoon and HIR programming languages; thesecond focuses on the optimization of scalar multiplications;the third focuses on our support of indexing to arrays at secretindices. For simplicity, the test program uses plaintext valuesinstead of obtaining them at runtime. We conducted our ex-periments on a single Ubuntu 18.04 LTS machine of 16 GBRAM with 8 Intel Core i7 Processors.
Recall the discussion of the practical importance of secureauctions in Section 2. In this experiment, we implement asecond-price auction that is designed to give bidders confi-dence to bid their best price without overpaying. In a second-price auction, bidders submit sealed bids. The bidder whosubmits the highest bid is awarded the object and pays theamount of the second-highest bid.Figure 8 and Figure 9 show the code snippets written inHarpoon and HIR respectively, where the elements in array bidders denote bidder’s identities, and the elements in array bid denote their bids. The implementation uses four variables( fst , snd , ifst and isnd ) to store the values of the first andsecond highest bids and their holders respectively. As you cansee from Figure 8, writing the Harpoon implementation doesnot need developers to have security concerns, or to have themindset of building circuits. Programmers can program func-tionally or imperatively, thanks to the expressivity of Scala.The HIR implementation in Figure 9 may look more tediousbut exposes some nature of building secure computations (cir-cuits). For example, the use of those if-constructs remindsadvanced users that they are using selectors. This allows ad-vanced users to experiment with advanced optimizations thatmay not be achieved by automatic program transformations.As a key example, observe that the linear sequence of oper-ations in Figures 8 and 9 will result in a suboptimal circuit.However, rewriting the HIR code in Figure 9 in a functionalstyle as (bids zip bidders).map(..).reduce(..) al-lows us to abstract over the reduction pattern and substitutethe linear sequence with a tree reduction pattern that yields acircuit of logarithmic depth that allows efficient parallel com-putation. Using known techniques for extracting functionaldependencies from imperative loops [16, 33], this transfor-mation could be automated and applied automatically to forloops in Harpoon.Figure 14 and Figure 15 show the generated SPDZ andHoneyBadgerMPC program respectively. As the loops areunrolled during code generation, we only show the gener-ated program for 3 bidders. For simplicity, the program usesplaintext values as input instead of obtaining them at runtime.For testing and development, the HoneyBadgerMPC programruns in a single-process simulated network. It contains linesof code dealing with network connections and synchroniza-tions. Those are concerns that Harpoon and HIR developersdo not have to worry about. var ifst = bidders(0) var isnd = bidders(0) var fst = bids(0) var snd = bids(0) if (bids(0) < bids(1)) {ifst = bidders(1)fst = bids(1)} else {isnd = bidders(1)snd = bids(1)} for (i <- 2 until bids.length) { if (fst < bids(i)) {isnd = ifstsnd = fstifst = bidders(i)fst = bids(i)} else if (snd < bids(i)) {isnd = bidders(i)snd = bids(i)}}(ifst, snd) Figure 8: Harpoon code snippet performing a second-price auction, where bidders and bids are stored in an array of private values. var ifst = bidders(0) var isnd = bidders(0) var fst = bids(0) var snd = bids(0) val b = bids(0) < bids(1)ifst = if (b) bidder(1) else ifstfst = if (b) bids(1) else fstisnd = if (b) isnd else bidders(1)snd = if (b) snd else bids(1) for (i <- 2 until bids.length){ val b0 = fst < bids(i) val b1 = snd < bids(i)isnd = if (b0) ifst else if (b1) bidders(i) else isndsnd = if (b0) fst else if (b1) bids(i) else sndifst = if (b0) bidders(i) else ifstfst = if (b0) bids(i) else fst}(ist, snd) Figure 9: HIR code snippet performing a second-price auction, where bidders and bids are stored in an array of private values.
This experiment performs a set of secure matrix-vector mul-tiplication, where one party (the client) has an input matrix,and the other party (the server) has a vector. Figure 10 showsour test program that randomly generates a 10 ∗ N matrix(where N is range from 100 to 500), and multiplies with afixed vector [ , , , , , , , , , ] . The goal isto show the effectiveness of our optimization discussed inSection 5.1. The HElib library serves as our testing backendthat implements the BGV homomorphic encryption schemeand effectively uses the Smart-Vercauteren cipher text pack-ing techniques [34]. Table 2 compares the running time of thegenerated HElib programs with and without optimizations.As N increases from 100 to 500, the speedup become moreobservable.As our compiler unrolls all the loops, the size of the gen-erated code is not small. Figure 16 shows the generated HE-lib program that computes computes matrix-vector multipli-cation of Array(Array(1, 2, 3), Array(4, 5, 6)) and
Array(1, 2, 1) .11 al rand = new scala.util.Random val start = 1000@sec(alice) val m = Array.fill(N)(Array.fill(10)(start + rand.nextInt(start + 1))) val v = Array(1, 399, 1, 413, 1, 587, 1, 354, 1, 444)m * v Figure 10: Test of Matrix-Vector Multiplications
N 100 200 300 400 500before 7.64s 20.69s 57.79s 140s 296safter 6.48s 15.21s 35.83s 82s 164s
Table 2: Execution time of the HElib programs that perform multiplicationsof a matrix of 10 ∗ N and a vector [ , , , , , , , , , ] beforeand after the optimization. This experiment implements MergeSort in HIR. It is inter-esting as the implementation involves array indexing andconditional executions, and an array lookup on a private indexis not supported by most languages [20]. The implementa-tion is written in HIR, not in Harpoon, as HIR exposes thelanguage features needed in writing secure computations.MergeSort is a divide and conquer algorithm. It recursivelydivides an input array into two halves and then merges thetwo sorted halves. Our implementation is shown in Figure 11.In the function mergesort , the variable r (at line 31) stores arecursion object initialized with the bound 10. The expression r.rec (at line 32) is the construct for defining a boundedrecursive function call. This allows one to explicitly specifythe bound of the defining recursive function. The NumArray is the type for arrays that allow private indexing. The twoparameters i and j are plaintext, which is important for un-rolling the recursive function at compile time. The function slice(i, j) returns a subarray from the i th element untilthe j th element, where i and j are plaintext integers. The if-statement at lines 35 to 40 is the standard one as its conditiondepends on a plain text value. The function merge is used formerging two halves. All the if-constructs appearing in thisfunction are oblivious as their conditions depend on ciphertext values. The loop at line 12 is bounded as the length ofan array is known at compile time. Figure 18, Figure 19 andFigure 20 show the generated TFHE program, where loopsand recursions are all unrolled. There have been many MPC frameworks proposed in recentyears and several of them are already integrated into HACCLE.We list the prominent MPC frameworks as follows.SCALE-MAMBA [24] is an existing MPC framework thatis closest to HACCLE. We utilize it as one of our crypto-graphic backends to implement secret sharing based protocolsand FHE based protocols. It is a combination of a compilerand a runtime environment where optimizations can be per-formed at lower level. Compared with SCALE-MAMBA, val o1 = Owner() // input var = NumArray(o1, 3, 1, 5, 2) val s = 0 val e = arr.length // merge def merge(o: Owner, arr1: NumArray, arr2: NumArray) = { var res = NewNumArray(o, arr1.length + arr2.length) var i = Num(o, 0) var j = Num(o, 0) var k = 0 while (k < res.length) { val b1 = i < Num(o, arr1.length) val b2 = j < Num(o, arr2.length) val p = if (b1.not) arr2(j) else if (b2.not) arr1(i) else if (arr1(i) <= arr2(j)) arr1(i) else arr2(j) res = res.update(k, p) // updating arr1 index i = if (b1.not) i else if (b2.not) i + Num(o, 1) else if (p == arr1(i)) i + Num(o, 1) else i // updating arr2 index j = if (b1.not) j + Num(o, 1) else if (b2.not) j else if (p == arr2(j)) j + Num(o, 1) else j k = k + 1 } res } val r = recFuel(10) val mergesort = r.rec[NumArray, Owner, Int , Int ] { f => (a, o, i, j) => { val mid = (j - i) / 2 if (mid == 0 || i >= j){ a } else { val left = a.slice(i, mid) val right = a.slice(mid, j) merge(o, f(left, o, 0, left.length), f(right, o, 0, right.length)) } } } val res = mergesort(arr, o1, s, e) output(res.eval(o1)) Figure 11: MergeSort implemented in HIR
HACCLE also provides a resource estimation framework andfocuses more on optimization at higher level.HoneybadgerMPC [27] is another MPC backend of HAC-CLE that supports secret-sharing based protocols. The unique-ness of HoneybadgerMPC is the combination of a robust on-line phase and an optimistic non-robust offline phase. It pro-vides fairness guarantees even in the asynchronous networksetting and also preserves efficiency to make MPC programspractical to run.As privacy preserving machine learning becomes moreand more popular, many frameworks have been developedspecifically for this use case, such as ABY [14], ABY3 [29],CHET [13], EzPC [7], CrypTFlow [25] and secureNN [39].These frameworks are highly optimized for machine learn-ing and are designed for the two-party setting or three-partysetting. We choose not to include them due to our desire tosupport an arbitrary number of parties.There are also many other MPC frameworks such as Viff[2], Jiff [30], MPyC [3] and PICCO [45]. Theoretically HAC-CLE can embed any framework as a backend so while wehave not integrated all of these at this time, they should all fitinto HACCLE if desired in the future.12
Conclusion
Secure MPC-based applications play crucial role in solvingmany important practical problems such as high-valued pro-curements. But building performant MPC-based applicationsfrom scratch is a notoriously difficult task as it requires ex-pertise ranging from cryptography to circuit optimization.Therefore software developers need an ecosystem for build-ing MPC-based applications. As a solution to this problem,we have introduced the HACCLE toolchain, a multi-stagecompiler for optimized circuit generation. We believe that theHACCLE toolchain offers a compelling approach to the de-sign and implementation of secure MPC-based applications.
References [1] PALISADE homomorphic encryption softare library.https://palisade-crypto.org/.[2] VIFF, the virtual ideal functionality framework. http://viff.dk/ .[3] Barry Schoenmakers. MPyC: Secure multiparty com-putation in Python. https://github.com/lschoe/mpyc , 2020.[4] Peter Bogetoft, Ivan Damgård, Thomas Jakobsen, KurtNielsen, Jakob Pagter, and Tomas Toft. A practical im-plementation of secure auctions based on multipartyinteger computation. In Giovanni Di Crescenzo and AviRubin, editors,
Financial Cryptography and Data Secu-rity , pages 142–147, Berlin, Heidelberg, 2006. SpringerBerlin Heidelberg.[5] Zvika Brakerski, Craig Gentry, and Vinod Vaikun-tanathan. Fully homomorphic encryption without boot-strapping. Cryptology ePrint Archive, Report 2011/277,2011. https://eprint.iacr.org/2011/277 .[6] Martin Burkhart, Mario Strasser, Dilip Many, and Xeno-fontas Dimitropoulos. Sepia: Privacy-preserving aggre-gation of multi-domain network events and statistics.
Network , 2010.[7] N. Chandran, D. Gupta, A. Rastogi, R. Sharma, andS. Tripathi. Ezpc: Programmable and efficient securetwo-party computation for machine learning. In , pages 496–511, 2019.[8] Ilaria Chillotti, Nicolas Gama, Mariya Georgieva, andMalika Izabachene. Faster fully homomorphic encryp-tion: Bootstrapping in less than 0.1 seconds. In interna-tional conference on the theory and application of cryp-tology and information security , pages 3–33. Springer,2016. [9] Ilaria Chillotti, Nicolas Gama, Mariya Georgieva, andMalika Izabachène. Faster packed homomorphic op-erations and efficient circuit bootstrapping for tfhe. In
International Conference on the Theory and Applicationof Cryptology and Information Security , pages 377–408.Springer, 2017.[10] Ilaria Chillotti, Nicolas Gama, Mariya Georgieva,and Malika Izabachène. TFHE: Fast fully ho-momorphic encryption library, August 2016.https://tfhe.github.io/tfhe/.[11] Geoffroy Couteau. Efficient secure comparison proto-cols.
IACR Cryptology ePrint Archive , 2016:544, 2016.[12] Ivan Damgard, Marcel Keller, Enrique Larraia, Vale-rio Pastro, Peter Scholl, and Nigel P. Smart. Practicalcovertly secure MPC for dishonest majority – or: Break-ing the SPDZ limits, 2012.[13] Roshan Dathathri, Olli Saarikivi, Hao Chen, Kim Laine,Kristin Lauter, Saeed Maleki, Madanlal Musuvathi, andTodd Mytkowicz. Chet: An optimizing compiler forfully-homomorphic neural-network inferencing. In
Pro-ceedings of the 40th ACM SIGPLAN Conference onProgramming Language Design and Implementation ,PLDI 2019, New York, NY, USA, 2019. Association forComputing Machinery.[14] Daniel Demmler, Thomas Schneider, and MichaelZohner. Aby-a framework for efficient mixed-protocolsecure two-party computation. In
NDSS , 2015.[15] Jack Doerner, David Evans, and abhi shelat. Secure sta-ble matching at scale. In
Proceedings of the 2016 ACMSIGSAC Conference on Computer and CommunicationsSecurity , CCS ’16, New York, NY, USA. Associationfor Computing Machinery.[16] Grégory M Essertel, Guannan Wei, and Tiark Rompf.Precise reasoning with structured time, structured heaps,and collective operations.
Proceedings of the ACM onProgramming Languages , 3(OOPSLA):1–30, 2019.[17] David Evans, Vladimir Kolesnikov, and Mike Rosulek.A pragmatic introduction to secure multi-party computa-tion.
Foundations and Trends R (cid:13) in Privacy and Security ,2(2-3), 2017.[18] Craig Gentry, Shai Halevi, and Nigel P. Smart.Homomorphic evaluation of the aes circuit. Cryp-tology ePrint Archive, Report 2012/099, 2012.https://eprint.iacr.org/2012/099.[19] Trinabh Gupta, Henrique Fingler, Lorenzo Alvisi, andMichael Walfish. Pretzel: Email encryption andprovider-supplied functions are compatible. In Pro-ceedings of the Conference of the ACM Special InterestGroup on Data Communication , 2017.1320] Marcella Hastings, Brett Hemenway, Daniel Noble, andSteve Zdancewic. Sok: General purpose compilers forsecure multi-party computation. In , pages 1220–1237.IEEE, 2019.[21] Andreas Holzer, Martin Franz, Stefan Katzenbeisser,and Helmut Veith. Secure two-party computations inansi c. In
Proceedings of the 2012 ACM conference onComputer and communications security , pages 772–783,2012.[22] Karthik A Jagadeesh, David J Wu, Johannes ABirgmeier, Dan Boneh, and Gill Bejerano. Derivinggenomic diagnoses without revealing patient genomes.
Science , 2017.[23] B Kreuter. "secure mpc at google". 2017". "Real WorldCrypto".[24] KU Leuven. SCALE-MAMBA Software. https://homes.esat.kuleuven.be/~nsmart/SCALE/ , 2019.[25] Nishant Kumar, Mayank Rathee, Nishanth Chandran,Divya Gupta, Aseem Rastogi, and Rahul Sharma. Crypt-flow: Secure tensorflow inference. arXiv preprintarXiv:1909.07814 , 2019.[26] Chang Liu, Xiao Shaun Wang, Kartik Nayak, YanHuang, and Elaine Shi. Oblivm: A programming frame-work for secure computation. In , pages 359–376. IEEE, 2015.[27] Donghang Lu, Thomas Yurek, Samarth Kulshreshtha,Rahul Govind, Aniket Kate, and Andrew Miller. Hon-eybadgermpc and asynchromix: Practical asynchronousmpc and its application to anonymous communication.In
Proceedings of the 2019 ACM SIGSAC Conferenceon Computer and Communications Security , pages 887–903, 2019.[28] Dahlia Malkhi, Noam Nisan, Benny Pinkas, Yaron Sella,et al. Fairplay-secure two-party computation system. In
USENIX Security Symposium , volume 4, page 9. SanDiego, CA, USA, 2004.[29] Payman Mohassel and Peter Rindal. Aby3: A mixedprotocol framework for machine learning. In
Proceed-ings of the 2018 ACM SIGSAC Conference on Computerand Communications Security , 2018.[30] Multiparty.org Development Team. JavaScript im-plementation of federated functionalities. https://github.com/multiparty/jiff , 2020.[31] Aseem Rastogi, Matthew A Hammer, and MichaelHicks. Wysteria: A programming language for generic,mixed-mode multiparty computations. In , pages 655–670.IEEE, 2014.[32] Tord Ingolf Reistad and Tomas Toft. Secret sharing com-parison by transformation and rotation. In
InternationalConference on Information Theoretic Security , pages169–180. Springer, 2007.[33] Tiark Rompf and Kevin J Brown. Functional parallelsof sequential imperatives (short paper). In
Proceedingsof the 2017 ACM SIGPLAN Workshop on Partial Evalu-ation and Program Manipulation , pages 83–88, 2017.[34] Victor Shoup Shai Halevi. HElib: Design and im-plementation of a homomorophic-encryption library.https://github.com/shaih/HElib, April 2013.[35] Adi Shamir. How to share a secret.
Commun. ACM ,22(11):612–613, nov 1979.[36] N.P. Smart and F. Vercauteren. Fully homomorphicsimd operations. Cryptology ePrint Archive, Report2011/133, 2011. https://eprint.iacr.org/2011/133.[37] Walid Taha. A gentle introduction to multi-stage pro-gramming. In
Domain-Specific Program Generation ,pages 30–50. Springer, 2004.[38] William Vickrey. Counterspeculation, auctions, andcompetitive sealed tenders.
The Journal of Finance ,16(1):8–37, 1961.[39] Sameer Wagh, Divya Gupta, and Nishanth Chandran. Se-curenn: 3-party secure computation for neural networktraining.
Proceedings on Privacy Enhancing Technolo-gies , 2019(3):26–49, 2019.[40] Xiao Wang, Alex J. Malozemoff, and Jonathan Katz.EMP-toolkit: Efficient MultiParty computation toolkit. https://github.com/emp-toolkit , 2016.[41] Xiao Shaun Wang, Yan Huang, Yongan Zhao, HaixuTang, XiaoFeng Wang, and Diyue Bu. Efficient genome-wide, privacy-preserving similar patient query based onprivate edit distance. SIGSAC, 2015.[42] A. C. Yao. Protocols for secure computations. In , pages 160–164, Los Alamitos, CA, USA,nov 1982. IEEE Computer Society.[43] Samee Zahur and David Evans. Obliv-c: A language forextensible data-oblivious computation.
IACR Cryptol-ogy ePrint Archive , 2015(1153), 2015.[44] Samee Zahur and David Evans. Obliv-c: A language forextensible data-oblivious computation.
IACR Cryptol-ogy ePrint Archive , 2015(1153), 2015.1445] Yihua Zhang, Aaron Steele, and Marina Blanton. Picco:a general-purpose compiler for private distributed com-putation. In
Proceedings of the 2013 ACM SIGSACconference on Computer & communications security ,pages 813–826, 2013. 15
Appendix void
TFHE_Addition(TFheGateBootstrappingCloudKeySet* x0,TFheGateBootstrappingParameterSet* x1){TFheGateBootstrappingSecretKeySet* x2 =new_random_gate_bootstrapping_secret_keyset(x1);x0 = &x2->cloud;LweSample* x3 =new_gate_bootstrapping_ciphertext_array(64, x2->params); for (uint64_t i = 0; i < 64; i ++) {bootsSymEncrypt(&x3[i], ((uint64_t) value>>i)&10, x2);}LweSample* x4 =new_gate_bootstrapping_ciphertext_array(64, x2->params); for (uint64_t i = 0; i < 64; i ++) {bootsSymEncrypt(&x4[i], ((uint64_t) value>>i)&5, x2);}LweSample* x5 =new_gate_bootstrapping_ciphertext_array(64, x2->params);fhe_add(x5, a, b, 64, bk);printf("%ld\n", num_eval(x5, 64, x2));}
Figure 12: Generated the TFHE program that perforom a secure additionof two 64-bit integers (10 and 5). For simplicity, the program uses plaintextvalues as input instead of obtaining them at runtime. /************* Functions **************/ static inline Plaintext num_eval(CryptoContext
LPPrivateKey
Ciphertext
BGV_Addition(CryptoContext
Figure 13: Generated the HElib program that perforom a secure addition oftwo integers (10 and 5). For simplicity, the program uses plaintext values asinput instead of obtaining them at runtime. def auction():x0 = sint(15)
Figure 14: Generate SPDZ program that computes a second-price auction.For simplicity, the program uses plaintext values as input instead of obtainingthem at runtime. There are three bids (15, 12 and 20) whose holders aredenoted as 0, 1 and 2 respectively. rom honeybadgermpc.progs.mixins.share_comparison import Equality, LessThan from honeybadgermpc.preprocessing import (PreProcessedElements as FakePreProcessedElements,) from honeybadgermpc.progs.mixins.share_arithmetic import (BeaverMultiply, BeaverMultiplyArrays, MixinConstants, ) import logging import sys import asyncio from time import timempc_config = {MixinConstants.MultiplyShareArray: BeaverMultiplyArrays(),MixinConstants.MultiplyShare: BeaverMultiply(),MixinConstants.ShareLessThan: LessThan(),} async def
Auction(x0): print ( await (x7 * x6 + (1 - x7) * x4).open()) print ( await (x7 * (x5 * num(x0,1) + (1 - x5) * num(x0,0))+ (1 - x7) * num(x0,2)).open()) async def _run(peers, n, t, my_id, k): from honeybadgermpc.ipc import ProcessProgramRunner async with ProcessProgramRunner(peers, n, t, my_id, mpc_config) as runner: await runner.execute("0", Auction)bytes_sent = runner.node_communicator.bytes_sent print (f"[my_id] Total bytes sent out: bytes_sent") if __name__ == "__main__": from honeybadgermpc.config import HbmpcConfig import sysHbmpcConfig.load_config()asyncio.set_event_loop(asyncio.new_event_loop())loop = asyncio.get_event_loop()loop.set_debug(False) try :pp_elements = FakePreProcessedElements()k = 3 if HbmpcConfig.my_id == 0:num_bits = 40pp_elements.generate_triples(num_bits * 20 * k,HbmpcConfig.N, HbmpcConfig.t)pp_elements.generate_share_bits(num_bits * k,HbmpcConfig.N, HbmpcConfig.t)pp_elements.preprocessing_done() else :loop.run_until_complete(pp_elements.wait_for_preprocessing())loop.run_until_complete(_run(HbmpcConfig.peers, HbmpcConfig.N,HbmpcConfig.t, HbmpcConfig.my_id, k)) finally :loop.close()
Figure 15: Generate HoneyBadgerMPC that computes a second-price auc-tion. For simplicity, the program uses plaintext values as input instead ofobtaining them at runtime. There are three bids (15, 12 and 20) whose hold-ers are denoted as 0, 1 and 2 respectively. For testing and development, theHoneyBadgerMPC program runs in a single-process simulated network.
FHEPubKey& publicKey) {Ctxt ret(publicKey);publicKey.Encrypt(ret, NTL::to_ZZX(value)); return ret;} static inline Ctxt unum_mul_plain(Ctxt n1, int n2) {Ctxt ret = n1;ret.multByConstant(NTL::to_ZZX(n2)); return ret;}/**************** Multiply ****************/ void multiply(FHEcontext& x0, int x1){FHESecKey x2(x0);x2.GenSecKey();cout << unum_eval(unum_add(unum_add(unum_init(1, x2), unum_mul_plain(unum_init(2, x2), 2)),unum_init(3, x2)), x2) < L = 16;// Number of levels in the modulus chain [default=heuristic] long c = 3;// Number of columns in key-switching matrix [default=2] long w = 64;// Hamming weight of secret key long d = 0;// Degree of the field extension [default=1] long k = 128;// Security parameter [default=80] long s = 0;// Minimum number of slots [default=0]m = FindM(k, L, c, p, d, s, 0);FHEcontext context(m, p, r);buildModChain(context, L, c);NTL::ZZX G = context.alMod.getFactorsOverZZ()[0];multiply(context, 0); return Figure 16: Generated HElib program that computes matrix-vector multipli-cation of Array(Array(1, 2, 3), Array(4, 5, 6)) and Array(1, 2,1) . For simplicity, the program uses plaintext values as input instead ofobtaining them at runtime. include LweSample* b, uint64_t size, const TFheGateBootstrappingCloudKeySet* bk) {LweSample* ret =new_gate_bootstrapping_ciphertext_array(size, bk->params);fhe_add(ret, a, b, size, bk); return ret;} static inline LweSample* unum_init(uint64_t value,uint64_t size, const TFheGateBootstrappingSecretKeySet* bk) {LweSample* ret =new_gate_bootstrapping_ciphertext_array(size, bk->params); for (uint64_t i = 0; i < size; i++) {bootsSymEncrypt(&ret[i], ((uint64_t) value>>i)&1, bk);} return ret;} static inline LweSample* unum_mul_plain( const LweSample* a, const int b, uint64_t size, const TFheGateBootstrappingCloudKeySet* bk) {LweSample* c_b =new_gate_bootstrapping_ciphertext_array(size, bk->params); for (uint64_t i = 0; i < size; i ++) {bootsCONSTANT(&c_b[i], ((uint64_t) b>>i)&1, bk);}LweSample* ret =new_gate_bootstrapping_ciphertext_array(size, bk->params);fhe_mul(ret, a, c_b, size, bk); return ret;} static inline uint64_t unum_eval( const LweSample* a,uint64_t size, const TFheGateBootstrappingSecretKeySet* key) {assert(size <= 64);uint64_t res[size];uint64_t ret = 0; for (uint64_t i=0; i LweSample** x3 =( const LweSample**)malloc(2 * sizeof ( const LweSample*));LweSample* x4 = add(unum_init(1, 64 ,x2), x3[0], 64 , x0);x3[0] = x4;LweSample* x5 = add(unum_mul_plain(unum_init(2, 64 ,x2), 2, 64, x0), x4, 64 , x0);x3[0] = x5;LweSample* x6 = add(unum_init(3, 64 ,x2), x5, 64 , x0);x3[0] = x6;printf("%ld\n", unum_eval(x6, 64, x2));x3[1] = add(unum_init(6, 64 ,x2),add(unum_mul_plain(unum_init(5, 64 ,x2), 2, 64, x0),add(unum_init(4, 64 ,x2), x3[1], 64, x0), 64, x0), 64, x0);} int main(){ const int minimum_lambda = 110;TFheGateBootstrappingParameterSet* params =new_default_gate_bootstrapping_parameters(minimum_lambda);uint32_t seed[] = { 314, 1592, 657 };tfhe_random_generator_setSeed(seed,3);TFheGateBootstrappingSecretKeySet* s_key =new_random_gate_bootstrapping_secret_keyset(params); const TFheGateBootstrappingCloudKeySet* c_key = &s_key->cloud;multiply(NULL, params); return Figure 17: Generated TFHE code that computes matrix-vector multipli-cation of Array(Array(1, 2, 3), Array(4, 5, 6)) and Array(1, 2,1) . For simplicity, the program uses plaintext values as input instead ofobtaining them at runtime. LweSample* a, const LweSample* b, uint64_t size, const TFheGateBootstrappingCloudKeySet* bk) {LweSample* ret =new_gate_bootstrapping_ciphertext_array(size, bk->params);fhe_add(ret, a, b, size, bk); return ret;}LweSample* num_array_init( const uint64_t bit_width, const TFheGateBootstrappingSecretKeySet* bk, const uint64_t size, ...){va_list valist;va_start(valist, size);LweSample* ret =new_gate_bootstrapping_ciphertext_array(bit_width * size,bk->params); for (uint64_t i = 0; i < size ; i ++) {uint64_t tmp = va_arg(valist, int ); for (uint64_t j = 0; j < bit_width; j ++) {bootsSymEncrypt(&ret[i * bit_width + j],((uint64_t) tmp>>j)&1, bk);}}va_end(valist); return ret;} static inline LweSample* num_init(uint64_t value, uint64_t size, const TFheGateBootstrappingSecretKeySet* bk) {LweSample* ret =new_gate_bootstrapping_ciphertext_array(size, bk->params); for (uint64_t i = 0; i < size; i ++) {bootsSymEncrypt(&ret[i], ((uint64_t) value>>i)&1, bk);} return ret;} static inline LweSample* num_leq( const LweSample* a, const LweSample* b, uint64_t size, const TFheGateBootstrappingCloudKeySet* bk) {LweSample* ret =new_gate_bootstrapping_ciphertext(bk->params);fhe_compare_signed(ret, a, b, size, bk); return ret;} static inline LweSample* num_array_index( const LweSample* i, const LweSample* arr, uint64_t length, uint64_t bit_width, const TFheGateBootstrappingCloudKeySet* bk) {LweSample * res =new_gate_bootstrapping_ciphertext_array(bit_width, bk->params);fhe_array_index(res, i, arr, length, bit_width, bk); return res;} static inline LweSample* num_less( const LweSample* a, const LweSample* b, uint64_t size, const TFheGateBootstrappingCloudKeySet* bk) {LweSample* ret =new_gate_bootstrapping_ciphertext(bk->params);LweSample* t1 =new_gate_bootstrapping_ciphertext_array(size, bk->params);LweSample* t2 =new_gate_bootstrapping_ciphertext_array(size, bk->params);fhe_neg(t1, b, size, bk);fhe_add(t2, a, t1, size, bk);bootsCOPY(ret, &t2[size-1], bk); return ret;} static inline LweSample* bit_and( const LweSample* a, const LweSample* b, const TFheGateBootstrappingCloudKeySet* bk) {LweSample* ret = new_gate_bootstrapping_ciphertext(bk->params);bootsAND(ret, a, b, bk); return ret;} Figure 18: Generated TFHE program that merge sort an array of [3, 1, 5, 2].For simplicity, the program uses plaintext values as input instead of obtainingthem at runtime. (part 1) tatic inline LweSample* num_mux( const LweSample* a, const LweSample* b, const LweSample* c, uint64_t size, const TFheGateBootstrappingCloudKeySet* bk) {LweSample* ret =new_gate_bootstrapping_ciphertext_array(size,bk->params);fhe_mux(ret, a, b, c, size, bk); return ret;} static inline int64_t num_eval( const LweSample* a,uint64_t size, const TFheGateBootstrappingSecretKeySet* key) {assert(size <= 64);uint64_t res[size];int64_t ret = 0; for (uint64_t i=0; i LweSample* x8 =num_less(x5, num_init(2, 16 ,x2), 16 , x0); const LweSample* x9 =num_less(x6, num_init(2, 16 ,x2), 16 , x0);LweSample* x10 = bit_and(x8, x9, x0);LweSample* x11 = num_mux(num_mux(add(x5,num_init(1, 16 ,x2), 16, x0), x5, x7, 16 , x0),num_mux(add(x5, num_init(1, 16 ,x2), 16, x0),x5, x8, 16 , x0), x10, 16 , x0);LweSample* x12 = num_mux(num_mux(x6, add(x6,num_init(1, 16 ,x2), 16, x0), x7, 16 , x0),num_mux(add(x6, num_init(1, 16 ,x2), 16, x0),x6, x9, 16 , x0), x10, 16 , x0);LweSample* x13 = num_leq(num_array_index(x11, x3, 2, 16, x0),num_array_index(x12, x4, 2, 16, x0), 16 , x0); const LweSample* x14 =num_less(x11, num_init(2, 16 ,x2), 16 , x0); const LweSample* x15 =num_less(x12, num_init(2, 16 ,x2), 16 , x0);LweSample* x16 = bit_and(x14, x15, x0);LweSample* x17 = num_mux(num_mux(add(x11, num_init(1, 16 ,x2), 16, x0),x11, x13, 16 , x0), num_mux(add(x11, num_init(1, 16 ,x2),16, x0), x11, x14, 16 , x0), x16, 16 , x0);LweSample* x18 =num_mux(num_mux(x12,add(x12, num_init(1, 16 ,x2), 16, x0),x13, 16 , x0), num_mux(add(x12, num_init(1, 16 ,x2),16, x0), x12, x15, 16 , x0), x16, 16 , x0);LweSample* x19 =num_leq(num_array_index(x17, x3, 2, 16, x0),num_array_index(x18, x4, 2, 16, x0), 16 , x0); const LweSample* x20 =num_less(x17, num_init(2, 16 ,x2), 16 , x0); Figure 19: Generated TFHE program that merge sort an array of [3, 1, 5, 2].For simplicity, the program uses plaintext values as input instead of obtainingthem at runtime. (part 2) const LweSample* x21 =num_less(x18, num_init(2, 16 ,x2), 16 , x0);LweSample* x22 = bit_and(x20, x21, x0);LweSample* x23 = num_mux(num_mux(add(x17,num_init(1, 16 ,x2), 16, x0), x17, x19, 16 , x0),num_mux(add(x17, num_init(1, 16 ,x2),16, x0), x17, x20, 16 , x0), x22, 16 , x0);LweSample* x24 = num_mux(num_mux(x18,add(x18, num_init(1, 16 ,x2), 16, x0), x19, 16 , x0),num_mux(add(x18, num_init(1, 16 ,x2), 16, x0),x18, x21, 16 , x0), x22, 16 , x0); const LweSample* x25 =num_less(x23, num_init(2, 16 ,x2), 16 , x0);printf("%ld\n", num_eval(num_mux(num_mux(num_array_index(x5, x3, 2, 16, x0),num_array_index(x6, x4, 2, 16, x0), x7, 16 , x0),num_mux(num_array_index(x5, x3, 2, 16, x0),num_array_index(x6, x4, 2, 16, x0), x8, 16 , x0),x10, 16 , x0), 16, x2));printf("%ld\n", num_eval(num_mux(num_mux(num_array_index(x11, x3, 2, 16, x0),num_array_index(x12, x4, 2, 16, x0), x13, 16 , x0),num_mux(num_array_index(x11, x3, 2, 16, x0),num_array_index(x12, x4, 2, 16, x0), x14, 16 , x0),x16, 16 , x0), 16, x2));printf("%ld\n", num_eval(num_mux(num_mux(num_array_index(x17, x3, 2, 16, x0),num_array_index(x18, x4, 2, 16, x0), x19, 16 , x0),num_mux(num_array_index(x17, x3, 2, 16, x0),num_array_index(x18, x4, 2, 16, x0), x20, 16 , x0),x22, 16 , x0), 16, x2));printf("%ld\n", num_eval(num_mux(num_mux(num_array_index(x23, x3, 2, 16, x0),num_array_index(x24, x4, 2, 16, x0),num_leq(num_array_index(x23, x3, 2, 16, x0),num_array_index(x24, x4, 2, 16, x0), 16 , x0), 16 , x0),num_mux(num_array_index(x23, x3, 2, 16, x0),num_array_index(x24, x4, 2, 16, x0), x25, 16 , x0),bit_and(x25, num_less(x24, num_init(2, 16 ,x2), 16 , x0),x0), 16 , x0), 16, x2));} int main(){ const int minimum_lambda = 110;TFheGateBootstrappingParameterSet* params =new_default_gate_bootstrapping_parameters(minimum_lambda);uint32_t seed[] = { 314, 1592, 657 };tfhe_random_generator_setSeed(seed,3);TFheGateBootstrappingSecretKeySet* s_key =new_random_gate_bootstrapping_secret_keyset(params); const TFheGateBootstrappingCloudKeySet* c_key = &s_key->cloud;MergeSort(NULL, params); return