示例#1
0
    def scan_animated_qr_pbst(self, controller) -> str:
        self.controller = controller
        self.buttons = controller.buttons
        self.controller.menu_view.draw_modal(["Initializing Camera"
                                              ])  # TODO: Move to Controller
        # initialize camera
        self.controller.to_camera_queue.put(["start"])
        # First get blocking, this way it's clear when the camera is ready for the end user
        self.controller.from_camera_queue.get()
        self.camera_loop_timer = CameraPoll(0.05, self.process_camera_data)

        input = self.buttons.wait_for([B.KEY_LEFT, B.KEY_RIGHT])
        if input in (B.KEY_LEFT, B.KEY_RIGHT):
            self.camera_loop_timer.stop()
            self.controller.to_camera_queue.put(["stop"])
            return "nodata"
        elif input == B.OVERRIDE:
            self.camera_loop_timer.stop()
            self.controller.to_camera_queue.put(["stop"])
            if self.qr_data[0] == "invalid":
                return "invalid"
            time.sleep(
                0.5
            )  # give time for camera loop to complete before returning data
            return "".join(self.qr_data)
示例#2
0
    def display_io_test_screen(self):

        # display loading screen
        self.draw_modal(["Initializing I/O Test"])
        print("Initializing I/O Test")
        self.qr_text = "Scan ANY QR Code"

        # initialize camera
        self.controller.to_camera_queue.put(["start"])

        # First get blocking, this way it's clear when the camera is ready for the end user
        self.controller.from_camera_queue.get()

        self.camera_loop_timer = CameraPoll(
            0.05,
            self.get_camera_data)  # it auto-starts, no need of rt.start()

        while True:

            self.draw_io_screen()

            input = self.buttons.wait_for([
                B.KEY_UP, B.KEY_DOWN, B.KEY_PRESS, B.KEY_RIGHT, B.KEY_LEFT,
                B.KEY1, B.KEY2, B.KEY3
            ], False)
            if input == B.KEY_UP:
                ret_val = self.up_button()
            elif input == B.KEY_DOWN:
                ret_val = self.down_button()
            elif input == B.KEY_RIGHT:
                ret_val = self.right_button()
            elif input == B.KEY_LEFT:
                ret_val = self.left_button()
            elif input == B.KEY_PRESS:
                ret_val = self.press_button()
            elif input == B.KEY1:
                ret_val = self.a_button()
            elif input == B.KEY2:
                ret_val = self.b_button()
            elif input == B.KEY3:
                ret_val = self.c_button()
                return True
示例#3
0
    def scan_animated_qr_pbst(self, controller) -> str:
        self.controller = controller
        self.buttons = controller.buttons
        self.controller.menu_view.draw_modal(["Initializing Camera"
                                              ])  # TODO: Move to Controller
        # initialize camera
        self.controller.to_camera_queue.put(["start"])
        # First get blocking, this way it's clear when the camera is ready for the end user
        self.controller.from_camera_queue.get()
        self.camera_loop_timer = CameraPoll(0.05, self.process_camera_data)

        input = self.buttons.wait_for([B.KEY_LEFT, B.KEY_RIGHT])
        if input in (B.KEY_LEFT, B.KEY_RIGHT):
            self.camera_loop_timer.stop()
            self.controller.to_camera_queue.put(["stop"])
            return "nodata"
        elif input == B.OVERRIDE:
            self.camera_loop_timer.stop()
            self.controller.to_camera_queue.put(["stop"])
            if self.qr_data[0] == "invalid":
                return "invalid"
            elif self.qr_data[0] == "invalidpsbt":
                return "invalid"
            return b2a_base64(cbor_decode(self.ur_decoder.result.cbor))
示例#4
0
class BlueWallet(Wallet):
    def __init__(self,
                 current_network="main",
                 qr_density=Wallet.QRMEDIUM,
                 policy="PKWSH") -> None:
        Wallet.__init__(self, current_network, qr_density, policy)

    def set_seed_phrase(self, seed_phrase, passphrase):
        Wallet.set_seed_phrase(self, seed_phrase, passphrase)
        self.ur_decoder = URDecoder()

    def get_name(self) -> str:
        return "Blue Wallet"

    def parse_psbt(self, raw_psbt) -> bool:
        try:
            base64_psbt = a2b_base64(raw_psbt)
            self.tx = psbt.PSBT.parse(base64_psbt)
            self._parse_psbt()
        except Exception:
            return False

        return True

    def sign_transaction(self) -> (str):

        # sign the transaction
        self.tx.sign_with(self.root)

        #added section to trim psbt
        trimmed_psbt = psbt.PSBT(self.tx.tx)
        sigsEnd = 0
        for i, inp in enumerate(self.tx.inputs):
            sigsEnd += len(list(inp.partial_sigs.keys()))
            trimmed_psbt.inputs[i].partial_sigs = inp.partial_sigs

        raw_trimmed_signed_psbt = trimmed_psbt.serialize()

        # convert to base64
        b64_psbt = b2a_base64(raw_trimmed_signed_psbt)
        # somehow b2a ends with \n...
        if b64_psbt[-1:] == b"\n":
            b64_psbt = b64_psbt[:-1]

        return b64_psbt.decode('utf-8')

    def scan_animated_qr_pbst(self, controller) -> str:
        self.controller = controller
        self.buttons = controller.buttons
        self.controller.menu_view.draw_modal(["Initializing Camera"
                                              ])  # TODO: Move to Controller
        # initialize camera
        self.controller.to_camera_queue.put(["start"])
        # First get blocking, this way it's clear when the camera is ready for the end user
        self.controller.from_camera_queue.get()
        self.camera_loop_timer = CameraPoll(0.05, self.process_camera_data)

        input = self.buttons.wait_for([B.KEY_LEFT, B.KEY_RIGHT])
        if input in (B.KEY_LEFT, B.KEY_RIGHT):
            self.camera_loop_timer.stop()
            self.controller.to_camera_queue.put(["stop"])
            return "nodata"
        elif input == B.OVERRIDE:
            self.camera_loop_timer.stop()
            self.controller.to_camera_queue.put(["stop"])
            if self.qr_data[0] == "invalid":
                return "invalid"
            elif self.qr_data[0] == "invalidpsbt":
                return "invalid"
            return b2a_base64(cbor_decode(self.ur_decoder.result.cbor))

    def process_camera_data(self):
        try:
            data = self.controller.from_camera_queue.get(False)
        except:
            data = ["nodata"]

        if data[0] != "nodata":
            if self.qr_total_frames == 0:
                # get total frames if not set
                self.qr_total_frames = type(self).total_frames_parse(data[0])
                if self.qr_total_frames == -1:
                    # when invalid, trigger override to display error
                    self.qr_data = ["invalid"]
                    self.buttons.trigger_override(
                    )  # something went wrong, invalid QR
                    return
                self.qr_data = ["empty"]

            # get data and percentage
            decoder_work_check = self.ur_decoder.receive_part(data[0])
            if decoder_work_check == False:
                self.qr_data = ["invalidpsbt"]
                self.buttons.trigger_override(
                )  # something went wrong, invalid QR
                return
            self.percentage_complete = self.ur_decoder.estimated_percent_complete(
            )

            # checking if all frames has been captured, exit camera processing
            if self.capture_complete():
                self.buttons.trigger_override()

            # if all frames have not all been captured, display progress to screen/display
            if not self.capture_complete() and self.scan_display_working == 0:
                self.scan_display_working = 1
                View.draw.rectangle(
                    (0, 0, View.canvas_width, View.canvas_height),
                    outline=0,
                    fill=0)
                tw, th = View.draw.textsize("Collecting QR Codes:",
                                            font=View.IMPACT25)
                View.draw.text(((240 - tw) / 2, 15),
                               "Collecting QR Codes:",
                               fill=View.color,
                               font=View.IMPACT25)
                tw, th = View.draw.textsize(
                    str(round(self.percentage_complete * 100)) + "% Complete",
                    font=View.IMPACT22)
                View.draw.text(
                    ((240 - tw) / 2, 125),
                    str(round(self.percentage_complete * 100)) + "% Complete",
                    fill=View.color,
                    font=View.IMPACT22)
                View.DispShowImage()
                self.scan_display_working = 0

        elif self.scan_started_ind == 0:
            self.scan_started_ind = 1
            self.controller.menu_view.draw_modal(["Scan Animated QR"], "",
                                                 "Right to Exit")

    def total_frames_parse(data) -> int:
        if re.search("^UR\:CRYPTO-PSBT\/(\d+)\-(\d+)\/", data,
                     re.IGNORECASE) != None:
            return 10  # valid
        elif re.search("^UR\:CRYPTO-PSBT", data, re.IGNORECASE) != None:
            return 1  # valid but only 1 segment
        else:
            return -1  #invalid

    def capture_complete(self) -> bool:
        if self.ur_decoder.is_complete():
            return True
        else:
            return False

    def set_network(self, network) -> bool:
        if network == "main":
            self.current_network = "main"
            self.hardened_derivation = "m/48h/0h/0h/2h"
        elif network == "test":
            self.current_network = "test"
            self.hardened_derivation = "m/48h/1h/0h/2h"
        else:
            return False

        return True

    def make_xpub_qr_codes(self, data, callback=None) -> []:
        qr = QR()
        images = []
        images.append(qr.qrimage(data))
        return images

    def make_signing_qr_codes(self, data, callback=None) -> []:
        qr = QR()
        images = []
        cnt = 0

        cbor_encoder = CBOREncoder()
        cbor_encoder.encodeBytes(a2b_base64(data))

        qr_ur_bytes = UR("crypto-psbt", cbor_encoder.get_bytes())
        ur2_encode = UREncoder(qr_ur_bytes, self.qrsize, 0)
        qr_cnt = ur2_encode.fountain_encoder.seq_len()

        while not ur2_encode.is_complete():

            part = ur2_encode.next_part().upper()
            images.append(qr.qrimage(part))
            print(part)
            cnt += 1

            if callback != None:
                callback((cnt * 100.0) / qr_cnt)

        return images

    def set_qr_density(self, density):
        self.cur_qr_density = density
        if density == Wallet.QRLOW:
            self.qrsize = 50
        elif density == Wallet.QRMEDIUM:
            self.qrsize = 70
        elif density == Wallet.QRHIGH:
            self.qrsize = 120

    ###
    ### Internal Wallet Transactions OVERRIDE
    ###

    def get_policy(self, scope, scriptpubkey, xpubs):
        """Parse scope and get policy"""
        # we don't know the policy yet, let's parse it
        script_type = scriptpubkey.script_type()
        # p2sh can be either legacy multisig, or nested segwit multisig
        # or nested segwit singlesig
        if script_type == "p2sh":
            if scope.witness_script is not None:
                script_type = "p2sh-p2wsh"
            elif scope.redeem_script is not None and scope.redeem_script.script_type(
            ) == "p2wpkh":
                script_type = "p2sh-p2wpkh"
        policy = {"type": script_type}
        # expected multisig
        if "p2wsh" in script_type and scope.witness_script is not None:
            m, n, pubkeys = super().parse_multisig(scope.witness_script)

            # check pubkeys are derived from cosigners
            policy.update({"m": m, "n": n})
        return policy
示例#5
0
class Wallet:

    QRLOW = 0
    QRMEDIUM = 1
    QRHIGH = 2

    def __init__(self, current_network, qr_density, policy) -> None:
        self.current_network = current_network

        if policy not in self.avaliable_wallet_policies():
            policy = "PKWSH"  #override policy to PKWSH when not found in wallet

        if policy == "PKWSH" and self.current_network == "main":
            self.hardened_derivation = "m/48h/0h/0h/2h"
        elif policy == "PKWSH" and self.current_network == "test":
            self.hardened_derivation = "m/48h/1h/0h/2h"
        elif policy == "PKWPKH" and self.current_network == "main":
            self.hardened_derivation = "m/84h/0h/0h"
        elif policy == "PKWPKH" and self.current_network == "test":
            self.hardened_derivation = "m/84h/1h/0h"
        else:
            raise Exception("Unsupported Derivation Path or Policy")

        self.qrsize = 80  # Default
        self.set_qr_density(qr_density)
        self.cur_qr_density = qr_density
        self.cur_policy = policy

    def set_seed_phrase(self, seed_phrase, passphrase):
        # requires a valid seed phrase or error will be thrown
        self.seed_phrase = seed_phrase
        self.seed = bip39.mnemonic_to_seed(
            (" ".join(self.seed_phrase)).strip(), passphrase)
        self.root = bip32.HDKey.from_seed(
            self.seed, version=NETWORKS[self.current_network]["xprv"])
        self.fingerprint = self.root.child(0).fingerprint
        self.bip48_xprv = self.root.derive(self.hardened_derivation)
        self.bip48_xpub = self.bip48_xprv.to_public()

        self.tx = None
        self.inp_amount = None
        self.fee = None
        self.spend = None
        self.destinationaddress = None
        self.dest_addr_cnt = None
        self.change = None
        self.controller = None
        self.buttons = None
        self.ins = None
        self.outs = None

        self.camera_loop_timer = None
        self.camera_data = None
        self.is_camera_data = False

        self.qr_total_frames = 0
        self.qr_cur_frame_count = 0
        self.qr_data = []
        self.frame_display = []
        self.percentage_complete = 0

        self.scan_started_ind = 0
        self.scan_display_working = 0

    ###
    ### Required Methods to implement for Child Wallet Class
    ###
    ### import_qr, parse_psbt, sign_transaction, total_frames_parse, current_frame_parse, data_parse, capture_complete
    ### get_name, set_network, make_xpub_qr_codes, make_signing_qr_codes, set_qr_density

    def import_qr(self) -> str:
        if self.cur_policy == "PKWPKH":
            xpubstring = "[%s%s]%s" % (
                hexlify(self.fingerprint).decode('utf-8'),
                self.hardened_derivation[1:],
                self.bip48_xpub.to_base58(
                    NETWORKS[self.current_network]["zpub"]))
        else:
            xpubstring = "[%s%s]%s" % (
                hexlify(self.fingerprint).decode('utf-8'),
                self.hardened_derivation[1:],
                self.bip48_xpub.to_base58(
                    NETWORKS[self.current_network]["Zpub"]))

        return xpubstring

    def get_xpub_info(self) -> (str, str, str):
        if self.cur_policy == "PKWPKH":
            xpub = self.bip48_xpub.to_base58(
                NETWORKS[self.current_network]["zpub"])
        else:
            xpub = self.bip48_xpub.to_base58(
                NETWORKS[self.current_network]["Zpub"])

        derivation = self.hardened_derivation[1:].replace("h", "'")

        return (hexlify(self.fingerprint).decode('utf-8'), derivation, xpub)

    def parse_psbt(self, raw_psbt) -> bool:
        # decodes and parses raw_psbt, also calculates the following instance values
        self.inp_amount = None
        self.change = None
        self.fee = None
        self.spend = None
        self.destinationaddress = None
        return False

    def sign_transaction(self) -> (bool, str):
        # signs transaction/pbst last passed to parse_psbt method
        return (False, '')

    def total_frames_parse(data) -> int:
        # parse and returns total number of frames from qr data frame
        return -1

    def current_frame_parse(data) -> int:
        # parses and returns current frame number from qr data frame
        return -1

    def data_parse(data) -> str:
        # parse qr data to string to be cancatinated together into a pbst transaction
        return "empty"

    def capture_complete() -> bool:
        # returns true if the qr data list is complete
        return False

    def scan_animated_qr_pbst(self, controller) -> str:
        self.controller = controller
        self.buttons = controller.buttons
        self.controller.menu_view.draw_modal(["Initializing Camera"
                                              ])  # TODO: Move to Controller
        # initialize camera
        self.controller.to_camera_queue.put(["start"])
        # First get blocking, this way it's clear when the camera is ready for the end user
        self.controller.from_camera_queue.get()
        self.camera_loop_timer = CameraPoll(0.05, self.process_camera_data)

        input = self.buttons.wait_for([B.KEY_LEFT, B.KEY_RIGHT])
        if input in (B.KEY_LEFT, B.KEY_RIGHT):
            self.camera_loop_timer.stop()
            self.controller.to_camera_queue.put(["stop"])
            return "nodata"
        elif input == B.OVERRIDE:
            self.camera_loop_timer.stop()
            self.controller.to_camera_queue.put(["stop"])
            if self.qr_data[0] == "invalid":
                return "invalid"
            time.sleep(
                0.5
            )  # give time for camera loop to complete before returning data
            return "".join(self.qr_data)

    def process_camera_data(self):
        try:
            data = self.controller.from_camera_queue.get(False)
        except:
            data = ["nodata"]

        if data[0] != "nodata":
            if self.qr_total_frames == 0:
                # get total frames if not set
                self.qr_total_frames = type(self).total_frames_parse(data[0])
                if self.qr_total_frames == -1:
                    # when invalid, trigger override to display error
                    self.qr_data = ["invalid"]
                    self.buttons.trigger_override(
                    )  # something went wrong, invalid QR
                    return

                # create qr_data list with number of total frames
                self.qr_data = ["empty"] * self.qr_total_frames
                # create frame display / progress with number of total frames
                self.frame_display = ["-"] * self.qr_total_frames

            # get current frame
            current_frame = type(self).current_frame_parse(data[0])
            if self.qr_data[current_frame - 1] == "empty":
                # if frame has never been captured, store data element in it
                self.qr_data[current_frame - 1] = type(self).data_parse(
                    data[0])
                # increment number of frames captured
                self.qr_cur_frame_count += 1
                # show in frame display / progress of captured frame
                self.frame_display[current_frame - 1] = "*"
                # calculate percentage complete of captured frames
                self.percentage_complete = int(
                    (self.qr_cur_frame_count / self.qr_total_frames) * 100)

            # checking if all frames has been captured, exit camera processing
            if self.capture_complete():
                self.buttons.trigger_override()

            # if all frames have not all been captured, display progress to screen/display
            if not self.capture_complete() and self.scan_display_working == 0:
                self.scan_display_working = 1
                View.draw.rectangle(
                    (0, 0, View.canvas_width, View.canvas_height),
                    outline=0,
                    fill=0)
                tw, th = View.draw.textsize("Collecting QR Codes:",
                                            font=View.IMPACT22)
                View.draw.text(((240 - tw) / 2, 15),
                               "Collecting QR Codes:",
                               fill=View.color,
                               font=View.IMPACT22)
                lines = textwrap.wrap("".join(self.frame_display), width=11)
                yheight = 60
                for line in lines:
                    tw, th = View.draw.textsize(line, font=View.COURIERNEW30)
                    View.draw.text(((240 - tw) / 2, yheight),
                                   line,
                                   fill=View.color,
                                   font=View.COURIERNEW30)
                    yheight += 30
                tw, th = View.draw.textsize("Right to Exit",
                                            font=View.IMPACT18)
                View.draw.text(((240 - tw) / 2, 215),
                               "Right to Exit",
                               fill=View.color,
                               font=View.IMPACT18)
                View.DispShowImage()
                self.scan_display_working = 0

        elif self.scan_started_ind == 0:
            self.scan_started_ind = 1
            self.controller.menu_view.draw_modal(["Scan Animated QR"], "",
                                                 "Right to Exit")

    def make_xpub_qr_codes(self, data, callback=None) -> []:
        return []

    def make_signing_qr_codes(self, data, callback=None) -> []:
        return []

    def qr_sleep(self):
        time.sleep(0.2)

    def set_qr_density(self, density):
        self.cur_qr_density = density
        if density == Wallet.QRLOW:
            self.qrsize = 60
        elif density == Wallet.QRMEDIUM:
            self.qrsize = 80
        elif density == Wallet.QRHIGH:
            self.qrsize = 100

    def get_qr_density(self):
        return self.cur_qr_density

    def get_qr_density_name(self) -> str:
        if self.cur_qr_density == Wallet.QRLOW:
            return "Low"
        elif self.cur_qr_density == Wallet.QRMEDIUM:
            return "Medium"
        elif self.cur_qr_density == Wallet.QRHIGH:
            return "High"
        else:
            return "Unknown"

    def get_wallet_policy_name(self) -> str:
        if self.cur_policy == "PKWSH":
            return "Multi Sig"
        elif self.cur_policy == "PKWPKH":
            return "Single Sig"
        else:
            return self.cur_policy

    def get_wallet_policy(self) -> str:
        return self.cur_policy

    def avaliable_wallet_policies(self) -> []:
        return ["PKWSH", "PKWPKH"]

    ###
    ### Network Related Methods
    ###

    def get_network(self) -> str:
        return self.current_network

    def get_hardened_derivation(self) -> str:
        return self.hardened_derivation

    def set_network(self, network) -> bool:
        return False

    ###
    ### Internal Wallet Transactions
    ###

    def input_amount(self, tx) -> (float, str, float):
        # Check inputs of the transaction and check that they use the same script type
        # For multisig parsed policy will look like this:
        # { script_type: p2wsh, cosigners: [xpubs strings], m: 2, n: 3}
        policy = None
        inp_amount = 0.0
        ins = 0
        for inp in tx.inputs:
            ins += 1
            inp_amount += inp.witness_utxo.value
            # get policy of the input
            inp_policy = self.get_policy(inp, inp.witness_utxo.script_pubkey,
                                         tx.xpubs)
            # if policy is None - assign current
            if policy is None:
                policy = inp_policy
            # otherwise check that everything in the policy is the same
            else:
                # check policy is the same
                if policy != inp_policy:
                    raise RuntimeError("Mixed inputs in the transaction")

        return (inp_amount, policy, ins)

    def change_fee_spend_amounts(
            self, tx, inp_amount, policy,
            currentnetwork) -> (float, float, float, str, float, float):
        spend = 0
        change = 0
        destinationaddress = ""
        dest_addr_cnt = 0
        outs = 0
        for i, out in enumerate(tx.outputs):
            outs += 1
            out_policy = self.get_policy(out, tx.tx.vout[i].script_pubkey,
                                         tx.xpubs)
            is_change = False
            # if policy is the same - probably change
            if out_policy == policy:
                # double-check that it's change
                # we already checked in get_cosigners and parse_multisig
                # that pubkeys are generated from cosigners,
                # and witness script is corresponding multisig
                # so we only need to check that scriptpubkey is generated from
                # witness script

                # empty script by default
                sc = script.Script(b"")
                # multisig, we know witness script
                if policy["type"] == "p2wsh":
                    sc = script.p2wsh(out.witness_script)
                elif policy["type"] == "p2sh-p2wsh":
                    sc = script.p2sh(script.p2wsh(out.witness_script))
                # single-sig native segwit
                elif "pkh" in policy["type"]:
                    for pub in out.bip32_derivations:
                        # check if it is our key
                        if out.bip32_derivations[
                                pub].fingerprint == self.fingerprint:
                            hdkey = self.root.derive(
                                out.bip32_derivations[pub].derivation)
                            mypub = hdkey.key.get_public_key()
                            if mypub != pub:
                                raise ValueError(
                                    "Derivation path doesn't look right")
                            # now check if provided scriptpubkey matches
                            sc = script.p2wpkh(mypub)
                            if sc == tx.tx.vout[i].script_pubkey:
                                is_change = True
                if sc.data == tx.tx.vout[i].script_pubkey.data:
                    is_change = True
            if is_change:
                change += tx.tx.vout[i].value
                print("Change %d sats" % tx.tx.vout[i].value)
            else:
                spend += tx.tx.vout[i].value
                print(
                    "Spending %d sats to %s" %
                    (tx.tx.vout[i].value, tx.tx.vout[i].script_pubkey.address(
                        NETWORKS[currentnetwork])))
                destinationaddress = tx.tx.vout[i].script_pubkey.address(
                    NETWORKS[currentnetwork])
                dest_addr_cnt += 1

        fee = inp_amount - change - spend

        return (change, fee, spend, destinationaddress, outs, dest_addr_cnt)

    def _parse_psbt(self):
        (self.inp_amount, policy, self.ins) = self.input_amount(self.tx)
        (self.change, self.fee, self.spend, self.destinationaddress, self.outs,
         self.dest_addr_cnt) = self.change_fee_spend_amounts(
             self.tx, self.inp_amount, policy, self.current_network)

    def parse_multisig(self, sc):
        """Takes a script and extracts m,n and pubkeys from it"""
        # OP_m <len:pubkey> ... <len:pubkey> OP_n OP_CHECKMULTISIG
        # check min size
        if len(sc.data) < 37 or sc.data[-1] != 0xae:
            raise ValueError("Not a multisig script")
        m = sc.data[0] - 0x50
        if m < 1 or m > 16:
            raise ValueError("Invalid multisig script")
        n = sc.data[-2] - 0x50
        if n < m or n > 16:
            raise ValueError("Invalid multisig script")
        s = BytesIO(sc.data)
        # drop first byte
        s.read(1)
        # read pubkeys
        pubkeys = []
        for i in range(n):
            char = s.read(1)
            if char != b"\x21":
                raise ValueError("Invlid pubkey")
            pubkeys.append(ec.PublicKey.parse(s.read(33)))
        # check that nothing left
        if s.read() != sc.data[-2:]:
            raise ValueError("Invalid multisig script")
        return m, n, pubkeys

    def get_cosigners(self, pubkeys, derivations, xpubs):
        """Returns xpubs used to derive pubkeys using global xpub field from psbt"""
        cosigners = []
        for i, pubkey in enumerate(pubkeys):
            if pubkey not in derivations:
                raise ValueError("Missing derivation")
            der = derivations[pubkey]
            for xpub in xpubs:
                origin_der = xpubs[xpub]
                # check fingerprint
                if origin_der.fingerprint == der.fingerprint:
                    # check derivation - last two indexes give pub from xpub
                    if origin_der.derivation == der.derivation[:-2]:
                        # check that it derives to pubkey actually
                        if xpub.derive(der.derivation[-2:]).key == pubkey:
                            # append strings so they can be sorted and compared
                            cosigners.append(xpub.to_base58())
                            break
        if len(cosigners) != len(pubkeys):
            raise RuntimeError("Can't get all cosigners")
        return sorted(cosigners)

    def get_policy(self, scope, scriptpubkey, xpubs):
        """Parse scope and get policy"""
        # we don't know the policy yet, let's parse it
        script_type = scriptpubkey.script_type()
        # p2sh can be either legacy multisig, or nested segwit multisig
        # or nested segwit singlesig
        if script_type == "p2sh":
            if scope.witness_script is not None:
                script_type = "p2sh-p2wsh"
            elif scope.redeem_script is not None and scope.redeem_script.script_type(
            ) == "p2wpkh":
                script_type = "p2sh-p2wpkh"
        policy = {"type": script_type}
        # expected multisig
        if "p2wsh" in script_type and scope.witness_script is not None:
            m, n, pubkeys = self.parse_multisig(scope.witness_script)

            # check pubkeys are derived from cosigners
            cosigners = self.get_cosigners(pubkeys, scope.bip32_derivations,
                                           xpubs)
            policy.update({"m": m, "n": n, "cosigners": cosigners})
        return policy
示例#6
0
class IOTestView(View):
    def __init__(self) -> None:
        View.__init__(self)
        self.camera_loop_timer = None
        self.redraw = False
        self.redraw_complete = False
        self.qr_text = "Scan ANY QR Code"

    def display_io_test_screen(self):

        # display loading screen
        self.draw_modal(["Initializing I/O Test"])
        print("Initializing I/O Test")
        self.qr_text = "Scan ANY QR Code"

        # initialize camera
        self.controller.to_camera_queue.put(["start"])

        # First get blocking, this way it's clear when the camera is ready for the end user
        self.controller.from_camera_queue.get()

        self.camera_loop_timer = CameraPoll(
            0.05,
            self.get_camera_data)  # it auto-starts, no need of rt.start()

        while True:

            self.draw_io_screen()

            input = self.buttons.wait_for([
                B.KEY_UP, B.KEY_DOWN, B.KEY_PRESS, B.KEY_RIGHT, B.KEY_LEFT,
                B.KEY1, B.KEY2, B.KEY3
            ], False)
            if input == B.KEY_UP:
                ret_val = self.up_button()
            elif input == B.KEY_DOWN:
                ret_val = self.down_button()
            elif input == B.KEY_RIGHT:
                ret_val = self.right_button()
            elif input == B.KEY_LEFT:
                ret_val = self.left_button()
            elif input == B.KEY_PRESS:
                ret_val = self.press_button()
            elif input == B.KEY1:
                ret_val = self.a_button()
            elif input == B.KEY2:
                ret_val = self.b_button()
            elif input == B.KEY3:
                ret_val = self.c_button()
                return True

    def get_camera_data(self):
        try:
            data = self.controller.from_camera_queue.get(False)
            if data[0] != "nodata":
                self.draw_scan_detected()
        except:
            data = ["nodata"]

        if self.redraw == True and self.redraw_complete == True:
            self.draw_io_screen()

    def draw_io_screen(self):
        self.redraw_complete = False
        self.redraw = False
        self.draw.rectangle((0, 0, View.canvas_width, View.canvas_height),
                            outline=0,
                            fill=0)
        self.draw.text((45, 5),
                       "Input/Output Check:",
                       fill=View.color,
                       font=View.IMPACT18)
        self.draw.polygon([(61, 89), (80, 46), (99, 89)],
                          outline=View.color,
                          fill=0)
        self.draw.polygon([(51, 100), (8, 119), (51, 138)],
                          outline=View.color,
                          fill=0)
        self.draw.polygon([(109, 100), (152, 119), (109, 138)],
                          outline=View.color,
                          fill=0)
        self.draw.polygon([(61, 151), (80, 193), (99, 151)],
                          outline=View.color,
                          fill=0)
        self.draw.ellipse([(61, 99), (99, 141)], outline=View.color, fill=0)
        self.draw.ellipse([(198, 40), (238, 80)], outline=View.color, fill=0)
        self.draw.ellipse([(198, 95), (238, 135)], outline=View.color, fill=0)
        self.draw.text((200, 160), "EXIT", fill=View.color, font=View.IMPACT18)
        self.draw.rectangle((30, 205, 210, 235),
                            outline=View.color,
                            fill="BLACK")
        tw, th = self.draw.textsize(self.qr_text, font=View.IMPACT22)
        self.draw.text(((240 - tw) / 2, 205),
                       self.qr_text,
                       fill=View.color,
                       font=View.IMPACT22)
        View.DispShowImage()
        self.redraw_complete = True

    def a_button(self):
        if self.redraw == False and self.redraw_complete == True:
            self.draw.ellipse([(198, 40), (238, 80)],
                              outline=View.color,
                              fill=View.color)
            View.DispShowImage()
            self.redraw = True

    def b_button(self):
        if self.redraw == False and self.redraw_complete == True:
            self.draw.ellipse([(198, 95), (238, 135)],
                              outline=View.color,
                              fill=View.color)
            View.DispShowImage()
            self.redraw = True

    def c_button(self):
        self.camera_loop_timer.stop()
        self.controller.to_camera_queue.put(["stop"])

    def up_button(self):
        if self.redraw == False and self.redraw_complete == True:
            self.draw.polygon([(61, 89), (80, 46), (99, 89)],
                              outline=View.color,
                              fill=View.color)
            View.DispShowImage()
            self.redraw = True

    def down_button(self):
        if self.redraw == False and self.redraw_complete == True:
            self.draw.polygon([(61, 151), (80, 193), (99, 151)],
                              outline=View.color,
                              fill=View.color)
            View.DispShowImage()
            self.redraw = True

    def left_button(self):
        if self.redraw == False and self.redraw_complete == True:
            self.draw.polygon([(51, 100), (8, 119), (51, 138)],
                              outline=View.color,
                              fill=View.color)
            View.DispShowImage()
            self.redraw = True

    def right_button(self):
        if self.redraw == False and self.redraw_complete == True:
            self.draw.polygon([(109, 100), (152, 119), (109, 138)],
                              outline=View.color,
                              fill=View.color)
            View.DispShowImage()
            self.redraw = True

    def press_button(self):
        if self.redraw == False and self.redraw_complete == True:
            self.draw.ellipse([(61, 99), (99, 141)],
                              outline=View.color,
                              fill=View.color)
            View.DispShowImage()
            self.redraw = True

    def draw_scan_detected(self):
        self.qr_text = "QR Scanned"
        if self.redraw == False and self.redraw_complete == True:
            self.draw.rectangle((30, 205, 210, 235),
                                outline=View.color,
                                fill=View.color)
            tw, th = self.draw.textsize(self.qr_text, font=View.IMPACT22)
            self.draw.text(((240 - tw) / 2, 205),
                           self.qr_text,
                           fill="BLACK",
                           font=View.IMPACT22)
            View.DispShowImage()
            self.redraw = True
示例#7
0
class GenericUR2Wallet(Wallet):

    def __init__(self, current_network = "main", qr_density = Wallet.QRMEDIUM, policy = "PKWSH") -> None:
        Wallet.__init__(self, current_network, qr_density, policy)

    def set_seed_phrase(self, seed_phrase, passphrase):
        Wallet.set_seed_phrase(self, seed_phrase, passphrase)
        self.ur_decoder = URDecoder()

    def get_name(self) -> str:
        return "UR 2.0 Generic"

    def parse_psbt(self, raw_psbt) -> bool:
        self.tx = psbt.PSBT.parse(raw_psbt)

        self._parse_psbt()

        return True

    def sign_transaction(self) -> (str):

        # sign the transaction
        self.tx.sign_with(self.root)

        signed_psbt = self.tx.serialize()

        return signed_psbt

    def scan_animated_qr_pbst(self, controller) -> str:
        self.controller = controller
        self.buttons = controller.buttons
        self.controller.menu_view.draw_modal(["Initializing Camera"]) # TODO: Move to Controller
        # initialize camera
        self.controller.to_camera_queue.put(["start"])
        # First get blocking, this way it's clear when the camera is ready for the end user
        self.controller.from_camera_queue.get()
        self.camera_loop_timer = CameraPoll(0.05, self.process_camera_data)

        input = self.buttons.wait_for([B.KEY_LEFT, B.KEY_RIGHT])
        if input in (B.KEY_LEFT, B.KEY_RIGHT):
            self.camera_loop_timer.stop()
            self.controller.to_camera_queue.put(["stop"])
            return "nodata"
        elif input == B.OVERRIDE:
            self.camera_loop_timer.stop()
            self.controller.to_camera_queue.put(["stop"])
            if self.qr_data[0] == "invalid":
                return "invalid"
            return cbor_decode(self.ur_decoder.result.cbor)

    def process_camera_data(self):
        try:
            data = self.controller.from_camera_queue.get(False)
        except:
            data = ["nodata"]

        if data[0] != "nodata":
            if self.qr_total_frames == 0:
                # get total frames if not set
                self.qr_total_frames = type(self).total_frames_parse(data[0])
                if self.qr_total_frames == -1:
                    # when invalid, trigger override to display error
                    self.qr_data = ["invalid"]
                    self.buttons.trigger_override() # something went wrong, invalid QR
                    return
                self.qr_data = ["empty"]

            # get data and percentage
            self.ur_decoder.receive_part(data[0])
            self.percentage_complete = self.ur_decoder.estimated_percent_complete()

            # checking if all frames has been captured, exit camera processing
            if self.capture_complete():
                self.buttons.trigger_override()

            # if all frames have not all been captured, display progress to screen/display
            if not self.capture_complete():
                View.draw.rectangle((0, 0, View.canvas_width, View.canvas_height), outline=0, fill=0)
                tw, th = View.draw.textsize("Collecting QR Codes:", font=View.IMPACT25)
                View.draw.text(((240 - tw) / 2, 15), "Collecting QR Codes:", fill=View.color, font=View.IMPACT25)
                tw, th = View.draw.textsize(str(round(self.percentage_complete * 100)) + "% Complete", font=View.IMPACT22)
                View.draw.text(((240 - tw) / 2, 125), str(round(self.percentage_complete * 100)) + "% Complete", fill=View.color, font=View.IMPACT22)
                View.DispShowImage()

        elif self.scan_started_ind == 0:
            self.scan_started_ind = 1
            self.controller.menu_view.draw_modal(["Scan Animated QR"], "", "Right to Exit")

    def total_frames_parse(data) -> int:
        if re.search("^UR\:CRYPTO-PSBT\/(\d+)\-(\d+)\/", data, re.IGNORECASE) != None:
            return 10 #valid
        else:
            return -1 #invalid

    def capture_complete(self) -> bool:
        if self.ur_decoder.is_complete():
            return True
        else:
            return False

    def set_network(self, network) -> bool:
        if network == "main":
            self.current_network = "main"
            self.hardened_derivation = "m/48h/0h/0h/2h"
        elif network == "test":
            self.current_network = "test"
            self.hardened_derivation = "m/48h/1h/0h/2h"
        else:
            return False

        return True

    def make_xpub_qr_codes(self, data, callback = None) -> []:
        qr = QR()
        images = []
        images.append(qr.qrimage(data))
        return images

    def make_signing_qr_codes(self, data, callback = None) -> []:
        qr = QR()
        images = []
        cnt = 0

        cbor_encoder = CBOREncoder()
        cbor_encoder.encodeBytes(data)
        qr_ur_bytes = UR("crypto-psbt", cbor_encoder.get_bytes())
        ur2_encode = UREncoder(qr_ur_bytes,self.qrsize,0)
        qr_cnt = ur2_encode.fountain_encoder.seq_len()
        
        while not ur2_encode.is_complete():

            part = ur2_encode.next_part().upper()
            images.append(qr.qrimage(part))
            print(part)
            cnt += 1

            if callback != None:
                callback((cnt * 100.0) / qr_cnt)

        return images

    def qr_sleep(self):
        time.sleep(0.5)

    def set_qr_density(self, density):
        self.cur_qr_density = density
        if density == Wallet.QRLOW:
            self.qrsize = 60
        elif density == Wallet.QRMEDIUM:
            self.qrsize = 80
        elif density == Wallet.QRHIGH:
            self.qrsize = 120
示例#8
0
class SparrowWallet(Wallet):
    def __init__(self,
                 current_network="main",
                 qr_density=Wallet.QRMEDIUM,
                 policy="PKWSH") -> None:
        Wallet.__init__(self, current_network, qr_density, policy)

        self.blink = False

    def set_seed_phrase(self, seed_phrase, passphrase):
        Wallet.set_seed_phrase(self, seed_phrase, passphrase)
        self.ur_decoder = URDecoder()

    def get_name(self) -> str:
        return "Sparrow"

    def parse_psbt(self, raw_psbt) -> bool:
        try:
            base64_psbt = a2b_base64(raw_psbt)
            self.tx = psbt.PSBT.parse(base64_psbt)
            self._parse_psbt()
        except Exception:
            return False

        return True

    def sign_transaction(self) -> (str):

        # sign the transaction
        self.tx.sign_with(self.root)

        #added section to trim psbt
        trimmed_psbt = psbt.PSBT(self.tx.tx)
        sigsEnd = 0
        for i, inp in enumerate(self.tx.inputs):
            sigsEnd += len(list(inp.partial_sigs.keys()))
            trimmed_psbt.inputs[i].partial_sigs = inp.partial_sigs

        raw_trimmed_signed_psbt = trimmed_psbt.serialize()

        # convert to base64
        b64_psbt = b2a_base64(raw_trimmed_signed_psbt)
        # somehow b2a ends with \n...
        if b64_psbt[-1:] == b"\n":
            b64_psbt = b64_psbt[:-1]

        return b64_psbt.decode('utf-8')

    def scan_animated_qr_pbst(self, controller) -> str:
        self.controller = controller
        self.buttons = controller.buttons
        self.controller.menu_view.draw_modal(["Initializing Camera"
                                              ])  # TODO: Move to Controller
        # initialize camera
        self.controller.to_camera_queue.put(["start"])
        # First get blocking, this way it's clear when the camera is ready for the end user
        self.controller.from_camera_queue.get()
        self.camera_loop_timer = CameraPoll(0.05, self.process_camera_data)

        input = self.buttons.wait_for([B.KEY_LEFT, B.KEY_RIGHT])
        if input in (B.KEY_LEFT, B.KEY_RIGHT):
            self.camera_loop_timer.stop()
            self.controller.to_camera_queue.put(["stop"])
            return "nodata"
        elif input == B.OVERRIDE:
            self.camera_loop_timer.stop()
            self.controller.to_camera_queue.put(["stop"])
            if self.qr_data[0] == "invalid":
                return "invalid"
            return b2a_base64(cbor_decode(self.ur_decoder.result.cbor))

    def process_camera_data(self):
        try:
            data = self.controller.from_camera_queue.get(False)
        except:
            data = ["nodata"]

        if data[0] != "nodata":
            if self.qr_total_frames == 0:
                # get total frames if not set
                self.qr_total_frames = type(self).total_frames_parse(data[0])
                if self.qr_total_frames == -1:
                    # when invalid, trigger override to display error
                    self.qr_data = ["invalid"]
                    self.buttons.trigger_override(
                    )  # something went wrong, invalid QR
                    return
                self.qr_data = ["empty"]

            # get data and percentage
            decoder_work_check = self.ur_decoder.receive_part(data[0])
            if decoder_work_check == False:
                self.qr_data = ["invalidpsbt"]
                self.buttons.trigger_override(
                )  # something went wrong, invalid QR
                return
            self.percentage_complete = self.ur_decoder.estimated_percent_complete(
            )

            # checking if all frames has been captured, exit camera processing
            if self.capture_complete():
                self.buttons.trigger_override()

            # if all frames have not all been captured, display progress to screen/display
            if not self.capture_complete() and self.scan_display_working == 0:
                self.scan_display_working = 1
                View.draw.rectangle(
                    (0, 0, View.canvas_width, View.canvas_height),
                    outline=0,
                    fill=0)
                tw, th = View.draw.textsize("Collecting QR Codes:",
                                            font=View.IMPACT25)
                View.draw.text(((240 - tw) / 2, 15),
                               "Collecting QR Codes:",
                               fill=View.color,
                               font=View.IMPACT25)
                tw, th = View.draw.textsize(
                    str(round(self.percentage_complete * 100)) + "% Complete",
                    font=View.IMPACT22)
                View.draw.text(
                    ((240 - tw) / 2, 125),
                    str(round(self.percentage_complete * 100)) + "% Complete",
                    fill=View.color,
                    font=View.IMPACT22)
                View.DispShowImage()
                self.scan_display_working = 0

        elif self.scan_started_ind == 0:
            self.scan_started_ind = 1
            self.controller.menu_view.draw_modal(["Scan Animated QR"], "",
                                                 "Right to Exit")

    def total_frames_parse(data) -> int:
        if re.search("^UR\:CRYPTO-PSBT\/(\d+)\-(\d+)\/", data,
                     re.IGNORECASE) != None:
            return 10  # valid
        elif re.search("^UR\:CRYPTO-PSBT", data, re.IGNORECASE) != None:
            return 1  # valid but only 1 segment
        else:
            return -1  #invalid

    def capture_complete(self) -> bool:
        if self.ur_decoder.is_complete():
            return True
        else:
            return False

    def set_network(self, network) -> bool:
        if network == "main":
            self.current_network = "main"
            self.hardened_derivation = "m/48h/0h/0h/2h"
        elif network == "test":
            self.current_network = "test"
            self.hardened_derivation = "m/48h/1h/0h/2h"
        else:
            return False

        return True

    def make_xpub_qr_codes(self, data, callback=None) -> []:
        qr = QR()
        images = []
        images.append(qr.qrimage(data))
        return images

    def make_signing_qr_codes(self, data, callback=None) -> []:
        qr = QR()

        cnt = 0
        images = []
        start = 0
        print(self.qrsize)
        stop = self.qrsize
        qr_cnt = ((len(data) - 1) // self.qrsize) + 1

        while cnt < qr_cnt:
            part = "p" + str(cnt +
                             1) + "of" + str(qr_cnt) + " " + data[start:stop]
            images.append(qr.qrimage(part))
            print(part)
            start = start + self.qrsize
            stop = stop + self.qrsize
            if stop > len(data):
                stop = len(data)
            cnt += 1

            if callback != None:
                callback((cnt * 100.0) / qr_cnt)

        return images

    def qr_sleep(self):
        time.sleep(0.2)

    def set_qr_density(self, density):
        self.cur_qr_density = density
        if density == Wallet.QRLOW:
            self.qrsize = 50
        elif density == Wallet.QRMEDIUM:
            self.qrsize = 70
        elif density == Wallet.QRHIGH:
            self.qrsize = 120