예제 #1
0
    def sign_message(self, sequence, message, password):
        sig = None
        try:
            message = message.encode('utf8')
            inputPath = self.get_derivation() + "/%d/%d" % sequence
            msg_hash = Hash(msg_magic(message))
            inputHash = to_hexstr(msg_hash)
            hasharray = []
            hasharray.append({'hash': inputHash, 'keypath': inputPath})
            hasharray = json.dumps(hasharray)

            msg = ('{"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."))
            reply = dbb_client.hid_send_encrypt(msg) # Send twice, first returns an echo for smart verification (not implemented)
            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_string = binascii.unhexlify(reply['sign'][0]['sig'])
                recid = int(reply['sign'][0]['recid'], 16)
                sig = ecc.construct_sig65(sig_string, recid, True)
                pubkey, compressed = ecc.ECPubkey.from_signature65(sig, msg_hash)
                addr = public_key_to_p2pkh(pubkey.get_public_key_bytes(compressed=compressed))
                if ecc.verify_message_with_address(addr, sig, message) is False:
                    raise Exception(_("Could not sign message"))
            elif 'pubkey' in reply['sign'][0]:
                # firmware <= v2.1.1
                for recid in range(4):
                    sig_string = binascii.unhexlify(reply['sign'][0]['sig'])
                    sig = ecc.construct_sig65(sig_string, recid, True)
                    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 BaseException as e:
            self.give_error(e)
        return sig
예제 #2
0
 def hid_send_encrypt(self, msg):
     reply = ""
     try:
         secret = Hash(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:
         print_error('Exception caught ' + str(e))
     return reply
예제 #3
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 o in tx.outputs():
                assert o.type == TYPE_ADDRESS
                info = tx.output_info.get(o.address)
                if info is not None:
                    index = info.address_index
                    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_to_network()
            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 = list(filter(None, txin['signatures']))
                    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 = ecc.ECPubkey.from_sig_string(s, recid, h)
                        pk = pk.get_public_key_hex(compressed=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 = ecc.der_sig_from_r_and_s(sig_r, sig_s)
                    sig = to_hexstr(sig) + '01'
                    tx.add_signature_to_txin(i, ii, sig)
        except UserCancelled:
            raise
        except BaseException as e:
            self.give_error(e, True)
        else:
            print_error("Transaction is_complete", tx.is_complete())
            tx.raw = tx.serialize()