예제 #1
0
    def update_signatures(self, signatures):
        """Add new signatures to a transaction

        `signatures` is expected to be a list of binary sigs with signatures[i]
        intended for self._inputs[i], without the SIGHASH appended.
        This is used by hardware device code.
        """
        if self.is_complete():
            return
        if len(self.inputs()) != len(signatures):
            raise RuntimeError('expected {} signatures; got {}'.format(
                len(self.inputs()), len(signatures)))
        for i, txin in enumerate(self.inputs()):
            pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin)
            sig = bh2u(signatures[i] + bytes([self.nHashType() & 255]))
            logger.warning(f'Signature {i}: {sig}')
            if sig in txin.get('signatures'):
                continue
            pre_hash = self.preimage_hash(i)
            rec_sig_base = der_signature_to_compact(signatures[i])
            for recid in range(4):
                rec_sig = rec_sig_base + bytes([recid])
                try:
                    public_key = PublicKey.from_recoverable_signature(
                        rec_sig, pre_hash, None)
                except (InvalidSignatureError, ValueError):
                    # the point might not be on the curve for some recid values
                    continue
                # public key is compressed
                pubkey_hex = public_key.to_hex()
                if pubkey_hex in pubkeys:
                    try:
                        public_key.verify_recoverable_signature(
                            rec_sig, pre_hash, None)
                    except Exception:
                        logger.exception('')
                        continue
                    j = pubkeys.index(pubkey_hex)
                    logger.debug(f'adding sig {i} {j} {pubkey_hex} {sig}')
                    self.add_signature_to_txin(i, j, sig)
                    break
        # redo raw
        self.raw = self.serialize()
예제 #2
0
    def update_signatures(self, signatures):
        """Add new signatures to a transaction

        `signatures` is expected to be a list of binary sigs with signatures[i]
        intended for self.inputs[i], without the SIGHASH appended.
        This is used by hardware device code.
        """
        if self.is_complete():
            return
        if len(self.inputs) != len(signatures):
            raise RuntimeError('expected {} signatures; got {}'.format(
                len(self.inputs), len(signatures)))
        for txin, signature in zip(self.inputs, signatures):
            full_sig = signature + bytes([self.nHashType()])
            logger.warning(f'Signature: {full_sig.hex()}')
            if full_sig in txin.signatures:
                continue
            pubkeys = [x_pubkey.to_public_key() for x_pubkey in txin.x_pubkeys]
            pre_hash = self.preimage_hash(txin)
            rec_sig_base = der_signature_to_compact(signature)
            for recid in range(4):
                rec_sig = rec_sig_base + bytes([recid])
                try:
                    public_key = PublicKey.from_recoverable_signature(
                        rec_sig, pre_hash, None)
                except (InvalidSignatureError, ValueError):
                    # the point might not be on the curve for some recid values
                    continue
                if public_key in pubkeys:
                    try:
                        public_key.verify_recoverable_signature(
                            rec_sig, pre_hash, None)
                    except Exception:
                        logger.exception('')
                        continue
                    j = pubkeys.index(public_key)
                    logger.debug(f'adding sig {j} {public_key} {full_sig}')
                    txin.signatures[j] = full_sig
                    break
예제 #3
0
    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())
예제 #4
0
    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()