Skip to content

Commit

Permalink
Add annotations, doctests with full coverage, and Coveralls; update R…
Browse files Browse the repository at this point in the history
…EADME.
  • Loading branch information
lapets committed Dec 6, 2023
1 parent 02993ff commit 2add605
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 26 deletions.
1 change: 1 addition & 0 deletions .github/workflows/lint-test-cover-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
if: matrix.python-version == '3.11'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
- name: Test auto-generation of documentation.
run: |
pip install -U .[docs]
Expand Down
44 changes: 43 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
tinybio
=======

Minimal pure-Python library that implements a basic version of a secure decentralized biometric authentication functionality via a `secure multi-party computation protocol <https://eprint.iacr.org/2023/1740>`__.
Minimal pure-Python library that implements a basic version of a `secure decentralized biometric authentication <https://nillion.pub/decentralized-multifactor-authentication.pdf>`__ functionality via a `secure multi-party computation protocol <https://eprint.iacr.org/2023/1740>`__.

|pypi| |readthedocs| |actions|

Expand All @@ -18,6 +18,10 @@ Minimal pure-Python library that implements a basic version of a secure decentra
:target: https://github.com/nillion-oss/tinybio/actions/workflows/lint-test-cover-docs.yml
:alt: GitHub Actions status.

.. |coveralls| image:: https://coveralls.io/repos/github/nillion-oss/tinybio/badge.svg?branch=main
:target: https://coveralls.io/github/nillion-oss/tinybio?branch=main
:alt: Coveralls test coverage summary.

Installation and Usage
----------------------

Expand All @@ -34,6 +38,44 @@ The library can be imported in the usual way:
import tinybio
from tinybio import *
Basic Example
^^^^^^^^^^^^^

Suppose that a workflows is supported by three nodes (parties performing the decentralized registration and authentication functions). The node objects would be instantiated locally by each of these three parties:

.. code-block:: python
>>> nodes = [node(), node(), node()]
The preprocessing phase that the nodes must execute can be simulated:

.. code-block:: python
>>> preprocess(3, nodes)
It is then possible to register some data (such as a biometric descriptor represented as a vector of floating point values) by requesting the masks from each node and submitting a registration *token* (*i.e.*, a masked descriptor that is computed locally by the registering party) to the nodes:

.. code-block:: python
>>> reg_descriptor = [0.5, 0.3, 0.7]
>>> reg_masks = [node.masks(registration_request(reg_descriptor)) for node in nodes]
>>> reg_token = registration_token(reg_masks, reg_descriptor)
At a later point, it is possible to perform an authentication workflow. After requesting masks for the authentication descriptor, the authentication token (*i.e.*, a masked descriptor) can be generated locally by the party interested in authenticating itself:

.. code-block:: python
>>> auth_descriptor = [0.1, 0.4, 0.8]
>>> auth_masks = [node.masks(authentication_request(auth_descriptor)) for node in nodes]
>>> auth_token = authentication_token(auth_masks, auth_descriptor)
Finally, the party interested in authenticating itself can broadcast its original registration token together with its authentication token. Each node then computes locally its share of the authentication result. These shares can be reconstructed by a designated authority to obtain a result:

.. code-block:: python
>>> shares = [node.authenticate([reg_token, auth_token]) for node in nodes]
>>> round(1000 * reveal(shares)) # Rounded floating point value to keep test stable.
180
Development
-----------
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def rtd_url_for_installed_version(name, subdomain=None):

intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
'modulo': (rtd_url_for_installed_version('modulo', 'modulo-lib'), None),
'tinynmc': (rtd_url_for_installed_version('tinynmc'), None),
}

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ authors = [
readme = "README.rst"
requires-python = ">=3.7"
dependencies = [
"modulo~=2.1",
"tinynmc~=0.2"
]

Expand Down
101 changes: 76 additions & 25 deletions src/tinybio/tinybio.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,74 +11,125 @@

_PRECISION = 16
"""
Precision (*i.e.*, number of digits after the decimal point) for fixed-point
rationals.
Precision (*i.e.*, number of digits after the decimal point in a binary
representation of a value) for fixed-point rationals.
"""

def _encode(data, is_login=0):
def _encode(
descriptor: Sequence[float],
authentication: bool = False
) -> dict[tuple[int, int], int]:
"""
Encode data for requests to nodes.
"""
encoding = [round(value * (2 ** _PRECISION)) for value in data]
authentication = int(authentication)
encoding = [round(value * (2 ** _PRECISION)) for value in descriptor]

coords_to_values = {(is_login, 0): sum(value * value for value in encoding)}
coords_to_values = {(authentication, 0): sum(value * value for value in encoding)}
for (index, value) in enumerate(encoding, 2):
coords_to_values[(index, is_login)] = ((is_login * 3) - 2) * value
coords_to_values[(index, authentication)] = ((authentication * 3) - 2) * value

return coords_to_values

class node(tinynmc.node):
"""
Data structure for maintaining the information associated with a node
and performing node operations.
"""
def authenticate(self, tokens):
Suppose that a workflows is supported by three nodes (parties performing
the decentralized registration and authentication functions). The node
objects would be instantiated locally by each of these three parties.
>>> nodes = [node(), node(), node()]
The preprocessing phase that the nodes must execute can be simulated using
the :obj:`preprocess` function.
>>> preprocess(3, nodes)
It is then possible to register some data (such as a biometric descriptor
represented as a vector of floating point values) by requesting the masks
from each node and submitting a registration *token* (*i.e.*, a masked
descriptor that is computed locally by the registering party) to the nodes.
>>> reg_descriptor = [0.5, 0.3, 0.7]
>>> reg_masks = [node.masks(registration_request(reg_descriptor)) for node in nodes]
>>> reg_token = registration_token(reg_masks, reg_descriptor)
At a later point, it is possible to perform an authentication workflow.
After requesting masks for the authentication descriptor, the authentication
token (*i.e.*, a masked descriptor) can be generated locally by the party
interested in authenticating itself.
>>> auth_descriptor = [0.1, 0.4, 0.8]
>>> auth_masks = [node.masks(authentication_request(auth_descriptor)) for node in nodes]
>>> auth_token = authentication_token(auth_masks, auth_descriptor)
Finally, the party interested in authenticating itself can broadcast its
original registration token together with its authentication token. Each
node then computes locally its share of the authentication result. These
shares can be reconstructed by a designated authority to obtain a result.
>>> shares = [node.authenticate([reg_token, auth_token]) for node in nodes]
>>> round(1000 * reveal(shares)) # Rounded floating point value to keep test stable.
180
"""
def authenticate(
self: node,
tokens: Sequence[dict[tuple[int, int], modulo]]
) -> modulo:
"""
Perform computation associated with a login operation.
Perform computation associated with an authentication workflow.
"""
return self.compute(getattr(self, '_signature'), tokens)

def preprocess(length: int, nodes: Sequence[node]):
"""
Simulate a preprocessing phase among the collection of nodes for a workflow
that supports registration and authentication data vectors of the specified
length.
that supports registration and authentication descriptor vectors of the
specified length.
"""
signature = [1, 1] + ([2] * length)
tinynmc.preprocess(signature, nodes)
for node_ in nodes:
setattr(node_, '_signature', signature)

def registration_request(data):
def registration_request(descriptor: Sequence[float]) -> Iterable[tuple[int, int]]:
"""
Encode data into a registration request.
Encode descriptor into a registration request.
"""
return _encode(data, 0).keys()
return _encode(descriptor, False).keys()

def authentication_request(data):
def authentication_request(descriptor: Sequence[float]) -> Iterable[tuple[int, int]]:
"""
Encode data into an authentication request.
Encode descriptor into an authentication request.
"""
return _encode(data, 1).keys()
return _encode(descriptor, True).keys()

def registration_token(masks, data):
def registration_token(
masks: Iterable[dict[tuple[int, int], modulo]],
descriptor: Sequence[float]
) -> dict[tuple[int, int], modulo]:
"""
Mask data to create a registration token.
Mask descriptor to create a registration token.
"""
return tinynmc.masked_factors(_encode(data, 0), masks)
return tinynmc.masked_factors(_encode(descriptor, False), masks)

def authentication_token(masks, data):
def authentication_token(
masks: Iterable[dict[tuple[int, int], modulo]],
descriptor: Sequence[float]
) -> dict[tuple[int, int], modulo]:
"""
Mask data to create an authentication token.
Mask descriptor to create an authentication token.
"""
return tinynmc.masked_factors(_encode(data, 1), masks)
return tinynmc.masked_factors(_encode(descriptor, True), masks)

def reveal(shares: Iterable[modulo]) -> int:
def reveal(shares: Iterable[modulo]) -> float:
"""
Reconstruct the result of the overall workflow from its shares and convert
it into a meaningful output (as a percentage).
"""
return (100 * int(sum(shares))) // (2 ** (2 * _PRECISION))
return int(sum(shares)) / (2 ** (2 * _PRECISION))

if __name__ == '__main__':
doctest.testmod() # pragma: no cover

0 comments on commit 2add605

Please sign in to comment.