From 46832c4abb79aef9383344ab8d2ceea04d0792cc Mon Sep 17 00:00:00 2001 From: Frederick Jansen Date: Mon, 6 May 2024 17:47:09 -0400 Subject: [PATCH 1/5] Add support for scalar addition, subtraction, and negation --- pyproject.toml | 6 +- src/oblivious/ristretto.py | 138 ++++++++++++++++++++++++++++++++++++- test/test_ristretto.py | 32 +++++++++ 3 files changed, 171 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4c81c0fe..9f46f432 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oblivious" -version = "7.0.0" +version = "7.1.0" description = """\ Python library that serves as an API for common \ cryptographic primitives used to implement OPRF, OT, \ @@ -33,8 +33,8 @@ mclbn256 = [ ] docs = [ "toml~=0.10.2", - "sphinx~=4.2.0", - "sphinx-rtd-theme~=1.0.0" + "sphinx~=5.3.0", + "sphinx-rtd-theme~=1.2.0" ] test = [ "pytest~=7.2", diff --git a/src/oblivious/ristretto.py b/src/oblivious/ristretto.py index 572015e9..010eee9d 100644 --- a/src/oblivious/ristretto.py +++ b/src/oblivious/ristretto.py @@ -184,7 +184,8 @@ class python: :obj:`python.pnt `, :obj:`python.bas `, :obj:`python.can `, :obj:`python.mul `, :obj:`python.add `, :obj:`python.sub `, - :obj:`python.neg `, + :obj:`python.neg `, :obj:`python.sad `, + :obj:`python.ssu `, :obj:`python.point `, and :obj:`python.scalar `. For example, you can perform addition of points using @@ -385,6 +386,54 @@ def smu(s: bytes, t: bytes) -> bytes: """ return _sc25519_mul(s, t) + @staticmethod + def sad(s: bytes, t: bytes) -> bytes: + """ + Return the sum of two scalars. + + >>> p = scalar.from_int(4) + >>> q = scalar.from_int(2) + >>> sodium.sad(p, q) == sodium.sad(q, p) + True + >>> sodium.sad(p, q).hex() + '0600000000000000000000000000000000000000000000000000000000000000' + >>> sodium.sad(-q, p).hex() + '0200000000000000000000000000000000000000000000000000000000000000' + """ + (s, t) = (int.from_bytes(s, 'little'), int.from_bytes(t, 'little')) + return ( + (s + t) % (pow(2, 252) + 27742317777372353535851937790883648493) + ).to_bytes(32, 'little') + + @staticmethod + def ssu(s: bytes, t: bytes) -> bytes: + """ + Return the result of subtracting the right-hand scalar from the + left-hand scalar. + + >>> p = scalar.from_int(4) + >>> q = scalar.from_int(2) + >>> sodium.ssu(p, q).hex() + '0200000000000000000000000000000000000000000000000000000000000000' + """ + (s, t) = (int.from_bytes(s, 'little'), int.from_bytes(t, 'little')) + return ( + (s - t) % (pow(2, 252) + 27742317777372353535851937790883648493) + ).to_bytes(32, 'little') + + @staticmethod + def sne(s: bytes) -> bytes: + """ + Return the additive inverse of a scalar. + + >>> p = scalar.from_int(4) + >>> sodium.sne(p).hex() + 'e9d3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010' + """ + return ( + (pow(2, 252) + 27742317777372353535851937790883648493 - int.from_bytes(s, 'little')) + ).to_bytes(32, 'little') + # # Attempt to load primitives from libsodium, if it is present; # otherwise, use the rbcl library, if it is present. Otherwise, @@ -527,7 +576,7 @@ class encapsulates shared/dynamic library variants of both classes :obj:`sodium.pnt `, :obj:`sodium.bas `, :obj:`sodium.can `, :obj:`sodium.mul `, :obj:`sodium.add `, :obj:`sodium.sub `, - :obj:`sodium.neg `, + :obj:`sodium.neg `, :obj:`sodium.sne `, :obj:`sodium.point `, and :obj:`sodium.scalar `. For example, you can perform addition of points using @@ -734,6 +783,54 @@ def smu(s: bytes, t: bytes) -> bytes: bytes(s), bytes(t) ) + @staticmethod + def sad(s: bytes, t: bytes) -> bytes: + """ + Return the sum of two scalars. + + >>> s = sodium.scl() + >>> t = sodium.scl() + >>> sodium.sad(s, t) == sodium.sad(t, s) + True + """ + return sodium._call( + sodium._lib.crypto_core_ristretto255_scalarbytes(), + sodium._lib.crypto_core_ristretto255_scalar_add, + bytes(s), bytes(t) + ) + + @staticmethod + def ssu(p: bytes, q: bytes) -> bytes: + """ + Return the result of subtracting the right-hand scalar from the + left-hand scalar. + + >>> p = scalar.hash('123'.encode()) + >>> q = scalar.hash('456'.encode()) + >>> sodium.ssu(p, q).hex() + 'd08dcedb3a8dc87951acd91334a1faed511f49c6e9296780634b858e42347908' + """ + return sodium._call( + _sodium.crypto_core_ristretto255_scalarbytes(), + sodium._lib.crypto_core_ristretto255_scalar_sub, + bytes(p), bytes(q) + ) + + @staticmethod + def sne(s: bytes) -> bytes: + """ + Return the additive inverse of a scalar. + + >>> p = scalar.from_int(4) + >>> sodium.sne(p).hex() + 'e9d3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010' + """ + return sodium._call( + _sodium.crypto_core_ristretto255_scalarbytes(), + sodium._lib.crypto_core_ristretto255_scalar_negate, + bytes(s) + ) + except: # pylint: disable=W0702 # pragma: no cover # Exported symbol. sodium = None # pragma: no cover @@ -1091,6 +1188,17 @@ def __invert__(self: scalar) -> scalar: return self._implementation.scalar(self._implementation.inv(self)) + def __neg__(self: scalar) -> scalar: + """ + Return the negation of this instance. + + >>> p = scalar.hash('123'.encode()) + >>> q = scalar.hash('456'.encode()) + >>> ((p + q) + (-q)) == p + True + """ + return self._implementation.scalar(self._implementation.sne(self)) + def __mul__(self: scalar, other: Union[scalar, point]) -> Union[scalar, point]: """ Multiply the supplied scalar or point by this instance. @@ -1155,6 +1263,32 @@ def __rmul__(self: scalar, other: Union[scalar, point]): 'scalar must be on left-hand side of multiplication operator' ) + def __add__(self: scalar, other: scalar) -> scalar: + """ + Return the sum of this instance and another scalar. + + >>> p = scalar.hash('123'.encode()) + >>> q = scalar.hash('456'.encode()) + >>> (p + q).hex() + '69117034205aa81808edae5d89128497ef75f5b71416d97ccfd18760ad117c0e' + >>> p + (q - q) == p + True + """ + return self._implementation.scalar(self._implementation.sad(self, other)) + + def __sub__(self: scalar, other: scalar) -> scalar: + """ + Return the result of subtracting another scalar from this instance. + + >>> p = scalar.hash('123'.encode()) + >>> q = scalar.hash('456'.encode()) + >>> (p - q).hex() + 'd08dcedb3a8dc87951acd91334a1faed511f49c6e9296780634b858e42347908' + >>> p - p == scalar.from_int(0) + True + """ + return self._implementation.scalar(self._implementation.ssu(self, other)) + def to_bytes(self: scalar) -> bytes: """ Return the bytes-like object that represents this instance. diff --git a/test/test_ristretto.py b/test/test_ristretto.py index 03e05756..c3277a69 100644 --- a/test/test_ristretto.py +++ b/test/test_ristretto.py @@ -551,6 +551,20 @@ def test_types_scalar_mul_point(self): sodium_hidden_and_fallback(hidden, fallback) self.assertTrue(isinstance(cls.scalar() * cls.point(), cls.point)) + def test_types_scalar_add(self): + sodium_hidden_and_fallback(hidden, fallback) + (s0, s1) = (cls.scalar.random(), cls.scalar.random()) + self.assertTrue(isinstance(s0 + s1, cls.scalar)) + + def test_types_scalar_sub(self): + sodium_hidden_and_fallback(hidden, fallback) + (s0, s1) = (cls.scalar.random(), cls.scalar.random()) + self.assertTrue(isinstance(s0 - s1, cls.scalar)) + + def test_types_scalar_neg(self): + sodium_hidden_and_fallback(hidden, fallback) + self.assertTrue(isinstance(-cls.scalar.random(), cls.scalar)) + class Test_algebra(TestCase): """ Tests of algebraic properties of primitive operations and class methods. @@ -689,6 +703,24 @@ def test_algebra_scalar_mul_point_on_left_hand_side(self): p = cls.point.hash(bytes(POINT_LEN)) self.assertRaises(TypeError, lambda: p * s) + def test_algebra_scalar_add_commute(self): + sodium_hidden_and_fallback(hidden, fallback) + for bs in fountains(SCALAR_LEN + SCALAR_LEN, limit=TRIALS_PER_TEST): + (s0, s1) = ( + cls.scalar.hash(bs[:SCALAR_LEN]), + cls.scalar.hash(bs[SCALAR_LEN:]) + ) + self.assertEqual(cls.sad(s0, s1), cls.sad(s1, s0)) + + def test_algebra_scalar_add_neg_add_identity(self): + sodium_hidden_and_fallback(hidden, fallback) + for bs in fountains(SCALAR_LEN + SCALAR_LEN, limit=TRIALS_PER_TEST): + (s0, s1) = ( + cls.scalar.hash(bs[:SCALAR_LEN]), + cls.scalar.hash(bs[SCALAR_LEN:]) + ) + self.assertEqual(cls.sad(cls.sad(s0, cls.sne(s0)), s1), s1) + return ( Test_primitives, Test_classes, From 4f56e58bca85277746b6b187669888b032fa0675 Mon Sep 17 00:00:00 2001 From: Frederick Jansen Date: Mon, 6 May 2024 17:54:27 -0400 Subject: [PATCH 2/5] Update GitHub Actions checkout and setup-python scripts --- .github/workflows/lint-test-cover-docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint-test-cover-docs.yml b/.github/workflows/lint-test-cover-docs.yml index 3c34bb28..bdd176c6 100644 --- a/.github/workflows/lint-test-cover-docs.yml +++ b/.github/workflows/lint-test-cover-docs.yml @@ -11,9 +11,9 @@ jobs: xcl: ['omit', 'install'] name: "Python ${{ matrix.python-version }}" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Python. - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} architecture: x64 From 198f47898149e16c9cfe36919b751280e70bb1ae Mon Sep 17 00:00:00 2001 From: Frederick Jansen Date: Mon, 6 May 2024 18:06:48 -0400 Subject: [PATCH 3/5] Add modulo for sne --- src/oblivious/ristretto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oblivious/ristretto.py b/src/oblivious/ristretto.py index 010eee9d..81fa0052 100644 --- a/src/oblivious/ristretto.py +++ b/src/oblivious/ristretto.py @@ -431,7 +431,7 @@ def sne(s: bytes) -> bytes: 'e9d3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010' """ return ( - (pow(2, 252) + 27742317777372353535851937790883648493 - int.from_bytes(s, 'little')) + (-int.from_bytes(s, 'little') % pow(2, 252) + 27742317777372353535851937790883648493) ).to_bytes(32, 'little') # From f254c6b9673e5ecef02ca382277897a6b8fe4795 Mon Sep 17 00:00:00 2001 From: Frederick Jansen Date: Wed, 8 May 2024 14:04:47 -0400 Subject: [PATCH 4/5] Exclude unsupported Python versions from macos-14 --- .github/workflows/lint-test-cover-docs.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint-test-cover-docs.yml b/.github/workflows/lint-test-cover-docs.yml index bdd176c6..8a4889ce 100644 --- a/.github/workflows/lint-test-cover-docs.yml +++ b/.github/workflows/lint-test-cover-docs.yml @@ -6,9 +6,16 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] - xcl: ['omit', 'install'] + os: [ ubuntu-latest, macos-13, macos-14, windows-latest ] + python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ] + exclude: + - python-version: '3.8' + os: macos-14 + - python-version: '3.9' + os: macos-14 + - python-version: '3.10' + os: macos-14 + xcl: [ 'omit', 'install' ] name: "Python ${{ matrix.python-version }}" steps: - uses: actions/checkout@v4 From b97647446d9c8d9e8d5b833780ee72aecfb20662 Mon Sep 17 00:00:00 2001 From: Frederick Jansen Date: Wed, 8 May 2024 14:18:54 -0400 Subject: [PATCH 5/5] Update Pylint to v3 for Python 12 Don't fail fast on GitHub Actions Make pyproject.toml description single line --- .github/workflows/lint-test-cover-docs.yml | 1 + pyproject.toml | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint-test-cover-docs.yml b/.github/workflows/lint-test-cover-docs.yml index 8a4889ce..48de426b 100644 --- a/.github/workflows/lint-test-cover-docs.yml +++ b/.github/workflows/lint-test-cover-docs.yml @@ -5,6 +5,7 @@ jobs: lint_test_cover_docs: runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ ubuntu-latest, macos-13, macos-14, windows-latest ] python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ] diff --git a/pyproject.toml b/pyproject.toml index 9f46f432..7de478db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,7 @@ [project] name = "oblivious" version = "7.1.0" -description = """\ - Python library that serves as an API for common \ - cryptographic primitives used to implement OPRF, OT, \ - and PSI protocols.\ - """ +description = "Python library that serves as an API for common cryptographic primitives used to implement OPRF, OT, and PSI protocols." license = {text = "MIT"} authors = [ {name = "Nth Party"}, @@ -43,7 +39,7 @@ test = [ "fountains~=2.1" ] lint = [ - "pylint~=2.17.0" + "pylint~=3.1.0" ] coveralls = [ "coveralls~=3.3.1"