diff --git a/examples/example_key_generation.py b/examples/example_key_generation.py index b3b3605..19e7306 100644 --- a/examples/example_key_generation.py +++ b/examples/example_key_generation.py @@ -1,5 +1,4 @@ import argparse -import secrets from pactus.crypto import CryptoConfig from pactus.crypto.address import AddressType @@ -34,21 +33,19 @@ def main() -> None: match AddressType(args.address_type): case AddressType.VALIDATOR: # Generate a cryptographically secure IKM (Initial Keying Material). - ikm = secrets.token_bytes(32) - sec = BLSPrivateKey.key_gen(ikm) + sec = BLSPrivateKey.random() pub = sec.public_key() addr = pub.validator_address() show(sec, pub, addr) case AddressType.BLS_ACCOUNT: - ikm = secrets.token_bytes(32) - sec = BLSPrivateKey.key_gen(ikm) + sec = BLSPrivateKey.random() pub = sec.public_key() addr = pub.account_address() show(sec, pub, addr) case AddressType.ED25519_ACCOUNT: - sec = Ed25519PrivateKey.key_gen() + sec = Ed25519PrivateKey.random() pub = sec.public_key() addr = pub.account_address() show(sec, pub, addr) diff --git a/pactus/crypto/bls/bls12_381/bls_sig_g1.py b/pactus/crypto/bls/bls12_381/bls_sig_g1.py index 6a7b7ed..e448dce 100644 --- a/pactus/crypto/bls/bls12_381/bls_sig_g1.py +++ b/pactus/crypto/bls/bls12_381/bls_sig_g1.py @@ -69,10 +69,15 @@ def _verify_aug(pk, sig, msg, ciphersuite, ver_fn=verify): # signature aggregation -def aggregate(sigs): +def aggregate_sigs(sigs): return reduce(point_add, sigs) +# public key aggregation +def aggregate_pubs(pubs): + return reduce(point_add, pubs) + + # aggregate verification def aggregate_verify(pks, msgs, sig, ciphersuite): assert len(pks) == len(msgs), ( diff --git a/pactus/crypto/bls/private_key.py b/pactus/crypto/bls/private_key.py index a96f0f1..f2dc5b4 100644 --- a/pactus/crypto/bls/private_key.py +++ b/pactus/crypto/bls/private_key.py @@ -1,5 +1,6 @@ from __future__ import annotations +import secrets from math import ceil, log2 from pactus.crypto import CryptoConfig @@ -24,7 +25,7 @@ def from_bytes(cls, buffer: bytes) -> PrivateKey: return cls(int.from_bytes(buffer, "big") % curve_order) @classmethod - def key_gen(cls, ikm: bytes, key_info: bytes = b"") -> PrivateKey: + def key_gen(cls, ikm: bytes = [], key_info: bytes = b"") -> PrivateKey: salt = b"BLS-SIG-KEYGEN-SALT-" sk = 0 while sk == 0: @@ -36,6 +37,12 @@ def key_gen(cls, ikm: bytes, key_info: bytes = b"") -> PrivateKey: return cls(sk) + @classmethod + def random(cls) -> PrivateKey: + ikm = secrets.token_bytes(32) + + return cls.key_gen(ikm) + @classmethod def from_string(cls, text: str) -> PrivateKey: hrp, typ, data = utils.decode_to_base256_with_type(text) diff --git a/pactus/crypto/bls/public_key.py b/pactus/crypto/bls/public_key.py index 2e7e694..91335ed 100644 --- a/pactus/crypto/bls/public_key.py +++ b/pactus/crypto/bls/public_key.py @@ -8,7 +8,7 @@ from pactus.crypto.address import Address, AddressType from pactus.utils import utils -from .bls12_381.bls_sig_g1 import verify +from .bls12_381.bls_sig_g1 import aggregate_pubs, verify from .bls12_381.serdesZ import deserialize, serialize from .signature import DST, SIGNATURE_TYPE_BLS, Signature @@ -39,6 +39,13 @@ def from_string(cls, text: str) -> PublicKey: return cls(point_g2) + @classmethod + def aggregate(cls, pubs: list[PublicKey]) -> PublicKey: + point_g2s = [] + point_g2s.extend(pub.point_g2 for pub in pubs) + + return cls(aggregate_pubs(point_g2s)) + def raw_bytes(self) -> bytes: return serialize(self.point_g2) diff --git a/pactus/crypto/bls/signature.py b/pactus/crypto/bls/signature.py index 66e0372..87ad917 100644 --- a/pactus/crypto/bls/signature.py +++ b/pactus/crypto/bls/signature.py @@ -1,6 +1,6 @@ from __future__ import annotations -from .bls12_381.bls_sig_g1 import aggregate +from .bls12_381.bls_sig_g1 import aggregate_sigs from .bls12_381.serdesZ import deserialize, serialize SIGNATURE_SIZE = 48 @@ -27,9 +27,9 @@ def from_string(cls, text: str) -> Signature: @classmethod def aggregate(cls, sigs: list[Signature]) -> Signature: point_g1s = [] - point_g1s.extend(sig for sig in sigs) + point_g1s.extend(sig.point_g1 for sig in sigs) - return cls(aggregate(point_g1s)) + return cls(aggregate_sigs(point_g1s)) def raw_bytes(self) -> bytes: return serialize(self.point_g1) diff --git a/pactus/crypto/ed25519/private_key.py b/pactus/crypto/ed25519/private_key.py index 7333e94..e37bafe 100644 --- a/pactus/crypto/ed25519/private_key.py +++ b/pactus/crypto/ed25519/private_key.py @@ -20,8 +20,9 @@ def from_bytes(cls, buffer: bytes) -> PrivateKey: return cls(ed25519.Ed25519PrivateKey.from_private_bytes(buffer)) @classmethod - def key_gen(cls) -> PrivateKey: + def random(cls) -> PrivateKey: sk = ed25519.Ed25519PrivateKey.generate() + return cls(sk) @classmethod diff --git a/tests/test_crypto_bls.py b/tests/test_crypto_bls.py index b71f91b..d1f74f5 100644 --- a/tests/test_crypto_bls.py +++ b/tests/test_crypto_bls.py @@ -94,6 +94,34 @@ def test_key_gen(self): if test["sk"] != "Err": self.fail(f"Test '{i}' failed. Unexpected error: {e}") + def test_aggregate_sig(self): + sig1 = BLSSignature.from_string( + "923d67a8624cbb7972b29328e15ec76cc846076ccf00a9e94d991c677846f334ae4ba4551396fbcd6d1cab7593baf3b7" + ) + sig2 = BLSSignature.from_string( + "ab025936daaed80ca2f85a418c8a47c3d9f4137d7b7651ca52646260d2018e55628bba118d4993a3aa75de268d55e72b" + ) + + agg = BLSSignature.aggregate([sig1, sig2]) + self.assertEqual( + agg.string(), + "ad747172697127cb08dda29a386e106eb24ab0edfbc044014c3bd7a5f583cc38b3a223ff2c1df9c0b4df110630e6946b", + ) + + def test_aggregate_pub(self): + pub1 = BLSPublicKey.from_string( + "public1p4u8hfytl2pj6l9rj0t54gxcdmna4hq52ncqkkqjf3arha5mlk3x4mzpyjkhmdl20jae7f65aamjrvqcvf4sudcapz52ctcwc8r9wz3z2gwxs38880cgvfy49ta5ssyjut05myd4zgmjqstggmetyuyg7v5jhx47a" + ) + pub2 = BLSPublicKey.from_string( + "public1pkms34vh00p0jwpdrv6hpqzsx3u26v547948h38wzpp0vc7j408sdy5cql5w5s4rpz60jnzm8rqw4crcw00lgrjeqydpagwstfgdfd79p9yr6rlrr2edtjaqp0shreqxmx0sk4gwlz336hyvnzh7lquxgwcw5nynk" + ) + + agg = BLSPublicKey.aggregate([pub1, pub2]) + self.assertEqual( + agg.string(), + "public1pk5pfgdfe9l6q8mc03wfksx2l4r0h3hrx309sjcyuaredzh5krsfh8a86fuk0kcv2nslcduwz3w0zyqlvv2d42ne04c87hha5dw7dc9r2au5l7vhrruud7wf9u5k4fzg5rma6n940uqgfpjph8d9yg20dzswk7wxj", + ) + if __name__ == "__main__": unittest.main()