async def process_host_command(self, stream, show_screen): """ If command with one of the prefixes is received it will be passed to this method. Should return a tuple: - stream (file, BytesIO etc) - meta object with title and note """ # reads prefix from the stream (until first space) prefix = self.get_prefix(stream) if prefix != b"signmessage": # WTF? It's not our data... raise AppError("Prefix is not valid: %s" % prefix.decode()) # data format: message to sign<space>derivation_path # read all and delete all crap at the end (if any) # also message should be utf-8 decodable data = stream.read().strip().decode() if " " not in data: raise AppError("Invalid data encoding") arr = data.split(" ") derivation_path = arr[-1] message = " ".join(arr[:-1]) # if we have fingerprint if not derivation_path.startswith("m/"): fingerprint = unhexlify(derivation_path[:8]) if fingerprint != self.keystore.fingerprint: raise AppError("Not my fingerprint") derivation_path = "m" + derivation_path[8:] derivation_path = bip32.parse_path(derivation_path) # ask the user if he really wants to sign this message scr = Prompt( "Sign message with private key at %s?" % bip32.path_to_str(derivation_path), "Message:\n%s" % message) res = await show_screen(scr) if res is False: return None sig = self.sign_message(derivation_path, message.encode()) # for GUI we can also return an object with helpful data xpub = self.keystore.get_xpub(derivation_path) address = script.p2pkh(xpub.key).address() obj = { "title": "Signature for the message:", "note": "using address %s" % address } return BytesIO(sig), obj
async def showaddr(self, paths: list, script_type: str, redeem_script=None, show_screen=None) -> str: net = self.Networks[self.network] if redeem_script is not None: redeem_script = script.Script(unhexlify(redeem_script)) # first check if we have corresponding wallet: address = None if redeem_script is not None: if script_type == b"wsh": address = script.p2wsh(redeem_script).address(net) elif script_type == b"sh-wsh": address = script.p2sh(script.p2wsh(redeem_script)).address(net) elif script_type == b"sh": address = script.p2sh(redeem_script).address(net) else: raise WalletError("Unsupported script type: %s" % script_type) else: if len(paths) != 1: raise WalletError("Invalid number of paths, expected 1") path = paths[0] if not path.startswith("m/"): path = "m" + path[8:] derivation = bip32.parse_path(path) pub = self.keystore.get_xpub(derivation) if script_type == b"wpkh": address = script.p2wpkh(pub).address(net) elif script_type == b"sh-wpkh": address = script.p2sh(script.p2wpkh(pub)).address(net) elif script_type == b"pkh": address = script.p2pkh(pub).address(net) else: raise WalletError("Unsupported script type: %s" % script_type) w, (idx, branch_idx) = self.find_wallet_from_address(address, paths=paths) if show_screen is not None: await show_screen( WalletScreen(w, self.network, idx, branch_index=branch_idx)) addr, _ = w.get_address(idx, self.network, branch_idx) return addr
def derive_wallet_xpubs(self, was_recovered: bool): xpub_keypath_tuples = [] if was_recovered: address_type_keypaths = [BIP_44_KEYPATH, BIP_49_KEYPATH, BIP_84_KEYPATH] else: address_type_keypaths = [BIP_84_KEYPATH] network_keypath = get_keypath_by_network(self.network) for address_type_keypath in address_type_keypaths: path_str = "{address_type_keypath}/{network_keypath}/{account_keypath}".format( address_type_keypath=address_type_keypath, network_keypath=network_keypath, account_keypath=FIRST_ACCOUNT_KEYPATH ) path = parse_path(path_str) xpub = self.master_xpriv.derive(path).to_public() xpub_keypath_tuples.append((xpub, path_str)) return xpub_keypath_tuples
def parse(cls, s): fingerprint = None parent_derivation = None address_derivation = "_" m = re.match("\[(.*)\](.*)", s) if m: parent_derivation = m.group(1) if not parent_derivation.startswith("m/"): arr = parent_derivation.split("/") fingerprint = unhexlify(arr[0]) if len(fingerprint) != 4: raise ValueError("Invalid fingerprint in derivation path") parent_derivation = "m/" + "/".join(arr[1:]) parent_derivation = bip32.parse_path(parent_derivation) s = m.group(2) if "/" in s: arr = s.split("/") address_derivation = "/".join(arr[1:]) s = arr[0] key = bip32.HDKey.from_base58(s) return cls(key, fingerprint, parent_derivation, address_derivation)
async def process_host_command(self, stream, show_screen): if self.keystore.is_locked: raise AppError("Device is locked") # reads prefix from the stream (until first space) prefix = self.get_prefix(stream) # get device fingerprint, data is ignored if prefix == b"fingerprint": return BytesIO(hexlify(self.keystore.fingerprint)), {} # get xpub, # data: derivation path in human-readable form like m/44h/1h/0 elif prefix == b"xpub": try: path = stream.read().strip() # convert to list of indexes path = bip32.parse_path(path.decode()) except: raise AppError('Invalid path: "%s"' % path.decode()) # get xpub xpub = self.keystore.get_xpub(bip32.path_to_str(path)) # send back as base58 return BytesIO(xpub.to_base58(NETWORKS[self.network]["xpub"]).encode()), {} raise AppError("Unknown command")
def parse_cc_wallet_txt(stream): """Parse coldcard wallet format""" name = "Imported wallet" script_type = None sigs_required = None global_derivation = None sigs_total = None cosigners = [] current_derivation = None # cycle until we read everything char = b"\n" while char is not None: line, char = read_until(stream, b"\r\n", max_len=300) # skip comments while char is not None and (line.startswith(b"#") or len(line.strip()) == 0): # BW comment on derivation if line.startswith(b"# derivation:"): current_derivation = bip32.parse_path( line.split(b":")[1].decode().strip()) line, char = read_until(stream, b"\r\n", max_len=300) if b":" not in line: continue arr = line.split(b":") if len(arr) > 2: raise AppError("Invalid file format") k, v = [a.strip().decode() for a in arr] if k == "Name": name = v elif k == "Policy": nums = [int(num) for num in v.split(" of ")] assert len(nums) == 2 m, n = nums assert m > 0 and n >= m sigs_required = m sigs_total = n elif k == "Format": assert v in CC_TYPES script_type = CC_TYPES[v] elif k == "Derivation": der = bip32.parse_path(v) if len(cosigners) == 0: global_derivation = der else: current_derivation = der # fingerprint elif len(k) == 8: cosigners.append( (unhexlify(k), current_derivation or global_derivation, bip32.HDKey.from_string(v))) current_derivation = None assert None not in [ global_derivation, sigs_total, sigs_required, script_type, name ] assert len(cosigners) == sigs_total xpubs = [ "[%s]%s/{0,1}/*" % (bip32.path_to_str(der, fingerprint=fgp), xpub) for fgp, der, xpub in cosigners ] desc = "sortedmulti(%d,%s)" % (sigs_required, ",".join(xpubs)) for sc in reversed(script_type.split("-")): desc = "%s(%s)" % (sc, desc) return name, desc
def get_address_type_keypath(keypath: str): return path_to_str(parse_path(keypath)[:1])