Skip to content
Merged
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
7 changes: 6 additions & 1 deletion pactus/crypto/address.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ def from_string(cls, text: str) -> Address:
raise ValueError(msg)

typ = AddressType(typ)
if typ in (AddressType.VALIDATOR, AddressType.BLS_ACCOUNT, AddressType.ED25519_ACCOUNT):
if typ in (
AddressType.VALIDATOR,
AddressType.BLS_ACCOUNT,
AddressType.ED25519_ACCOUNT,
AddressType.SECP256K1_ACCOUNT,
):
if len(data) != 20:
msg = f"Invalid length: {len(data) + 1}"
raise ValueError(msg)
Expand Down
7 changes: 3 additions & 4 deletions pactus/crypto/bls/private_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ def __init__(self, scalar: int) -> None:
self.scalar = scalar % curve_order

@classmethod
def from_bytes(cls, buffer: bytes) -> PrivateKey:
return cls(int.from_bytes(buffer, "big"))
def from_bytes(cls, data: bytes) -> PrivateKey:
return cls(int.from_bytes(data, "big"))

@classmethod
def key_gen(cls, ikm: bytes = [], key_info: bytes = b"") -> PrivateKey:
Expand Down Expand Up @@ -59,8 +59,7 @@ def from_string(cls, text: str) -> PrivateKey:
msg = "Private key data must be 32 bytes long"
raise ValueError(msg)

scalar = int.from_bytes(data, "big")
return cls(scalar)
return cls.from_bytes(data)

def raw_bytes(self) -> bytes:
return self.scalar.to_bytes(PRIVATE_KEY_SIZE, "big")
Expand Down
10 changes: 7 additions & 3 deletions pactus/crypto/bls/public_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ class PublicKey:
def __init__(self, point_g2: any) -> None:
self.point_g2 = point_g2

@classmethod
def from_bytes(cls, data: str) -> PublicKey:
point_g2 = deserialize(bytes(data), is_ell2=True)

return cls(point_g2)

@classmethod
def from_string(cls, text: str) -> PublicKey:
hrp, typ, data = utils.decode_to_base256_with_type(text)
Expand All @@ -35,9 +41,7 @@ def from_string(cls, text: str) -> PublicKey:
msg = "Public key data must be 96 bytes long"
raise ValueError(msg)

point_g2 = deserialize(bytes(data), is_ell2=True)

return cls(point_g2)
return cls.from_bytes(data)

@classmethod
def aggregate(cls, pubs: list[PublicKey]) -> PublicKey:
Expand Down
84 changes: 58 additions & 26 deletions tests/test_crypto_bls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,76 @@
from pactus.crypto.bls import PrivateKey as BLSPrivateKey
from pactus.crypto.bls import PublicKey as BLSPublicKey
from pactus.crypto.bls import Signature as BLSSignature
from pactus.crypto import Address


class TestBLSCrypto(unittest.TestCase):
def test_private_key_to_public_key(self):
prv_str = "SECRET1PDRWTLP5PX0FAHDX39GXZJP7FKZFALML0D5U9TT9KVQHDUC99CMGQQJVK67"
expected_pub_str = "public1p4u8hfytl2pj6l9rj0t54gxcdmna4hq52ncqkkqjf3arha5mlk3x4mzpyjkhmdl20jae7f65aamjrvqcvf4sudcapz52ctcwc8r9wz3z2gwxs38880cgvfy49ta5ssyjut05myd4zgmjqstggmetyuyg7v5jhx47a"
def test_encoding(self):
prv_data = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
pub_data = "a7290fc800d2d14f2dc5e5cb416bebf3267dfed1c6c3a79c6edc4ebd1e657d956daa06a2fcaafd42c94b65b32d4d43ea1368f861006829c475b7d54763a502dfd717e9d51c5cc7deae2981e56090a821c9c5bcafc129b8599203ab99031f4ce7"
val_addr_data = "01c40b914373d4fc9c1e4611ad0acd5f23abf58a0d"
acc_addr_data = "02c40b914373d4fc9c1e4611ad0acd5f23abf58a0d"

prv_str = "SECRET1PQQQSYQCYQ5RQWZQFPG9SCRGWPUGPZYSNZS23V9CCRYDPK8QARC0SEZYD4L"
pub_str = "public1p5u5sljqq6tg57tw9uh95z6lt7vn8mlk3cmp608rwm38t68n90k2km2sx5t724l2ze99ktvedf4p75ymglpssq6pfc36m0428vwjs9h7hzl5a28zucl02u2vpu4sfp2ppe8zmet7p9xu9nysr4wvsx86vuujrva2z"
val_addr_str = "pc1pcs9ezsmn6n7fc8jxzxks4n2lyw4ltzsdc9v8qn"
acc_addr_str = "pc1zcs9ezsmn6n7fc8jxzxks4n2lyw4ltzsd9wu6hw"

prv = BLSPrivateKey.from_string(prv_str)
pub = prv.public_key()
pub_str = pub.string()
pub = BLSPublicKey.from_string(pub_str)
val_addr = Address.from_string(val_addr_str)
acc_addr = Address.from_string(acc_addr_str)

self.assertEqual(pub_str, expected_pub_str)
self.assertEqual(prv_data, prv.raw_bytes().hex())
self.assertEqual(pub_data, pub.raw_bytes().hex())
self.assertEqual(val_addr_data, val_addr.raw_bytes().hex())
self.assertEqual(acc_addr_data, acc_addr.raw_bytes().hex())

def test_public_key_to_address(self):
pub_str = "public1p4u8hfytl2pj6l9rj0t54gxcdmna4hq52ncqkkqjf3arha5mlk3x4mzpyjkhmdl20jae7f65aamjrvqcvf4sudcapz52ctcwc8r9wz3z2gwxs38880cgvfy49ta5ssyjut05myd4zgmjqstggmetyuyg7v5jhx47a"
expected_acc_addr_str = "pc1z5x2a0lkt5nrrdqe0rkcv6r4pfkmdhrr3mawvua"
expected_val_addr_str = "pc1p5x2a0lkt5nrrdqe0rkcv6r4pfkmdhrr3xk73tq"
msg = b"pactus"
sig = BLSSignature.from_string(
"8bdda74336efdf43b428a3811d3d6867a19e20889c91261b02a6b950b130f5bb22621394667c27660bfed2a8719d9c52"
)

pub = BLSPublicKey.from_string(pub_str)
acc_add_str = pub.account_address().string()
val_add_str = pub.validator_address().string()
self.assertTrue(pub.verify(msg, sig))
self.assertEqual(sig.raw_bytes(), prv.sign(msg).raw_bytes())
self.assertEqual(pub.raw_bytes(), prv.public_key().raw_bytes())
self.assertEqual(val_addr.raw_bytes(), pub.validator_address().raw_bytes())
self.assertEqual(acc_addr.raw_bytes(), pub.account_address().raw_bytes())

self.assertEqual(acc_add_str, expected_acc_addr_str)
self.assertEqual(val_add_str, expected_val_addr_str)
def test_sign_and_verify(self):
prv1 = BLSPrivateKey.random()
prv2 = BLSPrivateKey.random()
pub1 = prv1.public_key()
pub2 = prv2.public_key()

def test_sign(self):
prv_str = "SECRET1PDRWTLP5PX0FAHDX39GXZJP7FKZFALML0D5U9TT9KVQHDUC99CMGQQJVK67"
prv = BLSPrivateKey.from_string(prv_str)
prv.public_key()
msg = b"pactus"
prv.sign(msg)
BLSSignature.from_string(
"923d67a8624cbb7972b29328e15ec76cc846076ccf00a9e94d991c677846f334ae4ba4551396fbcd6d1cab7593baf3b7"
)
sig1 = prv1.sign(msg)
sig2 = prv2.sign(msg)

self.assertTrue(pub1.verify(msg, sig1))
self.assertTrue(pub2.verify(msg, sig2))
self.assertFalse(pub1.verify(msg, sig2))
self.assertFalse(pub2.verify(msg, sig1))

# Verify a signature for a different message is rejected
different_msg = b"different"
sig3 = prv1.sign(different_msg)
self.assertFalse(pub1.verify(msg, sig3))

def test_multiple_signatures(self):
# Test that multiple signatures for the same message and key are deterministic
prv = BLSPrivateKey.random()
pub = prv.public_key()

msg = b"pactus"
sig1 = prv.sign(msg)
sig2 = prv.sign(msg)

# Both signatures should verify
self.assertTrue(pub.verify(msg, sig1))
self.assertTrue(pub.verify(msg, sig2))

# self.assertEqual(sig.string(), expected_sig.string())
# self.assertTrue(pub.verify(msg, sig))
# self.assertFalse(pub.verify(b"foo", sig))
self.assertEqual(sig1.raw_bytes(), sig2.raw_bytes())

def test_key_gen(self):
tests = [
Expand Down
80 changes: 51 additions & 29 deletions tests/test_crypto_ed25519.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,71 @@
from pactus.crypto.ed25519 import PrivateKey as Ed25519PrivateKey
from pactus.crypto.ed25519 import PublicKey as Ed25519PublicKey
from pactus.crypto.ed25519 import Signature as Ed25519Signature
from pactus.crypto import Address


class TestEd25519Crypto(unittest.TestCase):
def test_private_key_to_public_key(self):
prv_str = "SECRET1RJ6STNTA7Y3P2QLQF8A6QCX05F2H5TFNE5RSH066KZME4WVFXKE7QW097LG"
expected_pub_str = (
"public1ry2cqw5yfhmr7ve8nctgzg6wgcyc73xqr2uud486jgsq7wu253egsx6msep"
)
def test_encoding(self):
prv_data = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
pub_data = "03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8"
addr_data = "0396a882c41ef85a07c75a6416a57fcce95aad4a3f"

prv_str = "SECRET1RQQQSYQCYQ5RQWZQFPG9SCRGWPUGPZYSNZS23V9CCRYDPK8QARC0SW5D8X2"
pub_str = "public1rqwss00lnecgtu8tsm5vwwj7qn9n7f43snwjs6hcamjrxgyj4xxuq5agu5g"
addr_str = "pc1rj65g93q7lpdq0366vst22l7va9d26j3l2vr0em"

prv = Ed25519PrivateKey.from_string(prv_str)
pub = prv.public_key()
pub_str = pub.string()
pub = Ed25519PublicKey.from_string(pub_str)
addr = Address.from_string(addr_str)

self.assertEqual(pub_str, expected_pub_str)
self.assertEqual(prv_data, prv.raw_bytes().hex())
self.assertEqual(pub_data, pub.raw_bytes().hex())
self.assertEqual(addr_data, addr.raw_bytes().hex())

def test_public_key_to_address(self):
pub_str = "public1ry2cqw5yfhmr7ve8nctgzg6wgcyc73xqr2uud486jgsq7wu253egsx6msep"
expected_acc_addr_str = "pc1reer4damrrdxznmrrl7a9acy7x5cwe6dyt8ftv4"
msg = b"pactus"
sig = Ed25519Signature.from_string(
"1fc2c800499342d08242db9c3eb654027cb7b821e6af9ede56dfdb67e824f15bddb419d2db3fd5aaf3ef1a9ebb9a9deb749380f0d6a110cbe95319fe9f794305"
)

pub = Ed25519PublicKey.from_string(pub_str)
acc_add_str = pub.account_address().string()
self.assertTrue(pub.verify(msg, sig))
self.assertEqual(sig.raw_bytes(), prv.sign(msg).raw_bytes())
self.assertEqual(pub.raw_bytes(), prv.public_key().raw_bytes())
self.assertEqual(addr.raw_bytes(), pub.account_address().raw_bytes())

self.assertEqual(acc_add_str, expected_acc_addr_str)
def test_sign_and_verify(self):
prv1 = Ed25519PrivateKey.random()
prv2 = Ed25519PrivateKey.random()
pub1 = prv1.public_key()
pub2 = prv2.public_key()

def test_sign(self):
prv_str = "secret1r0up878rawjec2evjnd4k42a4g4pcardesjk48jtn64qwjnfv7veqal53e2"
prv = Ed25519PrivateKey.from_string(prv_str)
pub = prv.public_key()
msg = b"pactus"
sig = prv.sign(msg)
sig1 = prv1.sign(msg)
sig2 = prv2.sign(msg)

valid_sig = Ed25519Signature.from_string(
"55eaa9656158874bff726c8d62abe0a5b66d2434705f46b58c905a42f1b39fb95f640fbacac97e6e6862220fe7b4f249a0f79dc3d37b4460156c58580778b70e"
)
self.assertTrue(pub1.verify(msg, sig1))
self.assertTrue(pub2.verify(msg, sig2))
self.assertFalse(pub1.verify(msg, sig2))
self.assertFalse(pub2.verify(msg, sig1))

invalid_sig = Ed25519Signature.from_string(
"5a61ecb3c08825010678f12c036cec2e1dd1b8767ed9fd95a97f560dfd6196b600fdc7bba22f13eae19ca578b920eb807eb6cd956d55f1d778fee75155d4ea07"
)
# Verify a signature for a different message is rejected
different_msg = b"different"
sig3 = prv1.sign(different_msg)
self.assertFalse(pub1.verify(msg, sig3))

def test_multiple_signatures(self):
# Test that multiple signatures for the same message and key are deterministic
prv = Ed25519PrivateKey.random()
pub = prv.public_key()

msg = b"pactus"
sig1 = prv.sign(msg)
sig2 = prv.sign(msg)

# Both signatures should verify
self.assertTrue(pub.verify(msg, sig1))
self.assertTrue(pub.verify(msg, sig2))

self.assertTrue(pub.verify(msg, valid_sig))
self.assertFalse(pub.verify(msg, invalid_sig))
self.assertEqual(sig.string(), valid_sig.string())
self.assertEqual(sig.raw_bytes(), valid_sig.raw_bytes())
self.assertEqual(sig1.raw_bytes(), sig2.raw_bytes())


if __name__ == "__main__":
Expand Down
66 changes: 35 additions & 31 deletions tests/test_crypto_secp256k1.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,56 @@
from pactus.crypto.secp256k1 import PrivateKey as Secp256k1PrivateKey
from pactus.crypto.secp256k1 import PublicKey as Secp256k1PublicKey
from pactus.crypto.secp256k1 import Signature as Secp256k1Signature
from pactus.crypto import Address


class TestSecp256k1Crypto(unittest.TestCase):
def test_private_key_to_public_key(self):
prv_str = "secret1yqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsd25y3e"
expected_pub_str = (
"public1yqvdcf32k0vfxgsyet5ldt246q4jaw8scx3sysx0lnstlt6w4m5rc7k3ysjp"
)

prv = Secp256k1PrivateKey.from_string(prv_str)
pub = prv.public_key()
pub_str = pub.string()
def test_encoding(self):
prv_data = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
pub_data = "036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2"
addr_data = "042bc1db7e0797c45b918dc401093c9257c6012b4c"

self.assertEqual(pub_str, expected_pub_str)

def test_public_key_to_address(self):
pub_str = "public1yqvdcf32k0vfxgsyet5ldt246q4jaw8scx3sysx0lnstlt6w4m5rc7k3ysjp"
expected_acc_addr_str = "pc1yj7ag28h54jf4e09nnednjhgmg60srnvj7uu39v"
prv_str = "SECRET1YQQQSYQCYQ5RQWZQFPG9SCRGWPUGPZYSNZS23V9CCRYDPK8QARC0SPVXU8Z"
pub_str = "public1yqdkke2kzfzheda405lusfa2sy5aq70hn7k4zle5r322my9nfz35wyfamrfs"
addr_str = "pc1y90qakls8jlz9hyvdcsqsj0yj2lrqz26vqu7l0z"

prv = Secp256k1PrivateKey.from_string(prv_str)
pub = Secp256k1PublicKey.from_string(pub_str)
acc_add_str = pub.account_address().string()
addr = Address.from_string(addr_str)

self.assertEqual(acc_add_str, expected_acc_addr_str)
self.assertEqual(prv_data, prv.raw_bytes().hex())
self.assertEqual(pub_data, pub.raw_bytes().hex())
self.assertEqual(addr_data, addr.raw_bytes().hex())

def test_sign_and_verify(self):
prv_str = "secret1yqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsd25y3e"
prv = Secp256k1PrivateKey.from_string(prv_str)
pub = prv.public_key()
msg = b"pactus"
sig = prv.sign(msg)

# Verify the signature is correct length (64 bytes)
self.assertEqual(len(sig.raw_bytes()), 64)
sig = Secp256k1Signature.from_string(
"16e6f8bcdb92964a35773aae200628a5b470b6488d42ceef6538da0b4ffd3b42098dd821eea96f66ba02c9c4473443ab51c411ab78adfbb90d53b07ca1d6862b"
)

# Verify the signature verifies correctly
self.assertTrue(pub.verify(msg, sig))
self.assertEqual(sig.raw_bytes(), prv.sign(msg).raw_bytes())
self.assertEqual(pub.raw_bytes(), prv.public_key().raw_bytes())
self.assertEqual(addr.raw_bytes(), pub.account_address().raw_bytes())

def test_sign_and_verify(self):
prv1 = Secp256k1PrivateKey.random()
prv2 = Secp256k1PrivateKey.random()
pub1 = prv1.public_key()
pub2 = prv2.public_key()

msg = b"pactus"
sig1 = prv1.sign(msg)
sig2 = prv2.sign(msg)

# Verify an invalid signature is rejected
invalid_sig_bytes = b"\x00" * 64
invalid_sig = Secp256k1Signature.from_string(invalid_sig_bytes.hex())
self.assertFalse(pub.verify(msg, invalid_sig))
self.assertTrue(pub1.verify(msg, sig1))
self.assertTrue(pub2.verify(msg, sig2))
self.assertFalse(pub1.verify(msg, sig2))
self.assertFalse(pub2.verify(msg, sig1))

# Verify a signature for a different message is rejected
different_msg = b"different"
sig2 = prv.sign(different_msg)
self.assertFalse(pub.verify(msg, sig2))
sig3 = prv1.sign(different_msg)
self.assertFalse(pub1.verify(msg, sig3))

def test_key_generation(self):
# Test random key generation
Expand Down
Loading