def test_aggregate_verify(SKs, messages, result): PKs = [G2Basic.SkToPk(SK) for SK in SKs] messages = [bytes(msg) for msg in messages] signatures = [G2Basic.Sign(SK, msg) for SK, msg in zip(SKs, messages)] aggregate_signature = G2Basic.Aggregate(signatures) assert G2Basic.AggregateVerify(PKs, messages, aggregate_signature) == result
def test_aggregate_verify(SKs, messages, success): PKs = [G2Basic.PrivToPub(SK) for SK in SKs] messages = [bytes(msg) for msg in messages] signatures = [G2Basic.Sign(SK, msg) for SK, msg in zip(SKs, messages)] aggregate_signature = G2Basic.Aggregate(signatures) assert G2Basic.AggregateVerify(zip(PKs, messages), aggregate_signature) == success
def test_core_aggregate_verify(SKs, messages): PKs = [G2Basic.SkToPk(sk) for sk in SKs] messages = [bytes(msg) for msg in messages] signatures = [ G2Basic._CoreSign(sk, msg, G2Basic.DST) for sk, msg in zip(SKs, messages) ] aggregate_signature = G2Basic.Aggregate(signatures) assert G2Basic._CoreAggregateVerify(PKs, messages, aggregate_signature, G2Basic.DST)
def get_master_pubkey(root_G1: tuple, chaincode_G1: tuple) -> bytes: try: root_pubkey = bls_conv.G1_to_pubkey(root_G1) public_chaincode_address = bls_conv.G1_to_pubkey(chaincode_G1) except: raise DerivationErrorBasic('Encoding master public key') if not bls_basic.KeyValidate(root_pubkey): raise DerivationErrorBasic('Encoding public root key') if not bls_basic.KeyValidate(public_chaincode_address): raise DerivationErrorBasic('Encoding public root chaincode') joint = root_pubkey + public_chaincode_address return joint + sha512(joint).digest()[:6]
def derive_private_child(master_private_key_share: bytes, path: bytes, master_pubkey: bytes = None) -> int: root_private_key_share, root_private_chaincode_share = parse_master_private_key( master_private_key_share) if master_pubkey: root_public_key, root_public_chaincode = parse_master_pubkey( master_pubkey) root_pubkey_address = bls_conv.G1_to_pubkey(root_public_key) public_chaincode_address = bls_conv.G1_to_pubkey(root_public_chaincode) else: root_pubkey_address = bls_basic.SkToPk(root_private_key_share) public_chaincode_address = bls_basic.SkToPk( root_private_chaincode_share) h = _hash_to_bls_field( _derivation_format_hash_input(root_pubkey_address, public_chaincode_address, path)) derived_private = (root_private_key_share + root_private_chaincode_share * h) % bls_curve.curve_order return derived_private
def parse_master_pubkey(master_pubkey: bytes) -> Tuple[tuple, tuple]: if not len(master_pubkey) == 102: raise DerivationErrorBasic( f'Wrong length master public key, got {len(master_pubkey)} bytes, should be 102' ) checksum = master_pubkey[96:102] if sha512(master_pubkey[:96]).digest()[:6] != checksum: raise DerivationErrorBasic(f'Wrong master public key checksum') if not bls_basic.KeyValidate(master_pubkey[:48]): raise DerivationErrorBasic('Parsing master public key') if not bls_basic.KeyValidate(master_pubkey[48:96]): raise DerivationErrorBasic('Parsing master public chaincode') try: root_public_key = bls_conv.pubkey_to_G1(master_pubkey[:48]) public_chaincode = bls_conv.pubkey_to_G1(master_pubkey[48:96]) except: raise DerivationErrorBasic( 'Parsing master public to root public key and chaincode') return root_public_key, public_chaincode
def test_sk_to_pk(privkey, success): if success: G2Basic.SkToPk(privkey) else: with pytest.raises(ValidationError): G2Basic.SkToPk(privkey)
def test_verify(pubkey, message, signature, result): assert G2Basic.Verify(pubkey, message, signature) == result
(1, 123, False), # wrong type message ] ) def test_sign(privkey, message, success): if success: G2Basic.Sign(privkey, message) else: with pytest.raises(ValidationError): G2Basic.Sign(privkey, message) @pytest.mark.parametrize( 'signatures, success', [ ([G2Basic.Sign(1, b'helloworld')], True), ([G2Basic.Sign(1, b'helloworld'), G2Basic.Sign(2, b'helloworld')], True), ([Z2_SIGNATURE], True), (['hello'], False), ([], False), ] ) def test_aggregate(signatures, success): if success: G2Basic.Aggregate(signatures) else: with pytest.raises(ValidationError): G2Basic.Aggregate(signatures) SAMPLE_MESSAGE = b'helloworld'
def generate_bls_key_shares_with_verification(rsa_key_files:Dict[int,str], threshold:int, integrity_passphrase:str): parties_ids = list(rsa_key_files.keys()) # Generate and Shamir secret share root private key with related public keys and shares root_private_key_shares, root_private_key = _sample_shares(parties_ids, threshold) root_public_key = bls_conv.pubkey_to_G1(bls_basic.SkToPk(root_private_key)) root_public_key_shares = {id : bls_conv.pubkey_to_G1(bls_basic.SkToPk(val)) for id, val in root_private_key_shares.items()} # Generate and Shamir secret share private chaincode with related public chaincode private_chaincode_shares, private_chaincode = _sample_shares(parties_ids, threshold) public_chaincode = bls_conv.pubkey_to_G1(bls_basic.SkToPk(private_chaincode)) # Set master public key and private key shares master_pubkey = get_master_pubkey(root_public_key, public_chaincode) master_private_key_shares = {id : get_master_private_key(root_private_key_shares[id], private_chaincode_shares[id]) for id in parties_ids} # Senity check: verify all authorized set of keys (threshold size) generate the same root public key as above for auth_ids in itertools.combinations(parties_ids, threshold): curr_public_key = _interpolate_in_group({id : root_public_key_shares[id] for id in auth_ids}, bls_curve.G1) if not bls_curve.eq(root_public_key, curr_public_key): raise GenVerErrorBasic(f'Invalid Shamir secret sharing of public key for parties {auth_ids}') # Sign test message with each private key share, derived at test path test_derivation_path = _get_test_path() derived_pubkey_address = bls_conv.G1_to_pubkey(derive_public_child(master_pubkey, _get_test_path())) signature_shares = {} for id in parties_ids: try: derived_private_key_share = derive_private_child(master_private_key_shares[id], test_derivation_path, master_pubkey) test_msg, _ = _get_msg_for_address(derived_pubkey_address) signature_shares[id] = bls_basic.Sign(derived_private_key_share, test_msg) except: raise GenVerErrorBasic(f'Unable to sign test message for id {id}') # Scrypt checksum of public key - to avoid manipulation and brute-force integrity_passphrase = bytes(integrity_passphrase,'utf-8') scrypt_checksum = _compute_scrypt_checksum(integrity_passphrase, master_pubkey) # Encrypt master private key shares and (common) integrity passphrase encrypted_master_private_key_shares = {} encrypted_integrity_passphrase = {} for id, rsa_key_file in rsa_key_files.items(): try: rsa_key = RSA.importKey(open(rsa_key_file, 'r').read()) cipher = PKCS1_OAEP.new(rsa_key) except: raise GenVerErrorBasic(f'Reading RSA key file {rsa_key_file}') if rsa_key.n.bit_length() < 4096: GenVerErrorBasic(f'RSA key is too short: {rsa_key.n.bit_length()}, should be at least 4096 bits') try: encrypted_master_private_key_shares[id] = cipher.encrypt(master_private_key_shares[id]) encrypted_integrity_passphrase[id] = cipher.encrypt(integrity_passphrase) except: raise GenVerErrorBasic(f'Unable to encrypt master private key share for id {id}') # Prepare data to output to file data = {} data['master_pubkey'] = master_pubkey.hex() # Ingerity check for master public key data['integrity_checksum'] = scrypt_checksum.hex() data['threshold'] = threshold data['parties'] = {} for id in parties_ids: party = {} party['test_signature_share'] = signature_shares[id].hex() party['encrypted_master_private_key_share'] = encrypted_master_private_key_shares[id].hex() party['encrypted_integrity_passphrase'] = encrypted_integrity_passphrase[id].hex() data['parties'][id] = party # Output file for each party id (key id) for id in parties_ids: data['key_id'] = id # Write to file key_filename = f'id_{id}_fireblocks_bls_key_{master_pubkey[:4].hex()}.json' try: ver_file = open(key_filename, 'w+') json.dump(data, ver_file, indent=4) ver_file.close() print("Generated file:", colored(f'{key_filename}', "green")) except ValueError: raise GenVerErrorBasic(f'Error writing key file for id {id}') return master_pubkey
def verify_signature_files(signature_files:Sequence[str], threshold:int=None) -> bool: # Get data from signature share file parties_ids = [] signature_shares = {} master_pubkey = None msg = None derivation_index = None for sig_file in signature_files: in_data = {} try: in_file = open(sig_file, "r") in_data = json.load(in_file) in_file.close() except: raise GenVerErrorBasic(f'Reading key file {sig_file}') try: # Verify same master pubkey curr_master_pubkey = bytes.fromhex(in_data['master_pubkey']) if master_pubkey: if not master_pubkey == curr_master_pubkey: raise GenVerErrorBasic(f'Different master pubkey in different files') else: master_pubkey = curr_master_pubkey # Verify same message curr_msg = in_data['message'] if msg: if not msg == curr_msg: raise GenVerErrorBasic(f'Different messages in different files') else: msg = curr_msg # Verify same derivation indx curr_index = in_data['derivation_index'] if derivation_index: if not derivation_index == curr_index: raise GenVerErrorBasic(f'Different derivation index in different files') else: derivation_index = curr_index curr_id = int(in_data['signer_id']) parties_ids.append(curr_id) signature_shares[curr_id] = bytes.fromhex(in_data['signature']) except: raise GenVerErrorBasic(f'Parsing key file {sig_file}') msg_str = msg['payload'] if msg['hex']: try: msg_bytes = bytes.fromhex(msg['payload']) except: raise GenVerErrorBasic(f'Message string is not hex') else: msg_bytes = bytes(msg_str, 'ascii') # Convert signature shares to G2 elements to later interpolate on G2_signature_shares = {} for id, sig in signature_shares.items(): try: G2_signature_shares[id] = bls_conv.signature_to_G2(sig) except: raise GenVerErrorBasic(f'Invalid encoding of signature for id {id}') if not threshold: threshold = len(parties_ids) der_path = index_to_path(derivation_index) derived_pubkey_address = bls_conv.G1_to_pubkey(derive_public_child(master_pubkey, der_path)) print(f'Verifying signing threshold {threshold} out of {len(parties_ids)} parties') print("Message:", colored(msg_str, "green")) print("Public Key:", colored(derived_pubkey_address.hex(), "green")) # Verify joining threshold singature shares gives a valid signature auth_signature = b'' for auth_ids in itertools.combinations(parties_ids, threshold): auth_signature = bls_conv.G2_to_signature(_interpolate_in_group({id : G2_signature_shares[id] for id in auth_ids}, bls_curve.G2)) if not bls_basic.Verify(derived_pubkey_address, msg_bytes, auth_signature): raise GenVerErrorBasic(f'Failed verification of combined signature for id {auth_ids}.') print("Signature:", colored(auth_signature.hex(), "green")) print(colored("Success!", "green")) return True
def test_sign_verify(privkey): msg = str(privkey).encode('utf-8') pub = G2Basic.SkToPk(privkey) sig = G2Basic._CoreSign(privkey, msg, G2Basic.DST) assert G2Basic._CoreVerify(pub, msg, sig, G2Basic.DST)
from py_ecc.optimized_bls12_381 import ( G1, G2, multiply, ) from py_ecc.bls.g2_primatives import ( G1_to_pubkey, G2_to_signature, ) from py_ecc.bls import G2Basic @pytest.mark.parametrize( 'pubkey,success', [ (G2Basic.SkToPk(42), True), (b'\x11' * 48, False), ] ) def test_key_validate(pubkey, success): assert G2Basic.KeyValidate(pubkey) == success @pytest.mark.parametrize( 'privkey', [ (1), (5), (124), (735), (127409812145),
def test_sign(privkey, message, success): if success: G2Basic.Sign(privkey, message) else: with pytest.raises(ValidationError): G2Basic.Sign(privkey, message)
@pytest.mark.parametrize( 'privkey, message, success', [ (1, b'message', True), (0, b'message', False), ('hello', b'message', False), # wrong type privkey (1, 123, False), # wrong type message ]) def test_sign(privkey, message, success): if success: G2Basic.Sign(privkey, message) else: with pytest.raises(ValidationError): G2Basic.Sign(privkey, message) @pytest.mark.parametrize('signatures, success', [ ([G2Basic.Sign(1, b'helloworld')], True), ([G2Basic.Sign(1, b'helloworld'), G2Basic.Sign(2, b'helloworld')], True), ([Z2_SIGNATURE], True), (['hello'], False), ([], False), ]) def test_aggregate(signatures, success): if success: G2Basic.Aggregate(signatures) else: with pytest.raises(ValidationError): G2Basic.Aggregate(signatures)
def test_key_gen(ikm, result_sk): _, sk = G2Basic.KeyGen(ikm) assert sk == result_sk
(bytes.fromhex( '0099FF991111002299DD7744EE3355BBDD8844115566CC55663355668888CC00'), 45379166311535261329029945990467475187325618028073620882733843918126031931161 ), (bytes.fromhex( 'd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3'), 31740500954810567003972734830331791822878290325762596213711963944729383643688 ), ]) def test_key_gen(ikm, result_sk): _, sk = G2Basic.KeyGen(ikm) assert sk == result_sk @pytest.mark.parametrize('pubkey,success', [ (G2Basic.PrivToPub(42), True), (b'11' * 48, False), ]) def test_key_validate(pubkey, success): assert G2Basic.KeyValidate(pubkey) == success @pytest.mark.parametrize('privkey', [ (1), (5), (124), (735), (127409812145), (90768492698215092512159), (0), ])
def test_aggregate(signatures, success): if success: G2Basic.Aggregate(signatures) else: with pytest.raises(ValidationError): G2Basic.Aggregate(signatures)
def verify_key_file(key_file:str, passphrase:str, rsa_priv_key_file:str=None): try: in_file = open(key_file, "r") data = json.load(in_file) in_file.close() except: raise GenVerErrorBasic(f'Reading key file {key_file}') try: master_pubkey = bytes.fromhex(data['master_pubkey']) integrity_checksum = bytes.fromhex(data['integrity_checksum']) threshold = data['threshold'] my_id = data['key_id'] parties = data['parties'] encrypted_private_key_share = bytes.fromhex(parties[f'{my_id}']['encrypted_master_private_key_share']) encrypted_integrity_passphrase = bytes.fromhex(parties[f'{my_id}']['encrypted_integrity_passphrase']) parties_ids = [] test_signature_shares = {} for id_str, party in parties.items(): try: id = int(id_str) except: raise GenVerErrorBasic(f'Invalid id {id_str}') parties_ids.append(id) test_signature_shares[id] = bytes.fromhex(party['test_signature_share']) except: raise GenVerErrorBasic(f'Error parsing key file') # If Given RSA private key, use it to get integrity passphrase (if no file, assume integrity passphrase is given) master_private_key_share = None if rsa_priv_key_file is None: integrity_passphrase = bytes(passphrase, 'utf-8') else: try: in_file = open(rsa_priv_key_file, 'r') rsa_key = RSA.importKey(in_file.read(), passphrase=passphrase) cipher = PKCS1_OAEP.new(rsa_key) in_file.close() except: raise GenVerErrorBasic(f'Importing RSA key from file {rsa_priv_key_file} (perhaps wrong passphrase)') if not rsa_key.has_private(): raise GenVerErrorBasic(f'Not a private RSA key file {rsa_priv_key_file}') try: master_private_key_share = cipher.decrypt(encrypted_private_key_share) except: raise GenVerErrorBasic(f'Invalid decryption of private key share from key file') try: integrity_passphrase = cipher.decrypt(encrypted_integrity_passphrase) except: raise GenVerErrorBasic(f'Invalid decryption of integrity passphrase from key file') # Sanity checks if threshold > len(parties_ids) or threshold < 1: raise GenVerErrorBasic(f'Invalid threhsold {threshold} for ids {parties_ids}') if (len(parties_ids) != len(set(parties_ids))): raise GenVerErrorBasic(f'Non-unique ids in verification file') test_derivation_path = _get_test_path() derived_public_key = derive_public_child(master_pubkey, test_derivation_path) derived_pubkey_address = bls_conv.G1_to_pubkey(derived_public_key) test_msg, _ = _get_msg_for_address(derived_pubkey_address) # If decrypted master_private_key Verify my own signature wasn't modified by signing again if master_private_key_share: derived_private_key_share = derive_private_child(master_private_key_share, test_derivation_path, master_pubkey) if not test_signature_shares[my_id] == bls_basic.Sign(derived_private_key_share, test_msg): raise GenVerErrorBasic(f'Modified signature share for my key id {my_id}') else: print(colored('No RSA key - not verifying private key share validity!', "cyan")) # After getting scrypt integrity passphrase validate master pubkey wasn't changed computed_checksum = _compute_scrypt_checksum(integrity_passphrase, master_pubkey) if not computed_checksum == integrity_checksum: raise GenVerErrorBasic(f'Failure in validating master public key integrity checksum (perhaps wrong passphrase)') # Convert to group elements to allow interpolation of signatures G2_signature_shares = {} for id, sig in test_signature_shares.items(): try: G2_signature_shares[id] = bls_conv.signature_to_G2(sig) except: raise GenVerErrorBasic(f'Invalid encoding of signature shares for id {id}') # For each authorized set of the above, combine signature shares and verify print(f'Verifying signing threshold {threshold} out of {len(parties_ids)} parties...') for auth_ids in itertools.combinations(parties_ids, threshold): auth_signature = bls_conv.G2_to_signature(_interpolate_in_group({id : G2_signature_shares[id] for id in auth_ids}, bls_curve.G2)) if not bls_basic.Verify(derived_pubkey_address, test_msg, auth_signature): raise GenVerErrorBasic(f'Failed verification of combined signature for ids {auth_ids}') # Sanity check: check un-authorized set can't get valid signature (less then threhsold) for auth_ids in itertools.combinations(parties_ids, threshold-1): auth_signature = bls_conv.G2_to_signature(_interpolate_in_group({id : G2_signature_shares[id] for id in auth_ids}, bls_curve.G2)) if bls_basic.Verify(derived_pubkey_address, test_msg, auth_signature): raise GenVerErrorBasic(f'Valid signature for unauthorized ids {auth_ids}') print(colored("Success!", "green")) return True
def test_key_validate(pubkey, success): assert G2Basic.KeyValidate(pubkey) == success
def _sample_random_in_range(range:int): val = bls_basic.KeyGen(secrets.token_bytes(48), b'fireblocks_bls_randomness') if (val < 2**100): raise GenVerErrorBasic(f'Suspicious randomness samples') return val % range
def test_aggregate(signature_points, result_point): signatures = [G2_to_signature(pt) for pt in signature_points] result_signature = G2_to_signature(result_point) assert G2Basic.Aggregate(signatures) == result_signature
def derive_address_and_sign(key_file:str, derivation_index:int, passphrase:str, rsa_priv_key_file:str=None, sign_msg:str=None, hex_msg:bool=False): try: in_file = open(key_file, "r") in_data = json.load(in_file) in_file.close() except: raise GenVerErrorBasic(f'Reading key file {key_file}') try: master_pubkey = bytes.fromhex(in_data['master_pubkey']) integrity_checksum = bytes.fromhex(in_data['integrity_checksum']) my_id = in_data['key_id'] parties = in_data['parties'] encrypted_integrity_passphrase = bytes.fromhex(parties[f'{my_id}']['encrypted_integrity_passphrase']) if sign_msg: encrypted_private_key_share = bytes.fromhex(parties[f'{my_id}']['encrypted_master_private_key_share']) except: raise GenVerErrorBasic(f'Error parsing key file') # If Given RSA private key, use it to decrypt integrity passphrase (if no file, assume integrity passphrase is given) if rsa_priv_key_file is None: integrity_passphrase = bytes(passphrase, 'utf-8') else: try: in_file = open(rsa_priv_key_file, 'r') rsa_key = RSA.importKey(in_file.read(), passphrase=passphrase) cipher = PKCS1_OAEP.new(rsa_key) in_file.close() except: raise GenVerErrorBasic(f'Importing RSA key from file {rsa_priv_key_file} (perhaps wrong passphrase)') if not rsa_key.has_private(): raise GenVerErrorBasic(f'Not a private RSA key file {rsa_priv_key_file}') try: integrity_passphrase = cipher.decrypt(encrypted_integrity_passphrase) except: raise GenVerErrorBasic(f'Invalid decryption of integrity passphrase from key file') if sign_msg: try: master_private_key_share = cipher.decrypt(encrypted_private_key_share) except: raise GenVerErrorBasic(f'Invalid decryption of BLS private key share') # After getting scrypt integrity passphrase validate master pubkey wasn't changed computed_checksum = _compute_scrypt_checksum(integrity_passphrase, master_pubkey) if not computed_checksum == integrity_checksum: raise GenVerErrorBasic(f'Failure in validating master public key integrity checksum (perhaps wrong passphrase)') # Derive address at index derivation_path = index_to_path(derivation_index) derived_public_key = derive_public_child(master_pubkey, derivation_path) derived_pubkey_address = bls_conv.G1_to_pubkey(derived_public_key) # Sign message (and write to file) if given out_data = {} if sign_msg: derived_private_key_share = derive_private_child(master_private_key_share, derivation_path, master_pubkey) if hex_msg: msg_str = sign_msg msg_bytes = bytes.fromhex(msg_str) else: msg_bytes, msg_str = _get_msg_for_address(derived_pubkey_address, sign_msg) out_data['master_pubkey'] = master_pubkey.hex() out_data['signer_id'] = my_id out_data['message'] = {'payload' : msg_str, 'hex' : hex_msg} out_data['derivation_index'] = derivation_index out_data['signature'] = bls_basic.Sign(derived_private_key_share, msg_bytes).hex() # Write to file sig_filename = f'id_{my_id}_bls_signature_share_{sha512(msg_bytes).digest()[:4].hex()}_{derived_pubkey_address[:4].hex()}_index_{derivation_index}.json' try: sig_file = open(sig_filename, 'w+') json.dump(out_data, sig_file, indent=4) sig_file.close() print("Message:", colored(f'{msg_str}', "green")) print("Generated Signature File:", colored(f'{sig_filename}', "green")) except ValueError: raise GenVerErrorBasic(f'Error writing signature file for id {id}') print("Generated Address:", colored(derived_pubkey_address.hex(), "green")) print("( Withdrawl Credentials:", colored(_withdrawal_credentials(derived_pubkey_address).hex(), "green"), ")") return