Skip to content

Commit

Permalink
Adding a couple tutorial missions
Browse files Browse the repository at this point in the history
remove redundant gen_test_bit

Update tutorial

Create README.md

Update README.md

make more foolproof

Update README.md

Update README.md

Fixes to tutorial (thx sbellem)
  • Loading branch information
amiller committed Jun 8, 2019
1 parent 0636730 commit 39b3097
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 37 deletions.
3 changes: 2 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
max_line_length=89
exclude =
charm/,
.eggs/
.eggs/,
apps/tutorial/
4 changes: 2 additions & 2 deletions apps/asynchromix/asynchromix.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ def run_and_terminate_process(*args, **kwargs):
p = subprocess.Popen(*args, **kwargs)
yield p
finally:
logging.info("Killing ganache-cli", p.pid)
logging.info(f"Killing ganache-cli {p.pid}")
p.terminate() # send sigterm, or ...
p.kill() # send sigkill
p.wait()
Expand Down Expand Up @@ -507,7 +507,7 @@ def test_asynchromix():
# with run_and_terminate_process(cmd, shell=True,
# stdout=sys.stdout, stderr=sys.stderr) as proc:
cmd = "ganache-cli -p 8545 -a 50 -b 1 > acctKeys.json 2>&1"
logging.info("Running", cmd)
logging.info(f"Running {cmd}")
with run_and_terminate_process(cmd, shell=True):
time.sleep(5)
run_eth()
Expand Down
56 changes: 56 additions & 0 deletions apps/tutorial/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
HoneyBadgerMPC Tutorial
==
This folder [(`apps/tutorial`)](./) contains a few tutorials as a starting point for developing with HoneyBadgerMPC. The tutorials assume you have a working development environment (instructions).


I. [`hbmpc-tutorial-1.py`](./hbmpc-tutorial-1.py)
--
The first tutorial gives a tour of the MPC programming environment. To summarize:
- Secret shares are represented by a `Share` object.
- linear operations on `Share` objects are just computed locally
- `share.open()`: causes the servers to communicate with each other to open a share
- `ctx.preproc.get_random()`: (and related functions) can be used to get fetch random `Share`s from preprocessing. This is used when multiplying two `Share` objects
- a `Share` supports dataflow programming with Futures, like in Viff
- `SharreArray(shares).open()`: batch methods (TODO)
- an MPC program run is always run in an MPC context, like `def my_mpc_program(ctx, ...)`. When it's running, `ctx.myid` gives the name of the currently running server
- The simplest way to run the MPC program is with `TaskProgramRunner`, which runs each server as a lightweight process in a simulated network. This is the simplest operating mode, so it's the way to start when writing an MPC program and testing it
- There are some lines you can uncomment to simulate Byzantine faults and see how HoneyBadgerMPC handles them

Simple MPC programs:
- `beaver_multiply` the hello world of MPC program
- `random_permute_pair` as used in the mixing application
- `dot product`

More examples of this programming model can be found in:
- [`honeybadgermpc/progs/mixins/share_arithmetic.py`](../../honeybadgermpc/progs/mixins/share_arithmetic.py)
- operations on fixed-point numbers (rather than field elements) [honeybadgermpc/progs/fixedpoint.py](../../honeybadgermpc/progs/fixedpoint.py) (TODO)
- [`honeybadgermpc/progs/mimc.py`](../../honeybadgermpc/progs/mimc.py) symmetric key cryptography
- [`honeybadgermpc/progs/jubjub.py`](../../honeybadgermpc/progs/jubjub.py) public key cryptography
- [`apps/asynchromix/butterfly_network.py`](../../apps/asynchromix/butterfly_network.py) switching network based on the random pair permutation

To check the development environment works:
- Follow [these instructions](../../docs/development/getting-started.rst#managing-your-development-environment-with-docker-compose) to set up the `docker-compose` development environment
- The tutorials assume you have a shell session in the development container, so run:
```
$ docker-compose run --rm honeybadgermpc bash
root@{containerid}:/usr/src/HoneyBadgerMPC# python apps/tutorial/hbmpc-tutorial-1.py
```
and look for the output `Tutorial 1 ran successfully`

II. [`hbmpc-tutorial-2.py`](./hbmpc-tutorial-2.py)
---
The second tutorial shows how to run the MPC program in different processes that communicate over sockets. Run it with
```
scripts/launch-tmuxlocal.sh apps/tutorial/hbmpc-tutorial-2.py conf/mpc/local
```
This scripts launches `4` processes (in the `n=4,t=1` setting) each in its own terminal subwindow.
You can crash one other terminals at any time, the rest will still be available.

This script also creates a simulated latency using the `tc` tool (look for the call to [`scripts/latency-control.sh`](../../scripts/latency-control.sh). The latency and jitter can be changed by modifying the script.

III. Blockchain integration
---
Tutorial coming soon... but for now you can run and look at the examples in [`apps/asynchromix`](../asynchromix) which uses Ethereum (`web3py` library, and a Solidity smart contract) as an MPC coordinator and broadcast channel.
```
python apps/asynchromix/asynchromix.py
```
135 changes: 135 additions & 0 deletions apps/tutorial/hbmpc-tutorial-1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
"""
hbMPC tutorial 1. Running sample MPC programs in the testing simulator
"""
import asyncio
from honeybadgermpc.mpc import TaskProgramRunner
from honeybadgermpc.progs.mixins.dataflow import (
Share, ShareArray, ShareFuture, GFElementFuture)
from honeybadgermpc.preprocessing import (
PreProcessedElements as FakePreProcessedElements)
from honeybadgermpc.utils.typecheck import TypeCheck
from honeybadgermpc.progs.mixins.share_arithmetic import (
MixinConstants, BeaverMultiply, BeaverMultiplyArrays)
config = {MixinConstants.MultiplyShareArray: BeaverMultiplyArrays(),
MixinConstants.MultiplyShare: BeaverMultiply(), }


@TypeCheck()
async def beaver_multiply(ctx, x: Share, y: Share):
"""The hello world of MPC: beaver multiplication
- Linear operations on Share objects are easy
- Shares of random values are available from preprocessing
- Opening a Share returns a GFElementFuture
"""
a, b, ab = ctx.preproc.get_triple(ctx)
D = await (x - a).open()
E = await (y - b).open()

# D*E is multiplying GFElements
# D*b, E*a are multiplying GFElement x Share -> Share
# ab is a Share
# overall the sum is a Share

xy = (D * E) + (D * b) + (E * a) + ab
return xy


async def random_permute_pair(ctx, x, y):
"""
Randomly permute a pair of secret shared values.
Input: `x`, `y` are `Share` objects
Output: A pair of `Share` objects `(o1,o2)`, which are fresh
shares that take on the value `(x,y)` or `(y,x)` with equal
probability
Preprocessing:
- One random bit
- One beaver multiplication
"""
b = ctx.preproc.get_bit(ctx)
# just a local scalar multiplication
one_or_minus_one = ctx.field(2) * b - ctx.field(1)
m = one_or_minus_one * (x - y)
o1 = (x + y + m) * (1 / ctx.field(2))
o2 = (x + y - m) * (1 / ctx.field(2))
return (o1, o2)


# Working with arrays
def dot_product(ctx, x_shares, y_shares):
"""Although the above example of Beaver multiplication is perfectly valid,
you can also just use the `*` operator of the Share object, which does
the same thing.
This is also an example of dataflow programming. The return value of this
operation is a `ShareFuture`, which defines addition and multiplication
operations as well (like in Viff). As a result, all of these multiplications
can take place in parallel.
"""
res = ctx.ShareFuture()
res.set_result(ctx.Share(0))
for x, y in zip(x_shares, y_shares):
res += x * y
return res


async def prog(ctx):
# Test with random sharings of hardcoded values
ctx.preproc = FakePreProcessedElements()
x = ctx.Share(5) + ctx.preproc.get_zero(ctx)
y = ctx.Share(7) + ctx.preproc.get_zero(ctx)
xy = await beaver_multiply(ctx, x, y)

# Check openings of the multiplied values
X = await x.open()
Y = await y.open()
XY = await xy.open()
assert XY == X * Y
print(f'[{ctx.myid}] Beaver Multiplication OK')
# print(f'x:{y} y:{x}: xy:{xy}')
# print(f'x.open(): {X} y.open(): {Y} xy.open(): {XY}')

# Sample dot product (4 * 5 + 8 * 10) == 100
# Each product of two Shares returns a ShareFuture
a = ctx.Share(4) + ctx.preproc.get_zero(ctx)
b = ctx.Share(8) + ctx.preproc.get_zero(ctx)
c = ctx.Share(5) + ctx.preproc.get_zero(ctx)
d = ctx.Share(10) + ctx.preproc.get_zero(ctx)
res = dot_product(ctx, (a, b), (c, d))
res_ = await res.open()
assert res_ == res
print(f'[{ctx.myid}] Dot Product OK')

# Randomly permute (x,y) or (y,x)
o1, o2 = await random_permute_pair(ctx, x, y)
# Unless you open it, no one knows which permutation it is
O1 = await o1.open()
O2 = await o2.open()
# print(f'O1:{O1} O2:{O2}')
assert O1 in (X, Y) and O2 in (X, Y)
print(f'[{ctx.myid}] Permute Pair OK')


async def tutorial_1():
# Create a test network of 4 nodes (no sockets, just asyncio tasks)
n, t = 4, 1
pp = FakePreProcessedElements()
pp.generate_zeros(100, n, t)
pp.generate_triples(100, n, t)
pp.generate_bits(100, n, t)
program_runner = TaskProgramRunner(n, t, config)
program_runner.add(prog)
results = await program_runner.join()
return results


def main():
# Run the tutorials
asyncio.set_event_loop(asyncio.new_event_loop())
loop = asyncio.get_event_loop()
loop.run_until_complete(tutorial_1())
# loop.run_until_complete(tutorial_2())


if __name__ == '__main__':
main()
print("Tutorial 1 ran successfully")
74 changes: 74 additions & 0 deletions apps/tutorial/hbmpc-tutorial-2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
hbMPC tutorial 2.
Instructions:
run this with
```
scripts/launch-tmuxlocal.sh apps/tutorial/hbmpc-tutorial-2.py conf/mpc/local
```
"""
import asyncio
import logging
from honeybadgermpc.preprocessing import (
PreProcessedElements as FakePreProcessedElements,
wait_for_preprocessing, preprocessing_done)
from honeybadgermpc.progs.mixins.dataflow import (
Share, ShareArray, ShareFuture, GFElementFuture)
from honeybadgermpc.utils.typecheck import TypeCheck
from honeybadgermpc.progs.mixins.share_arithmetic import (
MixinConstants, BeaverMultiply, BeaverMultiplyArrays)
mpc_config = {MixinConstants.MultiplyShareArray: BeaverMultiplyArrays(),
MixinConstants.MultiplyShare: BeaverMultiply(), }


async def dot_product(ctx, xs, ys):
return sum((x * y for x, y in zip(xs, ys)), ctx.Share(0))

async def prog(ctx, k=50):
# Computing a dot product by MPC (k openings)
ctx.preproc = FakePreProcessedElements()
xs = [ctx.preproc.get_bit(ctx) for _ in range(k)]
ys = [ctx.preproc.get_bit(ctx) for _ in range(k)]
logging.info(f"[{ctx.myid}] Running prog 1.")
res = await dot_product(ctx, xs, ys)

R = await res.open()
XS = await ctx.ShareArray(xs).open()
YS = await ctx.ShareArray(ys).open()
assert R == sum([X * Y for X, Y in zip(XS, YS)])
logging.info(f"[{ctx.myid}] done")


async def _run(peers, n, t, my_id):
from honeybadgermpc.ipc import ProcessProgramRunner
async with ProcessProgramRunner(peers, n, t, my_id, mpc_config) as runner:
await runner.execute('0', prog)
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 sys
import os
if not HbmpcConfig.peers:
print(f'WARNING: the $CONFIG_PATH environment variable wasn\'t set. Please run this file with `scripts/launch_tmuxlocal.sh apps/tutorial/hbmpc-tutorial-2.py conf/local/mpc`')
sys.exit(1)

asyncio.set_event_loop(asyncio.new_event_loop())
loop = asyncio.get_event_loop()
loop.set_debug(True)
try:
if HbmpcConfig.my_id == 0:
k = 100 # How many of each kind of preproc
pp_elements = FakePreProcessedElements()
pp_elements.generate_bits(k, HbmpcConfig.N, HbmpcConfig.t)
pp_elements.generate_triples(k, HbmpcConfig.N, HbmpcConfig.t)
preprocessing_done()
else:
loop.run_until_complete(wait_for_preprocessing())

loop.run_until_complete(
_run(HbmpcConfig.peers, HbmpcConfig.N, HbmpcConfig.t, HbmpcConfig.my_id))
finally:
loop.close()
34 changes: 0 additions & 34 deletions honeybadgermpc/progs/mixins/share_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,40 +179,6 @@ def legendre_mod_p(a: GFElement):
return -1
return 0

@staticmethod
@TypeCheck()
async def _gen_test_bit(context: Mpc, diff: Share):
# # b \in {0, 1}
b = MixinBase.pp_elements.get_bit(context)

# # _b \in {5, 1}, for p = 1 mod 8, s.t. (5/p) = -1
# # so _b = -4 * b + 5
_b = (-4 * b) + context.Share(5)

_r = MixinBase.pp_elements.get_rand(context)
_rp = MixinBase.pp_elements.get_rand(context)

# c = a * r + b * rp * rp
# If b_i == 1, c_i is guaranteed to be a square modulo p if a is zero
# and with probability 1/2 otherwise (except if rp == 0).
# If b_i == -1 it will be non-square.
c = await ((diff * _r) + (_b * _rp * _rp)).open()

return c, _b

@staticmethod
@TypeCheck
async def gen_test_bit(context: Mpc, diff: Share):
cj, bj = await Equality._gen_test_bit(context, diff)
while cj == 0:
cj, bj = await Equality._gen_test_bit(context, diff)

legendre = Equality.legendre_mod_p(cj)
if legendre == 0:
return Equality.gen_test_bit(context, diff)

return (legendre / context.field(2)) * (bj + context.Share(legendre))

@staticmethod
@TypeCheck()
async def _prog(context: Mpc,
Expand Down

0 comments on commit 39b3097

Please sign in to comment.