def sign_message(self, sequence, message, password): sig = None try: message = message.encode('utf8') inputPath = self.get_derivation() + "/%d/%d" % sequence msg_hash = sha256d(msg_magic(message)) inputHash = msg_hash.hex() hasharray = [] hasharray.append({'hash': inputHash, 'keypath': inputPath}) hasharray = json.dumps(hasharray) msg = b'{"sign":{"meta":"sign message", "data":%s}}' % hasharray.encode( 'utf8') dbb_client = self.plugin.get_client(self) if not dbb_client.is_paired(): raise Exception(_("Bitbox does not appear to be connected.")) reply = dbb_client.hid_send_encrypt(msg) self.handler.show_message( _("Signing message ...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for " "3 seconds.") + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout." )) # Send twice, first returns an echo for smart verification (not implemented) reply = dbb_client.hid_send_encrypt(msg) self.handler.finished() if 'error' in reply: raise Exception(reply['error']['message']) if 'sign' not in reply: raise Exception(_("Bitbox did not sign the message.")) siginfo = reply['sign'][0] compact_sig = bytes.fromhex(siginfo['sig']) if 'recid' in siginfo: recids = [int(siginfo['recid'], 16)] else: recids = range(4) for recid in recids: # firmware > v2.1.1 message_sig = bytes([recid + 27]) + compact_sig try: pubkey = PublicKey.from_signed_message( message_sig, message) except Exception: logger.exception( f"If Digital Bitbox signing failed, this may be why") continue if pubkey.verify_message(message_sig, message): return message_sig raise RuntimeError( _("Unable to sign as Bitbox failed to provide a valid signature" )) except Exception as e: self.give_error(e) return sig
def window_opened(self, window): wallet = window.wallet if cosigner_pool.is_enabled() and type(wallet) == Multisig_Wallet: items = [] for key, keystore in wallet.keystores.items(): xpub = keystore.get_master_public_key() K = deserialize_xpub(xpub)[-1] K_hash = bh2u(sha256d(K)) items.append( CosignerItem(window, xpub, K, K_hash, keystore.is_watching_only())) # Presumably atomic self.items.extend(items)
def window_opened(self, window: 'ElectrumWindow'): wallet = window.parent_wallet.get_default_wallet() if cosigner_pool.is_enabled() and type(wallet) is Multisig_Wallet: items = [] for keystore in wallet.get_keystores(): xpub = keystore.get_master_public_key() pubkey = bip32_key_from_string(xpub) K = pubkey.to_bytes() K_hash = bh2u(sha256d(K)) items.append( CosignerItem(window, xpub, K, K_hash, keystore.is_watching_only())) # Presumably atomic self.items.extend(items)
def hid_send_encrypt(self, msg): reply = "" try: secret = sha256d(self.password) msg = EncodeAES(secret, msg) reply = self.hid_send_plain(msg) if 'ciphertext' in reply: reply = DecodeAES(secret, ''.join(reply["ciphertext"])) reply = to_string(reply, 'utf8') reply = json.loads(reply) if 'error' in reply: self.password = None except Exception as e: logger.error('Exception caught %s', e) return reply
def _window_opened(self, window: 'ElectrumWindow') -> None: if not cosigner_pool.is_enabled(): return for account in window._wallet.get_accounts(): if type(account) is not MultisigAccount: continue account_id = account.get_id() items = [] for keystore in account.get_keystores(): xpub = keystore.get_master_public_key() pubkey = bip32_key_from_string(xpub) pubkey_bytes = pubkey.to_bytes() keyhash_hex = sha256d(pubkey_bytes).hex() items.append(CosignerItem(window, account_id, xpub, pubkey_bytes, keyhash_hex, keystore.is_watching_only())) self._items.extend(items)
def test_sha256d(self): self.assertEqual( b'\x95MZI\xfdp\xd9\xb8\xbc\xdb5\xd2R&x)\x95\x7f~\xf7\xfalt\xf8\x84\x19\xbd\xc5\xe8"\t\xf4', sha256d(u"test"))
def sign_transaction(self, tx: Transaction, password: str) -> None: if tx.is_complete(): return try: p2pkhTransaction = True inputhasharray = [] hasharray = [] pubkeyarray = [] # Build hasharray from inputs for txin in tx.inputs: if txin.type() != ScriptType.P2PKH: p2pkhTransaction = False for x_pubkey in txin.x_pubkeys: if self.is_signature_candidate(x_pubkey): key_derivation = x_pubkey.bip32_path() assert len(key_derivation) == 2 inputPath = "%s/%d/%d" % (self.get_derivation(), *key_derivation) inputHash = tx.preimage_hash(txin) hasharray_i = { 'hash': inputHash.hex(), 'keypath': inputPath } hasharray.append(hasharray_i) inputhasharray.append(inputHash) break else: self.give_error("No matching x_key for sign_transaction" ) # should never happen # Build pubkeyarray from annotated change outputs. # The user is on their own if they have unannotated non-change self-outputs. for txout in tx.outputs: if txout.x_pubkeys: for xpubkey in [ xpk for xpk in txout.x_pubkeys if self.is_signature_candidate(xpk) ]: key_path_text = compose_chain_string( xpubkey.derivation_path())[1:] changePath = self.get_derivation( ) + key_path_text # "/1/0", no "m" pubkeyarray.append({ 'pubkey': xpubkey.to_public_key().to_hex(), 'keypath': changePath, }) # Special serialization of the unsigned transaction for # the mobile verification app. # At the moment, verification only works for p2pkh transactions. if p2pkhTransaction: class CustomTXSerialization(Transaction): @classmethod def input_script(self, txin, estimate_size=False): type_ = txin.type() if type_ == ScriptType.P2PKH: return Transaction.get_preimage_script(txin) if type_ == ScriptType.MULTISIG_P2SH: # Multisig verification has partial support, but is # disabled. This is the expected serialization though, so we # leave it here until we activate it. return '00' + push_script( Transaction.get_preimage_script(txin)) raise RuntimeError(f'unsupported type {type_}') tx_dbb_serialized = CustomTXSerialization.from_hex( tx.serialize()).serialize() else: # We only need this for the signing echo / verification. tx_dbb_serialized = None # Build sign command dbb_signatures: List[Dict[str, Any]] = [] steps = math.ceil(1.0 * len(hasharray) / self.maxInputs) for step in range(int(steps)): hashes = hasharray[step * self.maxInputs:(step + 1) * self.maxInputs] msg_data: Dict[str, Any] = { "sign": { "data": hashes, "checkpub": pubkeyarray, }, } if tx_dbb_serialized is not None: msg_data["sign"]["meta"] = sha256d(tx_dbb_serialized).hex() msg = json.dumps(msg_data).encode('ascii') assert self.plugin is not None dbb_client: DigitalBitbox_Client = self.plugin.get_client(self) if not dbb_client.is_paired(): raise Exception("Could not sign transaction.") reply = dbb_client.hid_send_encrypt(msg) if 'error' in reply: raise Exception(reply['error']['message']) if 'echo' not in reply: raise Exception("Could not sign transaction.") if self.plugin.is_mobile_paired( ) and tx_dbb_serialized is not None: reply['tx'] = tx_dbb_serialized self.plugin.comserver_post_notification(reply) if steps > 1: self.handler.show_message( _("Signing large transaction. Please be patient ...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for " "3 seconds.") + " " + _("(Touch {} of {})").format((step + 1), steps) + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout." ) + "\n\n") else: self.handler.show_message( _("Signing transaction...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for " "3 seconds.") + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout." )) # Send twice, first returns an echo for smart verification reply = dbb_client.hid_send_encrypt(msg) self.handler.finished() if 'error' in reply: if reply["error"].get('code') in (600, 601): # aborted via LED short touch or timeout raise UserCancelled() raise Exception(reply['error']['message']) if 'sign' not in reply: raise Exception("Could not sign transaction.") dbb_signatures.extend(reply['sign']) # Fill signatures if len(dbb_signatures) != len(tx.inputs): raise RuntimeError("Incorrect number of transactions signed") for txin, siginfo, pre_hash in zip(tx.inputs, dbb_signatures, inputhasharray): if txin.is_complete(): continue for pubkey_index, x_pubkey in enumerate(txin.x_pubkeys): compact_sig = bytes.fromhex(siginfo['sig']) if 'recid' in siginfo: # firmware > v2.1.1 recid = int(siginfo['recid'], 16) recoverable_sig = compact_sig + bytes([recid]) pk = PublicKey.from_recoverable_signature( recoverable_sig, pre_hash, None) elif 'pubkey' in siginfo: # firmware <= v2.1.1 pk = PublicKey.from_hex(siginfo['pubkey']) if pk != x_pubkey.to_public_key(): continue full_sig = (compact_signature_to_der(compact_sig) + bytes([Transaction.nHashType() & 255])) txin.signatures[pubkey_index] = full_sig except UserCancelled: raise except Exception as e: self.give_error(e, True) else: logger.debug("Transaction is_complete %s", tx.is_complete())
def derive_keys(x): h = sha256d(x) h = hashlib.sha512(h).digest() return (h[:32], h[32:])
def sign_transaction(self, tx, password): if tx.is_complete(): return try: p2pkhTransaction = True derivations = self.get_tx_derivations(tx) inputhasharray = [] hasharray = [] pubkeyarray = [] # Build hasharray from inputs for i, txin in enumerate(tx.inputs()): if txin['type'] == 'coinbase': self.give_error("Coinbase not supported") # should never happen if txin['type'] != 'p2pkh': p2pkhTransaction = False for x_pubkey in txin['x_pubkeys']: if x_pubkey in derivations: index = derivations.get(x_pubkey) inputPath = "%s/%d/%d" % (self.get_derivation(), index[0], index[1]) inputHash = sha256d(bytes.fromhex(tx.serialize_preimage(i))) hasharray_i = {'hash': inputHash.hex(), 'keypath': inputPath} hasharray.append(hasharray_i) inputhasharray.append(inputHash) break else: self.give_error("No matching x_key for sign_transaction") # should never happen # Build pubkeyarray from outputs for _type, address, amount in tx.outputs(): assert _type == TYPE_ADDRESS info = tx.output_info.get(address) if info is not None: index, xpubs, m = info changePath = self.get_derivation() + "/%d/%d" % index changePubkey = self.derive_pubkey(index[0], index[1]) pubkeyarray_i = {'pubkey': changePubkey, 'keypath': changePath} pubkeyarray.append(pubkeyarray_i) # Special serialization of the unsigned transaction for # the mobile verification app. # At the moment, verification only works for p2pkh transactions. if p2pkhTransaction: class CustomTXSerialization(Transaction): @classmethod def input_script(self, txin, estimate_size=False): if txin['type'] == 'p2pkh': return Transaction.get_preimage_script(txin) if txin['type'] == 'p2sh': # Multisig verification has partial support, but is # disabled. This is the expected serialization though, so we # leave it here until we activate it. return '00' + push_script(Transaction.get_preimage_script(txin)) raise Exception("unsupported type %s" % txin['type']) tx_dbb_serialized = CustomTXSerialization(tx.serialize()).serialize() else: # We only need this for the signing echo / verification. tx_dbb_serialized = None # Build sign command dbb_signatures = [] steps = math.ceil(1.0 * len(hasharray) / self.maxInputs) for step in range(int(steps)): hashes = hasharray[step * self.maxInputs : (step + 1) * self.maxInputs] msg = { "sign": { "data": hashes, "checkpub": pubkeyarray, }, } if tx_dbb_serialized is not None: msg["sign"]["meta"] = sha256d(tx_dbb_serialized).hex() msg = json.dumps(msg).encode('ascii') dbb_client = self.plugin.get_client(self) if not dbb_client.is_paired(): raise Exception("Could not sign transaction.") reply = dbb_client.hid_send_encrypt(msg) if 'error' in reply: raise Exception(reply['error']['message']) if 'echo' not in reply: raise Exception("Could not sign transaction.") if self.plugin.is_mobile_paired() and tx_dbb_serialized is not None: reply['tx'] = tx_dbb_serialized self.plugin.comserver_post_notification(reply) if steps > 1: self.handler.show_message( _("Signing large transaction. Please be patient ...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for " "3 seconds.") + " " + _("(Touch {} of {})").format((step + 1), steps) + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout.") + "\n\n") else: self.handler.show_message( _("Signing transaction...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for " "3 seconds.") + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout.")) # Send twice, first returns an echo for smart verification reply = dbb_client.hid_send_encrypt(msg) self.handler.finished() if 'error' in reply: if reply["error"].get('code') in (600, 601): # aborted via LED short touch or timeout raise UserCancelled() raise Exception(reply['error']['message']) if 'sign' not in reply: raise Exception("Could not sign transaction.") dbb_signatures.extend(reply['sign']) # Fill signatures if len(dbb_signatures) != len(tx.inputs()): raise Exception("Incorrect number of transactions signed.") # Should never occur for i, txin in enumerate(tx.inputs()): num = txin['num_sig'] for pubkey in txin['pubkeys']: signatures = [sig for sig in txin['signatures'] if sig] if len(signatures) == num: break # txin is complete ii = txin['pubkeys'].index(pubkey) siginfo = dbb_signatures[i] compact_sig = bytes.fromhex(siginfo['sig']) if 'recid' in siginfo: # firmware > v2.1.1 recid = int(siginfo['recid'], 16) recoverable_sig = compact_sig + bytes([recid]) h = inputhasharray[i] pk = PublicKey.from_recoverable_signature(recoverable_sig, h, None) pk = pk.to_hex(compressed=True) elif 'pubkey' in siginfo: # firmware <= v2.1.1 pk = siginfo['pubkey'] if pk != pubkey: continue sig = (compact_signature_to_der(compact_sig) + bytes([Transaction.nHashType() & 255])) tx.add_signature_to_txin(i, ii, sig.hex()) except UserCancelled: raise except Exception as e: self.give_error(e, True) else: logger.debug("Transaction is_complete %s", tx.is_complete()) tx.raw = tx.serialize()
def sign_message(self, sequence, message, password): sig = None try: message = message.encode('utf8') inputPath = self.get_derivation() + "/%d/%d" % sequence msg_hash = sha256d(msg_magic(message)) inputHash = to_hexstr(msg_hash) hasharray = [] hasharray.append({'hash': inputHash, 'keypath': inputPath}) hasharray = json.dumps(hasharray) msg = b'{"sign":{"meta":"sign message", "data":%s}}' % hasharray.encode( 'utf8') dbb_client = self.plugin.get_client(self) if not dbb_client.is_paired(): raise Exception(_("Could not sign message.")) reply = dbb_client.hid_send_encrypt(msg) self.handler.show_message( _("Signing message ...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for " "3 seconds.") + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout." )) # Send twice, first returns an echo for smart verification (not implemented) reply = dbb_client.hid_send_encrypt(msg) self.handler.finished() if 'error' in reply: raise Exception(reply['error']['message']) if 'sign' not in reply: raise Exception(_("Could not sign message.")) if 'recid' in reply['sign'][0]: # firmware > v2.1.1 sig = (bytes([27 + int(reply['sign'][0]['recid'], 16) + 4]) + binascii.unhexlify(reply['sign'][0]['sig'])) pk, compressed = pubkey_from_signature(sig, msg_hash) pk = point_to_ser(pk.pubkey.point, compressed) addr = public_key_to_p2pkh(pk) if not ecc.verify_message_with_address(addr, sig, message): raise Exception(_("Could not sign message")) elif 'pubkey' in reply['sign'][0]: # firmware <= v2.1.1 for i in range(4): sig = bytes([27 + i + 4]) + binascii.unhexlify( reply['sign'][0]['sig']) try: addr = public_key_to_p2pkh( binascii.unhexlify(reply['sign'][0]['pubkey'])) if ecc.verify_message_with_address(addr, sig, message): break except Exception: continue else: raise Exception(_("Could not sign message")) except Exception as e: self.give_error(e) return sig