tinybin: Secure Comparison of User's Guess to API-Supplied Target


This secure comparison demo (source code available on GitHub) simulates a secure multi-party computation (MPC) protocol entirely in your browser by importing and invoking the tinynmc libraries using PyScript.

Within the implementation of the library, vectors of finite field elements are used to represent both the masked data sent to nodes and the overall result. In the visualizations below, instances of these data structures are visually presented as a variant of a heat map in which the brightness value corresponds to the difference between the finite field element and zero. For example, a vector such as 1 mod 115 mod 118 mod 113 mod 1110 mod 11 is represented by   .

  1. For both the guess that is supplied by a user and the target that is obtained from a third-party API service, masks are requested from the nodes. The guess and target number are then each masked and broadcast to the nodes.

Submit Guess (from User)


Submit Target (from API Endpoint)

The target value obtained
via an API GET request sent to https://httpbin.org/bytes/1
will appear here.
  1. Each node receives the masked data, and then locally computes its secret share of the overall result.

Node 0

Masked Guess

Masked Target

Secret Share of Result

Node 1

Masked Guess

Masked Target

Secret Share of Result

Node 2

Masked Guess

Masked Target

Secret Share of Result

  1. After receiving the secret shares of the result from each node, the adjudicating party can reconstruct the overall output to determine the outcome.

Network Output

packages = ["tinynmc~=0.2"]
from __future__ import annotations from typing import Dict, List, Tuple, Sequence, Iterable import secrets from modulo import modulo import tinynmc from tinynmc import node, preprocess from pyodide.ffi import create_proxy import asyncio import js def encode( data: Sequence[float], for_target: bool = False ) -> Dict[Tuple[int, int], int]: return ( {(1, 1): data[0]} if for_target else {(0, 0): -data[0], (0, 1): data[1], (1, 0): data[0]} ) def modulo_to_uint8(m): return (256 * int(m)) // m.modulus def show_share(share, element_id): element = js.document.getElementById(element_id) share = str(int(share)) element.innerHTML = share def compute_and_reveal(guess_masked, target_masked): shares = [] for (node_id, node) in enumerate(nodes): result_share = node.compute(signature, [guess_masked, target_masked]) shares.append(result_share) js.nodeShowShare(node_id) show_share(result_share, 'node-' + str(node_id) + '-share') js.document.getElementById('recipient-output').innerHTML = ( 'User guessed correctly!' if int(sum(shares)) == 0 else 'User guessed incorrectly.' ) def mask_and_submit_guess(_): global guess_masked js.clientMaskAndSubmitGuess() parity = js.getGuess() guess = [secrets.randbelow(MODULUS), int(parity)] js.nodesEnable() masks = [node.masks(encode(guess, False).keys()) for node in nodes] guess_masked = tinynmc.masked_factors(encode(guess, False), masks) for node_id in range(3): js.nodeShowSubmissionMasked( 0, node_id, [modulo_to_uint8(m) for m in guess_masked.values()] ) async def mask_and_submit_target(_): response = await js.retrieveByteFromHTTPBin() target = [response % 2] js.recipientEnable() js.clientMaskAndSubmitTarget() masks = [node.masks(encode(target, True).keys()) for node in nodes] target_masked = tinynmc.masked_factors(encode(target, True), masks) for node_id in range(3): js.nodeShowSubmissionMasked( 1, node_id, [modulo_to_uint8(m) for m in target_masked.values()] ) compute_and_reveal(guess_masked, target_masked) MODULUS = 340282366920938463463374607431768196007 guess_masked = {} # Global. signature = [2, 2] nodes = [node(), node(), node()] preprocess(signature, nodes) js.document.getElementById('guess').addEventListener( 'click', create_proxy(mask_and_submit_guess) ) js.document.getElementById('retrieve').addEventListener( 'click', create_proxy(mask_and_submit_target) )