Skip to content

Commit

Permalink
WIP: asynchromix docs
Browse files Browse the repository at this point in the history
* Add documentation for asynchromix app.
* Remove extra whitespace in asynchromix contract
  • Loading branch information
sbellem committed Mar 18, 2020
1 parent 66ef014 commit dfe30ea
Show file tree
Hide file tree
Showing 11 changed files with 627 additions and 58 deletions.
70 changes: 58 additions & 12 deletions apps/asynchromix/asynchromix.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,30 @@ async def wait_for_receipt(w3, tx_hash):
########


class AsynchromixClient(object):
class AsynchromixClient:
"""An Asynchromix client sends "masked" messages to an Ethereum contract.
...
"""

def __init__(self, sid, myid, send, recv, w3, contract, req_mask):
"""
Parameters
----------
sid: int
Session id.
myid: int
Client id.
send:
Function used to send messages. Not used?
recv:
Function used to receive messages. Not used?
w3:
Connection instance to an Ethereum node.
contract:
Contract instance on the Ethereum blockchain.
req_mask:
Function used to request an input mask from a server.
"""
self.sid = sid
self.myid = myid
self.contract = contract
Expand All @@ -66,7 +88,8 @@ async def _run(self):
contract_concise = ConciseContract(self.contract)
await asyncio.sleep(60) # give the servers a head start
# Client sends several batches of messages then quits
for epoch in range(1000):
# for epoch in range(1000):
for epoch in range(10):
logging.info(f"[Client] Starting Epoch {epoch}")
receipts = []
for i in range(32):
Expand Down Expand Up @@ -125,13 +148,13 @@ async def send_message(self, m):
# Step 3. Fetch the input mask from the servers
inputmask = await self._get_inputmask(inputmask_idx)
message = int.from_bytes(m.encode(), "big")
maskedinput = message + inputmask
maskedinput_bytes = self.w3.toBytes(hexstr=hex(maskedinput.value))
maskedinput_bytes = maskedinput_bytes.rjust(32, b"\x00")
masked_message = message + inputmask
masked_message_bytes = self.w3.toBytes(hexstr=hex(masked_message.value))
masked_message_bytes = masked_message_bytes.rjust(32, b"\x00")

# Step 4. Publish the masked input
tx_hash = self.contract.functions.submit_message(
inputmask_idx, maskedinput_bytes
inputmask_idx, masked_message_bytes
).transact({"from": self.w3.eth.accounts[0]})
tx_receipt = await wait_for_receipt(self.w3, tx_hash)

Expand All @@ -142,19 +165,42 @@ async def send_message(self, m):


class AsynchromixServer(object):
"""Asynchromix server class to ..."""

def __init__(self, sid, myid, send, recv, w3, contract):
"""
Parameters
----------
sid: int
Session id.
myid: int
Client id.
send:
Function used to send messages.
recv:
Function used to receive messages.
w3:
Connection instance to an Ethereum node.
contract:
Contract instance on the Ethereum blockchain.
"""
self.sid = sid
self.myid = myid
self.contract = contract
self.w3 = w3

self._task1a = asyncio.ensure_future(self._offline_inputmasks_loop())
self._task1a.add_done_callback(print_exception_callback)

self._task1b = asyncio.ensure_future(self._offline_mixes_loop())
self._task1b.add_done_callback(print_exception_callback)

self._task2 = asyncio.ensure_future(self._client_request_loop())
self._task2.add_done_callback(print_exception_callback)

self._task3 = asyncio.ensure_future(self._mixing_loop())
self._task3.add_done_callback(print_exception_callback)

self._task4 = asyncio.ensure_future(self._mixing_initiate_loop())
self._task4.add_done_callback(print_exception_callback)

Expand Down Expand Up @@ -186,7 +232,7 @@ async def join(self):
The bits and triples are consumed by each mixing epoch.
The input masks may be claimed at a different rate than
than the mixing epochs so they are replenished in a separate
the mixing epochs so they are replenished in a separate
task
"""

Expand Down Expand Up @@ -326,13 +372,13 @@ async def _mixing_loop(self):
# 3.b. Collect the inputs
inputs = []
for idx in range(epoch * K, (epoch + 1) * K):
# Get the public input
masked_input, inputmask_idx = contract_concise.input_queue(idx)
masked_input = field(int.from_bytes(masked_input, "big"))
# Get the input masks
# Get the public input (masked message)
masked_message_bytes, inputmask_idx = contract_concise.input_queue(idx)
masked_message = field(int.from_bytes(masked_message_bytes, "big"))
# Get the input mask
inputmask = self._inputmasks[inputmask_idx]

m_share = masked_input - inputmask
m_share = masked_message - inputmask
inputs.append(m_share)

# 3.c. Collect the preprocessing
Expand Down
75 changes: 38 additions & 37 deletions apps/asynchromix/asynchromix.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ contract AsynchromixCoordinator {
* 3. Initiates mixing epochs (MPC computations)
* (makes use of preprocess triples, bits, powers)
*/

// Session parameters
uint public n;
uint public t;
address[] public servers;
mapping (address => uint) public servermap;

constructor(address[] _servers, uint _t) public {
n = _servers.length;
t = _t;
Expand All @@ -34,7 +34,7 @@ contract AsynchromixCoordinator {
"0xca35b7d915458ef540ade6068dfe2f44e8fa733c"]
*
*/

// ###############################################
// 1. Preprocessing Buffer (the MPC offline phase)
// ###############################################
Expand All @@ -44,26 +44,26 @@ contract AsynchromixCoordinator {
uint bits; // [b] with b in {-1,1}
uint inputmasks; // [r]
}

// Consensus count (min of the player report counts)
PreProcessCount public preprocess;

// How many of each have been reserved already
PreProcessCount public preprocess_used;

function inputmasks_available () public view returns(uint) {
return preprocess.inputmasks - preprocess_used.inputmasks;
}
}

// Report of preprocess buffer size from each server
mapping ( uint => PreProcessCount ) public preprocess_reports;

event PreProcessUpdated();

function min(uint a, uint b) private pure returns (uint) {
return a < b ? a : b;
}
}

function max(uint a, uint b) private pure returns (uint) {
return a > b ? a : b;
}
Expand Down Expand Up @@ -96,85 +96,86 @@ contract AsynchromixCoordinator {
preprocess.bits = mins.bits;
preprocess.inputmasks = mins.inputmasks;
}



// ######################
// 2. Accept client input
// ######################

// Step 2.a. Clients can reserve an input mask [r] from Preprocessing

// maps each element of preprocess.inputmasks to the client (if any) that claims it
mapping (uint => address) public inputmasks_claimed;

event InputMaskClaimed(address client, uint inputmask_idx);

// Client reserves a random values
function reserve_inputmask() public returns(uint) {
// Extension point: override this function to add custom token rules

// An unclaimed input mask must already be available
require(preprocess.inputmasks > preprocess_used.inputmasks);

// Acquire this input mask for msg.sender
uint idx = preprocess_used.inputmasks;
inputmasks_claimed[idx] = msg.sender;
preprocess_used.inputmasks += 1;
emit InputMaskClaimed(msg.sender, idx);
return idx;
}

// Step 2.b. Client requests (out of band, e.g. over https) shares of [r]
// from each server. Servers use this function to check authorization.
// Authentication using client's address is also out of band
function client_authorized(address client, uint idx) view public returns(bool) {
return inputmasks_claimed[idx] == client;
}

// Step 2.c. Clients publish masked message (m+r) to provide a new input [m]
// and bind it to the preprocess input
mapping (uint => bool) public inputmask_map; // Maps a mask

struct Input {
bytes32 masked_input; // (m+r)
uint inputmask; // index in inputmask of mask [r]

// Extension point: add more metadata about each input
}

Input[] public input_queue; // All inputs sent so far
function input_queue_length() public view returns(uint) {
return input_queue.length;
}

event MessageSubmitted(uint idx, uint inputmask_idx, bytes32 masked_input);

function submit_message(uint inputmask_idx, bytes32 masked_input) public {
// Must be authorized to use this input mask
require(inputmasks_claimed[inputmask_idx] == msg.sender);

// Extension point: add additional client authorizations,
// e.g. prevent the client from submitting more than one message per mix

uint idx = input_queue.length;
input_queue.length += 1;

input_queue[idx].masked_input = masked_input;
input_queue[idx].inputmask = inputmask_idx;


// QUESTION: What is the purpose of this event?
emit MessageSubmitted(idx, inputmask_idx, masked_input);

// The input masks are deactivated after first use
inputmasks_claimed[inputmask_idx] = address(0);
}

// #########################
// 3. Initiate Mixing Epochs
// #########################

uint public constant K = 32; // Mix Size

// Preprocessing requirements
uint public constant PER_MIX_TRIPLES = (K / 2) * 5 * 5; // k log^2 k
uint public constant PER_MIX_BITS = (K / 2) * 5 * 5;
Expand All @@ -187,7 +188,7 @@ contract AsynchromixCoordinator {
return min(triples_available / PER_MIX_TRIPLES,
bits_available / PER_MIX_BITS);
}

// Step 3.a. Trigger a mix to start
uint public inputs_mixed;
uint public epochs_initiated;
Expand All @@ -196,27 +197,27 @@ contract AsynchromixCoordinator {
function inputs_ready() public view returns(uint) {
return input_queue.length - inputs_mixed;
}

function initiate_mix() public {
// Must mix eactly K values in each epoch
require(input_queue.length >= inputs_mixed + K);

// Can only initiate mix if enough preprocessings are ready
require(preprocess.triples >= preprocess_used.triples + PER_MIX_TRIPLES);
require(preprocess.bits >= preprocess_used.bits + PER_MIX_BITS);
preprocess_used.triples += PER_MIX_TRIPLES;
preprocess_used.bits += PER_MIX_BITS;

inputs_mixed += K;
emit MixingEpochInitiated(epochs_initiated);
epochs_initiated += 1;
output_votes.length = epochs_initiated;
output_hashes.length = epochs_initiated;
}

// Step 3.b. Output reporting: the output is considered "approved" once
// at least t+1 servers report it

uint public outputs_ready;
event MixOutput(uint epoch, string output);
bytes32[] public output_hashes;
Expand All @@ -242,7 +243,7 @@ contract AsynchromixCoordinator {
} else {
output_hashes[epoch] = output_hash;
}

output_votes[epoch] += 1;
if (output_votes[epoch] == t + 1) { // at least one honest node agrees
emit MixOutput(epoch, output);
Expand Down
10 changes: 10 additions & 0 deletions apps/asynchromix/powermixing.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ async def async_mixing_in_processes(network_info, n, t, k, run_id, node_id):
if __name__ == "__main__":
from honeybadgermpc.config import HbmpcConfig

logging.info("Running powermixing app ...")
HbmpcConfig.load_config()

run_id = HbmpcConfig.extras["run_id"]
Expand All @@ -181,6 +182,10 @@ async def async_mixing_in_processes(network_info, n, t, k, run_id, node_id):

try:
if not HbmpcConfig.skip_preprocessing:
logging.info(
"Running preprocessing.\n"
'To skip preprocessing phase set "skip_preprocessing" config to true.'
)
# Need to keep these fixed when running on processes.
field = GF(Subgroup.BLS12_381)
a_s = [field(i) for i in range(1000 + k, 1000, -1)]
Expand All @@ -191,6 +196,11 @@ async def async_mixing_in_processes(network_info, n, t, k, run_id, node_id):
pp_elements.preprocessing_done()
else:
loop.run_until_complete(pp_elements.wait_for_preprocessing())
else:
logging.info(
"Skipping preprocessing.\n"
'To run preprocessing phase set "skip_preprocessing" config to false.'
)

loop.run_until_complete(
async_mixing_in_processes(
Expand Down
3 changes: 3 additions & 0 deletions doc8.ini
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
[doc8]
ignore-path=docs/_build,honeybadgermpc.egg-info/
# ignore math mode nowrap errors until problem is fixed
# see https://github.com/PyCQA/doc8/pull/32 for more details
ignore=D000
Loading

0 comments on commit dfe30ea

Please sign in to comment.