From 2f658ff495bec22f3c84e7010fe479a50f09aebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Heissler?= Date: Tue, 28 Dec 2021 22:25:23 +0100 Subject: [PATCH] Add high-level documentation for ristretto --- docs/api/ristretto.rst | 19 +++ docs/index.rst | 2 + docs/ristretto.rst | 317 +++++++++++++++++++++++++++++++++++++++++ docs/vectors/index.rst | 7 + 4 files changed, 345 insertions(+) create mode 100644 docs/api/ristretto.rst create mode 100644 docs/ristretto.rst diff --git a/docs/api/ristretto.rst b/docs/api/ristretto.rst new file mode 100644 index 000000000..d0429c9e9 --- /dev/null +++ b/docs/api/ristretto.rst @@ -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 +`__. +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__ diff --git a/docs/index.rst b/docs/index.rst index 010352bae..b31e9104f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,6 +11,7 @@ Contents signing hashing password_hashing + ristretto Support Features @@ -31,6 +32,7 @@ Support Features api/hash api/pwhash api/hashlib + api/ristretto .. toctree:: :caption: The PyNaCl open source project diff --git a/docs/ristretto.rst b/docs/ristretto.rst new file mode 100644 index 000000000..7157f26e9 --- /dev/null +++ b/docs/ristretto.rst @@ -0,0 +1,317 @@ +.. currentmodule:: nacl.ristretto +.. _finite-field-arithmetic: + +Finite field arithmetic +======================= +`Ristretto255 `__ 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 +`__, +`ElGamal encryption `__ or +`Schnorr signatures `__. + + +Two high-level classes are defined to wrap the `libsodium +`__ API: + +* :py:class:`Ristretto255Scalar` is the `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 + `__ 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 + `__. + +An `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 `__ + - 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() ` + + * - ``crypto_core_ristretto255_scalar_reduce(u, h)`` + - :py:meth:`u = Ristretto255Scalar.reduce(h) ` + + * - ``crypto_core_ristretto255_scalar_invert(u, s)`` + - :py:attr:`u = s.inverse ` + + * - ``crypto_core_ristretto255_scalar_complement(u, s)`` + - :py:attr:`u = s.complement ` + + * - ``crypto_core_ristretto255_scalar_add(u, s, t)`` + - :py:meth:`u = s + t ` + + * - ``crypto_core_ristretto255_scalar_sub(u, s, t)`` + - :py:meth:`u = s - t ` + + * - ``crypto_core_ristretto255_scalar_mul(u, s, t)`` + - :py:meth:`u = s * t ` + + * - ``crypto_core_ristretto255_scalar_mul(u, s, t.inverse)`` + - :py:meth:`u = s / t ` + + * - ``crypto_core_ristretto255_scalar_negate(u, s)`` + - :py:meth:`u = -s ` + + * - ``sodium_memcmp(s, t, 32)`` + - :py:meth:`s == t ` + + * - ``sodium_is_zero(s, 32)`` + - :py:meth:`bool(s) ` + +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 `__ + - 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) ` + + * - ``crypto_core_ristretto255_from_hash(r, h)`` + - :py:meth:`r = Ristretto255Point.from_hash(h) ` + + * - ``crypto_core_ristretto255_random(r)`` + - :py:meth:`r = Ristretto255Point.random() ` + + * - ``crypto_scalarmult_ristretto255_base(r, s)`` + - :py:meth:`r = Ristretto255Point.base_mul(s) ` + + * - ``crypto_scalarmult_ristretto255(r, -1, p)`` + - :py:meth:`r = -p ` + + * - ``crypto_core_ristretto255_add(r, p, q)`` + - :py:meth:`r = p + q ` + + * - ``crypto_core_ristretto255_sub(r, p, q)`` + - :py:meth:`r = p - q ` + + * - ``crypto_scalarmult_ristretto255(r, s, p)`` + - :py:meth:`r = p * s ` + + * - ``sodium_memcmp(p, q, 32)`` + - :py:meth:`p == q ` + + * - ``sodium_is_zero(p, 32)`` + - :py:meth:`bool(p) ` + + +Examples +-------- +There are two code examples for `ElGamal encryption +`__ and `Shamir's Secret +Sharing `__ in the +test cases. Two simpler examples follow: + +Secure two-party computation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This is the example from `libsodium +`__: + +.. 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 `__ +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 diff --git a/docs/vectors/index.rst b/docs/vectors/index.rst index 7f7e043fc..f555df4c8 100644 --- a/docs/vectors/index.rst +++ b/docs/vectors/index.rst @@ -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 ` in +``tests/data/ristretto255.json`` are taken from +https://ristretto.group/test_vectors/ristretto255.html. + Custom generated reference vectors ----------------------------------