Ejemplo n.º 1
0
 def test_int_to_hex(self):
     self.assertEqual('00', int_to_hex(0, 1))
     self.assertEqual('ff', int_to_hex(-1, 1))
     self.assertEqual('00000000', int_to_hex(0, 4))
     self.assertEqual('01000000', int_to_hex(1, 4))
     self.assertEqual('7f', int_to_hex(127, 1))
     self.assertEqual('7f00', int_to_hex(127, 2))
     self.assertEqual('80', int_to_hex(128, 1))
     self.assertEqual('80', int_to_hex(-128, 1))
     self.assertEqual('8000', int_to_hex(128, 2))
     self.assertEqual('ff', int_to_hex(255, 1))
     self.assertEqual('ff7f', int_to_hex(32767, 2))
     self.assertEqual('0080', int_to_hex(-32768, 2))
     self.assertEqual('ffff', int_to_hex(65535, 2))
     with self.assertRaises(OverflowError):
         int_to_hex(256, 1)
     with self.assertRaises(OverflowError):
         int_to_hex(-129, 1)
     with self.assertRaises(OverflowError):
         int_to_hex(-257, 1)
     with self.assertRaises(OverflowError):
         int_to_hex(65536, 2)
     with self.assertRaises(OverflowError):
         int_to_hex(-32769, 2)
Ejemplo n.º 2
0
    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
        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

            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}".format(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)
            inputs.append([txin['prev_tx'].raw, txin['prevout_n'], redeemScript,
                           txin['prevout_hash'], signingPos,
                           txin.get('sequence', 0xffffffff - 1)])
            inputsPaths.append(hwAddress)
            pubKeys.append(pubkeys)

        # Sanity check
        if p2shTransaction:
            for txin in tx.inputs():
                if txin['type'] != 'p2sh':   # should never happen
                    self.give_error(
                        "P2SH / regular input mixed in same transaction not supported")

        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(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:
            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):
                    index, xpubs, m = info
                    changePath = self.get_derivation()[2:] + "/{:d}/{:d}".format(*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)
                txtmp = bitcoinTransaction(bfh(utxo[0]))
                tmp = bfh(utxo[3])[::-1]
                tmp += bfh(int_to_hex(utxo[1], 4))
                tmp += txtmp.outputs[utxo[1]].amount
                chipInputs.append({'value' : tmp, 'witness' : True, 'sequence' : sequence})
                redeemScripts.append(bfh(utxo[2]))

            # Sign all inputs
            inputIndex = 0
            rawTx = tx.serialize()
            self.get_client().enableAlternate2fa(False)
            self.get_client().startUntrustedTransaction(True, inputIndex,
                                                        chipInputs, redeemScripts[inputIndex])
            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 ) # 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,
                                                                     sighashType=tx.nHashType())
                inputSignature[0] = 0x30 # force for 1.4.9+
                signatures.append(inputSignature)
                inputIndex = inputIndex + 1
        except UserWarning:
            self.handler.show_error(_('Cancelled by user'))
            return
        except BTChipException as e:
            if e.sw == 0x6985:  # cancelled by user
                return
            else:
                logging.exception("")
                self.give_error(e, True)
        except BaseException as e:
            logging.exception("")
            self.give_error(e, True)
        finally:
            self.handler.finished()

        for i, txin in enumerate(tx.inputs()):
            signingPos = inputs[i][4]
            txin['signatures'][signingPos] = bh2u(signatures[i])
        tx.raw = tx.serialize()
Ejemplo n.º 3
0
    def sign_transaction(self, tx, password):
        if tx.is_complete():
            return
        client = self.get_client()
        inputs = []
        inputsPaths = []
        chipInputs = []
        redeemScripts = []
        signatures = []
        preparedTrustedInputs = []
        changePath = ""
        changeAmount = None
        output = None
        outputAmount = None
        pin = ""
        self.get_client() # prompt for the PIN before displaying the dialog if necessary

        # Sanity check
        is_p2sh = any(txin.type() == 'p2sh' for txin in tx.inputs)
        if is_p2sh and not all(txin.type() == 'p2sh' for txin in tx.inputs):
            self.give_error("P2SH / regular input mixed in same transaction not supported")

        # Fetch inputs of the transaction to sign
        derivations = self.get_tx_derivations(tx)
        for txin in tx.inputs:
            for i, x_pubkey in enumerate(txin.x_pubkeys):
                if x_pubkey.to_hex() in derivations:
                    signingPos = i
                    s = derivations.get(x_pubkey.to_hex())
                    hwAddress = "{:s}/{:d}/{:d}".format(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)
            inputs.append([txin.value, txin.prev_idx, redeemScript,
                           txin.prev_hash, signingPos, txin.sequence])
            inputsPaths.append(hwAddress)

        # Concatenate all the tx outputs as binary
        txOutput = pack_list(tx.outputs, TxOutput.to_bytes)

        # Recognize outputs - only one output and one change is authorized
        if not is_p2sh:
            for tx_output, info in zip(tx.outputs, tx.output_info):
                if (info is not None) and len(tx.outputs) != 1:
                    index, xpubs, m = info
                    changePath = self.get_derivation()[2:] + "/{:d}/{:d}".format(*index)
                    changeAmount = tx_output.value
                else:
                    output = classify_tx_output(tx_output)
                    outputAmount = tx_output.value

        self.handler.show_message(_("Confirm Transaction on your Ledger device..."))
        try:
            for utxo in inputs:
                sequence = int_to_hex(utxo[5], 4)
                chipInputs.append({'value' : utxo[0], 'witness' : True, 'sequence' : sequence})
                redeemScripts.append(bfh(utxo[2]))

            # Sign all inputs
            inputIndex = 0
            rawTx = tx.serialize()
            self.get_client().enableAlternate2fa(False)
            self.get_client().startUntrustedTransaction(True, inputIndex,
                                                        chipInputs, redeemScripts[inputIndex])
            outputData = self.get_client().finalizeInputFull(txOutput)
            outputData['outputData'] = txOutput
            transactionOutput = outputData['outputData']
            if outputData['confirmationNeeded']:
                outputData['address'] = output.to_string(coin=Net.COIN)
                self.handler.finished()
                pin = self.handler.get_auth( outputData ) # the authenticate dialog and returns pin
                if not pin:
                    raise UserWarning()
                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,
                                                                     sighashType=tx.nHashType())
                inputSignature[0] = 0x30 # force for 1.4.9+
                signatures.append(inputSignature)
                inputIndex = inputIndex + 1
        except UserWarning:
            self.handler.show_error(_('Cancelled by user'))
            return
        except BTChipException as e:
            if e.sw == 0x6985:  # cancelled by user
                return
            else:
                logger.exception("")
                self.give_error(e, True)
        except Exception as e:
            logger.exception("")
            self.give_error(e, True)
        finally:
            self.handler.finished()

        for txin, input, signature in zip(tx.inputs, inputs, signatures):
            txin.signatures[input[4]] = signature
        tx.raw = tx.serialize()
Ejemplo n.º 4
0
    def sign_transaction(self, tx: Transaction, password: str,
                         tx_context: TransactionContext) -> None:
        if tx.is_complete():
            return

        assert self.handler is not None
        client = self.get_client()
        inputs: List[YInput] = []
        inputsPaths = []
        chipInputs = []
        redeemScripts = []
        signatures = []
        changePath = ""
        changeAmount = None
        output = None
        outputAmount = None
        pin = ""
        self.get_client(
        )  # prompt for the PIN before displaying the dialog if necessary

        # Fetch inputs of the transaction to sign
        foundP2SHSpend = False
        allSpendsAreP2SH = True
        for txin in tx.inputs:
            foundP2SHSpend = foundP2SHSpend or txin.type(
            ) == ScriptType.MULTISIG_P2SH
            allSpendsAreP2SH = allSpendsAreP2SH and txin.type(
            ) == ScriptType.MULTISIG_P2SH

            for i, x_pubkey in enumerate(txin.x_pubkeys):
                if self.is_signature_candidate(x_pubkey):
                    txin_xpub_idx = i
                    inputPath = "%s/%d/%d" % (self.get_derivation()[2:],
                                              *x_pubkey.bip32_path())
                    break
            else:
                self.give_error("No matching x_key for sign_transaction"
                                )  # should never happen

            inputs.append(
                YInput(txin.value, Transaction.get_preimage_script_bytes(txin),
                       txin_xpub_idx, txin.sequence))
            inputsPaths.append(inputPath)

        # Sanity check
        if foundP2SHSpend and not allSpendsAreP2SH:
            self.give_error(
                "P2SH / regular input mixed in same transaction not supported")

        # Concatenate all the tx outputs as binary
        txOutput = pack_list(tx.outputs, XTxOutput.to_bytes)

        # Recognize outputs - only one output and one change is authorized
        if not foundP2SHSpend:
            keystore_fingerprint = self.get_fingerprint()
            assert tx.output_info is not None
            for tx_output, output_metadatas in zip(tx.outputs, tx.output_info):
                info = output_metadatas.get(keystore_fingerprint)
                if (info is not None) and len(tx.outputs) != 1:
                    key_derivation, xpubs, m = info
                    key_subpath = compose_chain_string(key_derivation)[1:]
                    changePath = self.get_derivation()[2:] + key_subpath
                    changeAmount = tx_output.value
                else:
                    output = classify_tx_output(tx_output)
                    outputAmount = tx_output.value

        self.handler.show_message(
            _("Confirm Transaction on your Ledger device..."))
        try:
            for i, utxo in enumerate(inputs):
                txin = tx.inputs[i]
                sequence = int_to_hex(utxo.sequence, 4)
                prevout_bytes = txin.prevout_bytes()
                value_bytes = prevout_bytes + pack_le_int64(utxo.value)
                chipInputs.append({
                    'value': value_bytes,
                    'witness': True,
                    'sequence': sequence
                })
                redeemScripts.append(utxo.script_sig)

            # Sign all inputs
            inputIndex = 0
            rawTx = tx.serialize()
            self.get_client().enableAlternate2fa(False)
            self.get_client().startUntrustedTransaction(
                True, inputIndex, chipInputs, redeemScripts[inputIndex])
            outputData = self.get_client().finalizeInputFull(txOutput)
            outputData['outputData'] = txOutput
            transactionOutput = outputData['outputData']
            if outputData['confirmationNeeded']:
                outputData['address'] = cast(ScriptTemplate,
                                             output).to_string()
                self.handler.finished()
                # the authenticate dialog and returns pin
                auth_pin = self.handler.get_auth(self, outputData)
                if not auth_pin:
                    raise UserWarning()
                pin = auth_pin
                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,
                    sighashType=tx.nHashType())
                inputSignature[0] = 0x30  # force for 1.4.9+
                signatures.append(inputSignature)
                inputIndex = inputIndex + 1
        except UserWarning:
            self.handler.show_error(_('Cancelled by user'))
            return
        except BTChipException as e:
            if e.sw == 0x6985:  # cancelled by user
                return
            else:
                logger.exception("")
                self.give_error(e, True)
        except Exception as e:
            logger.exception("")
            self.give_error(e, True)
        finally:
            self.handler.finished()

        for txin, input, signature in zip(tx.inputs, inputs, signatures):
            txin.signatures[input.txin_xpub_idx] = signature
Ejemplo n.º 5
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 = Hash(
                            binascii.unhexlify(tx.serialize_preimage(i)))
                        hasharray_i = {
                            'hash': to_hexstr(inputHash),
                            '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"] = to_hexstr(Hash(tx_dbb_serialized))
                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)
                    signed = dbb_signatures[i]
                    if 'recid' in signed:
                        # firmware > v2.1.1
                        recid = int(signed['recid'], 16)
                        s = binascii.unhexlify(signed['sig'])
                        h = inputhasharray[i]
                        pk = MyVerifyingKey.from_signature(s,
                                                           recid,
                                                           h,
                                                           curve=SECP256k1)
                        pk = to_hexstr(point_to_ser(pk.pubkey.point, True))
                    elif 'pubkey' in signed:
                        # firmware <= v2.1.1
                        pk = signed['pubkey']
                    if pk != pubkey:
                        continue
                    sig_r = int(signed['sig'][:64], 16)
                    sig_s = int(signed['sig'][64:], 16)
                    sig = sigencode_der(sig_r, sig_s,
                                        generator_secp256k1.order())
                    txin['signatures'][ii] = (
                        to_hexstr(sig) +
                        int_to_hex(Transaction.nHashType() & 255, 1))
                    tx._inputs[i] = txin
        except UserCancelled:
            raise
        except BaseException as e:
            self.give_error(e, True)
        else:
            logger.debug("Transaction is_complete %s", tx.is_complete())
            tx.raw = tx.serialize()