def parse_multisig_xpubs(tx, psbt_in_out, multisig): try: old_pubs = [k.node.public_key for k in multisig.pubkeys] xpubs = [xpub for xpub in tx.unknown.keys() if xpub.startswith(b"\x01")] derivations = [tx.unknown[xpub] for xpub in xpubs] # unpack derivations = [list(struct.unpack("<" + "I" * (len(value) // 4), value)) for value in derivations] new_pubs = [] for pub in old_pubs: # derivation der = list(psbt_in_out.hd_keypaths[pub]) for i, derivation in py_enumerate(derivations): if der[0] == derivation[0]: idx = i for i in range(len(derivation)): if der[i] != derivation[i]: # derivations mismatch return multisig break xpub = xpubs[idx][1:] address_n = der[len(derivations[idx]):] xpub_obj = ExtendedKey() xpub_obj.deserialize(base58_encode(xpub + hash256(xpub)[:4])) hd_node = proto.HDNodeType(depth=xpub_obj.depth, fingerprint=der[0], child_num=xpub_obj.child_num, chain_code=xpub_obj.chaincode, public_key=xpub_obj.pubkey) new_pub = proto.HDNodePathType(node=hd_node, address_n=address_n) new_pubs.append(new_pub) return proto.MultisigRedeemScriptType(m=multisig.m, signatures=multisig.signatures, pubkeys=new_pubs) except: # If not all necessary data is available or malformatted, return the original multisig return multisig
def parse_multisig(script): # Get m m = script[0] - 80 if m < 1 or m > 15: return (False, None) # Get pubkeys and build HDNodePathType pubkeys = [] offset = 1 while True: pubkey_len = script[offset] if pubkey_len != 33: break offset += 1 key = script[offset:offset + 33] offset += 33 hd_node = proto.HDNodeType(depth=0, fingerprint=0, child_num=0, chain_code=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', public_key=key) pubkeys.append(proto.HDNodePathType(node=hd_node, address_n=[])) # Check things at the end n = script[offset] - 80 if n != len(pubkeys): return (False, None) offset += 1 op_cms = script[offset] if op_cms != 174: return (False, None) # Build MultisigRedeemScriptType and return it multisig = proto.MultisigRedeemScriptType(m=m, signatures=[b''] * n, pubkeys=pubkeys) return (True, multisig)
def display_multisig_address( self, addr_type: AddressType, multisig: MultisigDescriptor, ) -> str: self._check_unlocked() der_pks = list( zip([p.get_pubkey_bytes(0) for p in multisig.pubkeys], multisig.pubkeys) ) if multisig.is_sorted: der_pks = sorted(der_pks) pubkey_objs = [] for pk, p in der_pks: if p.extkey is not None: xpub = p.extkey hd_node = messages.HDNodeType( depth=xpub.depth, fingerprint=int.from_bytes(xpub.parent_fingerprint, "big"), child_num=xpub.child_num, chain_code=xpub.chaincode, public_key=xpub.pubkey, ) pubkey_objs.append( messages.HDNodePathType( node=hd_node, address_n=parse_path( "m" + p.deriv_path if p.deriv_path is not None else "" ), ) ) else: hd_node = messages.HDNodeType( depth=0, fingerprint=0, child_num=0, chain_code=b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", public_key=pk, ) pubkey_objs.append(messages.HDNodePathType(node=hd_node, address_n=[])) trezor_ms = messages.MultisigRedeemScriptType( m=multisig.thresh, signatures=[b""] * len(pubkey_objs), pubkeys=pubkey_objs ) # Script type if addr_type == AddressType.SH_WIT: script_type = messages.InputScriptType.SPENDP2SHWITNESS elif addr_type == AddressType.WIT: script_type = messages.InputScriptType.SPENDWITNESS elif addr_type == AddressType.LEGACY: script_type = messages.InputScriptType.SPENDMULTISIG else: raise BadArgumentError("Unknown address type") for p in multisig.pubkeys: keypath = p.origin.get_derivation_path() if p.origin is not None else "m/" keypath += p.deriv_path if p.deriv_path is not None else "" path = parse_path(keypath) try: address = btc.get_address( self.client, self.coin_name, path, show_display=True, script_type=script_type, multisig=trezor_ms, ) assert isinstance(address, str) return address except Exception: pass raise BadArgumentError("No path supplied matched device keys")
def parse_multisig( script: bytes, tx_xpubs: Dict[bytes, KeyOriginInfo], psbt_scope: Union[PartiallySignedInput, PartiallySignedOutput], ) -> Tuple[bool, Optional[messages.MultisigRedeemScriptType]]: # at least OP_M pub OP_N OP_CHECKMULTISIG if len(script) < 37: return (False, None) # Get m m = script[0] - 80 if m < 1 or m > 15: return (False, None) # Get pubkeys and build HDNodePathType pubkeys = [] offset = 1 while True: pubkey_len = script[offset] if pubkey_len != 33: break offset += 1 key = script[offset : offset + 33] offset += 33 hd_node = messages.HDNodeType( depth=0, fingerprint=0, child_num=0, chain_code=b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", public_key=key, ) pubkeys.append(messages.HDNodePathType(node=hd_node, address_n=[])) # Check things at the end n = script[offset] - 80 if n != len(pubkeys): return (False, None) offset += 1 op_cms = script[offset] if op_cms != 174: return (False, None) # check if we know corresponding xpubs from global scope for pub in pubkeys: if pub.node.public_key in psbt_scope.hd_keypaths: derivation = psbt_scope.hd_keypaths[pub.node.public_key] for xpub in tx_xpubs: hd = ExtendedKey.deserialize(base58.encode(xpub + hash256(xpub)[:4])) origin = tx_xpubs[xpub] # check fingerprint and derivation if (origin.fingerprint == derivation.fingerprint) and ( origin.path == derivation.path[: len(origin.path)] ): # all good - populate node and break pub.address_n = list(derivation.path[len(origin.path) :]) pub.node = messages.HDNodeType( depth=hd.depth, fingerprint=int.from_bytes(hd.parent_fingerprint, "big"), child_num=hd.child_num, chain_code=hd.chaincode, public_key=hd.pubkey, ) break # Build MultisigRedeemScriptType and return it multisig = messages.MultisigRedeemScriptType( m=m, signatures=[b""] * n, pubkeys=pubkeys ) return (True, multisig)
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None, descriptor=None): self._check_unlocked() # descriptor means multisig with xpubs if descriptor: pubkeys = [] xpub = ExtendedKey() for i in range(0, descriptor.multisig_N): xpub.deserialize(descriptor.base_key[i]) hd_node = proto.HDNodeType( depth=xpub.depth, fingerprint=int.from_bytes(xpub.parent_fingerprint, "big"), child_num=xpub.child_num, chain_code=xpub.chaincode, public_key=xpub.pubkey, ) pubkeys.append( proto.HDNodePathType( node=hd_node, address_n=tools.parse_path("m" + descriptor.path_suffix[i]), )) multisig = proto.MultisigRedeemScriptType( m=int(descriptor.multisig_M), signatures=[b""] * int(descriptor.multisig_N), pubkeys=pubkeys, ) # redeem_script means p2sh/multisig elif redeem_script: # Get multisig object required by Trezor's get_address multisig = parse_multisig(bytes.fromhex(redeem_script)) if not multisig[0]: raise BadArgumentError( "The redeem script provided is not a multisig. Only multisig scripts can be displayed." ) multisig = multisig[1] else: multisig = None # Script type if p2sh_p2wpkh: script_type = proto.InputScriptType.SPENDP2SHWITNESS elif bech32: script_type = proto.InputScriptType.SPENDWITNESS elif redeem_script: script_type = proto.InputScriptType.SPENDMULTISIG else: script_type = proto.InputScriptType.SPENDADDRESS # convert device fingerprint to 'm' if exists in path keypath = keypath.replace(self.get_master_fingerprint_hex(), "m") for path in keypath.split(","): if len(path.split("/")[0]) == 8: path = path.split("/", 1)[1] expanded_path = tools.parse_path(path) try: address = btc.get_address( self.client, self.coin_name, expanded_path, show_display=True, script_type=script_type, multisig=multisig, ) return {"address": address} except: pass raise BadArgumentError("No path supplied matched device keys")