44
55sys .path .append (os .path .join (dirname (dirname (__file__ )), 'thirdparty/bls-signatures/python-impl' ))
66
7- from schemes import PopSchemeMPL , PrivateKey as BLSSchemePrivateKey
7+ from schemes import PrivateKey as BLSSchemePrivateKey , core_sign_mpl
88
9+ from crypto .configuration .network import Network
10+ from crypto .exceptions import InvalidProofOfPossessionException
11+ from crypto .identity .address import Address
912from crypto .identity .bls_private_key import BLSPrivateKey
1013
14+ POP_DST = b'MAINSAIL_BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_'
15+
1116
1217class ProofOfPossession :
1318 @staticmethod
@@ -21,33 +26,63 @@ def derive_bls_public_key(cls, passphrase: str) -> str:
2126 sk = BLSSchemePrivateKey .from_bytes (cls .derive_bls_private_key (passphrase ))
2227 return bytes (sk .get_g1 ()).hex ()
2328
29+ @staticmethod
30+ def build_message (chain_id : int , registrant_address : str , public_key : bytes ) -> bytes :
31+ """abi.encodePacked(uint256 chainId, address registrantAddress, bytes blsPublicKey)"""
32+ return chain_id .to_bytes (32 , 'big' ) + bytes .fromhex (registrant_address [2 :]) + public_key
33+
34+ @staticmethod
35+ def validate_registrant_address (registrant_address : str ) -> None :
36+ if not isinstance (registrant_address , str ) or not Address .validate (registrant_address ):
37+ raise InvalidProofOfPossessionException (
38+ f'Invalid registrant address: { registrant_address } '
39+ )
40+
41+ hex_part = registrant_address [2 :]
42+ is_single_case = hex_part == hex_part .lower () or hex_part == hex_part .upper ()
43+
44+ checksum_address = Address .get_checksum_address (registrant_address )
45+
46+ if not is_single_case and checksum_address != registrant_address :
47+ raise InvalidProofOfPossessionException (
48+ f'Invalid registrant address checksum: { registrant_address } '
49+ )
50+
2451 @classmethod
25- def build_proof_of_possession (cls , private_key_bytes : bytes ) -> dict :
26- """Builds proof of possession for a given private key.
52+ def build_proof_of_possession (cls , private_key_bytes : bytes , registrant_address : str ) -> dict :
53+ """Builds a proof of possession bound to the registrant address and the configured
54+ network chain ID.
2755
2856 Args:
2957 private_key_bytes: 32-byte BLS private key
58+ registrant_address: address submitting the registration or update
3059
3160 Returns:
3261 dict with 'pk' (hex, 48 bytes G1) and 'pop' (hex, 96 bytes G2)
3362
3463 Raises:
3564 ValueError: if the key is not exactly 32 bytes or is the zero scalar
65+ InvalidProofOfPossessionException: if the registrant address is invalid
3666 """
3767 if int .from_bytes (private_key_bytes , 'big' ) == 0 :
3868 raise ValueError ('BLS secret key must not be zero' )
3969
40- sk = BLSSchemePrivateKey .from_bytes (private_key_bytes )
41- pk = bytes (sk .get_g1 ()).hex ()
42- pop = bytes (PopSchemeMPL .pop_prove (sk )).hex ()
70+ cls .validate_registrant_address (registrant_address )
71+
72+ sk = BLSSchemePrivateKey .from_bytes (private_key_bytes )
73+ pk = bytes (sk .get_g1 ())
74+ message = cls .build_message (Network .get_network ().chain_id (), registrant_address , pk )
75+ pop = bytes (core_sign_mpl (sk , message , POP_DST ))
4376
44- return {'pk' : pk , 'pop' : pop }
77+ return {'pk' : pk . hex () , 'pop' : pop . hex () }
4578
4679 @classmethod
47- def from_passphrase (cls , passphrase : str ) -> dict :
80+ def from_passphrase (cls , passphrase : str , registrant_address : str ) -> dict :
4881 """Convenience: derives private key from mnemonic and builds PoP.
4982
5083 Returns:
5184 dict with 'pk' (hex, 48 bytes G1) and 'pop' (hex, 96 bytes G2)
5285 """
53- return cls .build_proof_of_possession (cls .derive_bls_private_key (passphrase ))
86+ return cls .build_proof_of_possession (
87+ cls .derive_bls_private_key (passphrase ), registrant_address
88+ )
0 commit comments