Skip to content
35 changes: 35 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Test

on:
pull_request:
branches: [ main, develop ]
workflow_dispatch:

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.10"]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov
pip install -e .

- name: Run tests
run: |
pytest tests/
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "rubix-py"
version = "0.1.0"
version = "0.2.0"
description = "Rubix Client SDK for Python"
requires-python = ">=3.10"
license = { text = "MIT" }
Expand Down
25 changes: 23 additions & 2 deletions rubix/crypto/secp256k1.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ def public_key(self) -> str:
return self.__public_key

def sign(self, message: bytes) -> bytes:
"""Signs a message using the private key.
"""Signs a message using secp256k1 private key.

Args:
message (bytes): The message to sign.

Returns:
str: The hexadecimal signature.
bytes: The generated signature in bytes.
"""
cv = Curve.get_curve('secp256k1')
pv_key = ECPrivateKey(int(self.__private_key, 16), cv)
Expand All @@ -77,3 +77,24 @@ def sign(self, message: bytes) -> bytes:
raise ValueError("Failed to sign the message.")

return sig

def verify(self, message: bytes, signature: bytes) -> bool:
"""Verifies secp256k1 signature.

Args:
message (bytes): The original message that was signed.
signature (bytes): The signature to verify.

Returns:
bool: True if signature is valid, False otherwise.
"""
cv = Curve.get_curve('secp256k1')
priv_key = ECPrivateKey(int(self.__private_key, 16), cv)

pub_key = priv_key.get_public_key()
signer = ECDSA()

try:
return signer.verify(message, signature, pub_key)
except Exception:
return False
36 changes: 35 additions & 1 deletion tests/crypto/test_secp256k1.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,38 @@ def test_secp256k1_keypair_from_mnemonic_seed():
assert isinstance(keypair, Secp256k1Keypair)
assert keypair.public_key is not None
assert isinstance(keypair.public_key, str)
assert len(keypair.public_key) == 66, f"expected compressed public key length of 66 hex characters, got {len(keypair.public_key)}"
assert len(keypair.public_key) == 66, f"expected compressed public key length of 66 hex characters, got {len(keypair.public_key)}"

def test_secp256k1_verify_valid_message():
"""Test signing and verifying a message using Secp256k1 keypair where both messages
used for signing and verifying are the same."""

private_key_hex = "1e99423a4edf5c3d2e8f6b8c3f4e5d6c7b8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c"
private_key_bytes = bytes.fromhex(private_key_hex)

message = b"Test message for signing"

keypair = Secp256k1Keypair.from_private_key(private_key_bytes)
signature = keypair.sign(message)
assert isinstance(signature, bytes)

is_valid = keypair.verify(message, signature)
assert is_valid is True

def test_secp256k1_verify_invalid_message():
"""Test signing and verifying a message using Secp256k1 keypair where both messages
used for signing and verifying are different."""

private_key_hex = "1e99423a4edf5c3d2e8f6b8c3f4e5d6c7b8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c"
private_key_bytes = bytes.fromhex(private_key_hex)

message_1 = b"Test message for signing"
message_2 = b"Test message for signing "

keypair = Secp256k1Keypair.from_private_key(private_key_bytes)
signature = keypair.sign(message_1)
assert isinstance(signature, bytes)

is_valid = keypair.verify(message_2, signature)
assert is_valid is False