def test_sanity_tests(): seed = bytes.fromhex( "1077a46dc8545d372f22d9e110ae6c5c2bf7620fe9c4c911f5404d112233e1aa270567dd3554092e051ba3ba86c303590b0309116ac89964ff284db2219d7511" ) first_bip32 = BIP32.from_seed(seed) sec_bip32 = BIP32.from_xpriv( "xprv9s21ZrQH143K3o4KUs47P2x9afhH31ekMo2foNTYwrU9wwZ8g5EatR9bn6YmCacdvnHWMnPFUqieQrnunrzuF5UfgGbhbEW43zRnhpPDBUL" ) assert first_bip32.get_master_xpriv() == sec_bip32.get_master_xpriv() assert first_bip32.get_master_xpub() == sec_bip32.get_master_xpub() # Fuzz it a bit for i in range(50): path = [int.from_bytes(os.urandom(3), "big") for _ in range(5)] h_path = [ HARDENED_INDEX + int.from_bytes(os.urandom(3), "big") for _ in range(5) ] mixed_path = [int.from_bytes(os.urandom(3), "big") for _ in range(5)] for i in mixed_path: if int.from_bytes(os.urandom(32), "big") % 2: i += HARDENED_INDEX assert first_bip32.get_xpriv_from_path( path) == sec_bip32.get_xpriv_from_path(path) assert first_bip32.get_xpub_from_path( path) == sec_bip32.get_xpub_from_path(path) assert first_bip32.get_xpriv_from_path( h_path) == sec_bip32.get_xpriv_from_path(h_path) assert first_bip32.get_xpub_from_path( h_path) == sec_bip32.get_xpub_from_path(h_path) assert first_bip32.get_xpriv_from_path( mixed_path) == sec_bip32.get_xpriv_from_path(mixed_path) assert first_bip32.get_xpub_from_path( mixed_path) == sec_bip32.get_xpub_from_path(mixed_path) # Taken from iancoleman's website bip32 = BIP32.from_seed( bytes.fromhex( "ac8c2377e5cde867d7e420fbe04d8906309b70d51b8fe58d6844930621a9bc223929155dcfebb4da9d62c86ec0d15adf936a663f4f0cf39cbb0352e7dac073d6" )) assert bip32.get_master_xpriv() == bip32.get_xpriv_from_path( [] ) == "xprv9s21ZrQH143K2GzaKJsW7DQsxeDpY3zqgusaSx6owWGC19k4mhwnVAsm4qPsCw43NkY2h1BzVLyxWHt9NKF86QRyBj53vModdGcNxtpD6KX" assert bip32.get_master_xpub() == bip32.get_xpub_from_path( [] ) == "xpub661MyMwAqRbcEm53RLQWUMMcWg4JwWih48oBFLWRVqoAsx5DKFG32yCEv8iH29TWpmo5KTcpsjXcea6Zx4Hc6PAbGnHjEDCf3yHbj7qdpnf" # Sanity checks for m/0'/0'/14/0'/18 xpriv = bip32.get_xpriv_from_path( [HARDENED_INDEX, HARDENED_INDEX, 14, HARDENED_INDEX, 18]) xpub = bip32.get_xpub_from_path( [HARDENED_INDEX, HARDENED_INDEX, 14, HARDENED_INDEX, 18]) assert xpriv == "xprvA2YVbLvEeKaPedw7F6RLwG3RgYnTq1xGCyDNMgZNWdEQnSUBQmKEuLyA6TSPsggt5xvyJHLD9L25tNLpQiP4Q8ZkQNo8ueAgeYj5zYq8hSm" assert xpub == "xpub6FXqzrT8Uh8gs81aM7xMJPzAEacxEUg7aC8yA4xz4xmPfEoKxJdVT9Hdwm3LwVQrSos2rhGDt8aGGHvdLr5LLAjK8pXFkbSpzGoGTXjd4z9" # Now if we our master is m/0'/0'/14, we should derive the same keys for # m/0'/18 ! xpriv2 = bip32.get_xpriv_from_path([HARDENED_INDEX, HARDENED_INDEX, 14]) assert xpriv2 == "xprv9yQJmvQMywM5i7UNuZ4RQ1A9rEMwAJCExPardkmBCB46S3vBqNEatSwLUrwLNLHBu1Kd9aGxGKDD5YAfs6hRzpYthciAHjtGadxgV2PeqY9" bip32 = BIP32.from_xpriv(xpriv2) assert bip32.get_master_xpriv() == xpriv2 assert bip32.get_xpriv_from_path([HARDENED_INDEX, 18]) == xpriv assert bip32.get_xpub_from_path([HARDENED_INDEX, 18]) == xpub
def parse_key(key: str) -> BIP32: """ Try to parse an extended key, whether it is in xpub, xpriv or mnemonic format. """ try: private_key = BIP32.from_xpriv(key) print('🔑 Read master private key successfully') return private_key except Exception: pass try: public_key = BIP32.from_xpub(key) print('🔑 Read master public key successfully') return public_key except Exception: pass try: language = Mnemonic.detect_language(key) seed = Mnemonic(language).to_seed(key) private_key = BIP32.from_seed(seed) print('🔑 Read mnemonic successfully') return private_key except Exception: pass raise ValueError( 'The key is invalid or the format isn\'t recognized. Make sure it\'s a mnemonic, xpriv or xpub.' )
def __init__(self, xpriv, xpubs, emergency_pubkeys, bitcoin_conf_path, cosigning_url, sigserver_url, acked_addresses, current_index=0, birthdate=None): """ We need the xpub of all the other stakeholders to derive their pubkeys. :param xpriv: Who am I ? Has to correspond to one of the following xpub. As str. :param xpubs: A list of the xpub of all the stakeholders (as str), in the following order: 1) first trader 2) second trader 3) first "normie" stakeholder 4) second "normie" stakeholder. :param emergency_pubkeys: A list of the four offline keys of the stakeholders, as bytes. :param bitcoin_conf_path: Path to bitcoin.conf. :param cosigning_url: The url of the cosigning server. :param sigserver_url: The url of the server to post / get the sigs from other stakeholders. :param acked_addresses: Addresses to which we accept to spend. :param birthdate: The timestamp at which this wallet has been created. If not passed, will assume newly-created wallet. """ assert len(xpubs) == 4 self.our_bip32 = BIP32.from_xpriv(xpriv) self.keychains = [] for xpub in xpubs: if xpub != self.our_bip32.get_master_xpub(): self.keychains.append(BIP32.from_xpub(xpub)) else: self.keychains.append(None) self.all_xpubs = xpubs self.emergency_pubkeys = emergency_pubkeys # Ok, shitload of indexes. The current one is the lower bound of the # range we will import to bitcoind as watchonly. The max one is the # upper bond, the current "gen" one is to generate new addresses. self.current_index = current_index self.current_gen_index = self.current_index self.max_index = current_index + 500 self.index_treshold = self.max_index self.birthdate = int(time.time()) if birthdate is None else birthdate self.bitcoind = BitcoindApi(bitcoin_conf_path) # First of all, watch the emergency vault self.watch_emergency_vault() # And store the corresponding address.. txo = emergency_txout(self.emergency_pubkeys, 0) self.emergency_address = str( CBitcoinAddress.from_scriptPubKey(txo.scriptPubKey)) # The cosigning server, asked for its signature for the spend_tx self.cosigner = CosigningApi(cosigning_url) self.cosigner_pubkey = self.cosigner.get_pubkey() # The "sig" server, used to store and exchange signatures between # vaults and which provides us a feerate. # Who am I ? stk_id = self.keychains.index(None) + 1 self.sigserver = ServerApi(sigserver_url, stk_id) self.vault_addresses = [] self.unvault_addresses = [] self.update_watched_addresses() # We keep track of each vault, see below when we fill it for details # about what it contains. Basically all the transactions, the # signatures and some useful fields (like "are all txs signed ?"). self.vaults = [] self.vaults_lock = threading.Lock() # Poll for funds until we die self.funds_poller_stop = threading.Event() self.funds_poller = threading.Thread(target=self.poll_for_funds, daemon=True) self.funds_poller.start() # Poll for spends until we die self.acked_addresses = acked_addresses self.acked_spends = [] self.spends_poller_stop = threading.Event() self.spends_poller = threading.Thread(target=self.poll_for_spends, daemon=True) self.spends_poller.start() # Don't start polling for signatures just yet, we don't have any vault! self.update_sigs_stop = threading.Event() self.update_sigs_thread =\ threading.Thread(target=self.update_all_signatures, daemon=True) self.stopped = False
def test_sanity_tests(): seed = bytes.fromhex( "1077a46dc8545d372f22d9e110ae6c5c2bf7620fe9c4c911f5404d112233e1aa270567dd3554092e051ba3ba86c303590b0309116ac89964ff284db2219d7511" ) first_bip32 = BIP32.from_seed(seed) sec_bip32 = BIP32.from_xpriv( "xprv9s21ZrQH143K3o4KUs47P2x9afhH31ekMo2foNTYwrU9wwZ8g5EatR9bn6YmCacdvnHWMnPFUqieQrnunrzuF5UfgGbhbEW43zRnhpPDBUL" ) assert first_bip32.get_master_xpriv() == sec_bip32.get_master_xpriv() assert first_bip32.get_master_xpub() == sec_bip32.get_master_xpub() # Fuzz it a bit for i in range(50): path = [int.from_bytes(os.urandom(3), "big") for _ in range(5)] h_path = [ HARDENED_INDEX + int.from_bytes(os.urandom(3), "big") for _ in range(5) ] mixed_path = [int.from_bytes(os.urandom(3), "big") for _ in range(5)] for i in mixed_path: if int.from_bytes(os.urandom(32), "big") % 2: i += HARDENED_INDEX assert first_bip32.get_xpriv_from_path( path) == sec_bip32.get_xpriv_from_path(path) assert first_bip32.get_xpub_from_path( path) == sec_bip32.get_xpub_from_path(path) assert first_bip32.get_xpriv_from_path( h_path) == sec_bip32.get_xpriv_from_path(h_path) assert first_bip32.get_xpub_from_path( h_path) == sec_bip32.get_xpub_from_path(h_path) assert first_bip32.get_xpriv_from_path( mixed_path) == sec_bip32.get_xpriv_from_path(mixed_path) assert first_bip32.get_xpub_from_path( mixed_path) == sec_bip32.get_xpub_from_path(mixed_path) # Taken from iancoleman's website bip32 = BIP32.from_seed( bytes.fromhex( "ac8c2377e5cde867d7e420fbe04d8906309b70d51b8fe58d6844930621a9bc223929155dcfebb4da9d62c86ec0d15adf936a663f4f0cf39cbb0352e7dac073d6" )) assert bip32.get_master_xpriv() == bip32.get_xpriv_from_path( [] ) == "xprv9s21ZrQH143K2GzaKJsW7DQsxeDpY3zqgusaSx6owWGC19k4mhwnVAsm4qPsCw43NkY2h1BzVLyxWHt9NKF86QRyBj53vModdGcNxtpD6KX" assert bip32.get_master_xpub() == bip32.get_xpub_from_path( [] ) == "xpub661MyMwAqRbcEm53RLQWUMMcWg4JwWih48oBFLWRVqoAsx5DKFG32yCEv8iH29TWpmo5KTcpsjXcea6Zx4Hc6PAbGnHjEDCf3yHbj7qdpnf" # Sanity checks for m/0'/0'/14/0'/18 xpriv = bip32.get_xpriv_from_path( [HARDENED_INDEX, HARDENED_INDEX, 14, HARDENED_INDEX, 18]) xpub = bip32.get_xpub_from_path( [HARDENED_INDEX, HARDENED_INDEX, 14, HARDENED_INDEX, 18]) assert xpriv == "xprvA2YVbLvEeKaPedw7F6RLwG3RgYnTq1xGCyDNMgZNWdEQnSUBQmKEuLyA6TSPsggt5xvyJHLD9L25tNLpQiP4Q8ZkQNo8ueAgeYj5zYq8hSm" assert xpub == "xpub6FXqzrT8Uh8gs81aM7xMJPzAEacxEUg7aC8yA4xz4xmPfEoKxJdVT9Hdwm3LwVQrSos2rhGDt8aGGHvdLr5LLAjK8pXFkbSpzGoGTXjd4z9" # Now if we our master is m/0'/0'/14, we should derive the same keys for # m/0'/18 ! xpriv2 = bip32.get_xpriv_from_path([HARDENED_INDEX, HARDENED_INDEX, 14]) assert xpriv2 == "xprv9yQJmvQMywM5i7UNuZ4RQ1A9rEMwAJCExPardkmBCB46S3vBqNEatSwLUrwLNLHBu1Kd9aGxGKDD5YAfs6hRzpYthciAHjtGadxgV2PeqY9" bip32 = BIP32.from_xpriv(xpriv2) assert bip32.get_master_xpriv() == xpriv2 assert bip32.get_xpriv_from_path([HARDENED_INDEX, 18]) == xpriv assert bip32.get_xpub_from_path([HARDENED_INDEX, 18]) == xpub # We should recognize the networks.. # .. for xprivs: bip32 = BIP32.from_xpriv( "xprv9wHokC2KXdTSpEepFcu53hMDUHYfAtTaLEJEMyxBPAMf78hJg17WhL5FyeDUQH5KWmGjGgEb2j74gsZqgupWpPbZgP6uFmP8MYEy5BNbyET" ) assert bip32.network == "main" bip32 = BIP32.from_xpriv( "tprv8ZgxMBicQKsPeCBsMzQCCb5JcW4S49MVL3EwhdZMF1RF71rgisZU4ZRvrHX6PZQEiNUABDLvYqpx8Lsccq8aGGR59qHAoLoE3iXYuDa8JTP" ) assert bip32.network == "test" # .. for xpubs: bip32 = BIP32.from_xpub( "xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU" ) assert bip32.network == "main" bip32 = BIP32.from_xpub( "tpubD6NzVbkrYhZ4WN3WiKRjeo2eGyYNiKNg8vcQ1UjLNJJaDvoFhmR1XwJsbo5S4vicSPoWQBThR3Rt8grXtP47c1AnoiXMrEmFdRZupxJzH1j" ) assert bip32.network == "test" # We should create valid network encoding.. assert BIP32.from_seed(os.urandom(32), "test").get_master_xpub().startswith("tpub") assert BIP32.from_seed(os.urandom(32), "test").get_master_xpriv().startswith("tprv") assert BIP32.from_seed(os.urandom(32), "main").get_master_xpub().startswith("xpub") assert BIP32.from_seed(os.urandom(32), "main").get_master_xpriv().startswith("xprv")
def SignFundingTx(self, request, context): """BOLT #3 - Funding Transaction Sign the funding transaction TODO this must be done after we know the remote signature on the commitment tx - need an API call that provides this to the signer. """ node_id = request.node_id.data node = self.nodes.get(node_id) logger.debug("SignFundingTx node:%s" % node_id) xpub = node.bip32_key.get_xpriv_from_path('m/0/0') onchain_bip32_key = BIP32.from_xpriv(xpub) tx = request.tx tx = Transaction.import_raw(tx.raw_tx_bytes, self.network) reply = remotesigner_pb2.SignFundingTxReply() witnesses = remotesigner_pb2.Witness() local_prv = None local_key = None logger.debug(request) logger.debug(request.tx.raw_tx_bytes.hex()) for i, input_desc in enumerate(request.tx.input_descs): key_index = input_desc.key_loc.key_index spend_type = input_desc.spend_type amount = input_desc.prev_output.value_sat channel_nonce = input_desc.close_info.channel_nonce.data commitment_point = input_desc.close_info.commitment_point.data channel = node.channels.get(channel_nonce) if key_index != 0: local_prv = onchain_bip32_key.get_privkey_from_path("m/%d" % key_index) local_key = Key(import_key=local_prv, is_private=True) elif channel: if commitment_point: local_priv_key = derive_priv_key( commitment_point, payment_basepoint, channel.basepoints.keys.payment_basepoint_secret) local_key = Key(import_key=local_priv_key, is_private=True) else: local_key = Key(import_key=channel.basepoints.keys. payment_basepoint_secret, is_private=True) else: witnesses.signature.data = b'' witnesses.pubkey.data = b'' reply.witnesses.extend([witnesses]) continue spend_type = spend_type_to_string(spend_type) tx.inputs[i].script_type = spend_type logger.debug("funding signature: %s" % spend_type) tx.inputs[i].witness_type = "segwit" tx.inputs[i].sigs_required = 1 tx.inputs[i].value = amount if spend_type == 'p2pkh': tx.witness_type = 'legacy' else: tx.witness_type = 'segwit' tx.sign(keys=[local_key], tid=i) witnesses.signature.data = tx.inputs[i].witnesses[0] witnesses.pubkey.data = tx.inputs[i].witnesses[1] logger.debug("funding signature: %s" % tx.inputs[i].signatures[0].as_der_encoded().hex()) logger.debug("funding public key %s" % tx.inputs[i].as_dict()['public_keys']) reply.witnesses.extend([witnesses]) logger.debug(reply) return reply