Motivating Garbled Circuits

Cryptographic protocols can sometimes feel a bit like magic. If you’re like us, you’ve probably had the experience where you can verify that a protocol is probably complete (and if you’re lucky, you might even be convinced that it’s sound), but you might have no idea as to why the protocol works the way it does, or how one might even have arrived at the construction in the first place. So attempting to rederive a protocol is often helpful for understanding the key ideas or “cruxes.”

In this post, we’ll explain how one might rederive Yao’s Garbled Circuits protocol from scratch, from an intuitive perspective. Garbled Circuits are a neat primitive for general-purpose 2-party computation, but the construction may seem a bit “magical”–how would you come up with the idea of garbling, in a world where it doesn’t exist?

Garbled Circuits are a solution to the general problem of 2-party computation, or 2PC. In 2PC, Alice and Bob each have some secret values and , and would like to jointly compute some function over their respective inputs. Furthermore, they’d like to keep their secret values hidden from each other: if Alice and Bob follow the protocol honestly, they should both end up learning the correct value of , but Alice shouldn’t learn anything about (other than what could be learned by knowing both and ), and likewise for Bob.

Yao’s Garbled Circuits is one of the most well-known 2PC protocols (Vitalik has a great explanation here). The protocol is quite clever, and optimized variants of the protocol are being implemented and used today.

We don’t know exactly what led Yao to the idea of garbling, but we’ll describe one plausible path for someone today to independently rediscover garbling, assuming the existence of an earlier primitive called “Oblivious Transfer.”

The Problem

Here is our problem setting, slightly more formally:

Precursor: Oblivious Transfer

If you’re aware of Oblivious Transfer (OT), an earlier cryptographic primitive, it’s plausibly one of the first things you might try to use if you’re approaching the general problem of 2PC.

As a quick primer: Oblivious Transfer protocols also involve two parties, Alice and Bob. Alice has a tuple of some records, (imagine that each of these values is some 32-bit integer). Bob would like to query for the value , for some specific index , without letting Alice know which he is asking for. Alice would like for Bob to only learn the single value of , without learning anything about for .

This might seem almost paradoxical at first glance. However, with a bit of cleverness, you can design a protocol such that Alice and Bob can carry this procedure out by transmitting bits between each other, where is in the hundreds. Today, protocols where Alice and Bob communicate sublinear information in exist as well!

If you meditate on OT and 2PC for a bit, you might see how the “API” of Oblivious Transfer is nearly identical the “API” of 2PC. (We’ll explain exactly what that correspondence looks like in the next section.)

OTing a Huge Function Lookup Table is 2PC

In the previous section, we mentioned that OT and 2PC have the same “API.” This comes from the observation that a function can be seen as a big lookup table, so reading from a lookup table is somehow equivalent to evaluating a function.

The most straightforward thing Alice can do is to simply construct a huge lookup table of all the possible values of , given her fixed value of , and then allow Bob to query for the appropriate value of for his .

For a concrete example, suppose . Knowing , Alice is able to calculate the following lookup table:

000
001
010
011
100
101
110
111

Then she allows Bob to retrieve the value corresponding to , using Oblivious Transfer. Bob learns the correct value of and shares this with Alice.

While this protocol works, it’s clearly not very efficient. It requires Alice to construct and perform OT with a lookup table of size , which grows exponentially larger in the length of Bob’s input. How can we improve this?

Can we perform OT Gate-By-Gate?

has lots of structure: it can be broken down into a bunch of gates. A natural question to ask is whether we can somehow perform our OT-lookup idea gate-by-gate, instead of on the whole circuit. Since gates are small, our lookup tables would be much smaller.

Let’s start with a single AND gate: , where Alice knows , and Bob knows . The output of this gate is some intermediate result will get fed into some other gates deeper in the circuit.

What happens when we try to blindly apply our lookup OT procedure in computing ? According to our procedure, Alice constructs a lookup table that specifies the result of for the possible values of , and Bob will retrieve the value of corresponding to his value of . Finally, Bob tells Alice the value .

With this protocol, if Alice and Bob’s bits are both , they’ll learn this. But if either of their bits is , the person with a bit will learn nothing about the other person’s bit.

Can we build a multi-gate 2PC protocol off of this directly? Well, we’ll run into two problems:

So we can’t blindly apply our lookup OT gate-by-gate, but with a bit of tweaking we can maybe still construct a modified lookup-OT-procedure that can be “chained,” gate by gate.

Computing a Gate on Secret Shares

We’d like for Alice and Bob to each learn some information that would allow them to jointly compute the output of the gate, but we don’t want either of them to actually be able to know learn the result on their own without consulting the other. A common tactic for this kind of setting is to have Alice and Bob try to compute “secret shares” of the result.

What this means is that Alice and Bob might try to design a procedure where Alice will end up with some and Bob will end up with some such that , where is the expected output of the gate. If Alice’s is drawn from a random distribution, then neither Alice nor Bob have gained any information on , but they’ve managed to “jointly compute” the gate.

Even if Alice and Bob can do this, there’s still some trickiness: in future computations involving , Alice and Bob will have to both bring their respective shares, and we’ll have to figure out how to pass secret shares through gates.

But if we can figure out how to deal with that, a possible strategy emerges:

It turns out that it’s a pretty straightforward extension to modify our lookup-OT tactic to make it work for computing gates on secret shares. (We recommend trying to figure this out yourself before reading the next paragraph!)

Suppose that is gate with two inputs, and that computes for some operator . Alice knows two bits , and Bob knows two bits , such that and . Alice would like to end up computing some and Bob some such that is indeed the correct result of the gate.

First, Alice flips a coin to choose a random bit for . Then, she computes a lookup table that describes what should be, conditional on , and given ’s fixed values of :

00
01
10
11

How do we fill in the values in the column? Well, we know that ; that’s what it means to compute the gate over secret shares. Since Alice knows , for each row she can substitute in values of suggested by the row label, and then solve for .

To complete the computation, Alice and Bob can simply carry out an oblivious transfer. Bob knows his values and , so he should be able to ask for the appropriate row of the table containing the correct value of without Alice learning which row he is asking for. The two of them have now jointly computed a gate, in a “chainable” way, without learning anything about the gate outputs!

A full gate-by-gate protocol

Once we’ve built a gadget for computing secret-share-outputs of a gate from secret-share-inputs, we can chain these together into a complete protocol. For completeness, we’ve written down the full protocol below.

With this protocol, and can compute all values of and . At the end they simply reveal and xor their desired output bits together!

Note that we can reduce the number of OTs with a few optimizations. For example, any time is a linear operator (such as xor), and can simply apply that linear operator to their respective shares. So we only need to perform OTs for the nonlinear gates.

Yao’s Garbled Circuits: Replacing Per-Gate OTs With Encryption

Though it might not seem like it on the surface, we’ve now arrived at something that is actually quite close to Yao’s garbled circuits protocol. In fact, Yao’s protocol can be seen as the above protocol with some optimizations, that enable us to replace a bunch of OTs with “precomputation” from Alice.

OTs are expensive, and doing one OT per gate is still pretty inefficient (though it’s certainly far better than an OT of size !). Each OT also requires a round of interaction between Alice and Bob, meaning that this protocol involves many, many back-and-forths between Alice and Bob (not ideal).

A natural question to ask would be: is there a way that we can somehow “batch” all of the OTs, or else perform some kind of precomputation so that Alice and Bob can learn their secret shares at each gate non-interactively?

Observation: Alice never actually interacts with Bob

This question becomes especially interesting in light of the following observation: in our previous protocol, it turns out that Alice’s values are selected completely independently of anything Bob does. In other words, at the start of the protocol Alice could have simply decided to write down all of her ’s in advance.

In this view, Alice is essentially writing down a “scrambling” function for the circuit trace at the very start of the protocol. Concretely, the tuple that Bob computes over the course of the protocol will end up being a scrambled version of the “true” trace , where the scrambling is decided by Alice at the beginning of the protocol.

What does it mean to see Alice’s “scrambling” as a function, or set of functions? Well, Alice is essentially defining a scrambling map for each gate (including the input “gates”). For each gate , the scrambling map takes to some random shuffling of . Specifically:

So, to sum up this alternative view of our latest protocol: Alice writes down a bunch of scrambling functions at the start of the protocol, and then over the course of the protocol Bob gradually learns the scrambled version of the true circuit trace . In particular, he’s learning for all .

Replacing OTs with Encryption

Currently, Bob learns his “scrambled” values through OT. If we want to find a way to remove the requirement of “one OT per gate,” we need to figure out a way for Bob to learn the scrambled trace non-interactively.

This seems plausible. Alice shouldn’t really need to interact with Bob at all, until the very end of the protocol. She wrote down the scrambling at the beginning of the protocol, and she’s not supposed to learn anything about Bob’s values or what he’s doing anyway; so it seems like it might be possible for Alice to simply send Bob some information that allows him to figure out his ’s without too much more interaction with her.

Let’s try to do this in the most direct way. For a gate that represents the intermediate calculation , Alice might publish a table that looks something like the following for Bob:

00 )
01
10
11

(This table is the same as the lookup table from above, just with the ’s written as outputs of scrambling functions, and with the values in the second column “locked” by encryption).

This table should be constructed so that Bob can only “unlock” the appropriate value of for the row of values that he actually possesses–this simulates the OT property that Bob can’t learn the values of any other rows. For example, should only be possible for Bob to decrypt if and .

But it turns out that there’s a very direct way to do this! We simply have our scrambling and also output some symmetric encryption keys picked by Alice at the start of the protocol, in addition to the scrambled bit. The values in the second column should be encrypted with both of these keys, and the output of should also include a new symmetric encryption key as well that can be used to unlock later values.

For example, suppose that for some value we have , and for some value of we have . Then is symmetric encryption with a key derived from and - for example, .

In other words, the output of our “scrambling” function should actually be a tuple of two values: a scrambled bit, and a symmetric encryption key that can be used to decrypt values in lookup tables deeper in the circuit, where outputs of are involved:

Now, for each , Bob learns both the scrambled bit of , as well as a symmetric encryption key that can be used to decrypt future outputs for which is an input. At the very end, Bob asks Alice to reveal the scrambling functions for the final circuit outputs, and then unscrambles his output bits.

It turns out that there are still a few more kinks to iron out, like handling inputs–for example, we’ll find that our lazy default of setting for Alice’s inputs and for Bob’s inputs won’t work anymore. But we’ve essentially arrived at what we now know today as Yao’s Garbled Circuits protocol! In the Garbled Circuits protocol, our operation of “scrambling” is instead called “garbling.”

Final exercise for the reader: Work out the remaining kinks to arrive at a complete Garbled Circuits protocol.