def on_receive(self, keyhash, message): self.print_error("signal arrived for", keyhash) for key, _hash, window in self.keys: if _hash == keyhash: break else: self.print_error("keyhash not found") return wallet = window.wallet if wallet.has_password(): password = window.password_dialog('An encrypted transaction was retrieved from cosigning pool.\nPlease enter your password to decrypt it.') if not password: return else: password = None if not window.question(_("An encrypted transaction was retrieved from cosigning pool.\nDo you want to open it now?")): return xprv = wallet.keystore.get_master_private_key(password) if not xprv: return try: k = bh2u(bitcoin.deserialize_xprv(xprv)[-1]) EC = bitcoin.EC_KEY(bfh(k)) message = bh2u(EC.decrypt_message(message)) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_message(str(e)) return self.listener.clear(keyhash) tx = transaction.Transaction(message) show_transaction(tx, window, prompt_if_unsaved=True)
def test_mnemonic_to_seed(self): for test_name, test in SEED_TEST_CASES.items(): if test.words_hex is not None: self.assertEqual(test.words_hex, bh2u(test.words.encode('utf8')), msg=test_name) self.assertTrue(is_new_seed(test.words, prefix=test.seed_version), msg=test_name) m = mnemonic.Mnemonic(lang=test.lang) if test.entropy is not None: self.assertEqual(test.entropy, m.mnemonic_decode(test.words), msg=test_name) if test.passphrase_hex is not None: self.assertEqual(test.passphrase_hex, bh2u(test.passphrase.encode('utf8')), msg=test_name) seed = mnemonic.Mnemonic.mnemonic_to_seed(mnemonic=test.words, passphrase=test.passphrase) self.assertEqual(test.bip32_seed, bh2u(seed), msg=test_name)
def update(features): self.features = features set_label_enabled() if features.bootloader_hash: bl_hash = bh2u(features.bootloader_hash) bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]]) else: bl_hash = "N/A" noyes = [_("No"), _("Yes")] endis = [_("Enable Passphrases"), _("Disable Passphrases")] disen = [_("Disabled"), _("Enabled")] setchange = [_("Set a PIN"), _("Change PIN")] version = "%d.%d.%d" % (features.major_version, features.minor_version, features.patch_version) device_label.setText(features.label) pin_set_label.setText(noyes[features.pin_protection]) passphrases_label.setText(disen[features.passphrase_protection]) bl_hash_label.setText(bl_hash) label_edit.setText(features.label) device_id_label.setText(features.device_id) initialized_label.setText(noyes[features.initialized]) version_label.setText(version) clear_pin_button.setVisible(features.pin_protection) clear_pin_warning.setVisible(features.pin_protection) pin_button.setText(setchange[features.pin_protection]) pin_msg.setVisible(not features.pin_protection) passphrase_button.setText(endis[features.passphrase_protection]) language_label.setText(features.language)
def gen_random_versioned_seed(cls): version = cls.LATEST_VERSION hex_seed = bh2u(os.urandom(16)) checksum = cls.code_hashid(version + hex_seed) return VersionedSeed(version=version.upper(), seed=hex_seed.upper(), checksum=checksum.upper())
def test_push_script(self): # https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#push-operators self.assertEqual(push_script(''), bh2u(bytes([opcodes.OP_0]))) self.assertEqual(push_script('07'), bh2u(bytes([opcodes.OP_7]))) self.assertEqual(push_script('10'), bh2u(bytes([opcodes.OP_16]))) self.assertEqual(push_script('81'), bh2u(bytes([opcodes.OP_1NEGATE]))) self.assertEqual(push_script('11'), '0111') self.assertEqual(push_script(75 * '42'), '4b' + 75 * '42') self.assertEqual(push_script(76 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA1]) + bfh('4c' + 76 * '42'))) self.assertEqual(push_script(100 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA1]) + bfh('64' + 100 * '42'))) self.assertEqual(push_script(255 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA1]) + bfh('ff' + 255 * '42'))) self.assertEqual(push_script(256 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA2]) + bfh('0001' + 256 * '42'))) self.assertEqual(push_script(520 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA2]) + bfh('0802' + 520 * '42')))
def sign_transaction(self, keystore, tx, prev_tx, xpub_path): prev_tx = { bfh(txhash): self.electrum_tx_to_txtype(tx, xpub_path) for txhash, tx in prev_tx.items() } client = self.get_client(keystore) inputs = self.tx_inputs(tx, xpub_path, True) outputs = self.tx_outputs(keystore.get_derivation(), tx) details = SignTx(lock_time=tx.locktime, version=tx.version) signatures, _ = client.sign_tx(self.get_coin_name(), inputs, outputs, details=details, prev_txes=prev_tx) signatures = [(bh2u(x) + '01') for x in signatures] tx.update_signatures(signatures)
def sign_transaction(self, keystore, tx, prev_tx, xpub_path): self.prev_tx = prev_tx self.xpub_path = xpub_path client = self.get_client(keystore) inputs = self.tx_inputs(tx, True) outputs = self.tx_outputs(keystore.get_derivation(), tx) signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime)[0] signatures = [(bh2u(x) + '01') for x in signatures] tx.update_signatures(signatures)
def sign_transaction(self, keystore, tx, prev_tx, xpub_path): self.prev_tx = prev_tx self.xpub_path = xpub_path client = self.get_client(keystore) inputs = self.tx_inputs(tx, True, keystore.is_segwit()) outputs = self.tx_outputs(keystore.get_derivation(), tx, keystore.is_segwit()) signed_tx = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime)[1] raw = bh2u(signed_tx) tx.update_signatures(raw)
def do_send(self, tx): for window, xpub, K, _hash in self.cosigner_list: if not self.cosigner_can_sign(tx, xpub): continue message = bitcoin.encrypt_message(bfh(tx.raw), bh2u(K)).decode('ascii') try: server.put(_hash, message) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_message("Failed to send transaction to cosigning pool.") return window.show_message("Your transaction was sent to the cosigning pool.\nOpen your cosigner wallet to retrieve it.")
def open(self, *args, **kwargs): super(LightningOpenChannelDialog, self).open(*args, **kwargs) if self.lnaddr: fee = self.app.electrum_config.fee_per_kb() if not fee: fee = config.FEERATE_FALLBACK_STATIC_FEE self.amount = self.app.format_amount_and_units(self.lnaddr.amount * constants.net.COIN + fee * 2) self.pubkey = bh2u(self.lnaddr.pubkey.serialize()) if self.msg: self.app.show_info(self.msg)
def sign_transaction(self, keystore, tx: PartialTransaction, prev_tx): self.prev_tx = prev_tx client = self.get_client(keystore) inputs = self.tx_inputs(tx, for_sig=True, keystore=keystore) outputs = self.tx_outputs(tx, keystore=keystore) signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime, version=tx.version)[0] signatures = [(bh2u(x) + '01') for x in signatures] tx.update_signatures(signatures)
def create_menu(self, position): selected = self.selected_column_0_user_roles() if not selected: return menu = QMenu() menu.addAction(_("Renew"), lambda: self.renew_selected_items()) if len(selected) == 1: txid = selected[0].split(':')[0] tx = self.wallet.db.transactions.get(txid) if tx: label = self.wallet.get_label( txid ) or None # Prefer None if empty (None hides the Description: field in the window) menu.addAction(_("Configure"), lambda: self.configure_selected_item()) menu.addAction( _("Transaction Details"), lambda: self.parent.show_transaction(tx, tx_desc=label)) # "Copy ..." idx = self.indexAt(position) col = idx.column() if col == self.Columns.NAME: selected_data = self.selected_column_0_user_role_identifiers() selected_data_type = "identifier" elif col == self.Columns.VALUE: selected_data = self.selected_column_0_user_role_values() selected_data_type = "value" else: selected_data = None if selected_data is not None and len(selected_data) == 1: data = selected_data[0] # data will be None if this row is a name_new that we haven't # queued a name_firstupdate for, so we don't know the identifier or # value. if data is not None: try: copy_ascii = data.decode('ascii') menu.addAction( _("Copy {} as ASCII").format(selected_data_type), lambda: self.parent.app.clipboard().setText(copy_ascii )) except UnicodeDecodeError: pass copy_hex = bh2u(data) menu.addAction( _("Copy {} as hex").format(selected_data_type), lambda: self.parent.app.clipboard().setText(copy_hex)) menu.exec_(self.viewport().mapToGlobal(position))
def checkPassphrase(line): passw = "" seed = util.bh2u(keystore.bip39_to_seed(line, passw)) seed = util.bfh(seed) xprv, _xpub = bitcoin.bip32_root(seed, "standard") xprv, _xpub = bitcoin.bip32_private_derivation(xprv, "", "49'") xprv, _xpub = bitcoin.bip32_private_derivation(xprv, "", "0'") xprv, _xpub = bitcoin.bip32_private_derivation(xprv, "", "0'") xprv, _xpub = bitcoin.bip32_private_derivation(xprv, "", "0") for i in range(MAX_ADDR_IDX): deriveAddresses(line, xprv, i)
def __init__(self, chan: Channel, app: 'ElectrumWindow', **kwargs): Popup.__init__(self, **kwargs) Logger.__init__(self) self.is_closed = chan.is_closed() self.can_be_deleted = chan.can_be_deleted() self.app = app self.chan = chan self.title = _('Channel details') self.node_id = bh2u(chan.node_id) self.channel_id = bh2u(chan.channel_id) self.funding_txid = chan.funding_outpoint.txid self.short_id = format_short_channel_id(chan.short_channel_id) self.capacity = self.app.format_amount_and_units(chan.get_capacity()) self.state = chan.get_state_for_GUI() self.local_ctn = chan.get_latest_ctn(LOCAL) self.remote_ctn = chan.get_latest_ctn(REMOTE) self.local_csv = chan.config[LOCAL].to_self_delay self.remote_csv = chan.config[REMOTE].to_self_delay self.initiator = 'Local' if chan.constraints.is_initiator else 'Remote' feerate_kw = chan.get_latest_feerate(LOCAL) self.feerate = str( quantize_feerate(Transaction.satperbyte_from_satperkw(feerate_kw))) self.can_send = self.app.format_amount_and_units( chan.available_to_spend(LOCAL) // 1000) self.can_receive = self.app.format_amount_and_units( chan.available_to_spend(REMOTE) // 1000) self.is_open = chan.is_open() closed = chan.get_closing_height() if closed: self.closing_txid, closing_height, closing_timestamp = closed msg = ' '.join([ _("Trampoline routing is enabled, but this channel is with a non-trampoline node." ), _("This channel may still be used for receiving, but it is frozen for sending." ), _("If you want to keep using this channel, you need to disable trampoline routing in your preferences." ), ]) self.warning = '' if self.app.wallet.lnworker.channel_db or self.app.wallet.lnworker.is_trampoline_peer( chan.node_id) else _('Warning') + ': ' + msg
def sign_and_insert_remote_sig(self, tx: PartialTransaction, remote_pubkey, remote_signature, pubkey, privkey): assert type(remote_pubkey) is bytes assert len(remote_pubkey) == 33 assert type(remote_signature) is str assert type(pubkey) is bytes assert type(privkey) is bytes assert len(pubkey) == 33 assert len(privkey) == 33 tx.sign({bh2u(pubkey): (privkey[:-1], True)}) tx.add_signature_to_txin(txin_idx=0, signing_pubkey=remote_pubkey.hex(), sig=remote_signature + "01")
def sign_transaction(self, keystore, tx, prev_tx, xpub_path): self.prev_tx = prev_tx self.xpub_path = xpub_path client = self.get_client(keystore) inputs = self.tx_inputs(tx, True, keystore.get_script_gen()) outputs = self.tx_outputs(keystore.get_derivation(), tx, keystore.get_script_gen()) signed_tx = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime)[1] raw = bh2u(signed_tx) tx.update_signatures(raw)
def test_mnemonic_to_seed_japanese(self): words = SEED_WORDS_JAPANESE self.assertTrue(is_new_seed(words)) m = mnemonic.Mnemonic(lang='ja') self.assertEqual(1938439226660562861250521787963972783469, m.mnemonic_decode(words)) seed = mnemonic.Mnemonic.mnemonic_to_seed(mnemonic=words, passphrase='') self.assertEqual( 'd3eaf0e44ddae3a5769cb08a26918e8b308258bcb057bb704c6f69713245c0b35cb92c03df9c9ece5eff826091b4e74041e010b701d44d610976ce8bfb66a8ad', bh2u(seed))
def test_mnemonic_to_seed_chinese(self): words = SEED_WORDS_CHINESE self.assertTrue(is_new_seed(words, prefix=SEED_PREFIX_SW)) m = mnemonic.Mnemonic(lang='zh') self.assertEqual(3083737086352778425940060465574397809099, m.mnemonic_decode(words)) seed = mnemonic.Mnemonic.mnemonic_to_seed(mnemonic=words, passphrase='') self.assertEqual( '0b9077db7b5a50dbb6f61821e2d35e255068a5847e221138048a20e12d80b673ce306b6fe7ac174ebc6751e11b7037be6ee9f17db8040bb44f8466d519ce2abf', bh2u(seed))
def sign_transaction(self, keystore, tx: PartialTransaction, prev_tx): prev_tx = {bfh(txhash): self.electrum_tx_to_txtype(tx) for txhash, tx in prev_tx.items()} client = self.get_client(keystore) inputs = self.tx_inputs(tx, for_sig=True, keystore=keystore) outputs = self.tx_outputs(tx, keystore=keystore) signatures, _ = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime, version=tx.version, amount_unit=self.get_trezor_amount_unit(), prev_txes=prev_tx) signatures = [(bh2u(x) + '01') for x in signatures] tx.update_signatures(signatures)
def on_receive(self, keyhash, message): self.print_error("signal arrived for", keyhash) for key, _hash, window in self.keys: if _hash == keyhash: break else: self.print_error("keyhash not found") return wallet = window.wallet if wallet.has_keystore_encryption(): password = window.password_dialog( 'An encrypted transaction was retrieved from cosigning pool.\nPlease enter your password to decrypt it.' ) if not password: return else: password = None if not window.question( _("An encrypted transaction was retrieved from cosigning pool.\nDo you want to open it now?" )): return xprv = wallet.keystore.get_master_private_key(password) if not xprv: return try: k = bh2u(bitcoin.deserialize_xprv(xprv)[-1]) EC = bitcoin.EC_KEY(bfh(k)) message = bh2u(EC.decrypt_message(message)) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_message(str(e)) return self.listener.clear(keyhash) tx = transaction.Transaction(message) show_transaction(tx, window, prompt_if_unsaved=True)
def details(self): chan = self.chan return { _('Short Chan ID'): format_short_channel_id(chan.short_channel_id), _('Initiator'): 'Local' if chan.constraints.is_initiator else 'Remote', _('State'): chan.get_state(), _('Capacity'): self.app.format_amount_and_units(chan.constraints.capacity), _('Can send'): self.app.format_amount_and_units( chan.available_to_spend(LOCAL) // 1000), _('Current feerate'): str(chan.get_latest_feerate(LOCAL)), _('Node ID'): bh2u(chan.node_id), _('Channel ID'): bh2u(chan.channel_id), _('Funding TXID'): chan.funding_outpoint.txid, }
def __init__(self, chan: Channel, app: 'ElectrumWindow', **kwargs): Popup.__init__(self, **kwargs) Logger.__init__(self) self.is_closed = chan.is_closed() self.can_be_deleted = chan.can_be_deleted() self.app = app self.chan = chan self.title = _('Channel details') self.node_id = bh2u(chan.node_id) self.channel_id = bh2u(chan.channel_id) self.funding_txid = chan.funding_outpoint.txid self.short_id = format_short_channel_id(chan.short_channel_id) self.capacity = self.app.format_amount_and_units(chan.get_capacity()) self.state = chan.get_state_for_GUI() self.local_ctn = chan.get_latest_ctn(LOCAL) self.remote_ctn = chan.get_latest_ctn(REMOTE) self.local_csv = chan.config[LOCAL].to_self_delay self.remote_csv = chan.config[REMOTE].to_self_delay self.initiator = 'Local' if chan.constraints.is_initiator else 'Remote' feerate_kw = chan.get_latest_feerate(LOCAL) self.feerate = str( quantize_feerate(Transaction.satperbyte_from_satperkw(feerate_kw))) self.can_send = self.app.format_amount_and_units( chan.available_to_spend(LOCAL) // 1000) self.can_receive = self.app.format_amount_and_units( chan.available_to_spend(REMOTE) // 1000) self.is_open = chan.is_open() closed = chan.get_closing_height() if closed: self.closing_txid, closing_height, closing_timestamp = closed msg = messages.MSG_NON_TRAMPOLINE_CHANNEL_FROZEN_WITHOUT_GOSSIP self.warning = '' if self.app.wallet.lnworker.channel_db or self.app.wallet.lnworker.is_trampoline_peer( chan.node_id) else _('Warning') + ': ' + msg self.is_frozen_for_sending = chan.is_frozen_for_sending() self.is_frozen_for_receiving = chan.is_frozen_for_receiving() self.channel_type = chan.storage['channel_type'].name_minimal self.update_action_dropdown()
def new_channel_dialog(self): lnworker = self.parent.wallet.lnworker d = WindowModalDialog(self.parent, _('Open Channel')) d.setMinimumWidth(700) vbox = QVBoxLayout(d) h = QGridLayout() local_nodeid = QLineEdit() local_nodeid.setText(bh2u(lnworker.node_keypair.pubkey)) local_nodeid.setReadOnly(True) local_nodeid.setCursorPosition(0) remote_nodeid = QLineEdit() local_amt_inp = BTCAmountEdit(self.parent.get_decimal_point) local_amt_inp.setAmount(200000) push_amt_inp = BTCAmountEdit(self.parent.get_decimal_point) push_amt_inp.setAmount(0) h.addWidget(QLabel(_('Your Node ID')), 0, 0) h.addWidget(local_nodeid, 0, 1) h.addWidget( QLabel(_('Remote Node ID or connection string or invoice')), 1, 0) h.addWidget(remote_nodeid, 1, 1) h.addWidget(QLabel('Local amount'), 2, 0) h.addWidget(local_amt_inp, 2, 1) h.addWidget(QLabel('Push amount'), 3, 0) h.addWidget(push_amt_inp, 3, 1) vbox.addLayout(h) ok_button = OkButton(d) ok_button.setDefault(True) vbox.addLayout(Buttons(CancelButton(d), ok_button)) suggestion = lnworker.suggest_peer() or b'' remote_nodeid.setText(bh2u(suggestion)) remote_nodeid.setCursorPosition(0) if not d.exec_(): return local_amt = local_amt_inp.get_amount() push_amt = push_amt_inp.get_amount() connect_contents = str(remote_nodeid.text()).strip() self.parent.open_channel(connect_contents, local_amt, push_amt)
def make_htlc_item(self, i: UpdateAddHtlc, direction: Direction) -> HTLCItem: it = HTLCItem( _('Sent HTLC with ID {}' if Direction.SENT == direction else 'Received HTLC with ID {}').format(i.htlc_id)) it.appendRow( [HTLCItem(_('Amount')), HTLCItem(self.format(i.amount_msat))]) it.appendRow( [HTLCItem(_('CLTV expiry')), HTLCItem(str(i.cltv_expiry))]) it.appendRow( [HTLCItem(_('Payment hash')), HTLCItem(bh2u(i.payment_hash))]) return it
def sign_transaction(self, keystore, tx: PartialTransaction, prev_tx): prev_tx = {bfh(txhash): self.electrum_tx_to_txtype(tx) for txhash, tx in prev_tx.items()} if not self.client: raise Exception("client is None") xpub = keystore.xpub derivation = keystore.get_derivation_prefix() if not self.force_pair_with_xpub(self.client, xpub, derivation): raise Exception("Can't Pair With You Device When Sign tx") inputs = self.tx_inputs(tx, for_sig=True, keystore=keystore) outputs = self.tx_outputs(tx, keystore=keystore) details = SignTx(lock_time=tx.locktime, version=tx.version) signatures, _ = self.client.sign_tx(self.get_coin_name(), inputs, outputs, details=details, prev_txes=prev_tx) signatures = [(bh2u(x) + '01') for x in signatures] tx.update_signatures(signatures) raise Exception("sign success")
def sign_transaction(self, tx, password): # Build a PSBT in memory, upload it for signing. # - we can also work offline (without paired device present) if tx.is_complete(): return client = self.get_client() assert client.dev.master_fingerprint == self.ckcc_xfp raw_psbt = self.build_psbt(tx) #open('debug.psbt', 'wb').write(out_fd.getvalue()) try: try: self.handler.show_message("Authorize Transaction...") client.sign_transaction_start(raw_psbt, True) while 1: # How to kill some time, without locking UI? time.sleep(0.250) resp = client.sign_transaction_poll() if resp is not None: break rlen, rsha = resp # download the resulting txn. new_raw = client.download_file(rlen, rsha) finally: self.handler.finished() except (CCUserRefused, CCBusyError) as exc: print_error('[coldcard]', 'Did not sign:', str(exc)) self.handler.show_error(str(exc)) return except BaseException as e: traceback.print_exc(file=sys.stderr) self.give_error(e, True) return # trust the coldcard to re-searilize final product right? tx.update(bh2u(new_raw))
def on_receive(self, keyhash, message): self.print_error("signal arrived for", keyhash) for key, _hash, window in self.keys: if _hash == keyhash: break else: self.print_error("keyhash not found") return wallet = window.wallet if isinstance(wallet.keystore, keystore.Hardware_KeyStore): window.show_warning( _('An encrypted transaction was retrieved from cosigning pool.' ) + '\n' + _('However, hardware wallets do not support message decryption, ' 'which makes them not compatible with the current design of cosigner pool.' )) return elif wallet.has_keystore_encryption(): password = window.password_dialog( _('An encrypted transaction was retrieved from cosigning pool.' ) + '\n' + _('Please enter your password to decrypt it.')) if not password: return else: password = None if not window.question( _("An encrypted transaction was retrieved from cosigning pool." ) + '\n' + _("Do you want to open it now?")): return xprv = wallet.keystore.get_master_private_key(password) if not xprv: return try: k = bitcoin.deserialize_xprv(xprv)[-1] EC = ecc.ECPrivkey(k) message = bh2u(EC.decrypt_message(message)) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_error(_('Error decrypting message') + ':\n' + str(e)) return self.listener.clear(keyhash) tx = transaction.Transaction(message, window.network.get_server_height()) show_transaction(tx, window, prompt_if_unsaved=True)
def test_scrypt(self): #0200000011f1fe21e0b66dc214be46366465cb95d29830e31ddd225a11349a836a993bf7b5db36b3e5593d039779bff204d132b65ee029a2e499ebeb5a4b19cbe862eee2b623cc5276676c1c000e1c60 header = { 'block_height': 12095, 'nonce': 1612451328, 'timestamp': 1389110198, 'version': 2, 'prev_block_hash': 'f73b996a839a34115a22dd1de33098d295cb65643646be14c26db6e021fef111', 'merkle_root': 'e2ee62e8cb194b5aebeb99e4a229e05eb632d104f2bf7997033d59e5b336dbb5', 'bits': 476866422 } powhash = rev_hex(bh2u(scryptGetHash(bfh(serialize_header(header))))) self.assertEqual( powhash, '00000000335c88172421df73a1c1f22f4d7c23d8ef34c78d728c4eff3ba24a34')
def on_receive(self, keyhash, message): self.logger.info(f"signal arrived for {keyhash}") for key, _hash, window in self.keys: if _hash == keyhash: break else: self.logger.info("keyhash not found") return wallet = window.wallet if isinstance(wallet.keystore, keystore.Hardware_KeyStore): window.show_warning( _('An encrypted transaction was retrieved from cosigning pool.' ) + '\n' + _('However, hardware wallets do not support message decryption, ' 'which makes them not compatible with the current design of cosigner pool.' )) return elif wallet.has_keystore_encryption(): password = window.password_dialog( _('An encrypted transaction was retrieved from cosigning pool.' ) + '\n' + _('Please enter your password to decrypt it.')) if not password: return else: password = None if not window.question( _("An encrypted transaction was retrieved from cosigning pool." ) + '\n' + _("Do you want to open it now?")): return xprv = wallet.keystore.get_master_private_key(password) if not xprv: return try: privkey = BIP32Node.from_xkey(xprv).eckey message = bh2u(privkey.decrypt_message(message)) except Exception as e: self.logger.exception('') window.show_error(_('Error decrypting message') + ':\n' + str(e)) return self.listener.clear(keyhash) tx = transaction.Transaction(message) show_transaction(tx, window, prompt_if_unsaved=True)
def test_push_script(self): # https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#push-operators self.assertEqual(push_script(''), bh2u(bytes([opcodes.OP_0]))) self.assertEqual(push_script('07'), bh2u(bytes([opcodes.OP_7]))) self.assertEqual(push_script('10'), bh2u(bytes([opcodes.OP_16]))) self.assertEqual(push_script('81'), bh2u(bytes([opcodes.OP_1NEGATE]))) self.assertEqual(push_script('11'), '0111') self.assertEqual(push_script(75 * '42'), '4b' + 75 * '42') self.assertEqual( push_script(76 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA1]) + bfh('4c' + 76 * '42'))) self.assertEqual( push_script(100 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA1]) + bfh('64' + 100 * '42'))) self.assertEqual( push_script(255 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA1]) + bfh('ff' + 255 * '42'))) self.assertEqual( push_script(256 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA2]) + bfh('0001' + 256 * '42'))) self.assertEqual( push_script(520 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA2]) + bfh('0802' + 520 * '42')))
def test_should_reject_coinbase_root_too_late(self): header = self.deserialize_with_auxpow(header_without_mm) input_script = bfh(header['powdata']['auxpow'] ['parent_coinbase_tx'].inputs()[0]['scriptSig']) padded_script = bfh('00') * ( auxpow.MAX_INDEX_PC_BACKWARDS_COMPATIBILITY + 4) padded_script += input_script header['powdata']['auxpow']['parent_coinbase_tx']._inputs[0][ 'scriptSig'] = bh2u(padded_script) self.clear_coinbase_outputs(header['powdata']['auxpow']) with self.assertRaises(auxpow.AuxPoWCoinbaseRootTooLate): blockchain.Blockchain.verify_header(header, prev_hash_without_mm, target_without_mm)
def append_lnaddr(self, it: HTLCItem, lnaddr: LnAddr): invoice = HTLCItem(_('Invoice')) invoice.appendRow([ HTLCItem(_('Remote node public key')), HTLCItem(bh2u(lnaddr.pubkey.serialize())) ]) invoice.appendRow([ HTLCItem(_('Amount in sat')), HTLCItem(str(lnaddr.amount * COIN)) ]) # might have a comma because mSAT! invoice.appendRow([ HTLCItem(_('Description')), HTLCItem(dict(lnaddr.tags).get('d', _('N/A'))) ]) invoice.appendRow( [HTLCItem(_('Date')), HTLCItem(format_time(lnaddr.date))]) it.appendRow([invoice])
def format_fields(self, chan): labels = {} for subject in (REMOTE, LOCAL): bal_minus_htlcs = chan.balance_minus_outgoing_htlcs(subject)//1000 label = self.parent.format_amount(bal_minus_htlcs) other = subject.inverted() bal_other = chan.balance(other)//1000 bal_minus_htlcs_other = chan.balance_minus_outgoing_htlcs(other)//1000 if bal_other != bal_minus_htlcs_other: label += ' (+' + self.parent.format_amount(bal_other - bal_minus_htlcs_other) + ')' labels[subject] = label return [ format_short_channel_id(chan.short_channel_id), bh2u(chan.node_id), labels[LOCAL], labels[REMOTE], chan.get_state() ]
def do_send(self, tx): for window, xpub, K, _hash in self.cosigner_list: if not self.cosigner_can_sign(tx, xpub): continue raw_tx_bytes = bfh(str(tx)) message = bitcoin.encrypt_message(raw_tx_bytes, bh2u(K)).decode('ascii') try: server.put(_hash, message) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_error( _("Failed to send transaction to cosigning pool") + ':\n' + str(e)) return window.show_message( _("Your transaction was sent to the cosigning pool.") + '\n' + _("Open your cosigner wallet to retrieve it."))
def test_compact_size(self): s = transaction.BCDataStream() values = [ 0, 1, 252, 253, 2**16 - 1, 2**16, 2**32 - 1, 2**32, 2**64 - 1 ] for v in values: s.write_compact_size(v) with self.assertRaises(transaction.SerializationError): s.write_compact_size(-1) self.assertEqual( bh2u(s.input), '0001fcfdfd00fdfffffe00000100feffffffffff0000000001000000ffffffffffffffffff' ) for v in values: self.assertEqual(s.read_compact_size(), v) with self.assertRaises(transaction.SerializationError): s.read_compact_size()
def sign_transaction(self, keystore, tx: PartialTransaction, prev_tx): prev_tx = { bfh(txhash): self.electrum_tx_to_txtype(tx) for txhash, tx in prev_tx.items() } client = self.get_client(keystore) inputs = self.tx_inputs(tx, for_sig=True, keystore=keystore) outputs = self.tx_outputs(tx, keystore=keystore) details = SignTx(version=tx.version, overwintered=tx.overwintered, version_group_id=tx.versionGroupId, lock_time=tx.locktime, expiry=tx.expiryHeight) signatures, _ = client.sign_tx(self.get_coin_name(), inputs, outputs, details=details, prev_txes=prev_tx) signatures = [(bh2u(x) + '01') for x in signatures] tx.update_signatures(signatures)
def on_receive(self, keyhash, message): self.print_error("signal arrived for", keyhash) for key, _hash, window in self.keys: if _hash == keyhash: break else: self.print_error("keyhash not found") return wallet = window.wallet if isinstance(wallet.keystore, keystore.Hardware_KeyStore): window.show_warning(_('An encrypted transaction was retrieved from cosigning pool.') + '\n' + _('However, hardware wallets do not support message decryption, ' 'which makes them not compatible with the current design of cosigner pool.')) return elif wallet.has_keystore_encryption(): password = window.password_dialog(_('An encrypted transaction was retrieved from cosigning pool.') + '\n' + _('Please enter your password to decrypt it.')) if not password: return else: password = None if not window.question(_("An encrypted transaction was retrieved from cosigning pool.") + '\n' + _("Do you want to open it now?")): return xprv = wallet.keystore.get_master_private_key(password) if not xprv: return try: k = bitcoin.deserialize_xprv(xprv)[-1] EC = ecc.ECPrivkey(k) message = bh2u(EC.decrypt_message(message)) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_error(_('Error decrypting message') + ':\n' + str(e)) return self.listener.clear(keyhash) tx = transaction.Transaction(message) show_transaction(tx, window, prompt_if_unsaved=True)
def on_qr(self, data): from electrum.bitcoin import base_decode, is_address data = data.strip() if is_address(data): self.set_URI(data) return if data.startswith('bitcoin:'): self.set_URI(data) return # try to decode transaction from electrum.transaction import Transaction from electrum.util import bh2u try: text = bh2u(base_decode(data, None, base=43)) tx = Transaction(text) tx.deserialize() except: tx = None if tx: self.tx_dialog(tx) return # show error self.show_error("Unable to decode QR data")
def update(self, window): wallet = window.wallet if type(wallet) != Multisig_Wallet: return if self.listener is None: self.print_error("starting listener") self.listener = Listener(self) self.listener.start() elif self.listener: self.print_error("shutting down listener") self.listener.stop() self.listener = None self.keys = [] self.cosigner_list = [] for key, keystore in wallet.keystores.items(): xpub = keystore.get_master_public_key() pubkey = BIP32Node.from_xkey(xpub).eckey.get_public_key_bytes(compressed=True) _hash = bh2u(crypto.sha256d(pubkey)) if not keystore.is_watching_only(): self.keys.append((key, _hash, window)) else: self.cosigner_list.append((window, xpub, pubkey, _hash)) if self.listener: self.listener.set_keyhashes([t[1] for t in self.keys])
def update(self, window): wallet = window.wallet if type(wallet) != Multisig_Wallet: return if self.listener is None: self.print_error("starting listener") self.listener = Listener(self) self.listener.start() elif self.listener: self.print_error("shutting down listener") self.listener.stop() self.listener = None self.keys = [] self.cosigner_list = [] for key, keystore in wallet.keystores.items(): xpub = keystore.get_master_public_key() K = bitcoin.deserialize_xpub(xpub)[-1] _hash = bh2u(bitcoin.Hash(K)) if not keystore.is_watching_only(): self.keys.append((key, _hash, window)) else: self.cosigner_list.append((window, xpub, K, _hash)) if self.listener: self.listener.set_keyhashes([t[1] for t in self.keys])
def mock_fork(self, bad_header): forkpoint = bad_header['block_height'] b = blockchain.Blockchain(config=self.config, forkpoint=forkpoint, parent=None, forkpoint_hash=bh2u(sha256(str(forkpoint))), prev_hash=bh2u(sha256(str(forkpoint-1)))) return b
def test_mnemonic_to_seed_basic(self): # note: not a valid electrum seed seed = mnemonic.Mnemonic.mnemonic_to_seed(mnemonic='foobar', passphrase='none') self.assertEqual('741b72fd15effece6bfe5a26a52184f66811bd2be363190e07a42cca442b1a5bb22b3ad0eb338197287e6d314866c7fba863ac65d3f156087a5052ebc7157fce', bh2u(seed))
def import_ecc_key_to_gpg(self, privkey: str, pubkey: str) -> bool: assert len(pubkey) == 130 assert len(privkey) == 64 param_key = [ # Prime ("p", 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F), # Coefficient A ("a", 0x0000000000000000000000000000000000000000000000000000000000000000), # Coefficient B ("b", 0x0000000000000000000000000000000000000000000000000000000000000007), # Generator in 04 || x || y format ("g", 0x0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8), # Order of generator ("n", 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141), ] def compute_keygrip(k): md = sha1() for e in k: (e_name, e_value) = e if e_value == -1: l = 0 s = "" else: l = (e_value.bit_length() + 7) // 8 s = e_value.to_bytes(l, byteorder='big') v = "(1:%s%d:" % (e_name, l) vb = v.encode('utf-8') + s + b")" md.update(vb) return md.digest() GCRYPT_SEXP_FMT = "(private-key(ecc(curve secp256k1)(q #%s#)(d #%s#)))" keydata_str = GCRYPT_SEXP_FMT % (pubkey, privkey) param_key.append(('q', int.from_bytes(bfh(pubkey), 'big'))) grip = bh2u(compute_keygrip(param_key)).upper() self.pk_dir.mkdir(parents=True, exist_ok=True) keygrip = self.pk_dir / (grip + '.key') keygrip.open('w').write(keydata_str) key_spec = ''' %echo Generating a secp256k1 OpenPGP key ECDSA and subkey ECDH from {0} Key-Type: ECDSA Key-Grip: {0} Key-Curve: secp256k1 Key-Usage: sign,auth Creation-Date: 20171105T000000Z Name-Real: bitcoin Name-Email: [email protected] # Name-Comment: Expire-Date: 0 Subkey-Type: ECDH Subkey-Grip: {0} Subkey-Curve: secp256k1 Subkey-Usage: encrypt # Passphrase: abc %commit %echo done ''' key_spec = key_spec.format(grip) try: key = self._gpg.gen_key(key_spec) self.default_key = key.fingerprint except Exception as e: return False return True
def _get_key_address(self): root_xpub = self.wallet.keystore.xpub xpub = bip32_public_derivation(root_xpub, 'm/', 'm/0/0') pubk = bh2u(point_to_ser(ser_to_point(bfh(self._xpub_to_gpg_pub(xpub))), compressed=True)) addr = pubkey_to_address('p2pkh', pubk) return addr
def _xpub_to_gpg_pub(xpub: str) -> str: _, _, _, _, _, pubk = deserialize_xpub(xpub) pubkey = bh2u(point_to_ser(ser_to_point(pubk), compressed=False)) return pubkey
def _get_gpg_ecc_compat_keys(cls, xprv) -> (str, str): xprv, xpub = bip32_private_derivation(xprv, 'm/', 'm/0/0') _, _, _, _, c, pk = deserialize_xprv(xprv) pk = bh2u(pk) pubk = cls._xpub_to_gpg_pub(xpub) return pk, pubk
def sign_transaction(self, tx, password): if tx.is_complete(): return client = self.get_client() inputs = [] inputsPaths = [] pubKeys = [] chipInputs = [] redeemScripts = [] signatures = [] preparedTrustedInputs = [] changePath = "" changeAmount = None output = None outputAmount = None p2shTransaction = False segwitTransaction = False pin = "" self.get_client() # prompt for the PIN before displaying the dialog if necessary # Fetch inputs of the transaction to sign derivations = self.get_tx_derivations(tx) for txin in tx.inputs(): if txin['type'] == 'coinbase': self.give_error("Coinbase not supported") # should never happen if txin['type'] in ['p2sh']: p2shTransaction = True if txin['type'] in ['p2wpkh-p2sh', 'p2wsh-p2sh']: if not self.get_client_electrum().supports_segwit(): self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT) segwitTransaction = True if txin['type'] in ['p2wpkh', 'p2wsh']: if not self.get_client_electrum().supports_native_segwit(): self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT) segwitTransaction = True pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin) for i, x_pubkey in enumerate(x_pubkeys): if x_pubkey in derivations: signingPos = i s = derivations.get(x_pubkey) hwAddress = "%s/%d/%d" % (self.get_derivation()[2:], s[0], s[1]) break else: self.give_error("No matching x_key for sign_transaction") # should never happen redeemScript = Transaction.get_preimage_script(txin) txin_prev_tx = txin.get('prev_tx') if txin_prev_tx is None and not Transaction.is_segwit_input(txin): raise Exception(_('Offline signing with {} is not supported for legacy inputs.').format(self.device)) txin_prev_tx_raw = txin_prev_tx.raw if txin_prev_tx else None inputs.append([txin_prev_tx_raw, txin['prevout_n'], redeemScript, txin['prevout_hash'], signingPos, txin.get('sequence', 0xffffffff - 1), txin.get('value')]) inputsPaths.append(hwAddress) pubKeys.append(pubkeys) # Sanity check if p2shTransaction: for txin in tx.inputs(): if txin['type'] != 'p2sh': self.give_error("P2SH / regular input mixed in same transaction not supported") # should never happen txOutput = var_int(len(tx.outputs())) for txout in tx.outputs(): output_type, addr, amount = txout txOutput += int_to_hex(amount, 8) script = tx.pay_script(output_type, addr) txOutput += var_int(len(script)//2) txOutput += script txOutput = bfh(txOutput) # Recognize outputs - only one output and one change is authorized if not p2shTransaction: if not self.get_client_electrum().supports_multi_output(): if len(tx.outputs()) > 2: self.give_error("Transaction with more than 2 outputs not supported") for _type, address, amount in tx.outputs(): assert _type == TYPE_ADDRESS info = tx.output_info.get(address) if (info is not None) and len(tx.outputs()) > 1 \ and info[0][0] == 1: # "is on 'change' branch" index, xpubs, m = info changePath = self.get_derivation()[2:] + "/%d/%d"%index changeAmount = amount else: output = address outputAmount = amount self.handler.show_message(_("Confirm Transaction on your Ledger device...")) try: # Get trusted inputs from the original transactions for utxo in inputs: sequence = int_to_hex(utxo[5], 4) if segwitTransaction: tmp = bfh(utxo[3])[::-1] tmp += bfh(int_to_hex(utxo[1], 4)) tmp += bfh(int_to_hex(utxo[6], 8)) # txin['value'] chipInputs.append({'value' : tmp, 'witness' : True, 'sequence' : sequence}) redeemScripts.append(bfh(utxo[2])) elif not p2shTransaction: txtmp = bitcoinTransaction(bfh(utxo[0])) trustedInput = self.get_client().getTrustedInput(txtmp, utxo[1]) trustedInput['sequence'] = sequence chipInputs.append(trustedInput) redeemScripts.append(txtmp.outputs[utxo[1]].script) else: tmp = bfh(utxo[3])[::-1] tmp += bfh(int_to_hex(utxo[1], 4)) chipInputs.append({'value' : tmp, 'sequence' : sequence}) redeemScripts.append(bfh(utxo[2])) # Sign all inputs firstTransaction = True inputIndex = 0 rawTx = tx.serialize() self.get_client().enableAlternate2fa(False) if segwitTransaction: self.get_client().startUntrustedTransaction(True, inputIndex, chipInputs, redeemScripts[inputIndex]) if changePath: # we don't set meaningful outputAddress, amount and fees # as we only care about the alternateEncoding==True branch outputData = self.get_client().finalizeInput(b'', 0, 0, changePath, bfh(rawTx)) else: outputData = self.get_client().finalizeInputFull(txOutput) outputData['outputData'] = txOutput transactionOutput = outputData['outputData'] if outputData['confirmationNeeded']: outputData['address'] = output self.handler.finished() pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin if not pin: raise UserWarning() if pin != 'paired': self.handler.show_message(_("Confirmed. Signing Transaction...")) while inputIndex < len(inputs): singleInput = [ chipInputs[inputIndex] ] self.get_client().startUntrustedTransaction(False, 0, singleInput, redeemScripts[inputIndex]) inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime) inputSignature[0] = 0x30 # force for 1.4.9+ signatures.append(inputSignature) inputIndex = inputIndex + 1 else: while inputIndex < len(inputs): self.get_client().startUntrustedTransaction(firstTransaction, inputIndex, chipInputs, redeemScripts[inputIndex]) if changePath: # we don't set meaningful outputAddress, amount and fees # as we only care about the alternateEncoding==True branch outputData = self.get_client().finalizeInput(b'', 0, 0, changePath, bfh(rawTx)) else: outputData = self.get_client().finalizeInputFull(txOutput) outputData['outputData'] = txOutput if firstTransaction: transactionOutput = outputData['outputData'] if outputData['confirmationNeeded']: outputData['address'] = output self.handler.finished() pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin if not pin: raise UserWarning() if pin != 'paired': self.handler.show_message(_("Confirmed. Signing Transaction...")) else: # Sign input with the provided PIN inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime) inputSignature[0] = 0x30 # force for 1.4.9+ signatures.append(inputSignature) inputIndex = inputIndex + 1 if pin != 'paired': firstTransaction = False except UserWarning: self.handler.show_error(_('Cancelled by user')) return except BTChipException as e: if e.sw == 0x6985: # cancelled by user return elif e.sw == 0x6982: raise # pin lock. decorator will catch it else: traceback.print_exc(file=sys.stderr) self.give_error(e, True) except BaseException as e: traceback.print_exc(file=sys.stdout) self.give_error(e, True) finally: self.handler.finished() for i, txin in enumerate(tx.inputs()): signingPos = inputs[i][4] Transaction.add_signature_to_txin(txin, signingPos, bh2u(signatures[i])) tx.raw = tx.serialize()
def test_to_seed(self): seed = mnemonic.Mnemonic.mnemonic_to_seed(mnemonic='foobar', passphrase='none') self.assertEqual(bh2u(seed), '741b72fd15effece6bfe5a26a52184f66811bd2be363190e07a42cca442b1a5b' 'b22b3ad0eb338197287e6d314866c7fba863ac65d3f156087a5052ebc7157fce')