Secure Multi-Party Computation with Go. This project implements secure two-party computation with Garbled circuit protocol. The main components are:
- ot: oblivious transfer library
- circuit: garbled circuit parser, garbler, and evaluator
- compiler: Multi-Party Computation Language (MPCL) compiler
The easiest way to experiment with the system is to compile the
garbled application and use it to evaluate
programs. The garbled
application takes the following command line
options:
-e
: specifies circuit evaluator / garbler mode. The circuit evaluator creates a TCP listener and waits for garblers to connect with computation.-i
: specifies comma-separated input values for the circuit.-v
: enabled verbose output.
The examples directory contains various MPCL
example programs which can be executed with the garbled
application. For example, here's how you can run the Yao's
Millionaires'
Problem
which can be found from the
millionaire.mpcl file:
package main
func main(a, b int64) bool {
if a > b {
return true
} else {
return false
}
}
First, start the evaluator (these examples are run in the
apps/garbled
directory):
$ ./garbled -e -i 800000 examples/millionaire.mpcl
- In1: a{1,0}i64:int64
+ In2: b{1,0}i64:int64
- Out: %_{0,1}b1:bool1
- In: [800000]
Listening for connections at :8080
The evaluator's input is 800000 and it is set to the circuit inputs
In2
. The evaluator is now waiting for garblers to connect to the TCP
port :8080
.
Next, let's start the garbler:
$ ./garbled -i 750000 examples/millionaire.mpcl
+ In1: a{1,0}i64:int64
- In2: b{1,0}i64:int64
- Out: %_{0,1}b1:bool1
- In: [750000]
Result[0]: false
The garbler's input is 750000 and it is set to the circuit inputs
In1
. The garbler connects to the evaluator's TCP port and they run
the garbled circuit protocol. At the end, garbler (and evaluator)
print the result of the circuit, which is this case is single bool
value Result[0]
:
Result[0]: false
In our example, the evaluator's argument In2 is bound to the MPCL
program's b int64
argument, and garbler's In1 to a int64
. Therefore, the result of the computation is false
because
In1=750000 <= In2=800000. If we increase the garbler's input to 900000,
we see that the result is now true
since the garbler's input is now
bigger than the evaluator's input:
$ ./garbled -i 900000 examples/millionaire.mpcl
+ In1: a{1,0}i64:int64
- In2: b{1,0}i64:int64
- Out: %_{0,1}b1:bool1
- In: [900000]
Result[0]: true
The multi-party computation language is heavily inspired by the Go programming language, however it is not using the Go's compiler or any other related components. The compiler is an independent implementation of the relevant parts of the Go syntax.
The parser parses the MPCL input files, including any referenced packages, and creates an abstract syntax tree (AST).
The AST is then converted into Static Single Assignment form (SSA) where each variable is defined and assigned only once. The SSA transformation does also type checks so that all variable assignments and function call arguments and return values are checked (or converted) to be or correct type.
Name | Size | Signed |
---|---|---|
bool | 1 | no |
uint | unspecified | no |
int | unspecified | yes |
uintN | N | no |
intN | N | yes |
floatN | N | yes |
stringN | N | no |
The unsized uint
and int
types can be used as function arguments
and return values. Their sizes are resolved during compilation
time. The only exception is the main
function. Its arguments must
use fixed type sizes. The following example shows a MinMax
function
that returns the minimum and maximum arguments. This function works
for all argument sizes.
func MinMax(a, b int) (int, int) {
if a < b {
return a, b
} else {
return b, a
}
}
The MPCL runtime defines the following builtin functions:
copy(dst, src)
: copies the content of the array src to dst. The function returns the number of elements copied, which is the minimum of len(src) and len(dst).len(value)
: returns the length of the value as integer:- array: returns the number of array elements
- string: returns the number of bytes in the string
make(type, size)
: creates an instance of the type type with size bits.native(name, arg...)
: calls a builtin function name with arguments arg.... The name can specify a circuit file (*.circ) or one of the following builtin functions:hamming(a, b uint)
computes the bitwise hamming distance between argument values
size(variable)
: returns the bit size of the argument variable.
package main
func main(a, b int4) int4 {
if a > b {
return a
}
return b
}
The compiler creates the following SSA form assembly:
# Input0: a{1,0}i4:int4
# Input1: b{1,0}i4:int4
# Output0: %_{0,1}i4:int4
# main#0:
igt a{1,0}i4 b{1,0}i4 %_{0,0}b1
mov a{1,0}i4 %ret0{1,1}i4
mov b{1,0}i4 %ret0{1,2}i4
# main.ret#0:
phi %_{0,0}b1 %ret0{1,1}i4 %ret0{1,2}i4 %_{0,1}i4
gc %_{0,0}b1
gc %ret0{1,1}i4
gc %ret0{1,2}i4
gc a{1,0}i4
gc b{1,0}i4
ret %_{0,1}i4
The SSA assembly (and logical circuit) form a Directed Acyclic Graph
(DAG) without any mutable storage locations. This means that all
alternative execution paths must be evaluate and when the program is
returning its computation results, any conflicting values from
different execution paths must be resolved with the branching
condition. This value resolution is implemented as the phi
assembly
instruction, which effectively implements a MUX logical circuit:
O=(D0 XOR D1)C XOR D0
D0 | D1 | C | D0 XOR D1 | AND C | XOR D0 |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 0 | 0 |
1 | 0 | 0 | 1 | 0 | 1 |
1 | 1 | 0 | 0 | 0 | 1 |
0 | 0 | 1 | 0 | 0 | 0 |
0 | 1 | 1 | 1 | 1 | 1 |
1 | 0 | 1 | 1 | 1 | 0 |
1 | 1 | 1 | 0 | 0 | 1 |
The 3rd compiler phase converts SSA form assembly into logic gate circuit. The following circuit was generated from the previous SSA form assembly:
- Phase 0
- Oblivious Transfer
- Garbled circuit garbling and evaluation
- MPCL compiler for basic arithmetics
- Phase 1
- Compiler
- Conditionals
- Struct input types
- Binary serialization format for circuits
- RSA 128-bit signature
- Circuit & garbling
- RSA 128-bit signature
- BMR multi-party protocol
- Compiler
- Phase 2
- Incremental compiler
- Constant folding
- Implement using AST rewrite
- binary expressions
- if-blocks
- For-loop unrolling
- Function call and return
- peephole optimization
- sort blocks in topological order
- peephole optimization over block boundaries
- SSA aliasing is 1:1 but
amov
has 2:1 relation - variable liveness analysis for templates
- Signed / unsigned arithmetics
- unary expressions
- logical not
- BitShift
- Constant folding
- Circuit & garbling:
- Incremental (streaming) garbling and evaluation
- Row reduction
- Half AND
- Oblivious transfer extensions
- Misc:
- TLS for garbler-evaluator protocol
- Incremental compiler
- Ed25519
- Parsing Ed25519 MPCL files
- for-range statements
- local variables in for-loop unrolling
- Pointer handling
- Pointer to struct field
- Cleanup pointer r-value handling
- Compound init values must be zero-padded to full size
- Circuit generation:
- SSA variable liveness analysis must be optimized
- Optimizing ssa.Value memory usage
- Parsing Ed25519 MPCL files
Circuit: #gates=7366376 (XOR=3146111 AND=3133757 OR=1032350 INV=54158)
ββββββββββ³ββββββββββββββββ³βββββββββ³ββββββββ
β Op β Time β % β Xfer β
β£ββββββββββββββββββββββββββββββββββββββββββ«
β Wait β 10.72622192s β 75.33% β β
β Recv β 2.501608027s β 17.57% β 364MB β
β Inputs β 232.257386ms β 1.63% β 41kB β
β Eval β 778.170361ms β 5.47% β β
β Result β 158.838Β΅s β 0.00% β 1kB β
β Total β 14.238416532s β β β
ββββββββββ»ββββββββββββββββ»βββββββββ»ββββββββ
Optimized full-adder:
Circuit: #gates=7366376 (XOR=5210811 AND=2101407 OR=0 INV=54158)
ββββββββββ³βββββββββββββββ³βββββββββ³ββββββββ
β Op β Time β % β Xfer β
β£βββββββββββββββββββββββββββββββββββββββββ«
β Wait β 7.9561241s β 76.09% β β
β Recv β 1.660386022s β 15.88% β 199MB β
β Inputs β 232.583145ms β 2.22% β 41kB β
β Eval β 606.479521ms β 5.80% β β
β Result β 304.202Β΅s β 0.00% β 1kB β
β Total β 10.45587699s β β β
ββββββββββ»βββββββββββββββ»βββββββββ»ββββββββ
Karatsuba multiplication algorithm:
Circuit: #gates=6822632 (XOR=4828475 XNOR=58368 AND=1874719 OR=0 INV=61070)
ββββββββββ³βββββββββββββββ³βββββββββ³ββββββββ
β Op β Time β % β Xfer β
β£βββββββββββββββββββββββββββββββββββββββββ«
β Wait β 6.672590283s β 75.99% β β
β Recv β 1.320621876s β 15.04% β 179MB β
β Inputs β 231.659961ms β 2.64% β 41kB β
β Eval β 555.192105ms β 6.32% β β
β Result β 298.855Β΅s β 0.00% β 1kB β
β Total β 8.78036308s β β β
ββββββββββ»βββββββββββββββ»βββββββββ»ββββββββ
Optimized INV-gates:
Circuit: #gates=6769820 (XOR=4836732 XNOR=58368 AND=1874719 OR=0 INV=1)
ββββββββββ³βββββββββββββββ³βββββββββ³ββββββββ
β Op β Time β % β Xfer β
β£βββββββββββββββββββββββββββββββββββββββββ«
β Wait β 6.338729612s β 75.20% β β
β Recv β 1.352880139s β 16.05% β 177MB β
β Inputs β 227.12815ms β 2.69% β 41kB β
β Eval β 509.574258ms β 6.05% β β
β Result β 344.425Β΅s β 0.00% β 1kB β
β Total β 8.428656584s β β β
ββββββββββ»βββββββββββββββ»βββββββββ»ββββββββ
Labels by value:
Circuit: #gates=6717340 (XOR=4787324 XNOR=108545 AND=1821471 OR=0 INV=0)
ββββββββββ³βββββββββββββββ³βββββββββ³ββββββββ
β Op β Time β % β Xfer β
β£βββββββββββββββββββββββββββββββββββββββββ«
β Wait β 6.117743762s β 77.25% β β
β Recv β 1.196140342s β 15.10% β 172MB β
β Inputs β 236.647371ms β 2.99% β 41kB β
β Eval β 368.944904ms β 4.66% β β
β Result β 347.483Β΅s β 0.00% β 1kB β
β Total β 7.919823862s β β β
ββββββββββ»βββββββββββββββ»βββββββββ»ββββββββ
Labels by value in protocol, garbler, and evaluator:
Circuit: #gates=6717340 (XOR=4787324 XNOR=108545 AND=1821471 OR=0 INV=0)
ββββββββββ³βββββββββββββββ³βββββββββ³ββββββββ
β Op β Time β % β Xfer β
β£βββββββββββββββββββββββββββββββββββββββββ«
β Wait β 5.076677475s β 78.67% β β
β Recv β 940.758975ms β 14.58% β 143MB β
β Inputs β 229.741398ms β 3.56% β 41kB β
β Eval β 205.513944ms β 3.18% β β
β Result β 185.197Β΅s β 0.00% β 1kB β
β Total β 6.452876989s β β β
ββββββββββ»βββββββββββββββ»βββββββββ»ββββββββ
Gate wires by value in garbler:
Circuit: #gates=6717340 (XOR=4787324 XNOR=108545 AND=1821471 OR=0 INV=0)
ββββββββββ³βββββββββββββββ³βββββββββ³ββββββββ
β Op β Time β % β Xfer β
β£βββββββββββββββββββββββββββββββββββββββββ«
β Wait β 4.37061338s β 76.48% β β
β Recv β 962.20669ms β 16.84% β 143MB β
β Inputs β 229.360283ms β 4.01% β 41kB β
β Eval β 152.258636ms β 2.66% β β
β Result β 162.316Β΅s β 0.00% β 1kB β
β Total β 5.714601305s β β β
ββββββββββ»βββββββββββββββ»βββββββββ»ββββββββ
Garbler keeping wires in an array instead of map:
Circuit: #gates=6717340 (XOR=4787324 XNOR=108545 AND=1821471 OR=0 INV=0)
ββββββββββ³βββββββββββββββ³βββββββββ³ββββββββ
β Op β Time β % β Xfer β
β£βββββββββββββββββββββββββββββββββββββββββ«
β Wait β 1.710655914s β 53.63% β β
β Recv β 1.088403425s β 34.12% β 143MB β
β Inputs β 243.393526ms β 7.63% β 41kB β
β Eval β 146.879726ms β 4.61% β β
β Result β 222.8Β΅s β 0.01% β 1kB β
β Total β 3.189555391s β β β
ββββββββββ»βββββββββββββββ»βββββββββ»ββββββββ
Pruning dead gates:
Circuit: #gates=5972956 (XOR=4315452 XNOR=53761 AND=1603743 OR=0 INV=0)
ββββββββββ³βββββββββββββββ³βββββββββ³ββββββββ
β Op β Time β % β Xfer β
β£βββββββββββββββββββββββββββββββββββββββββ«
β Wait β 1.28140619s β 55.30% β β
β Recv β 676.432166ms β 29.19% β 126MB β
β Inputs β 229.527559ms β 9.91% β 41kB β
β Eval β 129.623668ms β 5.59% β β
β Result β 203.248Β΅s β 0.01% β 1kB β
β Total β 2.317192831s β β β
ββββββββββ»βββββββββββββββ»βββββββββ»ββββββββ
Optimized garbling:
Circuit: #gates=5972956 (XOR=4315452 XNOR=53761 AND=1603743 OR=0 INV=0)
ββββββββββ³βββββββββββββββ³βββββββββ³ββββββββ
β Op β Time β % β Xfer β
β£βββββββββββββββββββββββββββββββββββββββββ«
β Wait β 700.031233ms β 38.57% β β
β Recv β 706.339086ms β 38.92% β 126MB β
β Inputs β 233.615365ms β 12.87% β 41kB β
β Eval β 174.84741ms β 9.63% β β
β Result β 215.733Β΅s β 0.01% β 1kB β
β Total β 1.815048827s β β β
ββββββββββ»βββββββββββββββ»βββββββββ»ββββββββ
Optimized dynamic memory allocations from garbling:
Circuit: #gates=5972956 (XOR=4315452 XNOR=53761 AND=1603743 OR=0 INV=0) #w=5973116
ββββββββββ³βββββββββββββββ³βββββββββ³ββββββββ
β Op β Time β % β Xfer β
β‘βββββββββββββββββββββββββββββββββββββββββ©
β Wait β 574.859679ms β 35.11% β β
β Recv β 678.520719ms β 41.44% β 126MB β
β Inputs β 274.49709ms β 16.77% β 41kB β
β Eval β 109.1673ms β 6.67% β β
β Result β 158.416Β΅s β 0.01% β 1kB β
β Total β 1.637203204s β β 126MB β
ββββββββββ΄βββββββββββββββ΄βββββββββ΄ββββββββ
The first signature computation without SHA-256:
βββββββββββββββ³ββββββββββββββββββ³βββββββββ³βββββββ
β Op β Time β % β Xfer β
β‘ββββββββββββββββββββββββββββββββββββββββββββββββ©
β Init β 141.651Β΅s β 0.00% β 276B β
β OT Init β 243.714662ms β 0.06% β 264B β
β Peer Inputs β 69.857046ms β 0.02% β 10kB β
β Eval β 6m55.975310366s β 99.92% β 25GB β
β Total β 6m56.289023725s β β 25GB β
βββββββββββββββ΄ββββββββββββββββββ΄βββββββββ΄βββββββ
Max permanent wires: 43786395, cached circuits: 26
#gates=935552365, #non-XOR=291882258
Optimized p2p.Conn.SendUint{16,32}() not to allocate memory:
βββββββββββββββ³ββββββββββββββββββ³βββββββββ³βββββββ
β Op β Time β % β Xfer β
β‘ββββββββββββββββββββββββββββββββββββββββββββββββ©
β Init β 41.043Β΅s β 0.00% β 276B β
β OT Init β 166.70528ms β 0.07% β 264B β
β Peer Inputs β 66.153689ms β 0.03% β 10kB β
β Eval β 3m48.820978578s β 99.90% β 25GB β
β Total β 3m49.05387859s β β 25GB β
βββββββββββββββ΄ββββββββββββββββββ΄βββββββββ΄βββββββ
Max permanent wires: 43786395, cached circuits: 26
#gates=935552365, #non-XOR=291882258
Added one missing ed25519.ScReduce() (+1220540 gates, +381217 non-XOR gates):
βββββββββββββββ³ββββββββββββββββββ³βββββββββ³βββββββ
β Op β Time β % β Xfer β
β‘ββββββββββββββββββββββββββββββββββββββββββββββββ©
β Init β 64.159Β΅s β 0.00% β 276B β
β OT Init β 317.026065ms β 0.14% β 264B β
β Peer Inputs β 66.088379ms β 0.03% β 10kB β
β Eval β 3m46.188650399s β 99.83% β 25GB β
β Total β 3m46.571829002s β β 25GB β
βββββββββββββββ΄ββββββββββββββββββ΄βββββββββ΄βββββββ
Max permanent wires: 43892147, cached circuits: 26
#gates=936772905, #non-XOR=292263475
Optimizing p2p.Conn.SendLabel() not to allocate memory:
βββββββββββββββ³ββββββββββββββββββ³βββββββββ³βββββββ
β Op β Time β % β Xfer β
β‘ββββββββββββββββββββββββββββββββββββββββββββββββ©
β Init β 73.349Β΅s β 0.00% β 276B β
β OT Init β 231.818904ms β 0.13% β 264B β
β Peer Inputs β 67.641816ms β 0.04% β 10kB β
β Eval β 2m57.583645976s β 99.83% β 25GB β
β Total β 2m57.883180045s β β 25GB β
βββββββββββββββ΄ββββββββββββββββββ΄βββββββββ΄βββββββ
Max permanent wires: 43892147, cached circuits: 26
#gates=936772905, #non-XOR=292263475
Input | MODP | Gates | Non-XOR | Stream Gates | Stream Non-XOR |
---|---|---|---|---|---|
2 | 4 | 708 | 201 | 730 | 205 |
4 | 8 | 5596 | 1571 | 5640 | 1579 |
8 | 16 | 44796 | 12423 | 44884 | 12439 |
16 | 32 | 374844 | 102255 | 375052 | 102287 |
32 | 64 | 2986556 | 801887 | 2986972 | 801951 |
64 | 128 | 23171068 | 6137023 | 23171900 | 6137151 |
128 | 256 | 177580028 | 46495359 | 177581692 | 46495615 |
256 | 512 | 1356768508 | 351848191 |
Input Size | Total gates | Non-XOR gates |
---|---|---|
2 | 7 | 3 |
4 | 29 | 13 |
8 | 145 | 57 |
16 | 655 | 241 |
32 | 3347 | 1100 |
64 | 13546 | 4242 |
128 | 49249 | 14986 |
256 | 167977 | 50167 |
512 | 549965 | 162147 |
1024 | 1752826 | 512099 |
2048 | 5485700 | 1592234 |
4096 | 16954032 | 4897756 |
8192 | 51940803 | 14953708 |
Optimized circuits from pkg/math/:
Implementation | XOR gates | AND gates | % of circ |
---|---|---|---|
add64.circ | 313 | 63 | |
sub64.circ | 442 | 63 | |
mul64.circ | 9707 | 4033 | |
div64.circ | 25328 | 4664 | |
MPCL a+b | 316 | 63 | 100 |
MPCL a-b | 319 | 63 | 100 |
MPCL a*b | 9304 | 4242 | 105.2 |
MPCL a/b | 24833 | 8192 | 175.6 |
package main
func main(a, b int) (int, int) {
q, r := a / b
return q, r
}