From 30e5943564ec8630c61314f0ef33b65d901d686f Mon Sep 17 00:00:00 2001 From: msinkec Date: Mon, 13 Nov 2023 09:39:44 +0100 Subject: [PATCH] Add python tests, add travis config. --- .gitignore | 4 +++- .travis.yml | 40 +++++++++++++++++++++++++++++++++++ package.json | 3 ++- py/README.md | 31 +++++++++++++++++++++++---- py/rabin | 37 ++++++++++++++++++++++++++++++++ py/rabin.py | 55 ++++++++++++++++-------------------------------- py/test_rabin.py | 39 ++++++++++++++++++++++++++++++++++ 7 files changed, 166 insertions(+), 43 deletions(-) create mode 100644 .travis.yml create mode 100755 py/rabin create mode 100644 py/test_rabin.py diff --git a/.gitignore b/.gitignore index a02f2e1..b1824f4 100644 --- a/.gitignore +++ b/.gitignore @@ -112,4 +112,6 @@ dist .yarn/cache .yarn/unplugged .yarn/build-state.yml -.pnp.* \ No newline at end of file +.pnp.* + +__pycache__/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2b1ef5e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,40 @@ +dist: focal +language: node_js +node_js: + - 16 + - 18 + - 19 + +before_install: + +os: + - linux + - osx + - windows + +osx_image: + - xcode9.4 + - xcode14 + +jobs: + exclude: + - osx_image: xcode9.4 + node_js: 18 + - osx_image: xcode9.4 + node_js: 19 + - osx_image: xcode14 + node_js: 16 + # Only test on linux for regular test jobs + - if: branch != master + node_js: 18 + - if: branch != master + node_js: 19 + - if: type = pull_request + os: osx + - if: type = pull_request + os: windows + +script: + - npm i + - npm run test-all-lang + diff --git a/package.json b/package.json index e9f1e16..1f5f16d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build": "rimraf dist && tsc", "test": "npm run build && mocha", "example": "npm run build && node dist/example.js", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run build", + "test-all-lang": "npm run build && mocha && pytest -s py/test_rabin.py" }, "repository": { "type": "git", diff --git a/py/README.md b/py/README.md index 8585589..933c45f 100644 --- a/py/README.md +++ b/py/README.md @@ -1,9 +1,32 @@ -# A simple python script for Rabin Signature +# A simple Python library for generating Rabin signatures + +## Usage + +### As a Python module + +```python +import rabin + + +# Generate prime pair +seed = b'\x01' +p, q = rabin.gen_prime_pair(seed) +n = p * q + +# Sign message +message = bytes.fromhex('00112233445566778899aabbccddeeff') +sig, pad = rabin.sign_rabin(p, q, message) + +# Verify signature +res = rabin.verify_rabin(n, message, sig, pad) +``` + +### From the command line 1. Generate key pairs with a seed. ```bash -> python3 rabin.py G 01 +> rabin G 01 generate primes ... n_rabin = 0x4dd67a38e65c6d5d0877e892f1453fa09d27313f1431fcea6e703571fd56bf0b8bdd4788d94a7ec79c4232ead62eb34cd4f212e13fddaadf659ac6e45dc32c9 ``` @@ -11,7 +34,7 @@ n_rabin = 0x4dd67a38e65c6d5d0877e892f1453fa09d27313f1431fcea6e703571fd56bf0b8bdd 2. Sign a message: get number of padding bytes and signature ```bash -> python3 rabin.py S 00112233445566778899aabbccddeeff +> rabin S 00112233445566778899aabbccddeeff padding = 3 digital signature = 0x420818748a86065611c0e1be3c0bae9c22fe5e515a4a35601be8b4d8bc1049c75775e01e07e2257a689e916ea7751bdfc8b1eeb51d418e2714ae2fc8eadde1b ``` @@ -19,6 +42,6 @@ digital signature = 0x420818748a86065611c0e1be3c0bae9c22fe5e515a4a35601be8b4d8bc 3. Verify signature with results from step 2 ```bash -> python3 rabin.py V 00112233445566778899aabbccddeeff 3 420818748a86065611c0e1be3c0bae9c22fe5e515a4a35601be8b4d8bc1049c75775e01e07e2257a689e916ea7751bdfc8b1eeb51d418e2714ae2fc8eadde1b +> rabin V 00112233445566778899aabbccddeeff 3 420818748a86065611c0e1be3c0bae9c22fe5e515a4a35601be8b4d8bc1049c75775e01e07e2257a689e916ea7751bdfc8b1eeb51d418e2714ae2fc8eadde1b result of verification: True ``` diff --git a/py/rabin b/py/rabin new file mode 100755 index 0000000..ed9e4d6 --- /dev/null +++ b/py/rabin @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +import sys +from rabin import next_prime, sign, SECURITY_LEVEL, write_number, hash_to_int + + +if __name__ == '__main__': + print('\n rabin signature - sCrypt Inc 2020 adapted from Scheerer - all rights reserved') + print('\n rabin signature - copyright Scheerer Software 2018 - all rights reserved') + + print('\n\nFirst parameter is V (Verify) or S (Sign) or G (Generate)') + print('\n verify signature (2 parameters):') + print(' > python rabin.py V ') + print('\n create signature S (2 parameter):') + print(' > python rabin.py S ') + print('\n generate key pair G (2 parameter):') + print(' > python rabin.py G ') + + print(f'\n\nnumber of parameters is {len(sys.argv) - 1}') + + if len(sys.argv) == 5 and sys.argv[1] == 'V': + print(f'\n result of verification: {verify(sys.argv[2], sys.argv[3], sys.argv[4])}') + + if len(sys.argv) == 3 and sys.argv[1] == 'S': + sig, pad = sign(sys.argv[2]) + print(f'\n padding = {pad}') + print(f' digital signature = {hex(sig)}') + + if len(sys.argv) == 3 and sys.argv[1] == 'G': + print('\n generate primes ... ') + priv_range = 2 ** (256 * SECURITY_LEVEL) + p_rabin = next_prime(hash_to_int(bytes.fromhex(sys.argv[2])) % priv_range) + q_rabin = next_prime(hash_to_int(bytes.fromhex(sys.argv[2] + '00')) % priv_range) + write_number(p_rabin, 'p') + write_number(q_rabin, 'q') + write_number(p_rabin * q_rabin, 'n') + print(f'\n n_rabin = {hex(p_rabin * q_rabin)}') diff --git a/py/rabin.py b/py/rabin.py index 95821c9..886dfcc 100644 --- a/py/rabin.py +++ b/py/rabin.py @@ -13,6 +13,16 @@ def gcd(a: int, b: int) -> int: return a +def gen_prime_pair(seed) -> tuple: + if isinstance(seed, str): + seed = bytes.fromhex(seed) + + priv_range = 2 ** (256 * SECURITY_LEVEL) + p = next_prime(hash_to_int(seed) % priv_range) + q = next_prime(hash_to_int(seed + b'\x00') % priv_range) + return (p, q) + + def next_prime(p: int) -> int: while p % 4 != 3: p = p + 1 @@ -81,45 +91,16 @@ def read_number(filename: str) -> int: return int(f.read()) -def sign(hex_message: str) -> tuple: - p = read_number('p') - q = read_number('q') +def sign(hex_message: str, p=None, q=None) -> tuple: + if not p: + p = read_number('p') + if not q: + q = read_number('q') return sign_rabin(p, q, bytes.fromhex(hex_message)) -def verify(hex_message: str, padding: str, hex_signature: str): - n = read_number('n') +def verify(hex_message: str, padding: str, hex_signature: str, n=None): + if not n: + n = read_number('n') return verify_rabin(n, bytes.fromhex(hex_message), int(hex_signature, 16), int(padding)) - -if __name__ == '__main__': - print('\n rabin signature - sCrypt Inc 2020 adapted from Scheerer - all rights reserved') - print('\n rabin signature - copyright Scheerer Software 2018 - all rights reserved') - - print('\n\nFirst parameter is V (Verify) or S (Sign) or G (Generate)') - print('\n verify signature (2 parameters):') - print(' > python rabin.py V ') - print('\n create signature S (2 parameter):') - print(' > python rabin.py S ') - print('\n generate key pair G (2 parameter):') - print(' > python rabin.py G ') - - print(f'\n\nnumber of parameters is {len(sys.argv) - 1}') - - if len(sys.argv) == 5 and sys.argv[1] == 'V': - print(f'\n result of verification: {verify(sys.argv[2], sys.argv[3], sys.argv[4])}') - - if len(sys.argv) == 3 and sys.argv[1] == 'S': - sig, pad = sign(sys.argv[2]) - print(f'\n padding = {pad}') - print(f' digital signature = {hex(sig)}') - - if len(sys.argv) == 3 and sys.argv[1] == 'G': - print('\n generate primes ... ') - priv_range = 2 ** (256 * SECURITY_LEVEL) - p_rabin = next_prime(hash_to_int(bytes.fromhex(sys.argv[2])) % priv_range) - q_rabin = next_prime(hash_to_int(bytes.fromhex(sys.argv[2] + '00')) % priv_range) - write_number(p_rabin, 'p') - write_number(q_rabin, 'q') - write_number(p_rabin * q_rabin, 'n') - print(f'\n n_rabin = {hex(p_rabin * q_rabin)}') diff --git a/py/test_rabin.py b/py/test_rabin.py new file mode 100644 index 0000000..3981863 --- /dev/null +++ b/py/test_rabin.py @@ -0,0 +1,39 @@ +import rabin + +def test_gen_prime_pair(): + # Test prime pair generation + seed = b'\x01' + p, q = rabin.gen_prime_pair(seed) + assert isinstance(p, int) and isinstance(q, int), "p and q should be integers" + assert p != q, "p and q should be distinct primes" + +def test_sign_rabin(): + # Test signing a message + seed = b'\x01' + p, q = rabin.gen_prime_pair(seed) + message = bytes.fromhex('00112233445566778899aabbccddeeff') + sig, pad = rabin.sign_rabin(p, q, message) + assert isinstance(sig, int), "Signature should be an integer" + assert isinstance(pad, int), "Padding should be int" + +def test_verify_rabin(): + # Test verifying a signature + seed = b'\x01' + p, q = rabin.gen_prime_pair(seed) + n = p * q + message = bytes.fromhex('00112233445566778899aabbccddeeff') + sig, pad = rabin.sign_rabin(p, q, message) + res = rabin.verify_rabin(n, message, sig, pad) + assert res is True, "Signature verification failed" + +def test_verify_rabin_with_wrong_signature(): + # Test verifying with a wrong signature + seed = b'\x01' + p, q = rabin.gen_prime_pair(seed) + n = p * q + message = bytes.fromhex('00112233445566778899aabbccddeeff') + wrong_sig = 12345 # Intentionally wrong signature + pad = 8 # Intentionally wrong padding + res = rabin.verify_rabin(n, message, wrong_sig, pad) + assert res is False, "Verification should fail with wrong signature" +