Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Ristretto255 #646

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
python:
version: 3.5
version: 3.8
pip_install: true
extra_requirements:
- docs
19 changes: 19 additions & 0 deletions docs/api/ristretto.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
nacl.ristretto
==============
.. currentmodule:: nacl.ristretto

The classes :py:class:`Ristretto255Scalar` and :py:class:`Ristretto255Point`
provide a high-level abstraction around the low-level bindings to `libsodium
<https://doc.libsodium.org/advanced/point-arithmetic/ristretto>`__.
Several functions are accessible through operator overloading.

See :ref:`finite-field-arithmetic` for high-level documentation.

.. autoclass:: Ristretto255Scalar
:members:
:special-members: __init__, __add__, __bool__, __bytes__, __eq__, __int__, __mul__, __truediv__, __neg__, __sub__


.. autoclass:: Ristretto255Point
:members:
:special-members: __add__, __bool__, __bytes__, __eq__, __mul__, __neg__, __sub__
2 changes: 2 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Contents
signing
hashing
password_hashing
ristretto


Support Features
Expand All @@ -31,6 +32,7 @@ Support Features
api/hash
api/pwhash
api/hashlib
api/ristretto

.. toctree::
:caption: The PyNaCl open source project
Expand Down
317 changes: 317 additions & 0 deletions docs/ristretto.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
.. currentmodule:: nacl.ristretto
.. _finite-field-arithmetic:

Finite field arithmetic
=======================
`Ristretto255 <https://ristretto.group/>`__ is a prime order elliptic curve
group based on Curve25519. It can be used as a building block for cryptographic
protocols such as `Zero-knowledge proofs of knowledge
<https://en.wikipedia.org/wiki/Zero-knowledge_proof>`__,
`ElGamal encryption <https://en.wikipedia.org/wiki/ElGamal_encryption>`__ or
`Schnorr signatures <https://en.wikipedia.org/wiki/Schnorr_signature>`__.


Two high-level classes are defined to wrap the `libsodium
<https://doc.libsodium.org/advanced/point-arithmetic/ristretto>`__ API:

* :py:class:`Ristretto255Scalar` is the `finite field
<https://en.wikipedia.org/wiki/Finite_field>`__ over the set of integers
modulo the prime ``2 ** 252 + 27742317777372353535851937790883648493`` and
the four operations *addition*, *subtraction*, *multiplication* and
*division*. Each operation takes two elements from the set and computes
another element from the same set. Most operations are accessible through
operator overloading.

* :py:class:`Ristretto255Point` is the `cyclic group
<https://en.wikipedia.org/wiki/Cyclic_group>`__ with points from the
Curve25519 elliptic curve. Thanks to the Ristretto construction, all elements
in the group are unique, and each element (other than the identity) is a
generator of the complete group. The order of :py:class:`Ristretto255Scalar`
matches this group's order. The basic operation in the group is *point
addition*. Repeated addition of the same point is called `multiplicaton
<https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication>`__.

An `isomorphism <https://en.wikipedia.org/wiki/Isomorphism>`__ exists between
the two groups. This means that for scalars ``s, t`` and a point ``p``
equations such as this hold: ``p * (s + t) == (p * s) + (p * t)``.


Scalar field
------------
Each instance of :py:class:`Ristretto255Scalar` is a scalar value (integer
reduced modulo the group order). The internal representation is a 32 byte array
in little-endian order.

The operators and methods support arguments of various python types. They are
automatically reduced modulo the group order and converted into the internal
representation.

* Another :py:class:`Ristretto255Scalar`
* :py:class:`bytes`, an 32 byte integer in little-endian encoding.
* :py:class:`int`, an arbitrary integer.
* :py:class:`fractions.Fraction`.

Argument types can be mixed:

.. testcode::

from fractions import Fraction
from nacl.ristretto import Ristretto255Scalar

r = Ristretto255Scalar(42) / 11 * Fraction(5, 7) * (b"\x21" + bytes(31)) - -10
print(int(r))

.. testoutput::

100

Following table shows how to translate from libsodium functions:

.. list-table:: Translating from libsodium to Ristretto255Scalar
:header-rows: 1
:widths: auto

* - `libsodium <https://doc.libsodium.org/advanced/point-arithmetic/ristretto#scalar-arithmetic-over-l>`__
- PyNaCl

* - ``crypto_core_ristretto255_nonreducedscalarbytes()``
- :py:attr:`Ristretto255Scalar.NONREDUCED_SIZE`

* - ``crypto_core_ristretto255_scalarbytes()``
- :py:attr:`Ristretto255Scalar.SIZE`

* - ``crypto_core_ristretto255_scalar_random(u)``
- :py:meth:`u = Ristretto255Scalar.random() <Ristretto255Scalar.random>`

* - ``crypto_core_ristretto255_scalar_reduce(u, h)``
- :py:meth:`u = Ristretto255Scalar.reduce(h) <Ristretto255Scalar.reduce>`

* - ``crypto_core_ristretto255_scalar_invert(u, s)``
- :py:attr:`u = s.inverse <Ristretto255Scalar.inverse>`

* - ``crypto_core_ristretto255_scalar_complement(u, s)``
- :py:attr:`u = s.complement <Ristretto255Scalar.complement>`

* - ``crypto_core_ristretto255_scalar_add(u, s, t)``
- :py:meth:`u = s + t <Ristretto255Scalar.__add__>`

* - ``crypto_core_ristretto255_scalar_sub(u, s, t)``
- :py:meth:`u = s - t <Ristretto255Scalar.__sub__>`

* - ``crypto_core_ristretto255_scalar_mul(u, s, t)``
- :py:meth:`u = s * t <Ristretto255Scalar.__mul__>`

* - ``crypto_core_ristretto255_scalar_mul(u, s, t.inverse)``
- :py:meth:`u = s / t <Ristretto255Scalar.__truediv__>`

* - ``crypto_core_ristretto255_scalar_negate(u, s)``
- :py:meth:`u = -s <Ristretto255Scalar.__neg__>`

* - ``sodium_memcmp(s, t, 32)``
- :py:meth:`s == t <Ristretto255Scalar.__eq__>`

* - ``sodium_is_zero(s, 32)``
- :py:meth:`bool(s) <Ristretto255Scalar.__bool__>`

Ristretto group
---------------
The multiplication operators take a scalar as operand which must be one of the
types from above list. All other operands and arguments must be points.

Argument types can be mixed:

.. testcode::

from fractions import Fraction
from nacl.ristretto import Ristretto255Point, Ristretto255Scalar

p = Ristretto255Point.random()
q = (p * Fraction(5, 7) - p) * Ristretto255Scalar(7)
print(bytes(p * 2 + q).hex())


.. testoutput::

0000000000000000000000000000000000000000000000000000000000000000


Following table shows how to translate from libsodium functions:

.. list-table:: Translating from libsodium to Ristretto255Point
:header-rows: 1
:widths: auto

* - `libsodium <https://doc.libsodium.org/advanced/point-arithmetic/ristretto>`__
- PyNaCl

* - ``crypto_core_ristretto255_bytes()``
- :py:attr:`Ristretto255Point.SIZE`

* - ``crypto_core_ristretto255_hashbytes()``
- :py:attr:`Ristretto255Point.HASH_SIZE`

* - ``crypto_core_ristretto255_is_valid_point(p)``
- :py:meth:`r = Ristretto255Point(p) <Ristretto255Point.__init__>`

* - ``crypto_core_ristretto255_from_hash(r, h)``
- :py:meth:`r = Ristretto255Point.from_hash(h) <Ristretto255Point.from_hash>`

* - ``crypto_core_ristretto255_random(r)``
- :py:meth:`r = Ristretto255Point.random() <Ristretto255Point.random>`

* - ``crypto_scalarmult_ristretto255_base(r, s)``
- :py:meth:`r = Ristretto255Point.base_mul(s) <Ristretto255Point.base_mul>`

* - ``crypto_scalarmult_ristretto255(r, -1, p)``
- :py:meth:`r = -p <Ristretto255Point.__neg__>`

* - ``crypto_core_ristretto255_add(r, p, q)``
- :py:meth:`r = p + q <Ristretto255Point.__add__>`

* - ``crypto_core_ristretto255_sub(r, p, q)``
- :py:meth:`r = p - q <Ristretto255Point.__sub__>`

* - ``crypto_scalarmult_ristretto255(r, s, p)``
- :py:meth:`r = p * s <Ristretto255Point.__mul__>`

* - ``sodium_memcmp(p, q, 32)``
- :py:meth:`p == q <Ristretto255Point.__eq__>`

* - ``sodium_is_zero(p, 32)``
- :py:meth:`bool(p) <Ristretto255Point.__bool__>`


Examples
--------
There are two code examples for `ElGamal encryption
<https://en.wikipedia.org/wiki/ElGamal_encryption>`__ and `Shamir's Secret
Sharing <https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing>`__ in the
test cases. Two simpler examples follow:

Secure two-party computation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is the example from `libsodium
<https://doc.libsodium.org/advanced/point-arithmetic/ristretto>`__:

.. testcode::

from os import urandom
from nacl.ristretto import Ristretto255Point, Ristretto255Scalar

## First party: Send blinded p(x) ##
x = urandom(Ristretto255Point.HASH_SIZE)

# Compute px = p(x), a group element derived from x
px = Ristretto255Point.from_hash(x)

# Compute a = p(x) * g^r
r = Ristretto255Scalar.random()
gr = Ristretto255Point.base_mul(r)
a = px + Ristretto255Point.base_mul(r)


## Second party: Send g^k and a^k ##
k = Ristretto255Scalar.random()

# Compute v = g^k
v = Ristretto255Point.base_mul(k)

# Compute b = a^k
b = a * k


## First party: Unblind f(x) ##

# Compute f(x) = b * v^(-r)
# = (p(x) * g^r)^k * (g^k)^(-r)
# = (p(x) * g)^k * g^(-k)
# = p(x)^k
fx = b - v * r

# Compare result
print(px * k == fx)

.. testoutput::

True

Schnorr signature
~~~~~~~~~~~~~~~~~
The `Schnorr signature <https://en.wikipedia.org/wiki/Schnorr_signature>`__
scheme can adopted to use Ristretto255:

.. testcode::

from nacl.ristretto import Ristretto255Point, Ristretto255Scalar
import hashlib


## Choosing parameters ##

# Agree on group of prime order
G = Ristretto255Point

# Choose a random generator
g = G.random()

# Agree on a cryptographic hash function; needs to have 512 bits output
H = lambda data: Ristretto255Scalar.reduce(hashlib.sha3_512(data).digest())


## Key generation ##

# Choose a private signing key
x = Ristretto255Scalar.random()

# Compute the public verification key
y = g * x


## Signing ##

# Message to sign
M = b"Lorem ipsum dolor sit amet"

# Choose a random nonce
k = Ristretto255Scalar.random()

# Computate the signature
r = g * k
e = H(bytes(r) + M)
s = k - x * e

# Signature is the scalars (s, e)


## Verifying ##

r_v = g * s + y * e
e_v = H(bytes(r_v) + M)

if e_v == e:
print("Signature verified")


## Key leakage from nonce reuse ##

# Another message to sign
M_ = b"consectetur adipiscing elit"

# Reuse nonce. Don't do that!
k_ = k

# Computate the signature
r_ = g * k_
e_ = H(bytes(r_) + M_)
s_ = k_ - x * e_

# Compute private key
x_ = (s_ - s) / (e - e_)

if g * x_ == y:
print("Key was leaked")

.. testoutput::

Signature verified
Key was leaked
7 changes: 7 additions & 0 deletions docs/vectors/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ In particular, the original expected results come from siphash's vectors.h,
while the key and the input messages have been generated following
the respective definitions in siphash's test.c.

ristretto255
^^^^^^^^^^^^

The reference vectors for :ref:`ristretto255 <finite-field-arithmetic>` in
``tests/data/ristretto255.json`` are taken from
https://ristretto.group/test_vectors/ristretto255.html.

Custom generated reference vectors
----------------------------------

Expand Down
Loading