From 39b30978f07658f368fe4af2130b5179f3b1ebba Mon Sep 17 00:00:00 2001 From: Andrew Miller Date: Wed, 5 Jun 2019 03:17:58 -0500 Subject: [PATCH] Adding a couple tutorial missions 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) --- .flake8 | 3 +- apps/asynchromix/asynchromix.py | 4 +- apps/tutorial/README.md | 56 ++++++++ apps/tutorial/hbmpc-tutorial-1.py | 135 ++++++++++++++++++ apps/tutorial/hbmpc-tutorial-2.py | 74 ++++++++++ .../progs/mixins/share_arithmetic.py | 34 ----- 6 files changed, 269 insertions(+), 37 deletions(-) create mode 100644 apps/tutorial/README.md create mode 100644 apps/tutorial/hbmpc-tutorial-1.py create mode 100644 apps/tutorial/hbmpc-tutorial-2.py diff --git a/.flake8 b/.flake8 index 6da8b1dd..c17c0bdf 100644 --- a/.flake8 +++ b/.flake8 @@ -2,4 +2,5 @@ max_line_length=89 exclude = charm/, - .eggs/ + .eggs/, + apps/tutorial/ \ No newline at end of file diff --git a/apps/asynchromix/asynchromix.py b/apps/asynchromix/asynchromix.py index 04a96a9f..1c451d58 100644 --- a/apps/asynchromix/asynchromix.py +++ b/apps/asynchromix/asynchromix.py @@ -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() @@ -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() diff --git a/apps/tutorial/README.md b/apps/tutorial/README.md new file mode 100644 index 00000000..0a433b23 --- /dev/null +++ b/apps/tutorial/README.md @@ -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 +``` diff --git a/apps/tutorial/hbmpc-tutorial-1.py b/apps/tutorial/hbmpc-tutorial-1.py new file mode 100644 index 00000000..4800a234 --- /dev/null +++ b/apps/tutorial/hbmpc-tutorial-1.py @@ -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") diff --git a/apps/tutorial/hbmpc-tutorial-2.py b/apps/tutorial/hbmpc-tutorial-2.py new file mode 100644 index 00000000..c8f92754 --- /dev/null +++ b/apps/tutorial/hbmpc-tutorial-2.py @@ -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() diff --git a/honeybadgermpc/progs/mixins/share_arithmetic.py b/honeybadgermpc/progs/mixins/share_arithmetic.py index 8a95e268..c6f331da 100644 --- a/honeybadgermpc/progs/mixins/share_arithmetic.py +++ b/honeybadgermpc/progs/mixins/share_arithmetic.py @@ -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,