tinybio: Secure Decentralized Biometric Authentication Demo


This decentralized biometric authentication demo (source code available on GitHub) simulates a secure multi-party computation (MPC) protocol entirely in your browser by importing and invoking the tinybio and underlying tinynmc libraries using PyScript. The face-api.js library is used for face detection and to generate descriptors.

Within the implementation of the library, vectors of finite field elements are used to represent both submitted descriptors and the overall result. In the visualizations below, instances of these data structures are rendered using 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 would be represented by   .

  1. During both registration and authentication, the selected image is encoded as a descriptor (i.e., a vector numeric values), masks are requested from the nodes, and the masked descriptor (or token) is broadcast to all the nodes.

Registration

Image
Descriptor
Please select an image on the left

Authentication

Image
Descriptor
Please select an image on the left
  1. Each node receives the masked descriptor vectors corresponding the two images, and then locally computes its secret share of the overall Euclidean distance between the two vectors.

Node 0

Registration Token

Authentication Token

Secret Share of Result

Node 1

Registration Token

Authentication Token

Secret Share of Result

Node 2

Registration Token

Authentication Token

Secret Share of Result

  1. The validating party receives the secret shares of the result from each node and reconstructs the overall output.

Network Output

packages = ["tinybio~=0.1"]
from pyodide.ffi import create_proxy import js from tinybio import * def modulo_to_uint8(m): return (256 * int(m)) // m.modulus def show_with_cutoff(s, _limit=12): return s[:(_limit - 3)] + '...' if len(s) > _limit else s def show_share(share, element_id): element = js.document.getElementById(element_id) share = str(int(share)) element.innerHTML = show_with_cutoff(share, len(share)) def compute_and_reveal(reg_token, auth_token): shares = [] for (node_id, node) in enumerate(nodes): result_share = node.authenticate(reg_token, auth_token) shares.append(result_share) js.nodeShowShare(node_id) show_share(result_share, 'node-' + str(node_id) + '-share') result = reveal(shares) js.document.getElementById('recipient-output').innerHTML = \ 'Euclidean distance between descriptors: ' + str(result)[:7] + '.' def mask_and_register(_): global reg_token js.clientMaskAndRegister() descriptor = list(js.clientGetRegistrationDescriptor()) js.clientShowDescriptor(0, [64 + (int(255 * i) % 196) for i in descriptor]) js.nodesEnable() masks = [node.masks(request.registration(descriptor)) for node in nodes] reg_token = token.registration(masks, descriptor) for node_id in range(3): js.nodeShowTokenMasked(0, node_id, [modulo_to_uint8(m) for m in reg_token.values()]) def mask_and_authenticate(_): js.clientMaskAndAuthenticate() descriptor = list(js.clientGetAuthenticationDescriptor()) js.clientShowDescriptor(1, [64 + (int(255 * i) % 196) for i in descriptor]) js.recipientEnable() masks = [node.masks(request.authentication(descriptor)) for node in nodes] auth_token = token.authentication(masks, descriptor) for node_id in range(3): js.nodeShowTokenMasked(1, node_id, [modulo_to_uint8(m) for m in auth_token.values()]) compute_and_reveal(reg_token, auth_token) reg_token = {} # Global. nodes = [node(), node(), node()] preprocess(nodes, length=128) js.document.getElementById('register').addEventListener('click', create_proxy(mask_and_register)) js.document.getElementById('authenticate').addEventListener('click', create_proxy(mask_and_authenticate))