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_sign(privkey, message, success): if success: G2Basic.Sign(privkey, message) else: with pytest.raises(ValidationError): G2Basic.Sign(privkey, message)
(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 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
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
@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)