Source code for oprf.oprf

"""
Oblivious pseudo-random function (OPRF) protocol functionality implementations
based on `Curve25519 <https://cr.yp.to/ecdh.html>`__ and the
`Ristretto <https://ristretto.group>`__ group.
"""
from __future__ import annotations
from typing import Optional, Union
import doctest
import oblivious

[docs]class data(oblivious.ristretto.point): """ Wrapper class for a bytes-like object that corresponds to a piece of data that can be masked. """
[docs] @classmethod def hash(cls, argument: Union[str, bytes]) -> data: # pylint: disable=arguments-renamed """ Return data object constructed by hashing the supplied string or bytes-like object. >>> data.hash('abc').hex() '5a5dbd5c765abf60b2076133482c1ada189c319034ae0b933f4908b3b68d0225' >>> data.hash(bytes([123])).hex() 'be6f2de25b6907d7e07e6a75424c6f4bbed103c2957b9fa9fbe4fd63dfa5575b' >>> data.hash([1, 2, 3]) Traceback (most recent call last): ... TypeError: can only hash a string or bytes-like object to a data object """ if not isinstance(argument, (bytes, bytearray, str)): raise TypeError( 'can only hash a string or bytes-like object to a data object' ) argument = argument.encode() if isinstance(argument, str) else argument return bytes.__new__(cls, oblivious.ristretto.point.hash(argument))
[docs] @classmethod def from_base64(cls, s: str) -> data: """ Convert Base64 UTF-8 string representation of a data instance to a data object. >>> d = data.hash('abc') >>> data.from_base64(d.to_base64()) == d True """ return bytes.__new__(cls, oblivious.ristretto.point.from_base64(s))
[docs] def __new__(cls, bs: Optional[bytes] = None) -> data: """ Return data object corresponding to the supplied bytes-like object. No checks are performed to confirm that the bytes-like object is a valid representation of a data object. >>> d = data.hash('abc') >>> bs = bytes(d) >>> data(bs) == d True """ return bytes.__new__(cls, oblivious.ristretto.point(bs))
[docs] def __truediv__(self: data, argument: mask) -> data: """ Unmask this data object (assuming it has previously been masked with the supplied mask). >>> d = data.hash('abc') >>> m = mask.hash('abc') >>> ((m(d)) / m) == d True """ return data((~argument) * self)
[docs] def to_base64(self: data) -> str: """ Convert to Base64 UTF-8 string representation. >>> d = data.hash('abc') >>> d.to_base64() 'Wl29XHZav2CyB2EzSCwa2hicMZA0rguTP0kIs7aNAiU=' """ return oblivious.ristretto.point(self).to_base64()
[docs]class mask(oblivious.ristretto.scalar): """ Wrapper class for a bytes-like object that corresponds to a mask. """
[docs] @classmethod def random(cls) -> mask: """ Return random non-zero mask object. >>> m = mask.random() >>> len(m) == 32 and oblivious.ristretto.scalar(m) == m True """ return bytes.__new__(cls, oblivious.ristretto.scalar())
[docs] @classmethod def hash(cls, argument: Union[str, bytes]) -> mask: # pylint: disable=arguments-renamed """ Return mask object constructed by hashing the supplied string or bytes-like object. >>> mask.hash('abc').hex() 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f200150d' >>> mask.hash(bytes([123])).hex() '904ea0ec29650f3b2bcf481e3ea2553488030c865aae2decba8ce7016c4e380c' >>> mask.hash([1, 2, 3]) Traceback (most recent call last): ... TypeError: can only hash a string or bytes-like object to a mask object """ if not isinstance(argument, (bytes, bytearray, str)): raise TypeError( 'can only hash a string or bytes-like object to a mask object' ) argument = argument.encode() if isinstance(argument, str) else argument return bytes.__new__(cls, oblivious.ristretto.scalar.hash(argument))
[docs] @classmethod def from_base64(cls, s: str) -> mask: """ Convert Base64 UTF-8 string representation of a mask instance to a mask object. >>> m = mask.hash('abc') >>> mask.from_base64(m.to_base64()) == m True """ return bytes.__new__(cls, oblivious.ristretto.scalar.from_base64(s))
[docs] def __new__(cls, bs: Optional[bytes] = None) -> mask: """ Return mask object corresponding to the supplied bytes-like object. No checks are performed to confirm that the bytes-like object is a valid representation of a mask object. >>> m = mask() >>> bs = bytes(m) >>> mask(bs) == m True """ return bytes.__new__(cls, oblivious.ristretto.scalar(bs))
[docs] def __invert__(self: mask) -> mask: """ Return the inverse of this mask instance. >>> m = mask.hash('abc') >>> (~m).hex() '9d7c69e8dded15ba20544cee233db3148481e713863ddcf0dff9d56470ba8501' >>> d = data.hash('abc') >>> (~m)(m(d)) == d True >>> m((~m)(d)) == d True """ return mask(~oblivious.ristretto.scalar(self))
[docs] def mask(self: mask, argument: data) -> data: """ Mask a :obj:`data` object with this mask and return the masked data object. >>> d = data.hash('abc') >>> m = mask.hash('abc') >>> m.mask(d).hex() 'f47c8267b28ac5100e0e97b36190e16d4533b367262557a5aa7d97b811344d15' """ return data(oblivious.ristretto.scalar(self) * argument)
[docs] def __call__(self: mask, argument: data) -> data: """ Mask a :obj:`data` object with this mask and return the masked data object. >>> d = data.hash('abc') >>> m = mask.hash('abc') >>> m(d).hex() 'f47c8267b28ac5100e0e97b36190e16d4533b367262557a5aa7d97b811344d15' """ return data(oblivious.ristretto.scalar(self) * argument)
[docs] def __mul__(self: mask, argument: data) -> data: """ Mask a :obj:`data` object with this mask and return the masked data object. >>> d = data.hash('abc') >>> m = mask.hash('abc') >>> (m * d).hex() 'f47c8267b28ac5100e0e97b36190e16d4533b367262557a5aa7d97b811344d15' """ return data(oblivious.ristretto.scalar(self) * argument)
[docs] def unmask(self: mask, argument: data) -> data: """ Unmask a :obj:`data` object that has previously been masked with this mask (and return the original :obj:`data` object). >>> d = data.hash('abc') >>> m = mask.hash('abc') >>> m.unmask(m(d)) == d True """ return data(oblivious.ristretto.scalar(~self) * argument)
[docs] def to_base64(self: mask) -> str: """ Convert to Base64 UTF-8 string representation. >>> m = mask.hash('abc') >>> m.to_base64() 'ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFQ0=' """ return oblivious.ristretto.scalar(self).to_base64()
if __name__ == '__main__': doctest.testmod() # pragma: no cover